vue_zhongyou 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue_zhongyou",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "keywords": [],
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="mobile-sort-bar">
3
+ <div
4
+ v-for="option in normalizedOptions"
5
+ :key="option.value"
6
+ class="sort-item"
7
+ @click="handleSelect(option)"
8
+ >
9
+ <span class="label">{{ option.label }}</span>
10
+ <div class="icons">
11
+ <up-one
12
+ style="margin-bottom: -18px;"
13
+ theme="filled"
14
+ size="18"
15
+ :fill="option.value === currentField && currentOrder === 'asc' ? '#1989fa':'#ddd'"
16
+ />
17
+ <down-one
18
+ style="margin-top: 5px;margin-bottom: -2px;"
19
+ theme="filled"
20
+ size="18"
21
+ :fill="option.value === currentField && currentOrder === 'desc' ? '#1989fa':'#ddd'"
22
+ />
23
+
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { computed, ref, watch } from 'vue'
31
+ import { UpOne,DownOne } from '@icon-park/vue-next'
32
+ const props = defineProps({
33
+ modelValue: {
34
+ type: Object,
35
+ default: () => ({ field: '', order: 'desc' })
36
+ },
37
+ options: {
38
+ type: Array,
39
+ default: () => []
40
+ },
41
+ defaultOrder: {
42
+ type: String,
43
+ default: 'desc'
44
+ }
45
+ })
46
+
47
+ const emit = defineEmits(['update:modelValue', 'change'])
48
+
49
+ const currentField = ref(props.modelValue.field || props.options[0]?.value || '')
50
+ const currentOrder = ref(props.modelValue.order || props.defaultOrder)
51
+
52
+ const normalizedOptions = computed(() =>
53
+ (props.options.length ? props.options : [{ label: '默认排序', value: 'default' }]).map(
54
+ (item) => ({
55
+ ...item,
56
+ value: item.value ?? item.label
57
+ })
58
+ )
59
+ )
60
+
61
+ watch(
62
+ () => props.modelValue,
63
+ (val) => {
64
+ if (!val) return
65
+ if (val.field !== undefined) currentField.value = val.field
66
+ if (val.order !== undefined) currentOrder.value = val.order
67
+ }
68
+ )
69
+
70
+ const emitChange = () => {
71
+ const payload = {
72
+ field: currentField.value,
73
+ order: currentOrder.value
74
+ }
75
+ emit('update:modelValue', payload)
76
+ emit('change', payload)
77
+ }
78
+
79
+ const handleSelect = (option) => {
80
+ if (currentField.value === option.value) {
81
+ currentOrder.value = currentOrder.value === 'desc' ? 'asc' : 'desc'
82
+ } else {
83
+ currentField.value = option.value
84
+ currentOrder.value = option.defaultOrder || props.defaultOrder
85
+ }
86
+ emitChange()
87
+ }
88
+ </script>
89
+
90
+ <style scoped lang="scss">
91
+ .mobile-sort-bar {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ padding: 8px 12px;
96
+ background-color: #fff;
97
+ border-radius: 12px;
98
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
99
+ gap: 8px;
100
+ }
101
+
102
+ .sort-item {
103
+ flex: 1;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ gap: 4px;
108
+ padding: 6px;
109
+ border-radius: 999px;
110
+ color: #666;
111
+ font-size: 14px;
112
+ border: 1px solid transparent;
113
+
114
+ .icons {
115
+ display: flex;
116
+ flex-direction: column;
117
+ line-height: 1;
118
+ }
119
+
120
+ .icon {
121
+ font-size: 12px;
122
+ color: #c8c9cc;
123
+ &.on {
124
+ color: #1989fa;
125
+ }
126
+ }
127
+
128
+ &.active {
129
+ color: #1989fa;
130
+ border-color: rgba(25, 137, 250, 0.2);
131
+ background-color: rgba(25, 137, 250, 0.08);
132
+ }
133
+ }
134
+ </style>
135
+
@@ -0,0 +1,378 @@
1
+ <template>
2
+ <div class="chat-form">
3
+ <van-form :model="formData" ref="formRef">
4
+ <template v-for="field in schema" :key="field.field">
5
+ <!-- 输入框 -->
6
+ <van-field
7
+ v-if="field.type === 'input'"
8
+ v-model="formData[field.field]"
9
+ :label="field.label"
10
+ :placeholder="field.placeholder"
11
+ :rules="field.rules"
12
+ :required="field.rules?.some(rule => rule.required)"
13
+ />
14
+
15
+ <!-- 文本域 -->
16
+ <van-field
17
+ v-else-if="field.type === 'textarea'"
18
+ v-model="formData[field.field]"
19
+ :label="field.label"
20
+ type="textarea"
21
+ :placeholder="field.placeholder"
22
+ :rows="field.rows || 2"
23
+ :autosize="field.autosize || false"
24
+ />
25
+
26
+ <!-- 选择器 -->
27
+ <van-field
28
+ v-else-if="field.type === 'select'"
29
+ v-model="formData[field.field]"
30
+ :label="field.label"
31
+ is-link
32
+ readonly
33
+ :placeholder="field.placeholder"
34
+ @click="() => showPicker(field)"
35
+ >
36
+ <template #input>
37
+ <span v-if="formData[field.field]">{{ getOptionLabel(field, formData[field.field]) }}</span>
38
+ </template>
39
+ </van-field>
40
+
41
+ <!-- 单选框 -->
42
+ <van-cell-group v-else-if="field.type === 'radio'" :title="field.label">
43
+ <van-radio-group v-model="formData[field.field]" direction="horizontal">
44
+ <van-radio
45
+ v-for="option in field.options"
46
+ :key="option.value"
47
+ :name="option.value"
48
+ icon-size="16px"
49
+ checked-color="#1989fa"
50
+ >
51
+ {{ option.label }}
52
+ </van-radio>
53
+ </van-radio-group>
54
+ </van-cell-group>
55
+
56
+ <!-- 复选框 -->
57
+ <van-cell-group v-else-if="field.type === 'checkbox'" :title="field.label">
58
+ <van-checkbox-group v-model="formData[field.field]" direction="horizontal">
59
+ <van-checkbox
60
+ v-for="option in field.options"
61
+ :key="option.value"
62
+ :name="option.value"
63
+ icon-size="16px"
64
+ checked-color="#1989fa"
65
+ >
66
+ {{ option.label }}
67
+ </van-checkbox>
68
+ </van-checkbox-group>
69
+ </van-cell-group>
70
+
71
+ <!-- 日期选择 -->
72
+ <van-field
73
+ v-else-if="field.type === 'date'"
74
+ v-model="formData[field.field]"
75
+ :label="field.label"
76
+ is-link
77
+ readonly
78
+ :placeholder="field.placeholder"
79
+ @click="() => showDatePicker(field)"
80
+ />
81
+
82
+ <!-- 日期范围 -->
83
+ <van-cell-group v-else-if="field.type === 'dateRange'" :title="field.label">
84
+ <van-cell
85
+ :title="`${field.label}开始`"
86
+ :value="formData[field.field]?.[0] || '请选择'"
87
+ is-link
88
+ @click="() => showDateRangePicker(field, 0)"
89
+ />
90
+ <van-cell
91
+ :title="`${field.label}结束`"
92
+ :value="formData[field.field]?.[1] || '请选择'"
93
+ is-link
94
+ @click="() => showDateRangePicker(field, 1)"
95
+ />
96
+ </van-cell-group>
97
+ </template>
98
+
99
+ <!-- 操作按钮 -->
100
+ <div class="form-actions" v-if="showActions">
101
+ <van-button
102
+ v-if="submitButtonLabel"
103
+ type="primary"
104
+ block
105
+ :loading="isSubmitting"
106
+ @click="onSubmit"
107
+ >
108
+ {{ submitButtonLabel }}
109
+ </van-button>
110
+ <van-button
111
+ v-if="resetButtonLabel"
112
+ type="default"
113
+ block
114
+ @click="onReset"
115
+ >
116
+ {{ resetButtonLabel }}
117
+ </van-button>
118
+ </div>
119
+ </van-form>
120
+
121
+ <!-- 选择器弹窗 -->
122
+ <van-popup v-model:show="showPickerDialog" position="bottom">
123
+ <van-picker
124
+ :columns="currentPickerColumns"
125
+ @confirm="onPickerConfirm"
126
+ @cancel="showPickerDialog = false"
127
+ />
128
+ </van-popup>
129
+
130
+ <!-- 日期选择器弹窗 -->
131
+ <van-popup v-model:show="showDatePickerDialog" position="bottom">
132
+ <van-date-picker
133
+ v-model="currentDateValue"
134
+ @confirm="onDatePickerConfirm"
135
+ @cancel="showDatePickerDialog = false"
136
+ />
137
+ </van-popup>
138
+
139
+ <!-- 日期范围选择器弹窗 -->
140
+ <van-popup v-model:show="showDateRangePickerDialog" position="bottom">
141
+ <van-date-picker
142
+ v-model="currentDateRangeValue"
143
+ @confirm="onDateRangePickerConfirm"
144
+ @cancel="showDateRangePickerDialog = false"
145
+ />
146
+ </van-popup>
147
+ </div>
148
+ </template>
149
+
150
+ <script setup>
151
+ import { ref, reactive, watch, onMounted } from 'vue'
152
+ import { showToast } from 'vant'
153
+
154
+ // 定义组件属性
155
+ const props = defineProps({
156
+ modelValue: {
157
+ type: Object,
158
+ default: () => ({})
159
+ },
160
+ schema: {
161
+ type: Array,
162
+ required: true
163
+ },
164
+ submitButtonLabel: {
165
+ type: String,
166
+ default: '提交'
167
+ },
168
+ resetButtonLabel: {
169
+ type: String,
170
+ default: '重置'
171
+ },
172
+ showActions: {
173
+ type: Boolean,
174
+ default: true
175
+ }
176
+ })
177
+
178
+ // 定义事件
179
+ const emit = defineEmits(['update:modelValue', 'submit', 'change'])
180
+
181
+ // 表单数据
182
+ const formData = ref({})
183
+
184
+ // 表单引用
185
+ const formRef = ref(null)
186
+
187
+ // 提交状态
188
+ const isSubmitting = ref(false)
189
+
190
+ // 弹窗相关
191
+ const showPickerDialog = ref(false)
192
+ const showDatePickerDialog = ref(false)
193
+ const showDateRangePickerDialog = ref(false)
194
+ const currentField = ref(null)
195
+ const currentPickerColumns = ref([])
196
+ const currentDateValue = ref(new Date())
197
+ const currentDateRangeValue = ref([new Date(), new Date()])
198
+
199
+ // 初始化表单数据
200
+ const initializeFormData = () => {
201
+ const data = { ...props.modelValue }
202
+
203
+ props.schema.forEach(field => {
204
+ // 如果没有初始值但有默认值,则使用默认值
205
+ if (!(field.field in data) && 'default' in field) {
206
+ data[field.field] = field.default
207
+ }
208
+
209
+ // 确保所有字段都在数据中
210
+ if (!(field.field in data)) {
211
+ if (field.type === 'checkbox') {
212
+ data[field.field] = []
213
+ } else if (field.type === 'dateRange') {
214
+ data[field.field] = []
215
+ } else {
216
+ data[field.field] = ''
217
+ }
218
+ }
219
+ })
220
+
221
+ formData.value = data
222
+ }
223
+
224
+ // 获取选项标签
225
+ const getOptionLabel = (field, value) => {
226
+ const option = field.options.find(opt => opt.value === value)
227
+ return option ? option.label : value
228
+ }
229
+
230
+ // 显示选择器
231
+ const showPicker = (field) => {
232
+ currentField.value = field
233
+ currentPickerColumns.value = field.options.map(option => ({
234
+ text: option.label,
235
+ value: option.value
236
+ }))
237
+ showPickerDialog.value = true
238
+ }
239
+
240
+ // 选择器确认
241
+ const onPickerConfirm = ({ selectedValues }) => {
242
+ const value = selectedValues[0]
243
+ formData.value[currentField.value.field] = value
244
+ emit('update:modelValue', formData.value)
245
+ emit('change', { field: currentField.value.field, value })
246
+ showPickerDialog.value = false
247
+ }
248
+
249
+ // 显示日期选择器
250
+ const showDatePicker = (field) => {
251
+ currentField.value = field
252
+ currentDateValue.value = formData.value[field.field] ? new Date(formData.value[field.field]) : new Date()
253
+ showDatePickerDialog.value = true
254
+ }
255
+
256
+ // 日期选择器确认
257
+ const onDatePickerConfirm = ({ selectedValues }) => {
258
+ const date = new Date(selectedValues[0], selectedValues[1] - 1, selectedValues[2])
259
+ const dateString = date.toISOString().split('T')[0]
260
+ formData.value[currentField.value.field] = dateString
261
+ emit('update:modelValue', formData.value)
262
+ emit('change', { field: currentField.value.field, value: dateString })
263
+ showDatePickerDialog.value = false
264
+ }
265
+
266
+ // 显示日期范围选择器
267
+ const showDateRangePicker = (field, index) => {
268
+ currentField.value = field
269
+ const range = formData.value[field.field] || []
270
+ if (range[index]) {
271
+ currentDateRangeValue.value[index] = new Date(range[index])
272
+ }
273
+ showDateRangePickerDialog.value = true
274
+ }
275
+
276
+ // 日期范围选择器确认
277
+ const onDateRangePickerConfirm = ({ selectedValues }) => {
278
+ const startDate = new Date(selectedValues[0][0], selectedValues[0][1] - 1, selectedValues[0][2])
279
+ const endDate = new Date(selectedValues[1][0], selectedValues[1][1] - 1, selectedValues[1][2])
280
+
281
+ const range = [...(formData.value[currentField.value.field] || [])]
282
+ range[0] = startDate.toISOString().split('T')[0]
283
+ range[1] = endDate.toISOString().split('T')[0]
284
+
285
+ formData.value[currentField.value.field] = range
286
+ emit('update:modelValue', formData.value)
287
+ emit('change', { field: currentField.value.field, value: range })
288
+ showDateRangePickerDialog.value = false
289
+ }
290
+
291
+ // 提交表单
292
+ const onSubmit = async () => {
293
+ if (formRef.value) {
294
+ try {
295
+ await formRef.value.validate()
296
+ isSubmitting.value = true
297
+ emit('submit', formData.value)
298
+ } catch (error) {
299
+ showToast(error.message || '请检查表单填写是否正确')
300
+ } finally {
301
+ isSubmitting.value = false
302
+ }
303
+ }
304
+ }
305
+
306
+ // 重置表单
307
+ const onReset = () => {
308
+ initializeFormData()
309
+ emit('update:modelValue', formData.value)
310
+ }
311
+
312
+ // 监听表单数据变化
313
+ watch(formData, (newVal) => {
314
+ emit('update:modelValue', newVal)
315
+ }, { deep: true })
316
+
317
+ // 监听外部传入的值变化
318
+ watch(() => props.modelValue, (newVal) => {
319
+ formData.value = { ...newVal }
320
+ }, { deep: true })
321
+
322
+ // 组件挂载时初始化数据
323
+ onMounted(() => {
324
+ initializeFormData()
325
+ })
326
+ </script>
327
+
328
+ <style lang="scss" scoped>
329
+ .chat-form {
330
+ background: #fff;
331
+ border-radius: 12px;
332
+ padding: 16px;
333
+ margin: 8px 0;
334
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
335
+
336
+ // 当在消息气泡中显示时,调整样式
337
+ :deep(.message-bubble) & {
338
+ background: transparent;
339
+ box-shadow: none;
340
+ margin: 0;
341
+ }
342
+
343
+ .form-actions {
344
+ margin-top: 20px;
345
+ display: flex;
346
+ flex-direction: column;
347
+ gap: 12px;
348
+ }
349
+
350
+ :deep(.van-cell-group) {
351
+ margin-bottom: 16px;
352
+
353
+ .van-cell {
354
+ padding: 12px 0;
355
+ }
356
+ }
357
+
358
+ :deep(.van-radio-group--horizontal) {
359
+ flex-wrap: wrap;
360
+ gap: 8px;
361
+ padding: 8px 0;
362
+ }
363
+
364
+ :deep(.van-checkbox-group--horizontal) {
365
+ flex-wrap: wrap;
366
+ gap: 8px;
367
+ padding: 8px 0;
368
+ }
369
+
370
+ :deep(.van-radio) {
371
+ margin-bottom: 4px;
372
+ }
373
+
374
+ :deep(.van-checkbox) {
375
+ margin-bottom: 4px;
376
+ }
377
+ }
378
+ </style>