wl-design-mobile 0.8.2 → 0.8.4

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.
Files changed (41) hide show
  1. package/doc/file/compoents/lambo-file-row.vue +5 -92
  2. package/doc/file-type/file-type.js +286 -0
  3. package/doc/file-type/icons/icon-file-ai.svg +1 -0
  4. package/doc/file-type/icons/icon-file-avi.svg +1 -0
  5. package/doc/file-type/icons/icon-file-default.svg +1 -0
  6. package/doc/file-type/icons/icon-file-epub.svg +1 -0
  7. package/doc/file-type/icons/icon-file-excel.svg +1 -0
  8. package/doc/file-type/icons/icon-file-flv.svg +1 -0
  9. package/doc/file-type/icons/icon-file-gif.svg +1 -0
  10. package/doc/file-type/icons/icon-file-html.svg +1 -0
  11. package/doc/file-type/icons/icon-file-java.svg +1 -0
  12. package/doc/file-type/icons/icon-file-js.svg +1 -0
  13. package/doc/file-type/icons/icon-file-jsp.svg +1 -0
  14. package/doc/file-type/icons/icon-file-mp3.svg +1 -0
  15. package/doc/file-type/icons/icon-file-msi.svg +1 -0
  16. package/doc/file-type/icons/icon-file-pdf.svg +1 -0
  17. package/doc/file-type/icons/icon-file-png.svg +1 -0
  18. package/doc/file-type/icons/icon-file-ppt.svg +1 -0
  19. package/doc/file-type/icons/icon-file-psd.svg +1 -0
  20. package/doc/file-type/icons/icon-file-rar.svg +1 -0
  21. package/doc/file-type/icons/icon-file-raw.svg +1 -0
  22. package/doc/file-type/icons/icon-file-txt.svg +1 -0
  23. package/doc/file-type/icons/icon-file-word.svg +1 -0
  24. package/doc/img/comonents/lambo-img-grid.vue +6 -1
  25. package/flow/approval/flow-approval.vue +550 -0
  26. package/flow/comps/flow-attachment-list.vue +230 -0
  27. package/flow/comps/flow-attachment-modal.vue +132 -0
  28. package/flow/comps/flow-copy-users.vue +358 -0
  29. package/flow/comps/flow-node-card.vue +150 -0
  30. package/flow/comps/flow-node-header.vue +172 -0
  31. package/flow/comps/flow-util.js +156 -0
  32. package/flow/comps/icons/approved.svg +11 -0
  33. package/flow/comps/icons/canceled.svg +11 -0
  34. package/flow/comps/icons/rejected.svg +11 -0
  35. package/flow/comps/icons/underway.svg +11 -0
  36. package/flow/detail/flow-detail.vue +466 -0
  37. package/flow/index.js +51 -0
  38. package/flow/lambo-flow-detail.vue +894 -12
  39. package/flow/lambo-flow-edit.vue +6 -21
  40. package/flow/submit/flow-submit.vue +381 -0
  41. package/package.json +1 -1
@@ -0,0 +1,466 @@
1
+ <template>
2
+ <div class="flow-detail-container">
3
+ <!-- 流程头部信息 -->
4
+ <div class="flow-header" v-if="showHeader">
5
+ <div class="flow-header-left">
6
+ <div class="flow-title">
7
+ <span class="flow-name">{{ flowInfo.flowName || '审批流程详情' }}</span>
8
+ </div>
9
+ <div class="flow-meta" v-if="flowInfo.createTime || flowInfo.createUser || (copyUsers && copyUsers.length > 0)">
10
+ <div v-if="flowInfo.createUser" class="meta-item">
11
+ <span class="meta-label">发起人:</span>
12
+ <span class="meta-value">{{ flowInfo.createUser }}</span>
13
+ </div>
14
+ <div v-if="flowInfo.createTime" class="meta-item">
15
+ <span class="meta-label">发起时间:</span>
16
+ <span class="meta-value">{{ flowInfo.createTime }}</span>
17
+ </div>
18
+ <FlowCopyUsers v-if="copyUsers && copyUsers.length > 0" :copy-users="copyUsers" />
19
+ </div>
20
+ </div>
21
+ <img :src="getFlowStatusImage()" class="flow-status-image" />
22
+ </div>
23
+
24
+ <!-- 流程节点时间线 -->
25
+ <div class="flow-timeline">
26
+ <template v-for="(item, index) in sortedFlowNodes">
27
+ <!-- 折叠占位 -->
28
+ <div v-if="!isNodesExpanded && shouldShowCollapseBtn() && index === 2" :key="'collapse-' + index"
29
+ class="collapse-placeholder">
30
+ <div class="timeline-col">
31
+ <div class="timeline-dot dot-more">
32
+ <van-icon name="more-o" size="14"></van-icon>
33
+ </div>
34
+ <div class="timeline-line"></div>
35
+ </div>
36
+ <div class="content-col">
37
+ <div class="expand-btn" @click="isNodesExpanded = true">
38
+ <van-icon name="down"></van-icon>
39
+ 展开更多节点({{ sortedFlowNodes.length - 4 }}个)
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- 节点显示 -->
45
+ <div v-if="isNodesExpanded || shouldShowNode(index)" :key="index" class="timeline-item">
46
+ <!-- 左侧时间线 -->
47
+ <div class="timeline-col">
48
+ <div class="timeline-dot" :class="'dot-' + getStatusClass(item.status)">
49
+ <van-icon :name="getStatusIcon(item.status)" size="14"></van-icon>
50
+ </div>
51
+ <div class="timeline-line" v-if="index !== sortedFlowNodes.length - 1"></div>
52
+ </div>
53
+
54
+ <!-- 右侧内容 -->
55
+ <div class="content-col">
56
+ <!-- 节点头部信息 -->
57
+ <FlowNodeHeader
58
+ :node-info="item"
59
+ :node-index="index"
60
+ />
61
+
62
+ <!-- 审批详情卡片 -->
63
+ <FlowNodeCard
64
+ v-if="item.auditResult || item.auditComment || item.auditTime || item.applyDescription"
65
+ :node-info="item"
66
+ :is-apply-node="index === 0"
67
+ >
68
+ <template slot="attachments">
69
+ <FlowAttachmentList
70
+ v-if="item.attachments && item.attachments.length > 0"
71
+ :attachments="item.attachments"
72
+ @click="handleAttachmentClick"
73
+ />
74
+ </template>
75
+ </FlowNodeCard>
76
+
77
+ <!-- 待审批提示 -->
78
+ <div class="pending-tip" v-else-if="item.status === 'pending'">
79
+ <van-icon name="clock-o" size="14" color="#ff976a"></van-icon>
80
+ <span>等待审批中...</span>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </template>
85
+
86
+ <!-- 收起按钮 -->
87
+ <div v-if="isNodesExpanded && shouldShowCollapseBtn()" class="collapse-btn-wrapper">
88
+ <div class="collapse-btn" @click="isNodesExpanded = false">
89
+ <van-icon name="up"></van-icon>
90
+ 收起节点
91
+ </div>
92
+ </div>
93
+
94
+ <!-- 空状态 -->
95
+ <div v-if="sortedFlowNodes.length === 0" class="flow-empty">
96
+ <van-icon name="info-o" size="48" color="#ccc"></van-icon>
97
+ <p>暂无流程节点信息</p>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- 附件预览弹窗 -->
102
+ <FlowAttachmentModal
103
+ :visible.sync="previewModalVisible"
104
+ :file-url="previewFileUrl"
105
+ :file-name="previewFileName"
106
+ :modal-width="previewModalWidth"
107
+ />
108
+ </div>
109
+ </template>
110
+
111
+ <script>
112
+ import FlowNodeHeader from '../comps/flow-node-header.vue'
113
+ import FlowNodeCard from '../comps/flow-node-card.vue'
114
+ import FlowAttachmentList from '../comps/flow-attachment-list.vue'
115
+ import FlowAttachmentModal from '../comps/flow-attachment-modal.vue'
116
+ import FlowCopyUsers from '../comps/flow-copy-users.vue'
117
+ import { fileGetUrl } from '../../api/doc'
118
+ import {
119
+ getStatusIcon,
120
+ getStatusClass
121
+ } from '../comps/flow-util.js'
122
+
123
+ export default {
124
+ name: 'FlowDetail',
125
+ components: {
126
+ FlowNodeHeader,
127
+ FlowNodeCard,
128
+ FlowAttachmentList,
129
+ FlowAttachmentModal,
130
+ FlowCopyUsers
131
+ },
132
+ props: {
133
+ /**
134
+ * 流程基本信息
135
+ * @property {string} flowName - 流程名称
136
+ * @property {string} createUser - 发起人
137
+ * @property {string} createTime - 发起时间
138
+ */
139
+ flowInfo: {
140
+ type: Object,
141
+ default: () => ({})
142
+ },
143
+ /**
144
+ * 流程节点数组
145
+ * @property {number} seq - 节点序号
146
+ * @property {string} taskName - 节点任务名称
147
+ * @property {string} auditUserName - 审批人姓名
148
+ * @property {string} auditUserAvatar - 审批人头像
149
+ * @property {string} status - 节点状态
150
+ * @property {string} auditResult - 审批结果
151
+ * @property {string} auditTime - 审批时间
152
+ * @property {string} startTime - 开始时间
153
+ * @property {string} endTime - 结束时间
154
+ * @property {string} auditComment - 审批意见
155
+ * @property {Array} attachments - 附件列表
156
+ */
157
+ flowNodes: {
158
+ type: Array,
159
+ default: () => []
160
+ },
161
+ /**
162
+ * 是否显示头部区域
163
+ * @default true
164
+ */
165
+ showHeader: {
166
+ type: Boolean,
167
+ default: true
168
+ },
169
+ /**
170
+ * 是否启用节点折叠功能
171
+ * @default true
172
+ */
173
+ enableCollapse: {
174
+ type: Boolean,
175
+ default: true
176
+ },
177
+ /**
178
+ * 抄送人列表
179
+ * @property {string} name - 抄送人姓名
180
+ * @property {string} avatar - 抄送人头像地址
181
+ */
182
+ copyUsers: {
183
+ type: Array,
184
+ default: () => []
185
+ }
186
+ },
187
+ data() {
188
+ return {
189
+ isNodesExpanded: false,
190
+ previewModalVisible: false,
191
+ previewFileUrl: '',
192
+ previewFileName: '',
193
+ previewModalWidth: '320px'
194
+ }
195
+ },
196
+ computed: {
197
+ sortedFlowNodes() {
198
+ return [...this.flowNodes].sort((a, b) => (a.seq || 0) - (b.seq || 0))
199
+ },
200
+ overallFlowStatus() {
201
+ const nodes = this.sortedFlowNodes
202
+ if (nodes.length === 0) return 'processing'
203
+ if (nodes.some(n => n.status === 'rejected')) return 'rejected'
204
+ if (nodes.every(n => n.status === 'completed')) return 'completed'
205
+ if (nodes.some(n => n.status === 'processing')) return 'processing'
206
+ return 'processing'
207
+ }
208
+ },
209
+ methods: {
210
+ getStatusIcon,
211
+ getStatusClass,
212
+ // 获取整体流程状态图片
213
+ getFlowStatusImage() {
214
+ const statusMap = {
215
+ completed: require('../comps/icons/approved.svg'),
216
+ processing: require('../comps/icons/underway.svg'),
217
+ rejected: require('../comps/icons/rejected.svg'),
218
+ withdrawn: require('../comps/icons/canceled.svg')
219
+ }
220
+ return statusMap[this.overallFlowStatus] || statusMap.processing
221
+ },
222
+ handleAttachmentClick(file) {
223
+ this.$emit('attachment-click', file)
224
+ if (!file || !file.fileName) {
225
+ this.$toast({ message: '文件信息无效,无法预览', position: 'middle' })
226
+ return
227
+ }
228
+ const previewUrl = file.fileUrl || (file.fileId ? fileGetUrl + file.fileId : '')
229
+ if (!previewUrl) {
230
+ this.$toast({ message: '文件信息无效,无法预览', position: 'middle' })
231
+ return
232
+ }
233
+ this.previewFileUrl = previewUrl
234
+ this.previewFileName = file.fileName
235
+ this.previewModalVisible = true
236
+ },
237
+ shouldShowCollapseBtn() {
238
+ return this.enableCollapse && this.sortedFlowNodes.length > 4
239
+ },
240
+ shouldShowNode(index) {
241
+ const total = this.sortedFlowNodes.length
242
+ if (total <= 4) return true
243
+ return index < 2 || index >= total - 2
244
+ }
245
+ }
246
+ }
247
+ </script>
248
+
249
+ <style scoped>
250
+ .flow-detail-container {
251
+ border-radius: 4px;
252
+ padding: 16px;
253
+ background: #fff;
254
+ }
255
+
256
+ .flow-header {
257
+ position: relative;
258
+ border-bottom: 1px solid #f0f0f0;
259
+ margin-bottom: 16px;
260
+ padding-bottom: 16px;
261
+ padding-right: 80px;
262
+ }
263
+
264
+ .flow-header-left {
265
+ flex: 1;
266
+ }
267
+
268
+ .flow-title {
269
+ margin-bottom: 8px;
270
+ }
271
+
272
+ .flow-name {
273
+ font-size: 16px;
274
+ font-weight: 600;
275
+ color: #262626;
276
+ }
277
+
278
+ .flow-status-image {
279
+ position: absolute;
280
+ top: 2px;
281
+ right: 0;
282
+ height: 70px;
283
+ width: auto;
284
+ z-index: 10;
285
+ image-rendering: -webkit-optimize-contrast;
286
+ image-rendering: crisp-edges;
287
+ transform: translateZ(0);
288
+ backface-visibility: hidden;
289
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.08));
290
+ }
291
+
292
+ @keyframes float {
293
+ 0%,
294
+ 100% {
295
+ transform: translateY(0);
296
+ }
297
+ 50% {
298
+ transform: translateY(-4px);
299
+ }
300
+ }
301
+
302
+ .flow-meta {
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: 6px;
306
+ font-size: 13px;
307
+ }
308
+
309
+ .meta-item {
310
+ display: flex;
311
+ }
312
+
313
+ .meta-label {
314
+ color: #8c8c8c;
315
+ flex-shrink: 0;
316
+ }
317
+
318
+ .meta-value {
319
+ color: #262626;
320
+ }
321
+
322
+ .flow-timeline {
323
+ position: relative;
324
+ }
325
+
326
+ .timeline-item,
327
+ .collapse-placeholder {
328
+ display: flex;
329
+ margin-bottom: 16px;
330
+ }
331
+
332
+ .timeline-item:last-child {
333
+ margin-bottom: 0;
334
+ }
335
+
336
+ .timeline-col {
337
+ display: flex;
338
+ flex-direction: column;
339
+ align-items: center;
340
+ margin-right: 12px;
341
+ width: 28px;
342
+ flex-shrink: 0;
343
+ }
344
+
345
+ .timeline-dot {
346
+ width: 28px;
347
+ height: 28px;
348
+ border-radius: 50%;
349
+ display: flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+ background: #f0f0f0;
353
+ color: #999;
354
+ border: 2px solid #fff;
355
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
356
+ z-index: 2;
357
+ }
358
+
359
+ .timeline-dot.dot-more {
360
+ background: #e8eaed;
361
+ color: #86909c;
362
+ }
363
+
364
+ .timeline-dot.dot-success {
365
+ background: #07c160;
366
+ color: #fff;
367
+ }
368
+
369
+ .timeline-dot.dot-process {
370
+ background: #1989fa;
371
+ color: #fff;
372
+ animation: pulse 2s infinite;
373
+ }
374
+
375
+ .timeline-dot.dot-error {
376
+ background: #ee0a24;
377
+ color: #fff;
378
+ }
379
+
380
+ .timeline-dot.dot-warning {
381
+ background: #ff976a;
382
+ color: #fff;
383
+ }
384
+
385
+ .timeline-dot.dot-wait {
386
+ background: #f0f0f0;
387
+ color: #999;
388
+ }
389
+
390
+ @keyframes pulse {
391
+ 0%,
392
+ 100% {
393
+ box-shadow: 0 0 0 0 rgba(25, 137, 250, 0.4);
394
+ }
395
+ 50% {
396
+ box-shadow: 0 0 0 8px rgba(25, 137, 250, 0);
397
+ }
398
+ }
399
+
400
+ .timeline-line {
401
+ width: 2px;
402
+ flex: 1;
403
+ min-height: 24px;
404
+ background: #e8e8e8;
405
+ margin-top: 4px;
406
+ }
407
+
408
+ .content-col {
409
+ flex: 1;
410
+ min-width: 0;
411
+ }
412
+
413
+ .expand-btn {
414
+ color: #1989fa;
415
+ padding: 0;
416
+ height: auto;
417
+ line-height: 1.5;
418
+ margin-top: 6px;
419
+ font-size: 13px;
420
+ display: flex;
421
+ align-items: center;
422
+ gap: 4px;
423
+ }
424
+
425
+ .collapse-btn-wrapper {
426
+ padding-left: 42px;
427
+ margin-top: -8px;
428
+ }
429
+
430
+ .collapse-btn {
431
+ color: #86909c;
432
+ padding: 0;
433
+ height: auto;
434
+ line-height: 1.5;
435
+ font-size: 13px;
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 4px;
439
+ }
440
+
441
+ .pending-tip {
442
+ display: flex;
443
+ align-items: center;
444
+ gap: 6px;
445
+ padding: 8px 12px;
446
+ background: #fff7e8;
447
+ border: 1px solid #ffd9b3;
448
+ border-radius: 4px;
449
+ font-size: 13px;
450
+ color: #ff976a;
451
+ }
452
+
453
+ .flow-empty {
454
+ display: flex;
455
+ flex-direction: column;
456
+ align-items: center;
457
+ justify-content: center;
458
+ padding: 60px 20px;
459
+ color: #999;
460
+ }
461
+
462
+ .flow-empty p {
463
+ margin: 12px 0 0 0;
464
+ font-size: 14px;
465
+ }
466
+ </style>
package/flow/index.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 流程审批组件库(移动端版本)
3
+ * 参照 lambo-design-bi 组件库重新实现的 Vant 版本
4
+ */
5
+
6
+ // 公共子组件
7
+ import FlowNodeHeader from './comps/flow-node-header.vue'
8
+ import FlowNodeCard from './comps/flow-node-card.vue'
9
+ import FlowAttachmentList from './comps/flow-attachment-list.vue'
10
+ import FlowAttachmentModal from './comps/flow-attachment-modal.vue'
11
+ import FlowCopyUsers from './comps/flow-copy-users.vue'
12
+ import FlowDetail from './detail/flow-detail.vue'
13
+ import FlowSubmit from './submit/flow-submit.vue'
14
+ import FlowApproval from './approval/flow-approval.vue'
15
+ import LamboFlowEdit from './lambo-flow-edit.vue'
16
+
17
+ export {
18
+ FlowNodeHeader,
19
+ FlowNodeCard,
20
+ FlowAttachmentList,
21
+ FlowAttachmentModal,
22
+ FlowCopyUsers,
23
+ FlowDetail,
24
+ FlowSubmit,
25
+ FlowApproval,
26
+ LamboFlowEdit
27
+ }
28
+ export * from './comps/flow-util.js'
29
+
30
+ // 兼容旧版命名(向后兼容)
31
+ export const LamboFlowDetail = FlowDetail
32
+ export const LamboFlowSubmit = FlowSubmit
33
+ export const LamboFlowApproval = FlowApproval
34
+
35
+ export default {
36
+ install(Vue) {
37
+ Vue.component('FlowNodeHeader', FlowNodeHeader)
38
+ Vue.component('FlowNodeCard', FlowNodeCard)
39
+ Vue.component('FlowAttachmentList', FlowAttachmentList)
40
+ Vue.component('FlowAttachmentModal', FlowAttachmentModal)
41
+ Vue.component('FlowCopyUsers', FlowCopyUsers)
42
+ Vue.component('FlowDetail', FlowDetail)
43
+ Vue.component('FlowSubmit', FlowSubmit)
44
+ Vue.component('FlowApproval', FlowApproval)
45
+ // 兼容旧版命名
46
+ Vue.component('LamboFlowDetail', FlowDetail)
47
+ Vue.component('LamboFlowSubmit', FlowSubmit)
48
+ Vue.component('LamboFlowApproval', FlowApproval)
49
+ Vue.component('LamboFlowEdit', LamboFlowEdit)
50
+ }
51
+ }