vue2-components-plus 1.0.3 → 1.0.6

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.
@@ -0,0 +1,851 @@
1
+ <template>
2
+ <div class="demo-page">
3
+ <el-card shadow="never" class="demo-card">
4
+ <div slot="header" class="demo-card__header">
5
+ <div>
6
+ <div class="demo-card__title">NsForm 动态表单预览</div>
7
+ <div class="demo-card__desc">覆盖校验、回填、只读、级联、上传和动态表单项等常见场景。</div>
8
+ </div>
9
+ <el-tag size="small" :type="demoReadOnly ? 'info' : 'success'">
10
+ {{ demoReadOnly ? '只读模式' : '编辑模式' }}
11
+ </el-tag>
12
+ </div>
13
+
14
+ <el-alert
15
+ v-if="hintText"
16
+ :title="hintText"
17
+ type="info"
18
+ :closable="false"
19
+ class="demo-alert"
20
+ />
21
+
22
+ <div class="toolbar">
23
+ <el-button type="primary" @click="getFormData">获取表单数据</el-button>
24
+ <el-button @click="loadDetailData()">模拟详情回填</el-button>
25
+ <el-button @click="resetFormData()">重置表单</el-button>
26
+ <el-button @click="toggleReadOnly">切换只读</el-button>
27
+ <el-button @click="notifyInnerButton">触发自定义事件</el-button>
28
+ <el-button v-if="insideDialog" type="danger" plain @click="$emit('close')">从内容区关闭弹窗</el-button>
29
+ </div>
30
+
31
+ <el-form ref="shellForm" :model="formState" label-position="top" class="shell-form">
32
+ <NsFormTitle title="模型参数">
33
+ <NsForm
34
+ ref="row1Ref"
35
+ :readOnly="demoReadOnly"
36
+ :model="demoReadOnly ? '' : 'vertical'"
37
+ :rows="formState.rows"
38
+ formPropKey="rows"
39
+ backgroundColor="#fff"
40
+ labelColor="#606266"
41
+ labelWidth="140"
42
+ gapH="20px"
43
+ gapV="10px"
44
+ />
45
+ </NsFormTitle>
46
+
47
+ <NsFormTitle title="视频配置">
48
+ <NsForm
49
+ ref="row2Ref"
50
+ :readOnly="demoReadOnly"
51
+ :model="demoReadOnly ? '' : 'vertical'"
52
+ :rows="formState.rows2"
53
+ formPropKey="rows2"
54
+ backgroundColor="#fff"
55
+ labelColor="#606266"
56
+ labelWidth="140"
57
+ gapH="20px"
58
+ gapV="10px"
59
+ />
60
+ </NsFormTitle>
61
+
62
+ <NsFormTitle title="结果保存">
63
+ <NsForm
64
+ ref="row3Ref"
65
+ :readOnly="demoReadOnly"
66
+ :model="demoReadOnly ? '' : 'vertical'"
67
+ :rows="formState.rows3"
68
+ formPropKey="rows3"
69
+ backgroundColor="#fff"
70
+ labelColor="#606266"
71
+ labelWidth="140"
72
+ gapH="20px"
73
+ gapV="10px"
74
+ />
75
+ </NsFormTitle>
76
+
77
+ <NsFormTitle title="级联选择器">
78
+ <NsForm
79
+ ref="row4Ref"
80
+ :readOnly="demoReadOnly"
81
+ :model="demoReadOnly ? '' : 'vertical'"
82
+ :rows="formState.rows4"
83
+ formPropKey="rows4"
84
+ backgroundColor="#fff"
85
+ labelColor="#606266"
86
+ labelWidth="140"
87
+ gapH="20px"
88
+ gapV="10px"
89
+ />
90
+ </NsFormTitle>
91
+
92
+ <NsFormTitle title="文件上传">
93
+ <NsForm
94
+ ref="rowUploadRef"
95
+ :readOnly="demoReadOnly"
96
+ :model="demoReadOnly ? '' : 'vertical'"
97
+ :rows="formState.rowsUpload"
98
+ formPropKey="rowsUpload"
99
+ backgroundColor="#fff"
100
+ labelColor="#606266"
101
+ labelWidth="140"
102
+ gapH="20px"
103
+ gapV="10px"
104
+ />
105
+ </NsFormTitle>
106
+ </el-form>
107
+ </el-card>
108
+
109
+ <el-card shadow="never" class="result-card">
110
+ <div slot="header" class="result-card__header">
111
+ <span>输出结果</span>
112
+ <el-button type="text" @click="showToast('已通过组件实例调用方法')">调用实例方法示例</el-button>
113
+ </div>
114
+ <pre class="result-content">{{ outputText || '点击“获取表单数据”后在此查看结果。' }}</pre>
115
+ </el-card>
116
+ </div>
117
+ </template>
118
+
119
+ <script>
120
+ const CustomRegionEditor = {
121
+ name: 'CustomRegionEditor',
122
+ props: {
123
+ value: {
124
+ type: String,
125
+ default: '',
126
+ },
127
+ },
128
+ methods: {
129
+ handleInput(value) {
130
+ this.$emit('input', value)
131
+ },
132
+ },
133
+ render(h) {
134
+ return h('div', { class: 'custom-region-editor' }, [
135
+ h('div', { class: 'custom-region-editor__label' }, '模拟区域编辑器'),
136
+ h('el-input', {
137
+ props: {
138
+ type: 'textarea',
139
+ rows: 3,
140
+ value: this.value,
141
+ placeholder: '请输入区域 JSON 或坐标信息',
142
+ },
143
+ on: {
144
+ input: this.handleInput,
145
+ },
146
+ }),
147
+ ])
148
+ },
149
+ }
150
+
151
+ function createRows() {
152
+ return [
153
+ [
154
+ {
155
+ key: 'isEnable',
156
+ label: '是否启用',
157
+ value: false,
158
+ component: 'ElSwitch',
159
+ events: {},
160
+ params: {
161
+ activeText: '启用',
162
+ inactiveText: '禁用',
163
+ },
164
+ },
165
+ {
166
+ key: 'modelName',
167
+ label: '模型名称',
168
+ value: 'demo-detector',
169
+ component: 'ElInput',
170
+ params: {
171
+ clearable: true,
172
+ maxlength: 30,
173
+ rules: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
174
+ },
175
+ },
176
+ ],
177
+ [
178
+ {
179
+ key: 'confidence',
180
+ label: '置信度阈值',
181
+ value: '0.60',
182
+ component: 'ElInput',
183
+ params: {
184
+ clearable: true,
185
+ 'v-length.range': { min: 0, max: 1 },
186
+ rules: [{ required: true, message: '请输入置信度', trigger: 'blur' }],
187
+ },
188
+ },
189
+ {
190
+ key: 'iou',
191
+ label: 'IOU 阈值',
192
+ value: '0.45',
193
+ component: 'ElInput',
194
+ params: {
195
+ clearable: true,
196
+ 'v-length.range': { min: 0, max: 1 },
197
+ rules: [{ required: true, message: '请输入 IOU', trigger: 'blur' }],
198
+ },
199
+ },
200
+ ],
201
+ ]
202
+ }
203
+
204
+ function createRows2() {
205
+ return [
206
+ [
207
+ {
208
+ key: 'timeInterval',
209
+ label: '时间间隔(秒)',
210
+ value: '5',
211
+ component: 'ElInput',
212
+ params: {
213
+ clearable: true,
214
+ 'v-length.range': { min: 0, max: 6000, int: true },
215
+ rules: [{ required: true, message: '请输入时间间隔', trigger: 'blur' }],
216
+ },
217
+ },
218
+ {
219
+ key: 'stuck_threshold',
220
+ label: '所属工程',
221
+ value: ['component', 'form'],
222
+ component: 'ElCascader',
223
+ params: {
224
+ clearable: true,
225
+ props: {
226
+ checkStrictly: true,
227
+ emitPath: true,
228
+ },
229
+ options: [
230
+ {
231
+ value: 'guide',
232
+ label: 'Guide',
233
+ children: [
234
+ { value: 'disciplines', label: 'Disciplines' },
235
+ { value: 'navigation', label: 'Navigation' },
236
+ ],
237
+ },
238
+ {
239
+ value: 'component',
240
+ label: '组件库',
241
+ children: [
242
+ { value: 'form', label: 'Form' },
243
+ { value: 'table', label: 'Table' },
244
+ ],
245
+ },
246
+ ],
247
+ rules: [{ required: true, message: '请选择所属工程', trigger: 'change' }],
248
+ },
249
+ },
250
+ ],
251
+ [
252
+ {
253
+ key: 'maxRetries',
254
+ label: '最大重连次数',
255
+ value: '3',
256
+ component: 'ElInput',
257
+ params: {
258
+ clearable: true,
259
+ 'v-length.range': { min: 0, max: 20, int: true },
260
+ rules: [{ required: true, message: '请输入重连次数', trigger: 'blur' }],
261
+ },
262
+ },
263
+ {
264
+ key: 'streamUrl',
265
+ label: '视频流地址',
266
+ value: 'rtsp://example.com/live/001',
267
+ component: 'ElInput',
268
+ params: {
269
+ clearable: true,
270
+ rules: [{ required: true, message: '请输入视频流地址', trigger: 'blur' }],
271
+ },
272
+ },
273
+ ],
274
+ ]
275
+ }
276
+
277
+ function createRows3() {
278
+ return [
279
+ [
280
+ {
281
+ key: 'saveVideo',
282
+ label: '是否保存视频',
283
+ value: true,
284
+ component: 'ElRadioGroup',
285
+ params: {
286
+ options: [
287
+ { label: '是', value: true },
288
+ { label: '否', value: false },
289
+ ],
290
+ rules: [{ required: true, message: '请选择是否保存视频', trigger: 'change' }],
291
+ },
292
+ },
293
+ {
294
+ key: 'preBufferSecond',
295
+ label: '帧前缓存(秒)',
296
+ value: '10',
297
+ component: 'ElInput',
298
+ params: {
299
+ clearable: true,
300
+ 'v-length.range': { min: 0, max: 120, int: true },
301
+ rules: [{ required: true, message: '请输入缓存时长', trigger: 'blur' }],
302
+ },
303
+ },
304
+ ],
305
+ [
306
+ {
307
+ key: 'det_area_mode',
308
+ label: '检测区域模式',
309
+ value: 'normal',
310
+ component: 'ElRadioGroup',
311
+ events: {},
312
+ params: {
313
+ options: [
314
+ { label: '常规检测', value: 'normal' },
315
+ { label: '非常规检测', value: 'abnormal' },
316
+ ],
317
+ rules: [{ required: true, message: '请选择区域模式', trigger: 'change' }],
318
+ },
319
+ },
320
+ ],
321
+ ]
322
+ }
323
+
324
+ function createRows4() {
325
+ return [
326
+ [
327
+ {
328
+ key: 'region',
329
+ label: '地区选择',
330
+ value: ['beijing', 'chaoyang'],
331
+ component: 'ElCascader',
332
+ params: {
333
+ props: {
334
+ multiple: false,
335
+ checkStrictly: true,
336
+ emitPath: true,
337
+ },
338
+ showAllLevels: false,
339
+ options: [
340
+ {
341
+ value: 'beijing',
342
+ label: '北京市',
343
+ children: [
344
+ { value: 'chaoyang', label: '朝阳区' },
345
+ { value: 'haidian', label: '海淀区' },
346
+ ],
347
+ },
348
+ {
349
+ value: 'shanghai',
350
+ label: '上海市',
351
+ children: [{ value: 'pudong', label: '浦东新区' }],
352
+ },
353
+ ],
354
+ },
355
+ },
356
+ {
357
+ key: 'department',
358
+ label: '部门选择',
359
+ value: ['company', 'tech', 'frontend'],
360
+ component: 'ElCascader',
361
+ params: {
362
+ props: {
363
+ value: 'code',
364
+ label: 'name',
365
+ children: 'children',
366
+ checkStrictly: true,
367
+ emitPath: true,
368
+ },
369
+ separator: ' / ',
370
+ options: [
371
+ {
372
+ code: 'company',
373
+ name: '公司总部',
374
+ children: [
375
+ {
376
+ code: 'tech',
377
+ name: '技术部',
378
+ children: [
379
+ { code: 'frontend', name: '前端组' },
380
+ { code: 'backend', name: '后端组' },
381
+ ],
382
+ },
383
+ { code: 'sales', name: '销售部' },
384
+ ],
385
+ },
386
+ ],
387
+ },
388
+ },
389
+ ],
390
+ [
391
+ {
392
+ key: 'singleLevelCascader',
393
+ label: '单层级联',
394
+ value: 'shanghai',
395
+ component: 'ElCascader',
396
+ params: {
397
+ options: [
398
+ { value: 'beijing', label: '北京市' },
399
+ { value: 'shanghai', label: '上海市' },
400
+ { value: 'guangzhou', label: '广州市' },
401
+ ],
402
+ },
403
+ },
404
+ {
405
+ key: 'notifyEmails',
406
+ label: '通知邮箱',
407
+ value: 'demo@example.com',
408
+ component: 'ElInput',
409
+ params: {
410
+ clearable: true,
411
+ },
412
+ },
413
+ ],
414
+ ]
415
+ }
416
+
417
+ function createRowsUpload() {
418
+ return [
419
+ [
420
+ {
421
+ key: 'upload_file',
422
+ label: '上传模型文件',
423
+ value: [],
424
+ component: 'ElUpload',
425
+ events: {},
426
+ params: {
427
+ drag: true,
428
+ multiple: true,
429
+ action: '#',
430
+ limit: 2,
431
+ fileList: [],
432
+ accept: '.txt,.md,.json,.jpg,.png,.pdf',
433
+ autoUpload: true,
434
+ httpRequest: null,
435
+ rules: [{ required: true, message: '请上传至少一个文件', trigger: 'change' }],
436
+ },
437
+ slots: {},
438
+ },
439
+ ],
440
+ ]
441
+ }
442
+
443
+ function createDetailData() {
444
+ return {
445
+ isEnable: true,
446
+ modelName: 'helmet-detector-v2',
447
+ confidence: '0.72',
448
+ iou: '0.33',
449
+ timeInterval: '3',
450
+ stuck_threshold: ['component', 'table'],
451
+ maxRetries: '5',
452
+ streamUrl: 'rtsp://example.com/live/demo',
453
+ saveVideo: true,
454
+ preBufferSecond: '8',
455
+ det_area_mode: 'abnormal',
456
+ det_area_json: '{"x":10,"y":12,"width":320,"height":180}',
457
+ region: ['shanghai', 'pudong'],
458
+ department: ['company', 'tech', 'backend'],
459
+ singleLevelCascader: 'guangzhou',
460
+ notifyEmails: 'ops@example.com',
461
+ upload_file: [
462
+ {
463
+ name: '示例说明.md',
464
+ url: 'https://cdn.jsdelivr.net/gh/vuejs/art@master/logo.png',
465
+ fileName: '示例说明.md',
466
+ filePath: 'https://cdn.jsdelivr.net/gh/vuejs/art@master/logo.png',
467
+ fileSize: 2048,
468
+ },
469
+ ],
470
+ }
471
+ }
472
+
473
+ export default {
474
+ name: 'FormDemo',
475
+ props: {
476
+ readOnly: {
477
+ type: Boolean,
478
+ default: false,
479
+ },
480
+ insideDialog: {
481
+ type: Boolean,
482
+ default: false,
483
+ },
484
+ hintText: {
485
+ type: String,
486
+ default: '',
487
+ },
488
+ },
489
+ data() {
490
+ return {
491
+ demoReadOnly: this.readOnly,
492
+ outputText: '',
493
+ uploadFileList: [],
494
+ formState: {
495
+ rows: createRows(),
496
+ rows2: createRows2(),
497
+ rows3: createRows3(),
498
+ rows4: createRows4(),
499
+ rowsUpload: createRowsUpload(),
500
+ },
501
+ }
502
+ },
503
+ watch: {
504
+ readOnly: {
505
+ immediate: true,
506
+ handler(value) {
507
+ this.demoReadOnly = value
508
+ this.syncUploadDisabled()
509
+ },
510
+ },
511
+ demoReadOnly() {
512
+ this.syncUploadDisabled()
513
+ },
514
+ },
515
+ mounted() {
516
+ this.bindFieldEvents()
517
+ this.setUploadSlots()
518
+ this.loadDetailData(false)
519
+ },
520
+ methods: {
521
+ bindFieldEvents() {
522
+ this.formState.rows[0][0].events.change = this.handleEnableChange
523
+ this.formState.rows3[1][0].events.change = this.detAreaModeChange
524
+
525
+ const uploadField = this.getUploadField()
526
+ uploadField.params.httpRequest = this.mockUploadRequest
527
+ uploadField.events = {
528
+ success: this.handleUploadSuccess,
529
+ change: this.handleUploadChange,
530
+ remove: this.handleUploadRemove,
531
+ }
532
+ this.syncUploadDisabled()
533
+ },
534
+ setUploadSlots() {
535
+ const uploadField = this.getUploadField()
536
+ this.$set(uploadField, 'slots', {
537
+ default: () =>
538
+ this.$createElement('div', { class: 'upload-trigger' }, [
539
+ this.$createElement('i', { class: 'el-icon-upload upload-trigger__icon' }),
540
+ this.$createElement('div', { class: 'upload-trigger__title' }, '点击或拖拽文件到此处上传'),
541
+ this.$createElement('div', { class: 'upload-trigger__sub' }, '仅做本地模拟,不会真正请求后台'),
542
+ ]),
543
+ tip: () =>
544
+ this.$createElement('div', { class: 'el-upload__tip' }, '支持 txt / md / json / jpg / png / pdf,最多 2 个文件'),
545
+ })
546
+ },
547
+ getUploadField() {
548
+ return this.formState.rowsUpload[0][0]
549
+ },
550
+ getFormRefs() {
551
+ return ['row1Ref', 'row2Ref', 'row3Ref', 'row4Ref', 'rowUploadRef']
552
+ .map((name) => this.$refs[name])
553
+ .filter(Boolean)
554
+ },
555
+ syncUploadDisabled() {
556
+ const uploadField = this.getUploadField()
557
+ if (uploadField && uploadField.params) {
558
+ this.$set(uploadField.params, 'disabled', this.demoReadOnly)
559
+ }
560
+ },
561
+ toggleReadOnly() {
562
+ this.demoReadOnly = !this.demoReadOnly
563
+ },
564
+ showToast(message) {
565
+ this.$message.info(message || '来自 FormDemo 的实例方法调用')
566
+ return true
567
+ },
568
+ handleEnableChange(value) {
569
+ this.$message.info(value ? '已启用模型能力' : '已停用模型能力')
570
+ },
571
+ ensureAbnormalField(mode) {
572
+ const lastRow = this.formState.rows3[this.formState.rows3.length - 1]
573
+ const lastKey = lastRow && lastRow[0] && lastRow[0].key
574
+ if (lastKey === 'det_area_json') {
575
+ this.formState.rows3.pop()
576
+ }
577
+ if (mode === 'abnormal') {
578
+ this.formState.rows3.push([
579
+ {
580
+ key: 'det_area_json',
581
+ label: '感兴趣区域',
582
+ value: '',
583
+ component: CustomRegionEditor,
584
+ span: 24,
585
+ params: {
586
+ rules: [{ required: true, message: '请输入感兴趣区域', trigger: 'blur' }],
587
+ },
588
+ },
589
+ ])
590
+ }
591
+ },
592
+ detAreaModeChange(value) {
593
+ this.ensureAbnormalField(value)
594
+ },
595
+ validateShellForm() {
596
+ return new Promise((resolve, reject) => {
597
+ this.$refs.shellForm.validate((valid) => {
598
+ if (valid) {
599
+ resolve(true)
600
+ return
601
+ }
602
+ reject(new Error('表单校验失败'))
603
+ })
604
+ })
605
+ },
606
+ collectFormData() {
607
+ return this.getFormRefs().reduce((result, ref) => {
608
+ if (ref && typeof ref.getFormKvData === 'function') {
609
+ return Object.assign(result, ref.getFormKvData())
610
+ }
611
+ return result
612
+ }, {})
613
+ },
614
+ async getFormData() {
615
+ try {
616
+ await this.validateShellForm()
617
+ const data = this.collectFormData()
618
+ this.outputText = JSON.stringify(data, null, 2)
619
+ this.$message.success('表单校验成功')
620
+ return data
621
+ } catch (error) {
622
+ this.outputText = ''
623
+ this.$message.error('表单校验失败,请先完善必填项')
624
+ return false
625
+ }
626
+ },
627
+ resetFormData(showMessage = true) {
628
+ this.getFormRefs().forEach((ref) => {
629
+ if (ref && typeof ref.resetForm === 'function') {
630
+ ref.resetForm()
631
+ }
632
+ })
633
+ this.ensureAbnormalField('normal')
634
+ this.uploadFileList = []
635
+ const uploadField = this.getUploadField()
636
+ this.$set(uploadField.params, 'fileList', [])
637
+ this.$set(uploadField, 'value', [])
638
+ this.$set(uploadField, 'delValue', [])
639
+ this.$nextTick(() => {
640
+ this.$refs.shellForm.clearValidate()
641
+ this.outputText = ''
642
+ if (showMessage) {
643
+ this.$message.success('表单已重置')
644
+ }
645
+ })
646
+ },
647
+ loadDetailData(showMessage = true) {
648
+ if (showMessage) {
649
+ this.$message.info('开始模拟详情回填')
650
+ }
651
+ setTimeout(() => {
652
+ const detail = createDetailData()
653
+ this.resetFormData(false)
654
+ this.ensureAbnormalField(detail.det_area_mode)
655
+ this.$nextTick(() => {
656
+ this.getFormRefs().forEach((ref) => {
657
+ if (ref && typeof ref.setFormData === 'function') {
658
+ ref.setFormData(detail)
659
+ }
660
+ })
661
+ this.uploadFileList = detail.upload_file.slice()
662
+ this.$set(this.getUploadField().params, 'fileList', detail.upload_file.slice())
663
+ if (showMessage) {
664
+ this.$message.success('详情已回填')
665
+ }
666
+ })
667
+ }, 300)
668
+ },
669
+ notifyInnerButton() {
670
+ this.$emit('btnClick', this.collectFormData())
671
+ this.$message.success('已触发自定义事件')
672
+ },
673
+ normalizeUploadList(fileList) {
674
+ return (fileList || []).map((item) => {
675
+ const responseData = item && item.response && item.response.data ? item.response.data : item
676
+ const fileName = responseData.fileName || item.name || '未命名文件'
677
+ const filePath = responseData.filePath || item.url || ''
678
+ const fileSize = responseData.fileSize || item.size || (item.raw && item.raw.size) || 0
679
+ return {
680
+ name: fileName,
681
+ url: filePath,
682
+ fileName,
683
+ filePath,
684
+ fileSize,
685
+ }
686
+ })
687
+ },
688
+ syncUploadFieldValue(fileList, removedFile) {
689
+ const uploadField = this.getUploadField()
690
+ const normalizedList = this.normalizeUploadList(fileList)
691
+ this.uploadFileList = (fileList || []).slice()
692
+ this.$set(uploadField.params, 'fileList', (fileList || []).slice())
693
+ this.$set(uploadField, 'value', normalizedList)
694
+ if (removedFile) {
695
+ const removed = this.normalizeUploadList([removedFile])[0]
696
+ const delValue = Array.isArray(uploadField.delValue) ? uploadField.delValue.slice() : []
697
+ delValue.push(Object.assign({}, removed, { isDelete: 1 }))
698
+ this.$set(uploadField, 'delValue', delValue)
699
+ }
700
+ this.$nextTick(() => {
701
+ this.$refs.shellForm.validateField('rowsUpload.0.0.value', function () {})
702
+ })
703
+ },
704
+ mockUploadRequest(options) {
705
+ const file = options.file
706
+ const timer = setTimeout(() => {
707
+ const filePath = URL.createObjectURL(file)
708
+ const response = {
709
+ code: 0,
710
+ data: {
711
+ fileName: file.name,
712
+ filePath,
713
+ fileSize: file.size || 0,
714
+ },
715
+ }
716
+ if (typeof options.onSuccess === 'function') {
717
+ options.onSuccess(response, file)
718
+ }
719
+ }, 400)
720
+
721
+ return {
722
+ abort() {
723
+ clearTimeout(timer)
724
+ if (typeof options.onError === 'function') {
725
+ options.onError(new Error('上传已取消'))
726
+ }
727
+ },
728
+ }
729
+ },
730
+ handleUploadSuccess(_response, _file, fileList) {
731
+ this.syncUploadFieldValue(fileList)
732
+ this.$message.success('文件上传成功')
733
+ },
734
+ handleUploadChange(_file, fileList) {
735
+ this.syncUploadFieldValue(fileList)
736
+ },
737
+ handleUploadRemove(file, fileList) {
738
+ this.syncUploadFieldValue(fileList, file)
739
+ this.$message.warning('已移除文件')
740
+ },
741
+ },
742
+ }
743
+ </script>
744
+
745
+ <style scoped>
746
+ .demo-page {
747
+ display: flex;
748
+ flex-direction: column;
749
+ gap: 20px;
750
+ }
751
+
752
+ .demo-card,
753
+ .result-card {
754
+ border-radius: 12px;
755
+ }
756
+
757
+ .demo-card__header,
758
+ .result-card__header {
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: space-between;
762
+ }
763
+
764
+ .demo-card__title {
765
+ font-size: 18px;
766
+ font-weight: 700;
767
+ color: #303133;
768
+ }
769
+
770
+ .demo-card__desc {
771
+ margin-top: 6px;
772
+ font-size: 13px;
773
+ color: #909399;
774
+ }
775
+
776
+ .demo-alert {
777
+ margin-bottom: 16px;
778
+ }
779
+
780
+ .toolbar {
781
+ display: flex;
782
+ flex-wrap: wrap;
783
+ gap: 12px;
784
+ margin-bottom: 20px;
785
+ }
786
+
787
+ .shell-form {
788
+ display: flex;
789
+ flex-direction: column;
790
+ gap: 16px;
791
+ }
792
+
793
+ .result-content {
794
+ margin: 0;
795
+ padding: 16px;
796
+ min-height: 180px;
797
+ max-height: 360px;
798
+ overflow: auto;
799
+ white-space: pre-wrap;
800
+ word-break: break-all;
801
+ background: #0f172a;
802
+ border-radius: 10px;
803
+ color: #e2e8f0;
804
+ font-size: 13px;
805
+ line-height: 1.6;
806
+ }
807
+
808
+ .custom-region-editor {
809
+ border: 1px dashed #dcdfe6;
810
+ border-radius: 8px;
811
+ padding: 12px;
812
+ background: #fafafa;
813
+ }
814
+
815
+ .custom-region-editor__label {
816
+ margin-bottom: 8px;
817
+ font-size: 12px;
818
+ font-weight: 600;
819
+ color: #409eff;
820
+ }
821
+
822
+ .upload-trigger {
823
+ padding: 12px;
824
+ text-align: center;
825
+ color: #606266;
826
+ }
827
+
828
+ .upload-trigger__icon {
829
+ font-size: 28px;
830
+ color: #409eff;
831
+ }
832
+
833
+ .upload-trigger__title {
834
+ margin-top: 8px;
835
+ font-size: 14px;
836
+ font-weight: 600;
837
+ }
838
+
839
+ .upload-trigger__sub {
840
+ margin-top: 6px;
841
+ font-size: 12px;
842
+ color: #909399;
843
+ }
844
+
845
+ @media (max-width: 960px) {
846
+ .toolbar {
847
+ flex-direction: column;
848
+ align-items: stretch;
849
+ }
850
+ }
851
+ </style>