xt-element-ui 2.2.2 → 2.2.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 (153) hide show
  1. package/README.md +140 -192
  2. package/docs/README.md +162 -81
  3. package/docs/components/base/theme.md +248 -0
  4. package/docs/components/base/xt-button.md +11 -4
  5. package/docs/components/base/xt-card.md +5 -11
  6. package/docs/components/base/xt-config-provider.md +2 -0
  7. package/docs/components/base/xt-flow.md +404 -0
  8. package/docs/components/base/xt-list.md +1 -1
  9. package/docs/components/base/xt-multi.md +5 -5
  10. package/docs/components/base/xt-scroll-arrow.md +87 -1
  11. package/docs/components/base/xt-scroll.md +237 -0
  12. package/docs/components/business/xt-chart.md +1 -0
  13. package/docs/components/utils/config.md +18 -4
  14. package/docs/components/utils/theme.md +280 -183
  15. package/lib/index.common.1.js +4 -26
  16. package/lib/index.common.2.js +1 -1
  17. package/lib/index.common.js +4577 -109925
  18. package/lib/index.css +1 -1
  19. package/lib/index.umd.1.js +4 -26
  20. package/lib/index.umd.2.js +1 -1
  21. package/lib/index.umd.js +4583 -109930
  22. package/lib/index.umd.min.1.js +1 -1
  23. package/lib/index.umd.min.js +1 -1
  24. package/lib/theme/element-overrides.css +607 -0
  25. package/lib/theme/element-vars.css +280 -0
  26. package/lib/xt-badge/xt-badge.common.js +1 -1
  27. package/lib/xt-badge/xt-badge.css +1 -1
  28. package/lib/xt-badge/xt-badge.umd.js +1 -1
  29. package/lib/xt-button/xt-button.common.js +13 -8
  30. package/lib/xt-button/xt-button.css +1 -1
  31. package/lib/xt-button/xt-button.umd.js +13 -8
  32. package/lib/xt-button/xt-button.umd.min.js +1 -1
  33. package/lib/xt-card/xt-card.common.js +5 -5
  34. package/lib/xt-card/xt-card.umd.js +5 -5
  35. package/lib/xt-card/xt-card.umd.min.js +1 -1
  36. package/lib/xt-card-item/xt-card-item.common.js +4 -4
  37. package/lib/xt-card-item/xt-card-item.umd.js +4 -4
  38. package/lib/xt-card-item/xt-card-item.umd.min.js +1 -1
  39. package/lib/xt-chart/xt-chart.common.js +1969 -108924
  40. package/lib/xt-chart/xt-chart.css +1 -1
  41. package/lib/xt-chart/xt-chart.umd.js +1975 -108929
  42. package/lib/xt-chart/xt-chart.umd.min.js +1 -1
  43. package/lib/xt-config-provider/xt-config-provider.common.js +561 -107504
  44. package/lib/xt-config-provider/xt-config-provider.css +1 -1
  45. package/lib/xt-config-provider/xt-config-provider.umd.js +567 -107509
  46. package/lib/xt-config-provider/xt-config-provider.umd.min.js +1 -1
  47. package/lib/xt-date-picker/xt-date-picker.common.1.js +4 -26
  48. package/lib/xt-date-picker/xt-date-picker.common.2.js +1 -1
  49. package/lib/xt-date-picker/xt-date-picker.common.js +1 -47
  50. package/lib/xt-date-picker/xt-date-picker.css +1 -1
  51. package/lib/xt-date-picker/xt-date-picker.umd.1.js +4 -26
  52. package/lib/xt-date-picker/xt-date-picker.umd.2.js +1 -1
  53. package/lib/xt-date-picker/xt-date-picker.umd.js +1 -47
  54. package/lib/xt-date-picker/xt-date-picker.umd.min.1.js +1 -1
  55. package/lib/xt-date-picker/xt-date-picker.umd.min.js +1 -1
  56. package/lib/xt-flex-box/xt-flex-box.common.js +1 -1
  57. package/lib/xt-flex-box/xt-flex-box.umd.js +1 -1
  58. package/lib/xt-flow/demo.html +10 -0
  59. package/lib/xt-flow/index.js +1 -0
  60. package/lib/xt-flow/xt-flow.common.js +638 -0
  61. package/lib/xt-flow/xt-flow.css +1 -0
  62. package/lib/xt-flow/xt-flow.umd.js +648 -0
  63. package/lib/xt-flow/xt-flow.umd.min.js +1 -0
  64. package/lib/xt-form-schema/xt-form-schema.common.js +1 -1
  65. package/lib/xt-form-schema/xt-form-schema.umd.js +1 -1
  66. package/lib/xt-grid-box/xt-grid-box.common.js +1 -1
  67. package/lib/xt-grid-box/xt-grid-box.umd.js +1 -1
  68. package/lib/xt-grid-item/xt-grid-item.common.js +1 -1
  69. package/lib/xt-grid-item/xt-grid-item.umd.js +1 -1
  70. package/lib/xt-icon/xt-icon.common.js +1 -1
  71. package/lib/xt-icon/xt-icon.umd.js +1 -1
  72. package/lib/xt-input/xt-input.common.js +1 -1
  73. package/lib/xt-input/xt-input.umd.js +1 -1
  74. package/lib/xt-list/xt-list.common.js +457 -118
  75. package/lib/xt-list/xt-list.css +1 -1
  76. package/lib/xt-list/xt-list.umd.js +457 -118
  77. package/lib/xt-list/xt-list.umd.min.js +1 -1
  78. package/lib/xt-map/xt-map.common.js +1 -1
  79. package/lib/xt-map/xt-map.umd.js +1 -1
  80. package/lib/xt-map-provider/xt-map-provider.common.js +1 -1
  81. package/lib/xt-map-provider/xt-map-provider.umd.js +1 -1
  82. package/lib/xt-page/xt-page.common.js +1 -1
  83. package/lib/xt-page/xt-page.umd.js +1 -1
  84. package/lib/xt-progress/xt-progress.common.js +1 -1
  85. package/lib/xt-progress/xt-progress.umd.js +1 -1
  86. package/lib/xt-scroll/demo.html +10 -0
  87. package/lib/xt-scroll/index.js +1 -0
  88. package/lib/xt-scroll/xt-scroll.common.js +551 -0
  89. package/lib/xt-scroll/xt-scroll.css +1 -0
  90. package/lib/xt-scroll/xt-scroll.umd.js +561 -0
  91. package/lib/xt-scroll/xt-scroll.umd.min.js +1 -0
  92. package/lib/xt-scroll-arrow/xt-scroll-arrow.common.js +591 -170
  93. package/lib/xt-scroll-arrow/xt-scroll-arrow.css +1 -1
  94. package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.js +591 -170
  95. package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.min.js +1 -1
  96. package/lib/xt-select-tree/xt-select-tree.common.js +1 -1
  97. package/lib/xt-select-tree/xt-select-tree.umd.js +1 -1
  98. package/lib/xt-step-price/xt-step-price.common.js +2 -2
  99. package/lib/xt-step-price/xt-step-price.umd.js +2 -2
  100. package/lib/xt-step-price-item/xt-step-price-item.common.js +1 -1
  101. package/lib/xt-step-price-item/xt-step-price-item.umd.js +1 -1
  102. package/lib/xt-tab-pane/xt-tab-pane.common.js +1 -1
  103. package/lib/xt-tab-pane/xt-tab-pane.umd.js +1 -1
  104. package/lib/xt-table/xt-table.common.js +2 -2
  105. package/lib/xt-table/xt-table.umd.js +2 -2
  106. package/lib/xt-tabs/xt-tabs.common.js +1 -1
  107. package/lib/xt-tabs/xt-tabs.umd.js +1 -1
  108. package/lib/xt-text/xt-text.common.js +1 -1
  109. package/lib/xt-text/xt-text.css +1 -1
  110. package/lib/xt-text/xt-text.umd.js +1 -1
  111. package/lib/xt-time/xt-time.common.js +1 -1
  112. package/lib/xt-time/xt-time.umd.js +1 -1
  113. package/lib/xt-transfer-tree/xt-transfer-tree.common.js +1 -1
  114. package/lib/xt-transfer-tree/xt-transfer-tree.umd.js +1 -1
  115. package/lib/xt-upload/xt-upload.common.js +11 -11
  116. package/lib/xt-upload/xt-upload.umd.js +12 -12
  117. package/lib/xt-upload/xt-upload.umd.min.js +1 -1
  118. package/package.json +6 -5
  119. package/src/components/index.scss +7 -1
  120. package/src/components/theme/element-overrides.css +607 -0
  121. package/src/components/theme/element-vars.css +280 -0
  122. package/src/components/theme/iframe-injector.js +502 -0
  123. package/src/components/theme/index.js +64 -0
  124. package/src/components/xt-badge/style/index.scss +6 -6
  125. package/src/components/xt-button/index.vue +11 -6
  126. package/src/components/xt-button/style/index.scss +7 -5
  127. package/src/components/xt-card/index.vue +3 -3
  128. package/src/components/xt-card-item/index.vue +2 -2
  129. package/src/components/xt-chart/XtMulti.vue +8 -18
  130. package/src/components/xt-chart/XtPie.vue +1 -0
  131. package/src/components/xt-chart/utils.js +84 -50
  132. package/src/components/xt-config-provider/index.vue +56 -54
  133. package/src/components/xt-config-provider/style/index.scss +0 -11
  134. package/src/components/xt-date-picker/component/RangeDate.vue +1 -29
  135. package/src/components/xt-date-picker/style/index.scss +79 -54
  136. package/src/components/xt-flow/flowable-parser.js +244 -0
  137. package/src/components/xt-flow/index.js +8 -0
  138. package/src/components/xt-flow/index.vue +226 -0
  139. package/src/components/xt-flow/style/index.scss +359 -0
  140. package/src/components/xt-list/index.vue +23 -12
  141. package/src/components/xt-scroll/index.js +9 -0
  142. package/src/components/xt-scroll/index.vue +363 -0
  143. package/src/components/xt-scroll/style/index.scss +107 -0
  144. package/src/components/xt-scroll-arrow/index.vue +200 -83
  145. package/src/components/xt-scroll-arrow/style/index.scss +16 -5
  146. package/src/components/xt-text/style/index.scss +6 -6
  147. package/src/index.js +15 -13
  148. package/src/styles/css-variables.scss +73 -1
  149. package/src/utils/color.js +105 -0
  150. package/src/utils/index.js +456 -468
  151. package/src/utils/theme-vars.js +134 -0
  152. package/lib/css/1.87a493be.css +0 -1
  153. package/lib/xt-date-picker/css/1.87a493be.css +0 -1
@@ -1,64 +1,89 @@
1
1
 
2
2
  @import '../../../styles/variables.scss';
3
3
  .xt-date-picker-container {
4
- display: inline-block;
5
- width: 100%;
6
-
7
- }
8
- .xt-date .el-date-editor .el-input__inner {
9
- background-color: var(--xt-fill-color-blank);
10
- border-color: var(--xt-border-color);
11
- color: var(--xt-text-color-regular);
12
- font-size: var(--xt-font-size-base, 14px);
13
- border-radius: var(--xt-border-radius-small, 4px);
14
- height: var(--xt-input-height);
15
- line-height: var(--xt-input-height);
16
- padding: 0 15px;
4
+ display: inline-block;
5
+ width: 100%;
6
+ --xt-input-height: 32px;
7
+ }
8
+ .xt-date .el-date-editor .el-input__inner {
9
+ background-color: var(--xt-fill-color-blank);
10
+ border-color: var(--xt-border-color);
11
+ color: var(--xt-text-color-regular);
12
+ font-size: var(--xt-font-size-base, 14px);
13
+ border-radius: var(--xt-border-radius-small, 4px);
14
+ height: var(--xt-input-height);
15
+ line-height: var(--xt-input-height);
16
+ padding: 0 30px;
17
+ }
18
+ .xt-date-dimension {
19
+ margin-bottom: 8px;
20
+ }
21
+
22
+ .xt-date-picker-wrapper {
23
+ display: inline-block;
17
24
  }
18
- .xt-date-dimension {
19
- margin-bottom: 8px;
20
- }
21
25
 
22
- .xt-date-picker-wrapper {
23
- display: inline-block;
26
+
27
+
28
+
29
+ .quarter-panel {
30
+ padding: 10px;
31
+
32
+ .quarter-header {
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: space-between;
36
+ margin-bottom: 10px;
37
+ }
38
+
39
+ .quarter-list {
40
+ display: grid;
41
+ grid-template-columns: repeat(2, 1fr);
42
+ gap: 8px;
24
43
  }
25
-
26
44
 
45
+ .quarter-item {
46
+ text-align: center;
47
+ padding: 8px 0;
48
+ border: 1px solid #e4e7ed;
49
+ border-radius: 4px;
50
+ cursor: pointer;
51
+ }
27
52
 
53
+ .quarter-item.active {
54
+ background: #409eff;
55
+ color: #fff;
56
+ border-color: #409eff;
57
+ }
28
58
 
29
- .date-picker {
30
- .quarter-panel {
31
- padding: 10px;
32
- }
33
-
34
- .quarter-header {
35
- display: flex;
36
- align-items: center;
37
- justify-content: space-between;
38
- margin-bottom: 10px;
39
- }
40
-
41
- .quarter-list {
42
- display: grid;
43
- grid-template-columns: repeat(2, 1fr);
44
- gap: 8px;
45
- }
46
-
47
- .quarter-item {
48
- text-align: center;
49
- padding: 8px 0;
50
- border: 1px solid #e4e7ed;
51
- border-radius: 4px;
52
- cursor: pointer;
53
- }
54
-
55
- .quarter-item.active {
56
- background: #409eff;
57
- color: #fff;
58
- border-color: #409eff;
59
- }
59
+ ::v-deep .base-date-popper {
60
+ border-radius: 4px;
61
+ }
62
+ }
60
63
 
61
- ::v-deep .base-date-popper {
62
- border-radius: 4px;
63
- }
64
- }
64
+ .range-picker {
65
+ display: flex;
66
+ align-items: center;
67
+ flex-wrap: wrap;
68
+ gap: 8px;
69
+ width: 100%;
70
+ }
71
+ .range-picker .el-date-editor {
72
+ flex: 1;
73
+ min-width: 130px;
74
+ }
75
+ .split {
76
+ flex-shrink: 0;
77
+ padding: 0 4px;
78
+ }
79
+ /* 手机/窄侧边栏自动竖向排列 */
80
+ @media (max-width: 360px) {
81
+ .range-picker {
82
+ flex-direction: column;
83
+ align-items: stretch;
84
+ }
85
+ .split {
86
+ text-align: center;
87
+ margin: 4px 0;
88
+ }
89
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Flowable 工作流数据格式化工具
3
+ *
4
+ * 原生 Flowable 数据格式示例:
5
+ * {
6
+ * "id": "75001",
7
+ * "processInstanceId": "25001",
8
+ * "taskId": "50001",
9
+ * "taskName": "部门经理审批",
10
+ * "assignee": "张三",
11
+ * "startTime": "2024-01-01T10:00:00",
12
+ * "endTime": "2024-01-01T10:30:00",
13
+ * "deleteReason": null,
14
+ * "type": "startEvent" | "userTask" | "endEvent"
15
+ * }
16
+ *
17
+ * 历史任务(HistoricTaskInstance):
18
+ * {
19
+ * "id": "50001",
20
+ * "name": "部门经理审批",
21
+ * "assignee": "张三",
22
+ * "startTime": "2024-01-01T10:00:00",
23
+ * "endTime": "2024-01-01T10:30:00",
24
+ * "deleteReason": "rejected",
25
+ * "durationInMillis": 1800000
26
+ * }
27
+ */
28
+
29
+ // 默认待办节点
30
+ const DEFAULT_PENDING_NODE = {
31
+ status: 'pending',
32
+ name: '待处理',
33
+ assignee: '--',
34
+ createTime: null,
35
+ comment: '等待审批中...'
36
+ }
37
+
38
+ /**
39
+ * 解析 Flowable 原始数据为 xt-flow 组件格式
40
+ * @param {Array} rawData - Flowable 返回的原始数据
41
+ * @param {Object} options
42
+ * @param {Object} options.pendingNode - 自定义待办节点数据
43
+ * @param {Boolean} options.autoPending - 是否自动追加待办节点(默认 true)
44
+ * @returns {Array} 标准化后的节点数组
45
+ */
46
+ export function parseFlowData(rawData, options = {}) {
47
+ const { pendingNode = null, autoPending = true } = options
48
+
49
+ if (!rawData || !rawData.length) {
50
+ return []
51
+ }
52
+
53
+ const nodes = rawData
54
+ .filter((item) => {
55
+ // 过滤掉 startEvent 和无效数据
56
+ if (item.type === 'startEvent') return false
57
+ // 过滤掉没有任务名称的节点
58
+ if (!item.name && !item.taskName && !item.activityName) return false
59
+ return true
60
+ })
61
+ .map((item) => {
62
+ const node = {
63
+ id: item.id || item.taskId || '',
64
+ name: item.name || item.taskName || item.activityName || '',
65
+ assignee: item.assignee || '',
66
+ createTime: item.startTime || item.createTime || '',
67
+ endTime: item.endTime || '',
68
+ status: inferStatus(item),
69
+ comment: item.comment || item.deleteReason || '',
70
+ duration: formatDuration(item.durationInMillis || item.duration),
71
+ // 保留原始数据用于插槽扩展
72
+ raw: item
73
+ }
74
+ return node
75
+ })
76
+
77
+ // 自动追加待办节点
78
+ if (autoPending && nodes.length > 0) {
79
+ const lastNode = nodes[nodes.length - 1]
80
+ // 如果最后一个节点不是 pending 状态,追加待办节点
81
+ if (lastNode.status !== 'pending') {
82
+ const pending = pendingNode || DEFAULT_PENDING_NODE
83
+ nodes.push({
84
+ id: 'pending',
85
+ name: pending.name || '待处理',
86
+ assignee: pending.assignee || '--',
87
+ createTime: pending.createTime || null,
88
+ endTime: null,
89
+ status: 'pending',
90
+ comment: pending.comment || '',
91
+ duration: '',
92
+ raw: pending
93
+ })
94
+ }
95
+ }
96
+
97
+ return nodes
98
+ }
99
+
100
+ /**
101
+ * 推断节点状态
102
+ * @param {Object} item - Flowable 原始数据项
103
+ * @returns {String} 状态值
104
+ */
105
+ function inferStatus(item) {
106
+ const deleteReason = (item.deleteReason || '').toLowerCase()
107
+
108
+ // 根据 deleteReason 判断
109
+ if (deleteReason.includes('reject') || deleteReason.includes('驳回')) {
110
+ return 'rejected'
111
+ }
112
+ if (deleteReason.includes('delegate') || deleteReason.includes('委托')) {
113
+ return 'delegated'
114
+ }
115
+ if (deleteReason.includes('transfer') || deleteReason.includes('转办')) {
116
+ return 'transferred'
117
+ }
118
+ if (deleteReason.includes('revoke') || deleteReason.includes('撤回') || deleteReason.includes('recall')) {
119
+ return 'recalled'
120
+ }
121
+ if (deleteReason.includes('cancel') || deleteReason.includes('取消')) {
122
+ return 'cancelled'
123
+ }
124
+
125
+ // 有结束时间且无驳回原因 → 通过
126
+ if (item.endTime && !item.deleteReason) {
127
+ return 'approved'
128
+ }
129
+
130
+ // 无结束时间 → 待办中
131
+ if (!item.endTime) {
132
+ return 'pending'
133
+ }
134
+
135
+ return 'approved'
136
+ }
137
+
138
+ /**
139
+ * 格式化时长
140
+ * @param {Number} millis - 毫秒数
141
+ * @returns {String} 格式化后的时长
142
+ */
143
+ function formatDuration(millis) {
144
+ if (!millis && millis !== 0) return ''
145
+
146
+ const totalSeconds = Math.floor(millis / 1000)
147
+ if (totalSeconds < 60) {
148
+ return `${totalSeconds}秒`
149
+ }
150
+
151
+ const minutes = Math.floor(totalSeconds / 60)
152
+ if (minutes < 60) {
153
+ return `${minutes}分钟`
154
+ }
155
+
156
+ const hours = Math.floor(minutes / 60)
157
+ const remainMinutes = minutes % 60
158
+ if (hours < 24) {
159
+ return remainMinutes > 0 ? `${hours}小时${remainMinutes}分钟` : `${hours}小时`
160
+ }
161
+
162
+ const days = Math.floor(hours / 24)
163
+ const remainHours = hours % 24
164
+ return remainHours > 0 ? `${days}天${remainHours}小时` : `${days}天`
165
+ }
166
+
167
+ /**
168
+ * 格式化时间字符串
169
+ * @param {String} timeStr - 时间字符串
170
+ * @param {String} format - 格式
171
+ * @returns {String} 格式化后的时间
172
+ */
173
+ export function formatFlowTime(timeStr, format) {
174
+ if (!timeStr) return ''
175
+
176
+ try {
177
+ const d = new Date(timeStr)
178
+ if (isNaN(d.getTime())) return timeStr
179
+
180
+ const year = d.getFullYear()
181
+ const month = String(d.getMonth() + 1).padStart(2, '0')
182
+ const day = String(d.getDate()).padStart(2, '0')
183
+ const hour = String(d.getHours()).padStart(2, '0')
184
+ const minute = String(d.getMinutes()).padStart(2, '0')
185
+ const second = String(d.getSeconds()).padStart(2, '0')
186
+
187
+ return format
188
+ .replace('YYYY', year)
189
+ .replace('MM', month)
190
+ .replace('DD', day)
191
+ .replace('HH', hour)
192
+ .replace('mm', minute)
193
+ .replace('ss', second)
194
+ } catch (e) {
195
+ return timeStr
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 生成模拟审批链数据(用于开发和演示)
201
+ * @param {Number} count - 节点数量
202
+ * @returns {Array} 模拟数据
203
+ */
204
+ export function createMockFlowData(count = 7) {
205
+ const statuses = ['approved', 'approved', 'approved', 'rejected', 'pending', 'transferred', 'approved']
206
+ const names = [
207
+ '提交申请', '直属领导审批', '部门经理审批',
208
+ '总监审批', 'HR 审批', '总经理审批', '流程结束'
209
+ ]
210
+ const assignees = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九']
211
+ const comments = [
212
+ '', '同意', '同意', '驳回:请补充材料后重新提交',
213
+ '', '', '同意'
214
+ ]
215
+
216
+ const now = new Date()
217
+ const result = []
218
+
219
+ for (let i = 0; i < Math.min(count, names.length); i++) {
220
+ const startTime = new Date(now.getTime() - (count - i) * 3600000)
221
+ const endTime = statuses[i] === 'pending'
222
+ ? null
223
+ : new Date(startTime.getTime() + 1800000)
224
+
225
+ result.push({
226
+ id: `flow-${i + 1}`,
227
+ name: names[i],
228
+ assignee: assignees[i],
229
+ status: statuses[i],
230
+ createTime: startTime.toISOString(),
231
+ endTime: endTime ? endTime.toISOString() : null,
232
+ comment: comments[i],
233
+ duration: endTime ? '30分钟' : ''
234
+ })
235
+ }
236
+
237
+ return result
238
+ }
239
+
240
+ export default {
241
+ parseFlowData,
242
+ formatFlowTime,
243
+ createMockFlowData
244
+ }
@@ -0,0 +1,8 @@
1
+ import './style/index.scss'
2
+ import XtFlow from './index.vue'
3
+
4
+ XtFlow.install = function(Vue) {
5
+ Vue.component(XtFlow.name, XtFlow)
6
+ }
7
+
8
+ export default XtFlow
@@ -0,0 +1,226 @@
1
+ <template>
2
+ <div class="xt-flow" :class="rootClasses">
3
+ <div
4
+ v-for="(node, index) in displayNodes"
5
+ :key="node.id || index"
6
+ class="xt-flow__node"
7
+ :class="nodeClasses(node, index)"
8
+ >
9
+ <!-- 时间线左侧 -->
10
+ <div class="xt-flow__timeline">
11
+ <!-- 节点图标 -->
12
+ <div class="xt-flow__icon" :class="`xt-flow__icon--${node.status}`">
13
+ <slot name="node-avatar" :node="node" :index="index">
14
+ <span class="xt-flow__icon-dot"></span>
15
+ </slot>
16
+ </div>
17
+ <!-- 连接线 -->
18
+ <div
19
+ v-if="index < displayNodes.length - 1"
20
+ class="xt-flow__line"
21
+ :class="[
22
+ `xt-flow__line--${node.status}`,
23
+ { 'xt-flow__line--dashed': node.status === 'rejected' }
24
+ ]"
25
+ ></div>
26
+ </div>
27
+
28
+ <!-- 节点内容 -->
29
+ <div class="xt-flow__body">
30
+ <div class="xt-flow__header">
31
+ <span class="xt-flow__title">
32
+ <slot name="node" :node="node" :index="index">
33
+ {{ node.name || node.title }}
34
+ </slot>
35
+ </span>
36
+ <span class="xt-flow__status" :class="`xt-flow__status--${node.status}`">
37
+ {{ statusText(node.status) }}
38
+ </span>
39
+ </div>
40
+
41
+ <div class="xt-flow__meta">
42
+ <template v-if="node.assignee">
43
+ <span class="xt-flow__assignee">
44
+ <slot name="assignee" :node="node" :index="index">
45
+ <span class="xt-flow__assignee-icon">👤</span>
46
+ {{ node.assignee }}
47
+ </slot>
48
+ </span>
49
+ </template>
50
+ <template v-if="node.createTime || node.endTime">
51
+ <span class="xt-flow__time">
52
+ {{ formatTime(node) }}
53
+ </span>
54
+ </template>
55
+ <template v-if="node.duration">
56
+ <span class="xt-flow__duration">
57
+ {{ node.duration }}
58
+ </span>
59
+ </template>
60
+ </div>
61
+
62
+ <div v-if="node.comment || node.description" class="xt-flow__desc">
63
+ <slot name="node-desc" :node="node" :index="index">
64
+ <template v-if="node.status === 'rejected' && node.comment">
65
+ <span class="xt-flow__desc-label">驳回原因:</span>
66
+ </template>
67
+ <template v-else-if="node.comment">
68
+ <span class="xt-flow__desc-label">审批意见:</span>
69
+ </template>
70
+ {{ node.comment || node.description }}
71
+ </slot>
72
+ </div>
73
+
74
+ <div v-if="$scopedSlots['node-action']" class="xt-flow__actions">
75
+ <slot name="node-action" :node="node" :index="index"></slot>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ <!-- 折叠/展开触发器 -->
81
+ <div
82
+ v-if="isCollapsible && collapsed"
83
+ class="xt-flow__collapse"
84
+ @click="toggleCollapse"
85
+ >
86
+ <slot name="expand-trigger">
87
+ <span class="xt-flow__collapse-text">
88
+ 展开全部 {{ totalCount }} 条审批记录
89
+ <span class="xt-flow__collapse-arrow">▼</span>
90
+ </span>
91
+ </slot>
92
+ </div>
93
+ <div
94
+ v-if="isCollapsible && !collapsed"
95
+ class="xt-flow__collapse"
96
+ @click="toggleCollapse"
97
+ >
98
+ <slot name="collapse-trigger">
99
+ <span class="xt-flow__collapse-text">
100
+ 收起
101
+ <span class="xt-flow__collapse-arrow xt-flow__collapse-arrow--up">▲</span>
102
+ </span>
103
+ </slot>
104
+ </div>
105
+ </div>
106
+ </template>
107
+
108
+ <script>
109
+ import { parseFlowData, formatFlowTime } from './flowable-parser'
110
+
111
+ export default {
112
+ name: 'XtFlow',
113
+
114
+ props: {
115
+ // 数据源:数组格式,支持 Flowable 原始格式或自定义格式
116
+ data: {
117
+ type: Array,
118
+ default: () => []
119
+ },
120
+ // 是否自动折叠长审批链
121
+ collapse: {
122
+ type: Boolean,
123
+ default: true
124
+ },
125
+ // 折叠前可见节点数
126
+ collapseCount: {
127
+ type: Number,
128
+ default: 5
129
+ },
130
+ // 圆角风格:square 方形 / round 圆形
131
+ rounded: {
132
+ type: String,
133
+ default: 'square',
134
+ validator: (val) => ['square', 'round'].includes(val)
135
+ },
136
+ // 数据格式:flowable 原生 / custom 自定义
137
+ format: {
138
+ type: String,
139
+ default: 'flowable',
140
+ validator: (val) => ['flowable', 'custom'].includes(val)
141
+ },
142
+ // 时间格式(仅在 format='flowable' 时生效)
143
+ timeFormat: {
144
+ type: String,
145
+ default: 'YYYY-MM-DD HH:mm:ss'
146
+ },
147
+ // 自定义状态文本映射
148
+ statusMap: {
149
+ type: Object,
150
+ default: () => ({
151
+ approved: '已通过',
152
+ rejected: '已驳回',
153
+ pending: '待审批',
154
+ transferred: '已转办',
155
+ delegated: '已委托',
156
+ recalled: '已撤回',
157
+ started: '已发起',
158
+ cancelled: '已取消'
159
+ })
160
+ }
161
+ },
162
+
163
+ data() {
164
+ return {
165
+ collapsed: true
166
+ }
167
+ },
168
+
169
+ computed: {
170
+ rootClasses() {
171
+ return [
172
+ `xt-flow--${this.rounded}`
173
+ ]
174
+ },
175
+
176
+ parsedData() {
177
+ if (this.format === 'flowable') {
178
+ return parseFlowData(this.data)
179
+ }
180
+ return this.data
181
+ },
182
+
183
+ totalCount() {
184
+ return this.parsedData.length
185
+ },
186
+
187
+ isCollapsible() {
188
+ return this.collapse && this.totalCount > this.collapseCount
189
+ },
190
+
191
+ displayNodes() {
192
+ if (!this.isCollapsible || !this.collapsed) {
193
+ return this.parsedData
194
+ }
195
+ return this.parsedData.slice(0, this.collapseCount)
196
+ }
197
+ },
198
+
199
+ methods: {
200
+ nodeClasses(node, index) {
201
+ return [
202
+ `xt-flow__node--${node.status}`,
203
+ {
204
+ 'xt-flow__node--last': index === this.displayNodes.length - 1,
205
+ 'xt-flow__node--first': index === 0
206
+ }
207
+ ]
208
+ },
209
+
210
+ statusText(status) {
211
+ return this.statusMap[status] || status
212
+ },
213
+
214
+ formatTime(node) {
215
+ if (this.format === 'flowable') {
216
+ return formatFlowTime(node.createTime || node.endTime, this.timeFormat)
217
+ }
218
+ return node.createTime || node.endTime || ''
219
+ },
220
+
221
+ toggleCollapse() {
222
+ this.collapsed = !this.collapsed
223
+ }
224
+ }
225
+ }
226
+ </script>