@ebiz/designer-components 0.1.21 → 0.1.22

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": "@ebiz/designer-components",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,442 @@
1
+ <template>
2
+ <div
3
+ class="ebiz-detail-item"
4
+ :class="{ 'vertical': layout === 'vertical' }"
5
+ :style="{
6
+ 'min-height': minHeight + 'px',
7
+ 'margin-bottom': gap + 'px'
8
+ }"
9
+ >
10
+ <!-- 字段标签 -->
11
+ <div
12
+ class="field-label"
13
+ :style="{
14
+ color: labelColor,
15
+ 'min-width': layout === 'horizontal' ? labelWidth + 'px' : 'auto'
16
+ }"
17
+ >
18
+ {{ label }}
19
+ <span v-if="required" class="required-mark">*</span>
20
+ </div>
21
+
22
+ <!-- 字段值 -->
23
+ <div class="field-value">
24
+ <!-- 文本类型 -->
25
+ <span v-if="type === 'text'" class="text-value">
26
+ {{ displayValue }}
27
+ </span>
28
+
29
+ <!-- 用户类型 -->
30
+ <div v-else-if="type === 'user'" class="user-value">
31
+ <div class="user-list">
32
+ <div v-for="user in userList" :key="user.id" class="user-item" @click="handleUserClick(user)">
33
+ <div class="user-avatar">
34
+ <img v-if="user.avatar" :src="user.avatar" />
35
+ <div v-else class="user-avatar-default">{{ user.name?.[0] || 'U' }}</div>
36
+ </div>
37
+ <div class="user-info">
38
+ <div class="user-name">{{ user.name }}</div>
39
+ <div class="user-dept">{{ user.department }}</div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <!-- 文件类型 -->
46
+ <div v-else-if="type === 'file'" class="file-value">
47
+ <EbizFileList
48
+ :files="fileList"
49
+ :mode="fileMode"
50
+ :showDownload="showDownload"
51
+ @download="handleDownloadFile"
52
+ />
53
+ </div>
54
+
55
+ <!-- 日期类型 -->
56
+ <span v-else-if="type === 'date'" class="date-value">
57
+ {{ formatDate(displayValue) }}
58
+ </span>
59
+
60
+ <!-- 状态类型 -->
61
+ <div v-else-if="type === 'status'" class="status-value">
62
+ <t-tag
63
+ :theme="getStatusTheme(statusValue?.status)"
64
+ size="small"
65
+ >
66
+ {{ statusValue?.text }}
67
+ </t-tag>
68
+ </div>
69
+
70
+ <!-- 标签类型 -->
71
+ <div v-else-if="type === 'tags'" class="tags-value">
72
+ <t-tag
73
+ v-for="tag in tagList"
74
+ :key="tag"
75
+ size="small"
76
+ variant="outline"
77
+ class="tag-item"
78
+ >
79
+ {{ tag }}
80
+ </t-tag>
81
+ </div>
82
+
83
+ <!-- 链接类型 -->
84
+ <div v-else-if="type === 'link'" class="link-value" @click="handleLinkClick(linkValue)">
85
+ {{ linkValue?.text || linkValue?.url }}
86
+ </div>
87
+
88
+ <!-- 富文本类型 -->
89
+ <div v-else-if="type === 'html'" class="html-value" v-html="displayValue"></div>
90
+
91
+ <!-- 数字类型 -->
92
+ <span v-else-if="type === 'number'" class="number-value">
93
+ {{ formatNumber(displayValue) }}
94
+ </span>
95
+
96
+ <!-- 货币类型 -->
97
+ <span v-else-if="type === 'currency'" class="currency-value">
98
+ {{ formatCurrency(displayValue) }}
99
+ </span>
100
+
101
+ <!-- 默认文本 -->
102
+ <span v-else class="text-value">{{ displayValue }}</span>
103
+ </div>
104
+
105
+ <!-- 字段描述 -->
106
+ <div v-if="description" class="field-description">
107
+ {{ description }}
108
+ </div>
109
+ </div>
110
+ </template>
111
+
112
+ <script setup>
113
+ import { computed, defineProps, defineEmits } from 'vue'
114
+ import EbizFileList from './EbizFileList.vue'
115
+
116
+ const props = defineProps({
117
+ // 字段配置
118
+ label: {
119
+ type: String,
120
+ required: true
121
+ },
122
+ type: {
123
+ type: String,
124
+ default: 'text',
125
+ validator: (value) => [
126
+ 'text', 'user', 'file', 'date', 'status',
127
+ 'tags', 'link', 'html', 'number', 'currency'
128
+ ].includes(value)
129
+ },
130
+ value: {
131
+ type: [String, Number, Array, Object],
132
+ default: ''
133
+ },
134
+ required: {
135
+ type: Boolean,
136
+ default: false
137
+ },
138
+ description: {
139
+ type: String,
140
+ default: ''
141
+ },
142
+
143
+ // 布局配置
144
+ layout: {
145
+ type: String,
146
+ default: 'horizontal',
147
+ validator: (value) => ['horizontal', 'vertical'].includes(value)
148
+ },
149
+ labelWidth: {
150
+ type: Number,
151
+ default: 120
152
+ },
153
+ labelColor: {
154
+ type: String,
155
+ default: '#999999'
156
+ },
157
+ gap: {
158
+ type: Number,
159
+ default: 16
160
+ },
161
+ minHeight: {
162
+ type: Number,
163
+ default: 32
164
+ },
165
+
166
+ // 文件组件配置
167
+ fileMode: {
168
+ type: String,
169
+ default: 'list'
170
+ },
171
+ showDownload: {
172
+ type: Boolean,
173
+ default: true
174
+ }
175
+ })
176
+
177
+ const emits = defineEmits(['download-file', 'user-click', 'link-click'])
178
+
179
+ // 显示值
180
+ const displayValue = computed(() => {
181
+ return props.value !== undefined && props.value !== null ? props.value : ''
182
+ })
183
+
184
+ // 用户列表
185
+ const userList = computed(() => {
186
+ if (props.type !== 'user') return []
187
+ return Array.isArray(props.value) ? props.value : (props.value ? [props.value] : [])
188
+ })
189
+
190
+ // 文件列表
191
+ const fileList = computed(() => {
192
+ if (props.type !== 'file') return []
193
+ return Array.isArray(props.value) ? props.value : (props.value ? [props.value] : [])
194
+ })
195
+
196
+ // 状态值
197
+ const statusValue = computed(() => {
198
+ if (props.type !== 'status') return null
199
+ return typeof props.value === 'object' ? props.value : { text: props.value, status: 'default' }
200
+ })
201
+
202
+ // 标签列表
203
+ const tagList = computed(() => {
204
+ if (props.type !== 'tags') return []
205
+ return Array.isArray(props.value) ? props.value : (props.value ? [props.value] : [])
206
+ })
207
+
208
+ // 链接值
209
+ const linkValue = computed(() => {
210
+ if (props.type !== 'link') return null
211
+ return typeof props.value === 'object' ? props.value : { url: props.value, text: props.value }
212
+ })
213
+
214
+ // 格式化日期
215
+ const formatDate = (date) => {
216
+ if (!date) return ''
217
+ const d = new Date(date)
218
+ return d.toLocaleDateString('zh-CN', {
219
+ year: 'numeric',
220
+ month: '2-digit',
221
+ day: '2-digit'
222
+ })
223
+ }
224
+
225
+ // 格式化数字
226
+ const formatNumber = (num) => {
227
+ if (num === null || num === undefined || num === '') return ''
228
+ return Number(num).toLocaleString()
229
+ }
230
+
231
+ // 格式化货币
232
+ const formatCurrency = (amount) => {
233
+ if (amount === null || amount === undefined || amount === '') return ''
234
+ return `¥${Number(amount).toLocaleString()}`
235
+ }
236
+
237
+ // 获取状态主题
238
+ const getStatusTheme = (status) => {
239
+ const statusMap = {
240
+ 'success': 'success',
241
+ 'warning': 'warning',
242
+ 'error': 'danger',
243
+ 'info': 'primary'
244
+ }
245
+ return statusMap[status] || 'default'
246
+ }
247
+
248
+ // 事件处理
249
+ const handleDownloadFile = (file) => {
250
+ emits('download-file', file)
251
+ }
252
+
253
+ const handleUserClick = (user) => {
254
+ emits('user-click', user)
255
+ }
256
+
257
+ const handleLinkClick = (link) => {
258
+ emits('link-click', link)
259
+ }
260
+ </script>
261
+
262
+ <style scoped>
263
+ .ebiz-detail-item {
264
+ display: flex;
265
+ align-items: flex-start;
266
+ padding: 8px 0;
267
+ border-bottom: 1px solid #f0f0f0;
268
+ }
269
+
270
+ .ebiz-detail-item:last-child {
271
+ border-bottom: none;
272
+ }
273
+
274
+ .ebiz-detail-item.vertical {
275
+ flex-direction: column;
276
+ }
277
+
278
+ .field-label {
279
+ font-size: 14px;
280
+ line-height: 1.5;
281
+ font-weight: 500;
282
+ margin-right: 12px;
283
+ flex-shrink: 0;
284
+ }
285
+
286
+ .ebiz-detail-item.vertical .field-label {
287
+ margin-right: 0;
288
+ margin-bottom: 8px;
289
+ }
290
+
291
+ .required-mark {
292
+ color: #e34d59;
293
+ margin-left: 2px;
294
+ }
295
+
296
+ .field-value {
297
+ flex: 1;
298
+ font-size: 14px;
299
+ color: #333333;
300
+ line-height: 1.5;
301
+ word-break: break-all;
302
+ min-width: 0;
303
+ }
304
+
305
+ .field-description {
306
+ font-size: 12px;
307
+ color: #999999;
308
+ margin-top: 4px;
309
+ line-height: 1.4;
310
+ }
311
+
312
+ /* 用户信息样式 */
313
+ .user-list {
314
+ display: flex;
315
+ flex-wrap: wrap;
316
+ gap: 8px;
317
+ }
318
+
319
+ .user-item {
320
+ display: flex;
321
+ align-items: center;
322
+ padding: 6px 8px;
323
+ background: #f8f9fa;
324
+ border-radius: 4px;
325
+ cursor: pointer;
326
+ transition: background-color 0.2s;
327
+ }
328
+
329
+ .user-item:hover {
330
+ background: #e9ecef;
331
+ }
332
+
333
+ .user-avatar {
334
+ width: 24px;
335
+ height: 24px;
336
+ margin-right: 8px;
337
+ border-radius: 50%;
338
+ overflow: hidden;
339
+ flex-shrink: 0;
340
+ }
341
+
342
+ .user-avatar img {
343
+ width: 100%;
344
+ height: 100%;
345
+ object-fit: cover;
346
+ }
347
+
348
+ .user-avatar-default {
349
+ width: 100%;
350
+ height: 100%;
351
+ background: #0052d9;
352
+ color: white;
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+ font-size: 12px;
357
+ font-weight: bold;
358
+ }
359
+
360
+ .user-info {
361
+ flex: 1;
362
+ min-width: 0;
363
+ }
364
+
365
+ .user-name {
366
+ font-size: 13px;
367
+ color: #333333;
368
+ font-weight: 500;
369
+ }
370
+
371
+ .user-dept {
372
+ font-size: 12px;
373
+ color: #999999;
374
+ margin-top: 2px;
375
+ }
376
+
377
+ /* 文件值样式 */
378
+ .file-value {
379
+ width: 100%;
380
+ }
381
+
382
+ /* 状态值样式 */
383
+ .status-value {
384
+ display: inline-block;
385
+ }
386
+
387
+ /* 标签值样式 */
388
+ .tags-value {
389
+ display: flex;
390
+ flex-wrap: wrap;
391
+ gap: 6px;
392
+ }
393
+
394
+ .tag-item {
395
+ margin: 0;
396
+ }
397
+
398
+ /* 链接样式 */
399
+ .link-value {
400
+ color: #0052d9;
401
+ cursor: pointer;
402
+ text-decoration: underline;
403
+ word-break: break-all;
404
+ }
405
+
406
+ .link-value:hover {
407
+ color: #003a9b;
408
+ }
409
+
410
+ /* 富文本样式 */
411
+ .html-value {
412
+ line-height: 1.6;
413
+ }
414
+
415
+ .html-value :deep(p) {
416
+ margin: 0 0 8px 0;
417
+ }
418
+
419
+ .html-value :deep(p:last-child) {
420
+ margin-bottom: 0;
421
+ }
422
+
423
+ /* 数字和货币样式 */
424
+ .number-value,
425
+ .currency-value {
426
+ font-weight: 500;
427
+ font-variant-numeric: tabular-nums;
428
+ }
429
+
430
+ .currency-value {
431
+ color: #e34d59;
432
+ }
433
+
434
+ /* 空值样式 */
435
+ .text-value:empty::before,
436
+ .number-value:empty::before,
437
+ .currency-value:empty::before,
438
+ .date-value:empty::before {
439
+ content: '--';
440
+ color: #c8c9cc;
441
+ }
442
+ </style>