Commit 58231789bb70f9b55c4f49f3a4bede40dca58c6e

Authored by 史婷婷
1 parent 3429c884

feat: 多选组件+框架合同新增&编辑-品种 使用 多选组件

  1 +<template>
  2 + <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
  3 + <view class="sheet">
  4 + <view class="sheet-header">
  5 + <text class="cancel" @click="onCancel">取消</text>
  6 + <text class="title">{{ title }}</text>
  7 + <text class="ok" @click="onConfirm">确认</text>
  8 + </view>
  9 + <view class="sheet-body">
  10 + <view
  11 + v-for="(opt, i) in options"
  12 + :key="i"
  13 + :class="['option', { selected: isSelected(opt) }]"
  14 + @click="toggle(opt)"
  15 + >
  16 + <text class="label">{{ displayOf(opt) }}</text>
  17 + </view>
  18 + </view>
  19 + </view>
  20 + </uni-popup>
  21 +</template>
  22 +
  23 +<script>
  24 +export default {
  25 + name: 'MultiSelectSheet',
  26 + props: {
  27 + visible: { type: Boolean, default: false },
  28 + title: { type: String, default: '请选择' },
  29 + // 统一约定:options 为 [{ label: string, value: string|number }]
  30 + options: { type: Array, default: () => [] },
  31 + // 接收逗号分隔的字符串 '111,222,333'
  32 + value: { type: String, default: '' }
  33 + },
  34 + data() {
  35 + return {
  36 + innerValue: [] // 内部维护数组形式
  37 + }
  38 + },
  39 + watch: {
  40 + value(v) { this.initInnerValue(v) },
  41 + visible(v) { v ? this.open() : this.close() }
  42 + },
  43 + mounted() {
  44 + if (this.visible) this.open()
  45 + },
  46 + methods: {
  47 + initInnerValue(val) {
  48 + if (!val) {
  49 + this.innerValue = []
  50 + } else {
  51 + this.innerValue = String(val).split(',').filter(x => x)
  52 + }
  53 + },
  54 + open() {
  55 + this.initInnerValue(this.value)
  56 + this.$refs.popup && this.$refs.popup.open()
  57 + this.$emit('update:visible', true)
  58 + },
  59 + close() {
  60 + this.$refs.popup && this.$refs.popup.close()
  61 + this.$emit('update:visible', false)
  62 + },
  63 + displayOf(opt) {
  64 + if (!opt) return ''
  65 + return opt.label != null ? String(opt.label) : ''
  66 + },
  67 + valueOf(opt) {
  68 + if (!opt) return ''
  69 + return opt.value != null ? String(opt.value) : ''
  70 + },
  71 + isSelected(opt) {
  72 + const v = this.valueOf(opt)
  73 + return this.innerValue.includes(v)
  74 + },
  75 + toggle(opt) {
  76 + const v = this.valueOf(opt)
  77 + const idx = this.innerValue.indexOf(v)
  78 + if (idx > -1) {
  79 + this.innerValue.splice(idx, 1)
  80 + } else {
  81 + this.innerValue.push(v)
  82 + }
  83 + },
  84 + onCancel() {
  85 + this.close()
  86 + this.$emit('cancel')
  87 + },
  88 + onConfirm() {
  89 + const selectedValues = this.innerValue
  90 + // 保持原始选项顺序
  91 + const sortedValues = []
  92 + const sortedLabels = []
  93 +
  94 + this.options.forEach(opt => {
  95 + const v = this.valueOf(opt)
  96 + if (selectedValues.includes(v)) {
  97 + sortedValues.push(v)
  98 + sortedLabels.push(this.displayOf(opt))
  99 + }
  100 + })
  101 +
  102 + const valStr = sortedValues.join(',')
  103 + const labelStr = sortedLabels.join(',')
  104 +
  105 + this.$emit('confirm', { value: valStr, label: labelStr })
  106 + this.$emit('input', valStr)
  107 + this.$emit('update:value', valStr)
  108 + this.close()
  109 + }
  110 + }
  111 +}
  112 +</script>
  113 +
  114 +<style lang="scss" scoped>
  115 +.sheet {
  116 + width: 100%;
  117 + max-height: 45vh;
  118 + background: #fff;
  119 + border-radius: 20rpx 20rpx 0 0;
  120 + display: flex;
  121 + flex-direction: column;
  122 +}
  123 +.sheet-header {
  124 + display: flex;
  125 + align-items: center;
  126 + justify-content: space-between;
  127 + padding: 30rpx 32rpx;
  128 + border-bottom: 1rpx solid #f0f0f0;
  129 +}
  130 +.title {
  131 + font-size: 36rpx;
  132 + font-weight: 600;
  133 +}
  134 +.cancel {
  135 + color: rgba(0,0,0,0.6);
  136 + font-size: 28rpx;
  137 +}
  138 +.ok {
  139 + color: $theme-primary;
  140 + font-size: 28rpx;
  141 +}
  142 +.sheet-body {
  143 + flex: 1 1 auto;
  144 + overflow-y: auto;
  145 + padding: 32rpx;
  146 +}
  147 +.option {
  148 + padding: 20rpx;
  149 + line-height: 40rpx;
  150 + background: #fff;
  151 + text-align: center;
  152 + border-radius: 12rpx;
  153 + font-size: 32rpx;
  154 + margin-bottom: 20rpx;
  155 + .label {
  156 + color: rgba(0,0,0,0.6);
  157 + font-size: 32rpx;
  158 + }
  159 + &.selected {
  160 + background: #f3f3f3;
  161 + .label {
  162 + color: rgba(0,0,0,0.9);
  163 + }
  164 + }
  165 +}
  166 +</style>
\ No newline at end of file
... ...
... ... @@ -49,6 +49,8 @@
49 49
50 50 <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value"
51 51 @confirm="onSheetConfirm" />
  52 + <MultiSelectSheet :visible.sync="multiSheet.visible" :title="multiSheet.title" :options="multiSheet.options" v-model="multiSheet.value"
  53 + @confirm="onMultiSheetConfirm" />
52 54
53 55 <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
54 56 :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
... ... @@ -57,12 +59,13 @@
57 59 </template>
58 60 <script>
59 61 import SingleSelectSheet from '@/components/single-select/index.vue'
  62 +import MultiSelectSheet from '@/components/multi-select/index.vue'
60 63 import RelateSelectSheet from '@/components/relate-select/index.vue'
61 64 import { productVarietyQuery, getCodeApi, createApi } from '@/api/contract'
62 65 import { getDicByCodes } from '@/utils/dic'
63 66
64 67 export default {
65   - components: { SingleSelectSheet, RelateSelectSheet },
  68 + components: { SingleSelectSheet, RelateSelectSheet, MultiSelectSheet },
66 69 data() {
67 70 return {
68 71 form: {
... ... @@ -80,6 +83,7 @@ export default {
80 83 productVarietyList: [],
81 84 companyList: [],
82 85 sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  86 + multiSheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
83 87 relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' }
84 88 }
85 89 },
... ... @@ -132,14 +136,25 @@ export default {
132 136 const match = (options || []).find(o => String(o.label) === String(current) || String(o.value) === String(current))
133 137 this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
134 138 }
  139 + const setMultiSheet = (title, options) => {
  140 + const current = this.form[field]
  141 + this.multiSheet = { ...this.multiSheet, visible: true, title, options, field, value: current || '' }
  142 + }
  143 +
135 144 if (field === 'company') {
136 145 setSheet('所属单位', this.companyList)
137 146 } else if (field === 'materialTypeId') {
138   - setSheet('品种', this.productVarietyList)
  147 + setMultiSheet('品种', this.productVarietyList)
139 148 } else if (field === 'hasFrameworkAgreement') {
140 149 setSheet('是否签订框架合同', [{ label: '是', value: true }, { label: '否', value: false }])
141 150 }
142 151 },
  152 + onMultiSheetConfirm({ value, label }) {
  153 + const field = this.multiSheet.field
  154 + if (!field) return
  155 + this.form[field] = value || ''
  156 + this.form[field + 'Name'] = label || ''
  157 + },
143 158 onSheetConfirm({ value, label }) {
144 159 const field = this.sheet.field
145 160 if (!field) return
... ...
... ... @@ -49,6 +49,8 @@
49 49
50 50 <SingleSelectSheet :visible.sync="sheet.visible" :title="sheet.title" :options="sheet.options" v-model="sheet.value"
51 51 @confirm="onSheetConfirm" />
  52 + <MultiSelectSheet :visible.sync="multiSheet.visible" :title="multiSheet.title" :options="multiSheet.options" v-model="multiSheet.value"
  53 + @confirm="onMultiSheetConfirm" />
52 54 <RelateSelectSheet :visible.sync="relate.visible" :title="relate.title" :source="relate.source"
53 55 :display-fields="relate.display" :multiple="relate.multiple" :row-key="relate.rowKey"
54 56 :selectedKeys.sync="relate.selectedKeys" @confirm="onRelateConfirm" />
... ... @@ -57,12 +59,13 @@
57 59
58 60 <script>
59 61 import SingleSelectSheet from '@/components/single-select/index.vue'
  62 +import MultiSelectSheet from '@/components/multi-select/index.vue'
60 63 import RelateSelectSheet from '@/components/relate-select/index.vue'
61 64 import { productVarietyQuery, getCodeApi, getDetailApi, updateApi } from '@/api/contract'
62 65 import { getDicByCodes } from '@/utils/dic'
63 66
64 67 export default {
65   - components: { SingleSelectSheet, RelateSelectSheet },
  68 + components: { SingleSelectSheet, RelateSelectSheet, MultiSelectSheet },
66 69 data() {
67 70 return {
68 71 id: '',
... ... @@ -70,6 +73,7 @@ export default {
70 73 companyList: [],
71 74 productVarietyList: [],
72 75 sheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
  76 + multiSheet: { visible: false, title: '请选择', field: '', options: [], value: '' },
73 77 relate: { visible: false, title: '选择', source: '', display: [], multiple: false, rowKey: 'id', selectedKeys: [], fieldKey: '' }
74 78 }
75 79 },
... ... @@ -122,10 +126,21 @@ export default {
122 126 const match = (options || []).find(o => String(o.value) === String(current))
123 127 this.sheet = { ...this.sheet, visible: true, title, options, field, value: match ? match.value : '' }
124 128 }
  129 + const setMultiSheet = (title, options) => {
  130 + const current = this.form[field]
  131 + this.multiSheet = { ...this.multiSheet, visible: true, title, options, field, value: current || '' }
  132 + }
  133 +
125 134 if (field === 'company') setSheet('所属单位', this.companyList)
126   - else if (field === 'materialTypeId') setSheet('品种', this.productVarietyList)
  135 + else if (field === 'materialTypeId') setMultiSheet('品种', this.productVarietyList)
127 136 else if (field === 'hasFrameworkAgreement') setSheet('是否签订框架合同', [{ label: '是', value: true }, { label: '否', value: false }])
128 137 },
  138 + onMultiSheetConfirm({ value, label }) {
  139 + const field = this.multiSheet.field
  140 + if (!field) return
  141 + this.form[field] = value || ''
  142 + this.form[field + 'Name'] = label || ''
  143 + },
129 144 onSheetConfirm({ value, label }) {
130 145 const field = this.sheet.field
131 146 if (!field) return
... ...