ykt-wxapp/components/form-template/form-cell/form-multiSelectAndOther.vue

169 lines
4.4 KiB
Vue

<template>
<view class="multi-wrap">
<view class="label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
<view class="options" :class="hasOtherSelected ? 'with-other' : ''">
<view
v-for="opt in displayOptions"
:key="opt.value"
class="opt"
:class="isSelected(opt.value) ? 'active' : ''"
@click="toggle(opt.value)"
>
{{ opt.label }}
</view>
</view>
<view v-if="hasOtherSelected" class="other">
<input
:disabled="disableChange"
:value="otherText"
class="other-input"
placeholder="请补充其它内容"
placeholder-class="form__placeholder"
maxlength="50"
@input="onOtherInput"
/>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
const emits = defineEmits(['change']);
const props = defineProps({
form: { type: Object, default: () => ({}) },
range: { type: Array, default: () => [] },
name: { default: '' },
required: { type: Boolean, default: false },
title: { default: '' },
disableChange: { type: Boolean, default: false },
otherList: { type: Array, default: () => ['其他'] },
});
function normalizeOptions(options) {
if (!Array.isArray(options)) return [];
if (!options.length) return [];
if (typeof options[0] === 'string') return options.filter(Boolean).map((i) => ({ label: String(i), value: String(i) }));
return options
.map((i) => {
const label = i?.label ?? i?.name ?? i?.text ?? i?.title ?? '';
const value = i?.value ?? i?.id ?? i?.key ?? label;
if (!label && (value === undefined || value === null || value === '')) return null;
return { label: String(label || value), value: String(value) };
})
.filter(Boolean);
}
const displayOptions = computed(() => normalizeOptions(props.range));
const valueList = computed(() => (Array.isArray(props.form?.[props.title]) ? props.form[props.title] : []));
const otherValue = computed(() => {
const selected = valueList.value.map(String);
const rangeValues = new Set(displayOptions.value.map((i) => String(i.value)));
const extra = selected.filter((i) => !rangeValues.has(String(i)));
return extra[0] || '';
});
const hasOtherSelected = computed(() => {
const selected = valueList.value.map(String);
return selected.some((v) => props.otherList.map(String).includes(v));
});
const otherText = computed(() => otherValue.value);
function isSelected(v) {
return valueList.value.map(String).includes(String(v));
}
function emitValue(next) {
emits('change', { title: props.title, value: next });
}
function toggle(v) {
if (props.disableChange) return;
const str = String(v);
let next = valueList.value.map(String);
if (next.includes(str)) {
next = next.filter((i) => i !== str);
} else {
next.push(str);
}
const isOther = props.otherList.map(String).includes(str);
if (!isOther) {
// 如果取消了 other 的选择,清理 other 文本
const stillHasOther = next.some((i) => props.otherList.map(String).includes(i));
if (!stillHasOther) {
const rangeValues = new Set(displayOptions.value.map((i) => String(i.value)));
next = next.filter((i) => rangeValues.has(i));
}
}
emitValue(next);
}
function onOtherInput(e) {
const text = String(e?.detail?.value || '').trim();
const rangeValues = new Set(displayOptions.value.map((i) => String(i.value)));
let next = valueList.value.map(String).filter((i) => rangeValues.has(i));
const hasOther = valueList.value.map(String).some((i) => props.otherList.map(String).includes(i));
if (hasOther) {
next.push(...props.otherList.map(String).slice(0, 1));
if (text) next.push(text);
}
emitValue(next);
}
</script>
<style lang="scss" scoped>
@import '../cell-style.css';
.multi-wrap {
padding: 24rpx 30rpx;
border-bottom: 1px solid #eee;
}
.label {
font-size: 28rpx;
line-height: 42rpx;
}
.options {
margin-top: 18rpx;
display: flex;
flex-wrap: wrap;
gap: 18rpx;
}
.opt {
min-width: 150rpx;
padding: 12rpx 20rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
font-size: 26rpx;
text-align: center;
color: #333;
background: #fff;
}
.opt.active {
background: rgba(93, 138, 255, 0.12);
border-color: rgba(93, 138, 255, 0.35);
color: #5d8aff;
}
.other {
margin-top: 18rpx;
}
.other-input {
width: 100%;
font-size: 28rpx;
padding: 18rpx 20rpx;
border: 1px solid #eee;
border-radius: 8rpx;
box-sizing: border-box;
}
</style>