vue2-client 1.18.44 → 1.18.46

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,154 @@
1
+ <template>
2
+ <div class="image-preview-demo">
3
+ <a-card title="ImagePreviewModal 图片预览组件示例">
4
+ <a-space direction="vertical" :size="24" style="width: 100%">
5
+ <!-- 基础用法 -->
6
+ <a-card title="基础用法" size="small">
7
+ <p class="demo-desc">点击图片或按钮打开预览模态框,支持缩放、旋转、拖拽和下载功能</p>
8
+ <a-space>
9
+ <img
10
+ :src="demoImages[0]"
11
+ alt="示例图片1"
12
+ class="thumbnail"
13
+ @click="openPreview(demoImages[0], '风景图片')"
14
+ />
15
+ <a-button type="primary" @click="openPreview(demoImages[0], '风景图片')">
16
+ <a-icon type="eye" />
17
+ 预览图片
18
+ </a-button>
19
+ </a-space>
20
+ </a-card>
21
+
22
+ <!-- 多图片切换 -->
23
+ <a-card title="多图片预览" size="small">
24
+ <p class="demo-desc">点击不同图片进行预览</p>
25
+ <a-space>
26
+ <img
27
+ v-for="(img, index) in demoImages"
28
+ :key="index"
29
+ :src="img"
30
+ :alt="`示例图片${index + 1}`"
31
+ class="thumbnail"
32
+ @click="openPreview(img, `示例图片${index + 1}`)"
33
+ />
34
+ </a-space>
35
+ </a-card>
36
+
37
+ <!-- 功能说明 -->
38
+ <a-card title="功能说明" size="small">
39
+ <a-descriptions :column="1" bordered size="small">
40
+ <a-descriptions-item label="缩放">鼠标滚轮或点击工具栏 +/- 按钮,支持 10%-300% 缩放</a-descriptions-item>
41
+ <a-descriptions-item label="旋转">点击工具栏旋转按钮,每次旋转 90°</a-descriptions-item>
42
+ <a-descriptions-item label="拖拽">鼠标左键按住图片拖动</a-descriptions-item>
43
+ <a-descriptions-item label="下载">点击下载按钮保存图片到本地</a-descriptions-item>
44
+ <a-descriptions-item label="关闭">点击关闭按钮、遮罩层或按 ESC 键关闭</a-descriptions-item>
45
+ </a-descriptions>
46
+ </a-card>
47
+
48
+ <!-- API 文档 -->
49
+ <a-card title="API 属性" size="small">
50
+ <a-table :columns="apiColumns" :data-source="apiData" :pagination="false" size="small" />
51
+ </a-card>
52
+
53
+ <!-- 事件文档 -->
54
+ <a-card title="事件" size="small">
55
+ <a-table :columns="eventColumns" :data-source="eventData" :pagination="false" size="small" />
56
+ </a-card>
57
+ </a-space>
58
+ </a-card>
59
+
60
+ <!-- 图片预览组件 -->
61
+ <image-preview-modal
62
+ :visible="previewVisible"
63
+ :image-url="previewImage"
64
+ :image-name="previewImageName"
65
+ @close="previewVisible = false"
66
+ />
67
+ </div>
68
+ </template>
69
+
70
+ <script>
71
+ import ImagePreviewModal from './ImagePreviewModal.vue'
72
+
73
+ export default {
74
+ name: 'ImagePreviewModalDemo',
75
+ components: {
76
+ ImagePreviewModal
77
+ },
78
+ data() {
79
+ return {
80
+ // 预览状态
81
+ previewVisible: false,
82
+ previewImage: '',
83
+ previewImageName: '',
84
+ // 示例图片(使用占位图服务)
85
+ demoImages: [
86
+ 'https://picsum.photos/800/600?random=1',
87
+ 'https://picsum.photos/600/800?random=2',
88
+ 'https://picsum.photos/800/800?random=3'
89
+ ],
90
+ // API 表格列
91
+ apiColumns: [
92
+ { title: '属性', dataIndex: 'prop', width: 150 },
93
+ { title: '说明', dataIndex: 'desc' },
94
+ { title: '类型', dataIndex: 'type', width: 100 },
95
+ { title: '默认值', dataIndex: 'default', width: 120 }
96
+ ],
97
+ // API 数据
98
+ apiData: [
99
+ { key: '1', prop: 'visible', desc: '是否显示预览模态框', type: 'Boolean', default: 'false' },
100
+ { key: '2', prop: 'imageUrl', desc: '图片URL地址', type: 'String', default: "''" },
101
+ {
102
+ key: '3',
103
+ prop: 'imageName',
104
+ desc: '图片名称(用于下载时的文件名)',
105
+ type: 'String',
106
+ default: "'preview_image'"
107
+ }
108
+ ],
109
+ // 事件表格列
110
+ eventColumns: [
111
+ { title: '事件名', dataIndex: 'event', width: 150 },
112
+ { title: '说明', dataIndex: 'desc' },
113
+ { title: '回调参数', dataIndex: 'params', width: 150 }
114
+ ],
115
+ // 事件数据
116
+ eventData: [{ key: '1', event: 'close', desc: '关闭模态框时触发', params: '-' }]
117
+ }
118
+ },
119
+ methods: {
120
+ // 打开预览
121
+ openPreview(url, name) {
122
+ this.previewImage = url
123
+ this.previewImageName = name
124
+ this.previewVisible = true
125
+ }
126
+ }
127
+ }
128
+ </script>
129
+
130
+ <style lang="less" scoped>
131
+ .image-preview-demo {
132
+ padding: 24px;
133
+
134
+ .demo-desc {
135
+ color: #666;
136
+ margin-bottom: 16px;
137
+ }
138
+
139
+ .thumbnail {
140
+ width: 120px;
141
+ height: 90px;
142
+ object-fit: cover;
143
+ border-radius: 4px;
144
+ cursor: pointer;
145
+ border: 1px solid #d9d9d9;
146
+ transition: all 0.3s;
147
+
148
+ &:hover {
149
+ border-color: #1890ff;
150
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
151
+ }
152
+ }
153
+ }
154
+ </style>
@@ -4,12 +4,8 @@
4
4
  <template v-if="tabMode === 'left'">
5
5
  <a-col :span="4" v-if="realData.length">
6
6
  <a-tabs tab-position="left" v-model="activeTab" @change="scrollToGroup">
7
- <template v-for="(item,index) in realData">
8
- <a-tab-pane
9
- :tab="item.title"
10
- :key="index"
11
- v-if="item.title">
12
- </a-tab-pane>
7
+ <template v-for="(item, index) in realData">
8
+ <a-tab-pane :tab="item.title" :key="index" v-if="item.title"></a-tab-pane>
13
9
  </template>
14
10
  </a-tabs>
15
11
  </a-col>
@@ -18,45 +14,40 @@
18
14
  class="descriptions-item"
19
15
  :ref="`descriptions-item-${realDataIndex}`"
20
16
  :key="realDataIndex"
21
- v-for="(realDataItem, realDataIndex) in realData">
17
+ v-for="(realDataItem, realDataIndex) in realData"
18
+ >
22
19
  <!-- 渲染所有分组内容 -->
23
20
  <template v-if="!loadError">
24
21
  <!-- 带有子的详情 -->
25
- <template
26
- v-if="realDataItem.title && groups[realDataIndex]?.type ==='array'"
27
- >
22
+ <template v-if="realDataItem.title && groups[realDataIndex]?.type === 'array'">
28
23
  <div class="ant-descriptions-title">{{ realDataItem.title }}</div>
29
24
  <div class="descriptions-array-item">
30
25
  <a-descriptions
31
- v-for="(arrayItem,arrayIndex) in realDataItem.column"
26
+ v-for="(arrayItem, arrayIndex) in realDataItem.column"
32
27
  :column="isMobile ? 1 : column"
33
28
  size="small"
34
29
  :key="arrayIndex"
35
- :title="arrayItem.title">
30
+ :title="arrayItem.title"
31
+ >
36
32
  <template v-for="(item, index) in arrayItem.column">
37
33
  <!-- 大多数情况 循环下 空值省略不展示,todo 后期可能加配置处理 -->
38
- <a-descriptions-item
39
- :key="index"
40
- v-if="item.value"
41
- :label="item.key">
34
+ <a-descriptions-item :key="index" v-if="item.value" :label="item.key">
42
35
  {{ formatText(item.value) }}
43
36
  </a-descriptions-item>
44
37
  </template>
45
38
  </a-descriptions>
46
39
  </div>
47
40
  </template>
48
- <a-descriptions
49
- v-else-if="realDataItem.title"
50
- :column="isMobile ? 1 : column"
51
- :title="realDataItem.title">
41
+ <a-descriptions v-else-if="realDataItem.title" :column="isMobile ? 1 : column" :title="realDataItem.title">
52
42
  <a-descriptions-item
53
43
  v-for="(item, index) in realDataItem.column"
54
44
  :key="index"
55
45
  :span="item.span || 1"
56
- v-if="shouldShowField(item, data)">
46
+ v-if="shouldShowField(item, data)"
47
+ >
57
48
  <template #label>
58
49
  {{ item.key }}
59
- <span v-if="isFieldRequired(item, data)" style="color: red;"> *</span>
50
+ <span v-if="isFieldRequired(item, data)" style="color: red">*</span>
60
51
  </template>
61
52
  <span :style="getFieldStyle(item, data)">{{ formatFieldValue(item.value, item, data) || '--' }}</span>
62
53
  </a-descriptions-item>
@@ -70,27 +61,23 @@
70
61
  <template v-else-if="tabMode === 'top'">
71
62
  <a-col :span="24" v-if="realData.length">
72
63
  <a-tabs v-model="activeTab" size="small" :tabBarStyle="{ margin: 0, padding: '0 8px' }">
73
- <a-tab-pane v-for="(item,index) in realData" :key="index" :tab="item.title">
64
+ <a-tab-pane v-for="(item, index) in realData" :key="index" :tab="item.title">
74
65
  <div class="tab-content-scroll">
75
66
  <!-- 只渲染当前激活的分组内容 -->
76
67
  <template v-if="!loadError && realData[activeTab]">
77
68
  <!-- 带有子的详情 -->
78
- <template
79
- v-if="realData[activeTab].title && groups[activeTab]?.type ==='array'"
80
- >
69
+ <template v-if="realData[activeTab].title && groups[activeTab]?.type === 'array'">
81
70
  <div class="ant-descriptions-title" v-if="tabMode !== 'top'">{{ realData[activeTab].title }}</div>
82
71
  <div class="descriptions-array-item">
83
72
  <a-descriptions
84
- v-for="(arrayItem,arrayIndex) in realData[activeTab].column"
73
+ v-for="(arrayItem, arrayIndex) in realData[activeTab].column"
85
74
  :column="isMobile ? 1 : column"
86
75
  size="small"
87
76
  :key="arrayIndex"
88
- :title="arrayItem.title">
77
+ :title="arrayItem.title"
78
+ >
89
79
  <template v-for="(fieldItem, fieldIndex) in arrayItem.column">
90
- <a-descriptions-item
91
- :key="fieldIndex"
92
- v-if="fieldItem.value"
93
- :label="fieldItem.key">
80
+ <a-descriptions-item :key="fieldIndex" v-if="fieldItem.value" :label="fieldItem.key">
94
81
  {{ formatText(fieldItem.value) }}
95
82
  </a-descriptions-item>
96
83
  </template>
@@ -100,17 +87,21 @@
100
87
  <a-descriptions
101
88
  v-else-if="realData[activeTab].title"
102
89
  :column="isMobile ? 1 : column"
103
- :title="tabMode === 'top' ? undefined : realData[activeTab].title">
90
+ :title="tabMode === 'top' ? undefined : realData[activeTab].title"
91
+ >
104
92
  <a-descriptions-item
105
93
  v-for="(fieldItem, fieldIndex) in realData[activeTab].column"
106
94
  :key="fieldIndex"
107
95
  :span="fieldItem.span || 1"
108
- v-if="shouldShowField(fieldItem, data)">
96
+ v-if="shouldShowField(fieldItem, data)"
97
+ >
109
98
  <template #label>
110
99
  {{ fieldItem.key }}
111
- <span v-if="isFieldRequired(fieldItem, data)" style="color: red;"> *</span>
100
+ <span v-if="isFieldRequired(fieldItem, data)" style="color: red">*</span>
112
101
  </template>
113
- <span :style="getFieldStyle(fieldItem, fieldItem.value, data)">{{ formatFieldValue(fieldItem.value, fieldItem, data) || '--' }}</span>
102
+ <span :style="getFieldStyle(fieldItem, fieldItem.value, data)">
103
+ {{ formatFieldValue(fieldItem.value, fieldItem, data) || '--' }}
104
+ </span>
114
105
  </a-descriptions-item>
115
106
  </a-descriptions>
116
107
  </template>
@@ -123,49 +114,42 @@
123
114
  <!-- none 模式:无 tab,直接展示所有内容 -->
124
115
  <template v-else>
125
116
  <a-col :span="24" class="descriptionsGroupContext">
126
- <div
127
- class="descriptions-item"
128
- :key="realDataIndex"
129
- v-for="(realDataItem, realDataIndex) in realData">
117
+ <div class="descriptions-item" :key="realDataIndex" v-for="(realDataItem, realDataIndex) in realData">
130
118
  <!-- 渲染所有分组内容 -->
131
119
  <template v-if="!loadError">
132
120
  <!-- 带有子的详情 -->
133
- <template
134
- v-if="realDataItem.title && groups[realDataIndex]?.type ==='array'"
135
- >
121
+ <template v-if="realDataItem.title && groups[realDataIndex]?.type === 'array'">
136
122
  <div class="ant-descriptions-title">{{ realDataItem.title }}</div>
137
123
  <div class="descriptions-array-item">
138
124
  <a-descriptions
139
- v-for="(arrayItem,arrayIndex) in realDataItem.column"
125
+ v-for="(arrayItem, arrayIndex) in realDataItem.column"
140
126
  :column="isMobile ? 1 : column"
141
127
  size="small"
142
128
  :key="arrayIndex"
143
- :title="arrayItem.title">
129
+ :title="arrayItem.title"
130
+ >
144
131
  <template v-for="(item, index) in arrayItem.column">
145
- <a-descriptions-item
146
- :key="index"
147
- v-if="item.value"
148
- :label="item.key">
132
+ <a-descriptions-item :key="index" v-if="item.value" :label="item.key">
149
133
  {{ formatText(item.value) }}
150
134
  </a-descriptions-item>
151
135
  </template>
152
136
  </a-descriptions>
153
137
  </div>
154
138
  </template>
155
- <a-descriptions
156
- v-else-if="realDataItem.title"
157
- :column="isMobile ? 1 : column"
158
- :title="realDataItem.title">
139
+ <a-descriptions v-else-if="realDataItem.title" :column="isMobile ? 1 : column" :title="realDataItem.title">
159
140
  <a-descriptions-item
160
141
  v-for="(item, index) in realDataItem.column"
161
142
  :key="index"
162
143
  :span="item.span || 1"
163
- v-if="shouldShowField(item, data)">
144
+ v-if="shouldShowField(item, data)"
145
+ >
164
146
  <template #label>
165
147
  {{ item.key }}
166
- <span v-if="isFieldRequired(item, data)" style="color: red;"> *</span>
148
+ <span v-if="isFieldRequired(item, data)" style="color: red">*</span>
167
149
  </template>
168
- <span :style="getFieldStyle(item, item.value, data)">{{ formatFieldValue(item.value, item, data) || '--' }}</span>
150
+ <span :style="getFieldStyle(item, item.value, data)">
151
+ {{ formatFieldValue(item.value, item, data) || '--' }}
152
+ </span>
169
153
  </a-descriptions-item>
170
154
  </a-descriptions>
171
155
  </template>
@@ -175,7 +159,6 @@
175
159
  </a-row>
176
160
  </template>
177
161
  <script>
178
-
179
162
  import { mapState } from 'vuex'
180
163
  import { getRealKeyData } from '@vue2-client/utils/formatter'
181
164
  import { getConfigByName } from '@vue2-client/services/api/common'
@@ -203,20 +186,19 @@ export default {
203
186
  serviceName: {
204
187
  type: String,
205
188
  default: process.env.VUE_APP_SYSTEM_NAME
206
- },
207
- },
208
- mounted () {
189
+ }
209
190
  },
210
- beforeDestroy () {
191
+ mounted() {},
192
+ beforeDestroy() {
211
193
  const formGroupContext = this.$refs.formGroupContext?.$el
212
194
  if (formGroupContext && formGroupContext.removeEventListener) {
213
195
  formGroupContext.removeEventListener('scroll', this.onScroll)
214
196
  }
215
197
  },
216
- created () {
198
+ created() {
217
199
  this.initConfig()
218
200
  },
219
- data () {
201
+ data() {
220
202
  return {
221
203
  // 加载状态
222
204
  loading: false,
@@ -228,14 +210,14 @@ export default {
228
210
  // 从配置中获取的值
229
211
  column: 3, // 默认值
230
212
  getRealData: false, // 默认值
231
- tabMode: 'left', // 默认值
213
+ tabMode: 'left' // 默认值
232
214
  }
233
215
  },
234
216
  computed: {
235
217
  ...mapState('setting', { isMobile: 'isMobile' })
236
218
  },
237
219
  methods: {
238
- initConfig () {
220
+ initConfig() {
239
221
  this.loading = true
240
222
  if (this.configName) {
241
223
  this.getConfig()
@@ -253,14 +235,14 @@ export default {
253
235
  this.loadError = true
254
236
  }
255
237
  },
256
- scrollToGroup (index) {
238
+ scrollToGroup(index) {
257
239
  const groupElement = this.$refs[`descriptions-item-${index}`][0]
258
240
  if (groupElement) {
259
241
  groupElement.scrollIntoView({ behavior: 'smooth' })
260
242
  }
261
243
  },
262
- getConfig () {
263
- getConfigByName(this.configName, this.serviceName, (res) => {
244
+ getConfig() {
245
+ getConfigByName(this.configName, this.serviceName, res => {
264
246
  if (res.groups) {
265
247
  // 从配置中获取 column、getRealData 和 tabMode
266
248
  this.column = res.column !== undefined ? res.column : 3
@@ -270,34 +252,38 @@ export default {
270
252
  // 解析分组配置
271
253
  const groups = this.parseGroupsConfig(res.groups)
272
254
  this.groups = groups
273
- this.realData = groups.map(group => {
274
- const dataItem = { title: group.name }
255
+ this.realData = groups
256
+ .map(group => {
257
+ const dataItem = { title: group.name }
275
258
 
276
- if (group.type === 'array') {
277
- // 处理数组类型数据
278
- const arrayData = this.data[group.key] || []
279
- dataItem.column = arrayData.map((item, index) => ({
280
- title: `${group.name} ${index + 1}`,
281
- column: group.fields.map(field => ({
259
+ if (group.type === 'array') {
260
+ // 处理数组类型数据
261
+ const arrayData = this.data[group.key] || []
262
+ dataItem.column = arrayData
263
+ .map((item, index) => ({
264
+ title: `${group.name} ${index + 1}`,
265
+ column: group.fields.map(field => ({
266
+ key: field.name,
267
+ value: item[field.key]
268
+ }))
269
+ }))
270
+ .filter(Boolean)
271
+ } else {
272
+ dataItem.column = group.fields.map(field => ({
282
273
  key: field.name,
283
- value: item[field.key]
274
+ value: this.getRealKeyData(this.data, field.key),
275
+ span: field.span,
276
+ // 存储字段函数配置
277
+ showIf: field.showIf,
278
+ styleFunc: field.styleFunc,
279
+ formatFunc: field.formatFunc,
280
+ requireFunc: field.requireFunc
284
281
  }))
285
- })).filter(Boolean)
286
- } else {
287
- dataItem.column = group.fields.map(field => ({
288
- key: field.name,
289
- value: this.getRealKeyData(this.data, field.key),
290
- span: field.span,
291
- // 存储字段函数配置
292
- showIf: field.showIf,
293
- styleFunc: field.styleFunc,
294
- formatFunc: field.formatFunc,
295
- requireFunc: field.requireFunc
296
- }))
297
- }
282
+ }
298
283
 
299
- return dataItem.column.length > 0 ? dataItem : null
300
- }).filter(Boolean)
284
+ return dataItem.column.length > 0 ? dataItem : null
285
+ })
286
+ .filter(Boolean)
301
287
  }
302
288
  })
303
289
  },
@@ -357,7 +343,7 @@ export default {
357
343
  * ]
358
344
  * }]
359
345
  */
360
- parseGroupsConfig (groupsConfig) {
346
+ parseGroupsConfig(groupsConfig) {
361
347
  // 如果已经是数组格式,直接返回
362
348
  if (Array.isArray(groupsConfig)) {
363
349
  return groupsConfig
@@ -394,11 +380,11 @@ export default {
394
380
  })
395
381
  },
396
382
  // 文字格式化
397
- formatText (value) {
383
+ formatText(value) {
398
384
  return value ?? '--'
399
385
  },
400
386
  // 执行字段展示函数
401
- shouldShowField (field, data) {
387
+ shouldShowField(field, data) {
402
388
  if (!field.showIf) return true
403
389
  try {
404
390
  return executeStrFunctionByContext(this, field.showIf, [data, field.key])
@@ -408,7 +394,7 @@ export default {
408
394
  }
409
395
  },
410
396
  // 获取字段样式
411
- getFieldStyle (field, data) {
397
+ getFieldStyle(field, data) {
412
398
  if (!field.styleFunc) return {}
413
399
  try {
414
400
  return executeStrFunctionByContext(this, field.styleFunc, [data, field.key])
@@ -418,7 +404,7 @@ export default {
418
404
  }
419
405
  },
420
406
  // 格式化字段值
421
- formatFieldValue (value, field, data) {
407
+ formatFieldValue(value, field, data) {
422
408
  if (!field.formatFunc) return value
423
409
  try {
424
410
  return executeStrFunctionByContext(this, field.formatFunc, [data, field.key])
@@ -428,7 +414,7 @@ export default {
428
414
  }
429
415
  },
430
416
  // 判断字段是否必填(显示红色星号)
431
- isFieldRequired (field, data) {
417
+ isFieldRequired(field, data) {
432
418
  if (!field.requireFunc) return false
433
419
  try {
434
420
  return executeStrFunctionByContext(this, field.requireFunc, [data, field.key])
@@ -437,14 +423,14 @@ export default {
437
423
  return false
438
424
  }
439
425
  },
440
- getRealKeyData (data, key) {
426
+ getRealKeyData(data, key) {
441
427
  if (this.getRealData) {
442
428
  return getRealKeyData(data)[key] || ''
443
429
  } else {
444
430
  return this.data[key] || ''
445
431
  }
446
432
  },
447
- onScroll () {
433
+ onScroll() {
448
434
  // 只在 left 模式启用滚动联动
449
435
  if (this.tabMode !== 'left') return
450
436
 
@@ -466,17 +452,17 @@ export default {
466
452
  },
467
453
  watch: {
468
454
  content: {
469
- handler () {
455
+ handler() {
470
456
  this.initConfig()
471
457
  }
472
458
  },
473
459
  configName: {
474
- handler () {
460
+ handler() {
475
461
  this.initConfig()
476
462
  }
477
463
  },
478
464
  serviceName: {
479
- handler () {
465
+ handler() {
480
466
  this.initConfig()
481
467
  }
482
468
  }
@@ -502,6 +488,16 @@ export default {
502
488
  font-weight: 400;
503
489
  }
504
490
 
491
+ // 移除最后一行的 padding-bottom
492
+ :deep(.ant-descriptions-view) {
493
+ .ant-descriptions-row:last-child {
494
+ > th,
495
+ > td {
496
+ padding-bottom: 0;
497
+ }
498
+ }
499
+ }
500
+
505
501
  .descriptions-item {
506
502
  margin-bottom: 8px;
507
503
  }