@ebiz/designer-components 0.1.11 → 0.1.13

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 (206) hide show
  1. package/README.md +29 -29
  2. package/dist/designer-components.css +1 -1
  3. package/dist/index.mjs +11367 -11321
  4. package/package.json +1 -1
  5. package/src/App.vue +26 -26
  6. package/src/apiService/SIMPLE_DATA_SERVICE.md +284 -284
  7. package/src/apiService/mockDataService.js +115 -115
  8. package/src/apiService/simpleDataService.js +297 -297
  9. package/src/assets/base.css +86 -86
  10. package/src/assets/logo.svg +1 -1
  11. package/src/components/Button.vue +149 -149
  12. package/src/components/DataContainer.vue +40 -40
  13. package/src/components/EbizApproval.vue +389 -332
  14. package/src/components/EbizAutoForm.vue +596 -596
  15. package/src/components/EbizAvatar.vue +115 -115
  16. package/src/components/EbizCheckbox.vue +93 -93
  17. package/src/components/EbizCheckboxGroup.vue +69 -69
  18. package/src/components/EbizDepartmentSelector.vue +149 -149
  19. package/src/components/EbizDescriptions.vue +340 -340
  20. package/src/components/EbizDescriptionsItem.vue +47 -47
  21. package/src/components/EbizDetailBlock.vue +81 -81
  22. package/src/components/EbizDialog.vue +260 -260
  23. package/src/components/EbizDiv.vue +32 -32
  24. package/src/components/EbizDivider.vue +96 -96
  25. package/src/components/EbizDropdown.vue +135 -135
  26. package/src/components/EbizDropdownItem.vue +85 -85
  27. package/src/components/EbizEmployeeInfo.vue +138 -138
  28. package/src/components/EbizEmployeeSelector.vue +1074 -1100
  29. package/src/components/EbizFileUpload.vue +238 -238
  30. package/src/components/EbizMap.vue +541 -541
  31. package/src/components/EbizMeetingRoomSelector.vue +664 -664
  32. package/src/components/EbizMobileMeetingRoomSelector.vue +727 -727
  33. package/src/components/EbizOkrTree.vue +99 -99
  34. package/src/components/EbizPageHeader.vue +95 -95
  35. package/src/components/EbizPagination.vue +162 -162
  36. package/src/components/EbizPopconfirm.vue +47 -47
  37. package/src/components/EbizRadio.vue +86 -86
  38. package/src/components/EbizRadioGroup.vue +83 -83
  39. package/src/components/EbizRemoteSelect.vue +232 -232
  40. package/src/components/EbizRouteBreadcrumb.vue +46 -46
  41. package/src/components/EbizSApprovalProcess.vue +1134 -1132
  42. package/src/components/EbizSelect.vue +85 -85
  43. package/src/components/EbizSpace.vue +100 -100
  44. package/src/components/EbizStatistic.vue +149 -149
  45. package/src/components/EbizStatsCard.vue +113 -113
  46. package/src/components/EbizSwiper.vue +113 -113
  47. package/src/components/EbizSwiperItem.vue +13 -13
  48. package/src/components/EbizSwitch.vue +85 -85
  49. package/src/components/EbizTabHeader.vue +132 -132
  50. package/src/components/EbizTabPanel.vue +22 -22
  51. package/src/components/EbizTable.vue +469 -469
  52. package/src/components/EbizTableColumn.vue +116 -116
  53. package/src/components/EbizTableSort.vue +179 -179
  54. package/src/components/EbizTabs.vue +142 -142
  55. package/src/components/EbizTdesignButtonDialog.vue +332 -332
  56. package/src/components/EbizTdesignLoading.vue +107 -107
  57. package/src/components/EbizTimePicker.vue +143 -143
  58. package/src/components/EbizTitle.vue +91 -91
  59. package/src/components/EbizTree.vue +152 -152
  60. package/src/components/EbizTreeMergeTable.vue +1414 -1414
  61. package/src/components/EbizTreeSelector.vue +418 -418
  62. package/src/components/EbizVxeTable.vue +290 -290
  63. package/src/components/Form.vue +28 -28
  64. package/src/components/Home.vue +7 -7
  65. package/src/components/MyComponent.vue +39 -39
  66. package/src/components/Table.vue +45 -45
  67. package/src/components/TdesignAlert.vue +115 -115
  68. package/src/components/TdesignButton.vue +135 -135
  69. package/src/components/TdesignCalendar/index.vue +145 -145
  70. package/src/components/TdesignCard.vue +195 -195
  71. package/src/components/TdesignCol.vue +101 -101
  72. package/src/components/TdesignCollapse.vue +142 -142
  73. package/src/components/TdesignCollapsePanel.vue +79 -79
  74. package/src/components/TdesignDatePicker.vue +124 -124
  75. package/src/components/TdesignDescriptions.vue +74 -74
  76. package/src/components/TdesignDescriptionsItem.vue +50 -50
  77. package/src/components/TdesignDialog.vue +225 -225
  78. package/src/components/TdesignForm.vue +138 -138
  79. package/src/components/TdesignFormItem.vue +105 -105
  80. package/src/components/TdesignGrid.vue +55 -55
  81. package/src/components/TdesignIcon.vue +67 -67
  82. package/src/components/TdesignImage.vue +162 -162
  83. package/src/components/TdesignImageViewer.vue +200 -200
  84. package/src/components/TdesignInput.vue +242 -242
  85. package/src/components/TdesignSelect.vue +444 -444
  86. package/src/components/TdesignTag.vue +117 -117
  87. package/src/components/TdesignTextarea.vue +142 -142
  88. package/src/components/TdesignTimeline.vue +58 -58
  89. package/src/components/TdesignTimelineItem.vue +71 -71
  90. package/src/components/TdesignUpload.vue +405 -405
  91. package/src/components/TdesignWatermark.vue +107 -107
  92. package/src/components/ebiz-form/components/cascader.vue +61 -61
  93. package/src/components/ebiz-form/components/checkbox.vue +37 -37
  94. package/src/components/ebiz-form/components/city.vue +137 -137
  95. package/src/components/ebiz-form/components/date-panel.vue +52 -52
  96. package/src/components/ebiz-form/components/date-range-panel.vue +52 -52
  97. package/src/components/ebiz-form/components/date-range.vue +56 -56
  98. package/src/components/ebiz-form/components/date.vue +52 -52
  99. package/src/components/ebiz-form/components/editor-multi-language.vue +47 -47
  100. package/src/components/ebiz-form/components/editor.vue +78 -78
  101. package/src/components/ebiz-form/components/file-multi-language.vue +52 -52
  102. package/src/components/ebiz-form/components/file.vue +149 -149
  103. package/src/components/ebiz-form/components/images-multi-language.vue +52 -52
  104. package/src/components/ebiz-form/components/images.vue +129 -129
  105. package/src/components/ebiz-form/components/img-multi-language.vue +51 -51
  106. package/src/components/ebiz-form/components/img.vue +129 -129
  107. package/src/components/ebiz-form/components/number.vue +50 -50
  108. package/src/components/ebiz-form/components/radio.vue +28 -28
  109. package/src/components/ebiz-form/components/select.vue +119 -119
  110. package/src/components/ebiz-form/components/switch.vue +23 -23
  111. package/src/components/ebiz-form/components/text-multi-language.vue +47 -47
  112. package/src/components/ebiz-form/components/text.vue +52 -52
  113. package/src/components/ebiz-form/components/textarea-multi-language.vue +48 -48
  114. package/src/components/ebiz-form/components/textarea.vue +29 -29
  115. package/src/components/ebiz-form/components/video-multi-language.vue +51 -51
  116. package/src/components/ebiz-form/components/video.vue +97 -97
  117. package/src/components/ebiz-form/index.vue +157 -157
  118. package/src/components/examples/PopconfirmExample.vue +149 -149
  119. package/src/components/icons/IconCommunity.vue +7 -7
  120. package/src/components/icons/IconDocumentation.vue +7 -7
  121. package/src/components/icons/IconEcosystem.vue +7 -7
  122. package/src/components/icons/IconSupport.vue +7 -7
  123. package/src/components/icons/IconTooling.vue +19 -19
  124. package/src/components/senior/EbizSData/index.vue +262 -262
  125. package/src/components/senior/EbizSDialog/index.vue +715 -715
  126. package/src/components/senior/EbizSForm/README.md +157 -157
  127. package/src/components/senior/EbizSForm/index.vue +668 -668
  128. package/src/components/senior/EbizSForm/item.vue +512 -512
  129. package/src/components/senior/EbizSForm/mItems/DateTimePicker.vue +51 -51
  130. package/src/components/senior/EbizSForm/mItems/Picker.vue +63 -63
  131. package/src/index.js +232 -232
  132. package/src/main.js +55 -55
  133. package/src/router/index.js +386 -386
  134. package/src/utils/formatCode.js +24 -24
  135. package/src/utils/generateImportStatement.js +52 -52
  136. package/src/utils/hasJsx.js +25 -25
  137. package/src/utils/index.js +166 -166
  138. package/src/utils/mergeOptions.js +29 -29
  139. package/src/utils/parseRequiredBlocks.js +18 -18
  140. package/src/utils/upload.ts +126 -126
  141. package/src/utils/vue-sfc-validator.js +155 -155
  142. package/src/views/Button.vue +23 -23
  143. package/src/views/CheckboxDemo.vue +104 -104
  144. package/src/views/DataContainer.vue +19 -19
  145. package/src/views/DialogDemo.vue +125 -125
  146. package/src/views/EbizApprovalDemo.vue +87 -76
  147. package/src/views/EbizAutoFormDemo.vue +129 -129
  148. package/src/views/EbizAvatar.vue +223 -223
  149. package/src/views/EbizDepartmentSelectorDemo.vue +169 -169
  150. package/src/views/EbizDetailBlockDemo.vue +30 -30
  151. package/src/views/EbizEmployeeInfo.vue +249 -249
  152. package/src/views/EbizEmployeeSelector.vue +83 -83
  153. package/src/views/EbizMap.vue +201 -201
  154. package/src/views/EbizMeetingRoomSelectorDemo.vue +293 -293
  155. package/src/views/EbizMobileMeetingRoomSelectorDemo.vue +566 -566
  156. package/src/views/EbizRadioDemo.vue +151 -151
  157. package/src/views/EbizSDataDemo.vue +136 -136
  158. package/src/views/EbizSDialogDemo.vue +301 -301
  159. package/src/views/EbizSForm/index.vue +359 -359
  160. package/src/views/EbizSFormDemo.vue +420 -420
  161. package/src/views/EbizSpace.vue +185 -185
  162. package/src/views/EbizSwiper.vue +157 -157
  163. package/src/views/EbizTdesignButtonDialogExample.vue +437 -437
  164. package/src/views/Form.vue +19 -19
  165. package/src/views/GridDemo.vue +238 -238
  166. package/src/views/Home.vue +148 -148
  167. package/src/views/Mindmap.vue +17 -17
  168. package/src/views/MyComponent.vue +19 -19
  169. package/src/views/OkrTree.vue +19 -19
  170. package/src/views/PageHeaderDemo.vue +104 -104
  171. package/src/views/PaginationDemo.vue +96 -96
  172. package/src/views/PermissionBoxDemo.vue +85 -85
  173. package/src/views/PopconfirmDemo.vue +80 -80
  174. package/src/views/RemoteSelect.vue +350 -350
  175. package/src/views/StatisticDemo.vue +190 -190
  176. package/src/views/SwitchDemo.vue +79 -79
  177. package/src/views/Table.vue +19 -19
  178. package/src/views/TableDemo.vue +334 -334
  179. package/src/views/TableSortDemo.vue +143 -143
  180. package/src/views/TableView.vue +68 -68
  181. package/src/views/TabsDemo.vue +282 -282
  182. package/src/views/TagDemo.vue +101 -101
  183. package/src/views/TdesignAlert.vue +98 -98
  184. package/src/views/TdesignButton.vue +190 -190
  185. package/src/views/TdesignCalendar.vue +94 -94
  186. package/src/views/TdesignCard.vue +296 -296
  187. package/src/views/TdesignCollapse.vue +293 -293
  188. package/src/views/TdesignDatePicker.vue +187 -187
  189. package/src/views/TdesignDescriptions.vue +101 -101
  190. package/src/views/TdesignForm.vue +248 -248
  191. package/src/views/TdesignIcon.vue +203 -203
  192. package/src/views/TdesignImage.vue +215 -215
  193. package/src/views/TdesignImageViewer.vue +198 -198
  194. package/src/views/TdesignInput.vue +252 -252
  195. package/src/views/TdesignSelect.vue +473 -473
  196. package/src/views/TdesignSwiper.vue +157 -157
  197. package/src/views/TextareaDemo.vue +93 -93
  198. package/src/views/TimePickerDemo.vue +146 -146
  199. package/src/views/TimelineDemo.vue +160 -160
  200. package/src/views/Title.vue +19 -19
  201. package/src/views/TreeDemo.vue +254 -254
  202. package/src/views/TreeMergeTableDemo.vue +239 -239
  203. package/src/views/TreeSelectorDemo.vue +245 -245
  204. package/src/views/UploadDemo.vue +121 -121
  205. package/src/views/VxeTableDemo.vue +279 -279
  206. package/src/views/WatermarkDemo.vue +85 -85
@@ -1,1133 +1,1135 @@
1
- <template>
2
- <div class="ebiz-approval-process">
3
- <div class="block-base-style">
4
- <ebiz-tdesign-loading v-if="state.loading" :loading="true" theme="circular" size="small" text=""
5
- :reverse="false" layout="vertical" :delay="0" class="component-base-style">
6
- <span style="display: inline-block" class="component-base-style">加载中</span>
7
- </ebiz-tdesign-loading>
8
-
9
- <div v-if="!state.loading" class="card">
10
- <div style="margin: 10px 0" class="component-base-style">
11
- <span style="display: inline-block" class="title">审批流程</span>
12
- </div>
13
-
14
- <!-- 流程基本信息 -->
15
- <div v-if="state.processInfo" class="process-info">
16
- <div class="info-item" v-if="getStartUserInfo().userId">
17
- <span class="label">发起人:</span>
18
- <user-info :userId="getStartUserInfo().userId" :userInfo="getStartUserInfo().userInfo"
19
- avatar-size="small" name-size="small" :show-job-number="true" :show-department="false" />
20
- </div>
21
- <div class="info-item">
22
- <span class="label">发起时间:</span>
23
- <span>{{ state.processInfo.startTimeStr || '--' }}</span>
24
- </div>
25
- <div class="info-item">
26
- <span class="label">流程状态:</span>
27
- <t-tag :theme="getProcessStatusTheme(state.processInfo.processStatus)" size="small">
28
- {{ getProcessStatusText(state.processInfo.processStatus) }}
29
- </t-tag>
30
- </div>
31
- <!-- 抄送人信息 -->
32
- </div>
33
-
34
- <!-- 审批步骤 -->
35
- <div class="simple-approval-steps">
36
- <!-- 节点步骤 -->
37
- <div v-for="(node, index) in state.nodes" :key="'node-' + index" class="simple-step-item">
38
- <div class="step-header">
39
- <div class="step-icon" :class="getSimpleStepIconClass(node)">
40
- <t-icon name="user" size="12px" color="white" />
41
- </div>
42
- <div class="step-info">
43
- <div class="step-title-row">
44
- <span class="step-title">{{ node.nodeName }}</span>
45
- <t-tag :theme="getNodeStatusTheme(node)" size="small">
46
- {{ getNodeStatusText(node) }}
47
- </t-tag>
48
- </div>
49
- </div>
50
- </div>
51
-
52
- <!-- 简化的审批人信息 -->
53
- <div class="step-content">
54
- <!-- 已完成节点 -->
55
- <div v-for="approver in getCompletedApprovers(node)"
56
- :key="approver.nodeUser?.userId || approver.userId" class="simple-approver-wrapper">
57
- <div class="simple-approver">
58
- <!-- 操作用户信息 -->
59
- <div class="approver-user-info">
60
- <user-info :userId="approver.nodeUser?.userId || approver.userId"
61
- :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
62
- :show-department="false" />
63
- </div>
64
-
65
- <!-- 操作类型和时间 -->
66
- <div class="approver-action-info">
67
- <div class="action-type">{{ getOperationTypeText(approver) }}</div>
68
- <div class="approver-time">{{ formatTime(approver.operation?.operationTime ||
69
- approver.completedTime || approver.endTime) }}</div>
70
- </div>
71
- </div>
72
-
73
- <!-- 目标用户信息(转审、加签等操作) -->
74
- <div v-if="approver.targetUser && needShowTargetUser(approver)"
75
- class="target-user-info">
76
- <div class="target-label">{{ getTargetUserLabel(approver) }}</div>
77
- <user-info :userId="approver.targetUser.userId"
78
- :userInfo="getTargetUserInfo(approver)" avatar-size="small" name-size="small"
79
- :show-department="false" />
80
- </div>
81
-
82
- <!-- 评论信息 -->
83
- <div v-if="approver.comment" class="approver-comment">{{ approver.comment }}</div>
84
- </div>
85
-
86
- <!-- 进行中节点 -->
87
- <!-- 当前处理人 -->
88
- <div v-if="getCurrentApprover(node)" class="simple-approver">
89
- <user-info
90
- :userId="getCurrentApprover(node).nodeUser?.userId || getCurrentApprover(node).userId"
91
- :userInfo="getUserInfo(getCurrentApprover(node))" avatar-size="small"
92
- name-size="small" :show-department="false" />
93
- <div class="approver-time">处理中</div>
94
- </div>
95
-
96
- <!-- 待处理人 -->
97
- <div v-for="approver in getPendingApprovers(node)"
98
- :key="'pending-' + (approver.nodeUser?.userId || approver.userId)"
99
- class="simple-approver">
100
- <user-info :userId="approver.nodeUser?.userId || approver.userId"
101
- :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
102
- :show-department="false" />
103
- <!-- <div class="approver-time">待处理</div> -->
104
- </div>
105
- </div>
106
- </div>
107
-
108
- <!-- 抄送人步骤 -->
109
- <div v-if="state.processInfo" class="simple-step-item">
110
- <div class="step-header">
111
- <div class="step-icon" :class="getCcSimpleIconClass()">
112
- <t-icon name="mail" size="12px" color="white" />
113
- </div>
114
- <div class="step-info">
115
- <div class="step-title-row">
116
- <span class="step-title">抄送</span>
117
- <!-- 增加抄送人按钮 -->
118
- <ebiz-employee-selector content="增加抄送人" :single="false" defaultTab="organization"
119
- class="component-base-style" v-model="state.selectedCCList"
120
- @click="handleAddCcUser" @confirm="onAddCcListConfirm">
121
- </ebiz-employee-selector>
122
- <!-- <t-button theme="default" variant="outline" size="mini" @click="handleAddCcUser"
123
- class="add-cc-button-inline">
124
- <t-icon name="add" size="12px" />
125
- 增加抄送人
126
- </t-button> -->
127
- </div>
128
- </div>
129
- </div>
130
-
131
- <div class="step-content">
132
- <div v-for="ccUser in state.processInfo.ccUserList"
133
- :key="ccUser.nodeUser?.userId || ccUser.userId" class="simple-approver">
134
- <user-info :userId="ccUser.nodeUser?.userId || ccUser.userId"
135
- :userInfo="ccUser.nodeUser || ccUser" avatar-size="small" name-size="small"
136
- :show-department="false" />
137
- <div class="approver-time">{{ getCcTimeText() }}</div>
138
- </div>
139
- </div>
140
- </div>
141
- </div>
142
- </div>
143
-
144
- </div>
145
- </div>
146
- </template>
147
-
148
- <script>
149
- /**
150
- * @displayName 审批流程
151
- * @description 审批流程组件,用于展示审批流程的详细信息
152
- * @category 业务组件
153
- * @name EbizApprovalProcess
154
- */
155
- export default {
156
- name: "EbizApprovalProcess",
157
- components: {
158
- 't-tag': Tag,
159
- 't-icon': Icon,
160
- 't-button': Button
161
- }
162
- }
163
- </script>
164
-
165
- <script setup>
166
- import { reactive, onMounted } from 'vue'
167
- import { defineProps, defineEmits } from 'vue'
168
- import { Tag, Icon, Button, MessagePlugin as message } from 'tdesign-vue-next'
169
- import EbizEmployeeSelector from './EbizEmployeeSelector.vue'
170
- import EbizTdesignLoading from './EbizTdesignLoading.vue'
171
- import dataService from '../apiService/simpleDataService.js'
172
- import UserInfo from './mItems/UserInfo.vue'
173
-
174
- // 定义 props
175
- const props = defineProps({
176
- /**
177
- * 业务键
178
- */
179
- businessKey: {
180
- type: String,
181
- default: ''
182
- },
183
- /**
184
- * 业务类型
185
- */
186
- type: {
187
- type: String,
188
- default: ''
189
- }
190
- })
191
-
192
- // 定义 emits
193
- const emit = defineEmits(['load-success', 'add-cc-user'])
194
-
195
- // 响应式状态
196
- const state = reactive({
197
- loading: false,
198
- processInfo: null,
199
- nodes: [], // 直接保存节点数据
200
- historyRecords: [],
201
- currentNodes: [],
202
- futureNodes: [],
203
- approvalDetail: {},
204
- selectedCCList: [],
205
- showCcListSelector: false
206
- })
207
-
208
- // 处理审批详情响应
209
- const handleApprovalDetailResponse = (data) => {
210
- // 构建流程信息
211
- state.processInfo = {
212
- processInstanceId: data.processInstanceId,
213
- businessKey: data.businessKey,
214
- processDefinitionKey: data.processDefinitionKey,
215
- processDefinitionName: data.processDefinitionName,
216
- startTime: data.startTime,
217
- startTimeStr: data.startTime, // 使用startTime作为显示时间
218
- endTime: data.endTime,
219
- startUserId: data.startUserId,
220
- startUserName: data.startUserName,
221
- startUserInfo: data.startUserInfo,
222
- processStatus: data.processStatus,
223
- processStatusDesc: data.processStatusDesc,
224
- ccUserList: data.ccUserList || []
225
- }
226
-
227
- // 直接保存节点数据,不进行拆分
228
- state.nodes = data.nodes || []
229
-
230
- // 清空原有的分类数据
231
- state.historyRecords = []
232
- state.currentNodes = []
233
- state.futureNodes = []
234
-
235
- state.approvalDetail = data
236
- state.loading = false
237
-
238
- emit('load-success', data)
239
- }
240
-
241
- // 处理审批详情错误
242
- const handleApprovalDetailError = (err) => {
243
- state.loading = false
244
- message.error(err.message || '获取审批信息失败')
245
- }
246
-
247
- // 请求审批详情
248
- const requestApprovalDetail = (businessKey, type) => {
249
- state.loading = true
250
-
251
- const requestData = {
252
- businessKey: businessKey,
253
- businessType: type
254
- }
255
-
256
- // 使用新的接口获取审批详情
257
- dataService.fetch(
258
- requestData,
259
- {},
260
- '/tasks/approval-detail'
261
- ).then((res) => {
262
- console.log("approval-detail res", res)
263
- handleApprovalDetailResponse(res)
264
- }).catch((err) => {
265
- handleApprovalDetailError(err)
266
- })
267
-
268
- // 临时测试数据(可以删除)
269
- // const testData = {
270
- // "processInstanceId": "130953",
271
- // "businessKey": "357",
272
- // "processDefinitionKey": "Out_Visitor_Application",
273
- // "processDefinitionName": "访客自助登记",
274
- // "processStatus": "ACTIVE",
275
- // "processStatusDesc": "进行中",
276
- // "startTime": "2025-06-13 16:04:52",
277
- // "startUserId": null,
278
- // "endTime": null,
279
- // "ccUserList": [
280
- // {
281
- // "userId": "3180",
282
- // "name": "邓强杰",
283
- // "employeeNo": "4879",
284
- // "photo": "http://oas.guokeai.cn:9003/upload/20250529/20250529_26f77dfaf1324a96b341e4b9b142ae23.jpg",
285
- // "deptName": "技术部"
286
- // }
287
- // ],
288
- // "nodes": [
289
- // {
290
- // "nodeId": "receiver",
291
- // "nodeName": "接待人审批",
292
- // "nodeStatus": "COMPLETED",
293
- // "isMultiInstance": false,
294
- // "approvers": [
295
- // {
296
- // "userId": "3176",
297
- // "userName": "吴跃忠",
298
- // "employeeNo": "LaiRiFangZhang",
299
- // "deptName": "技术部",
300
- // "approvalStatus": "COMPLETED",
301
- // "completedTime": "2025-06-13 16:06:11"
302
- // }
303
- // ]
304
- // }
305
- // ]
306
- // }
307
- // handleApprovalDetailResponse(testData)
308
- }
309
-
310
- // 获取操作类型的样式类
311
- const getOperationClass = (operationType, operationResult) => {
312
- const typeMap = {
313
- 'START': 'start',
314
- 'COMPLETE': operationResult === '通过' ? 'approved' : operationResult === '拒绝' ? 'rejected' : 'completed',
315
- 'CLAIM': 'claimed',
316
- 'ASSIGN': 'assigned',
317
- 'TRANSFER': 'transferred',
318
- 'REJECT': 'rejected',
319
- 'ADD_SIGN': 'add-sign'
320
- }
321
- return typeMap[operationType] || 'default'
322
- }
323
-
324
- // 获取操作类型的文本
325
- const getOperationTypeText = (approver) => {
326
- if (!approver.operation) {
327
- return getSimpleStatusText(null, approver)
328
- }
329
-
330
- const operationType = approver.operation.operationType
331
- switch (operationType) {
332
- case 'COMPLETE':
333
- // 从operationData中获取审批结果
334
- try {
335
- const operationData = JSON.parse(approver.operation.operationData || '{}')
336
- if (operationData.approved === true) {
337
- return '已同意'
338
- } else if (operationData.approved === false) {
339
- return '已拒绝'
340
- }
341
- } catch (e) {
342
- // 解析失败,使用默认逻辑
343
- }
344
- return '已完成'
345
- case 'TRANSFER':
346
- return '转审'
347
- case 'ADD_SIGN':
348
- return '加签'
349
- case 'REJECT':
350
- return '退回'
351
- case 'CLAIM':
352
- return '认领'
353
- case 'ASSIGN':
354
- return '分配'
355
- default:
356
- return '已处理'
357
- }
358
- }
359
-
360
- // 获取流程状态主题
361
- const getProcessStatusTheme = (status) => {
362
- const statusMap = {
363
- 'ACTIVE': 'primary',
364
- 'COMPLETED': 'success',
365
- 'SUSPENDED': 'warning'
366
- }
367
- return statusMap[status] || 'default'
368
- }
369
-
370
- // 获取流程状态文本
371
- const getProcessStatusText = (status) => {
372
- const statusMap = {
373
- 'ACTIVE': '进行中',
374
- 'COMPLETED': '已完成',
375
- 'SUSPENDED': '已暂停'
376
- }
377
- return statusMap[status] || status
378
- }
379
-
380
- // 获取当前步骤索引
381
- const getCurrentStepIndex = () => {
382
- // 已完成节点数量
383
- const completedNodes = state.nodes.filter(node => node.nodeStatus === 'COMPLETED').length
384
- return completedNodes
385
- }
386
-
387
- // 获取节点步骤状态
388
- const getNodeStepStatus = (node) => {
389
- switch (node.nodeStatus) {
390
- case 'COMPLETED':
391
- return 'finish'
392
- case 'ACTIVE':
393
- return 'process'
394
- default:
395
- return 'wait'
396
- }
397
- }
398
-
399
- // 获取节点步骤样式类
400
- const getNodeStepClass = (node) => {
401
- switch (node.nodeStatus) {
402
- case 'ACTIVE':
403
- return 'current-step'
404
- case 'PENDING':
405
- case 'WAITING':
406
- return 'future-step'
407
- default:
408
- return ''
409
- }
410
- }
411
-
412
- // 获取节点状态主题
413
- const getNodeStatusTheme = (node) => {
414
- switch (node.nodeStatus) {
415
- case 'COMPLETED':
416
- return 'success'
417
- case 'ACTIVE':
418
- return 'primary'
419
- default:
420
- return 'default'
421
- }
422
- }
423
-
424
- // 获取节点状态文本
425
- const getNodeStatusText = (node) => {
426
- switch (node.nodeStatus) {
427
- case 'COMPLETED':
428
- return '已完成'
429
- case 'ACTIVE':
430
- return '进行中'
431
- default:
432
- return '未开始'
433
- }
434
- }
435
-
436
- // 获取已完成的审批人
437
- const getCompletedApprovers = (node) => {
438
- if (!node.approvers) return []
439
- return node.approvers.filter(approver => approver.approvalStatus === 'COMPLETED')
440
- }
441
-
442
- // 获取当前审批人
443
- const getCurrentApprover = (node) => {
444
- if (!node.approvers) return null
445
- return node.approvers.find(approver => approver.approvalStatus === 'ACTIVE' && approver.isCurrent)
446
- }
447
-
448
- // 获取待处理审批人
449
- const getPendingApprovers = (node) => {
450
- if (!node.approvers) return []
451
- return node.approvers.filter(approver => approver.approvalStatus === 'PENDING')
452
- }
453
-
454
- // 获取用户信息
455
- const getUserInfo = (approver) => {
456
- // 新数据结构:用户信息在nodeUser对象中
457
- const userInfo = approver.nodeUser || approver
458
- return {
459
- userId: userInfo.userId,
460
- name: userInfo.userName,
461
- employeeNo: userInfo.employeeNo,
462
- photo: userInfo.photo,
463
- departmentName: userInfo.deptName,
464
- position: userInfo.position,
465
- phone: userInfo.phone,
466
- email: userInfo.email
467
- }
468
- }
469
-
470
- // 判断流程是否已完成
471
- const isProcessCompleted = () => {
472
- return state.processInfo && state.processInfo.processStatus === 'COMPLETED'
473
- }
474
-
475
- // 获取抄送步骤状态
476
- const getCcStepStatus = () => {
477
- return isProcessCompleted() ? 'finish' : 'wait'
478
- }
479
-
480
- // 获取抄送步骤样式类
481
- const getCcStepClass = () => {
482
- return isProcessCompleted() ? '' : 'future-step'
483
- }
484
-
485
- // 获取抄送状态样式类
486
- const getCcStatusClass = () => {
487
- return isProcessCompleted() ? 'cc-status' : 'cc-pending'
488
- }
489
-
490
- // 获取抄送状态文本
491
- const getCcStatusText = () => {
492
- return isProcessCompleted() ? '已抄送' : '待抄送'
493
- }
494
-
495
- // 获取简单步骤图标类
496
- const getSimpleStepIconClass = (node) => {
497
- switch (node.nodeStatus) {
498
- case 'COMPLETED':
499
- return 'step-icon-completed'
500
- case 'ACTIVE':
501
- return 'step-icon-active'
502
- default:
503
- return 'step-icon-waiting'
504
- }
505
- }
506
-
507
- // 获取抄送简单图标类
508
- const getCcSimpleIconClass = () => {
509
- return isProcessCompleted() ? 'step-icon-completed' : 'step-icon-waiting'
510
- }
511
-
512
- // 获取简单状态文本
513
- const getSimpleStatusText = (node, approver) => {
514
- // 优先使用approvalResult
515
- if (approver.approvalResult) {
516
- return approver.approvalResult === '通过' ? '已同意' : approver.approvalResult === '拒绝' ? '已拒绝' : '已完成'
517
- }
518
- // 如果没有approvalResult,使用approved字段
519
- if (approver.approved !== null && approver.approved !== undefined) {
520
- return approver.approved ? '已同意' : '已拒绝'
521
- }
522
- // 默认返回已完成
523
- return '已完成'
524
- }
525
-
526
- // 格式化时间
527
- const formatTime = (timeStr) => {
528
- if (!timeStr) return '--'
529
-
530
- let date
531
- // 如果是数字(时间戳),直接使用
532
- if (typeof timeStr === 'number') {
533
- date = new Date(timeStr)
534
- } else {
535
- // 如果是字符串,尝试解析
536
- date = new Date(timeStr)
537
- }
538
-
539
- if (isNaN(date.getTime())) return timeStr
540
-
541
- const month = date.getMonth() + 1
542
- const day = date.getDate()
543
- const hours = date.getHours().toString().padStart(2, '0')
544
- const minutes = date.getMinutes().toString().padStart(2, '0')
545
-
546
- return `${month}/${day} ${hours}:${minutes}`
547
- }
548
-
549
- // 获取抄送时间文本
550
- const getCcTimeText = () => {
551
- if (isProcessCompleted()) {
552
- return `已抄送 · ${formatTime(state.processInfo.endTime || state.processInfo.startTimeStr)}`
553
- }
554
- return ''
555
- }
556
-
557
- // 获取发起人信息
558
- const getStartUserInfo = () => {
559
- // 优先从历史记录中查找发起人
560
- const startRecord = state.historyRecords.find(record => record.operationType === 'START')
561
- if (startRecord) {
562
- return {
563
- userId: startRecord.operatorId,
564
- userInfo: startRecord.assigneeInfo
565
- }
566
- }
567
-
568
- // 如果没有发起记录,从流程信息中获取
569
- if (state.processInfo && state.processInfo.startUserId) {
570
- return {
571
- userId: state.processInfo.startUserId,
572
- userInfo: state.processInfo.startUserInfo || {
573
- name: state.processInfo.startUserName || '未知用户'
574
- }
575
- }
576
- }
577
-
578
- // 如果都没有,尝试从第一个已完成节点的审批人获取(可能是发起人)
579
- if (state.historyRecords.length > 0) {
580
- const firstRecord = state.historyRecords[0]
581
- return {
582
- userId: firstRecord.operatorId,
583
- userInfo: firstRecord.assigneeInfo
584
- }
585
- }
586
-
587
- return { userId: '', userInfo: {} }
588
- }
589
-
590
- // 处理增加抄送人
591
- const handleAddCcUser = () => {
592
- state.showCcListSelector = true
593
- emit('add-cc-user', {
594
- processInstanceId: state.processInfo?.processInstanceId,
595
- businessKey: state.processInfo?.businessKey,
596
- processDefinitionKey: state.processInfo?.processDefinitionKey
597
- })
598
- }
599
-
600
- function onAddCcListConfirm(event) {
601
- if (state.selectedCCList?.length === 0) return
602
- state.showCcListSelector = false
603
- dataService.fetch(
604
- {
605
- businessKey: state.processInfo?.businessKey,
606
- type: state.processInfo?.processDefinitionKey,
607
- processInstanceId: state.processInfo?.processInstanceId,
608
- ccUsers: state.selectedCCList.map((item) => Number(item))
609
- },
610
- {},
611
- '/tasks/batchAddCcInfo'
612
- )
613
- .then((res) => { })
614
- .catch((err) => {
615
- message.error(err.message)
616
- })
617
- }
618
-
619
- // 判断是否需要显示目标用户
620
- const needShowTargetUser = (approver) => {
621
- if (!approver.operation || !approver.targetUser) return false
622
-
623
- const operationType = approver.operation.operationType
624
- return ['TRANSFER', 'ADD_SIGN'].includes(operationType)
625
- }
626
-
627
- // 获取目标用户标签
628
- const getTargetUserLabel = (approver) => {
629
- if (!approver.operation) return ''
630
-
631
- const operationType = approver.operation.operationType
632
- switch (operationType) {
633
- case 'TRANSFER':
634
- return '转审给:'
635
- case 'ADD_SIGN':
636
- return '加签:'
637
- default:
638
- return '目标:'
639
- }
640
- }
641
-
642
- // 获取目标用户信息
643
- const getTargetUserInfo = (approver) => {
644
- const targetUser = approver.targetUser
645
- return {
646
- userId: targetUser.userId,
647
- name: targetUser.userName,
648
- employeeNo: targetUser.employeeNo,
649
- photo: targetUser.photo,
650
- departmentName: targetUser.deptName,
651
- position: targetUser.position,
652
- phone: targetUser.phone,
653
- email: targetUser.email
654
- }
655
- }
656
-
657
- // 组件挂载时请求数据
658
- onMounted(() => {
659
- if (props.businessKey && props.type) {
660
- requestApprovalDetail(String(props.businessKey), props.type)
661
- }
662
- })
663
-
664
- // 暴露方法供外部调用
665
- defineExpose({
666
- requestApprovalDetail,
667
- state
668
- })
669
- </script>
670
-
671
- <style scoped>
672
- .ebiz-approval-process {
673
- width: 100%;
674
- }
675
-
676
- .card {
677
- background-color: #ffffff;
678
- padding: 10px;
679
- }
680
-
681
- .title {
682
- font-size: 16px;
683
- font-weight: bold;
684
- color: #333;
685
- }
686
-
687
- .process-info {
688
- background-color: #fafafa;
689
- padding: 10px 12px;
690
- border-radius: 6px;
691
- margin-bottom: 12px;
692
- border: 1px solid #f0f0f0;
693
- }
694
-
695
- .info-item {
696
- display: flex;
697
- align-items: center;
698
- margin-bottom: 6px;
699
- }
700
-
701
- .info-item:last-child {
702
- margin-bottom: 0;
703
- }
704
-
705
- .label {
706
- font-weight: 500;
707
- color: #595959;
708
- margin-right: 8px;
709
- min-width: 70px;
710
- font-size: 13px;
711
- }
712
-
713
- .step-content {
714
- /* padding: 8px 0; */
715
- }
716
-
717
- .operation-info {
718
- margin-bottom: 8px;
719
- }
720
-
721
- .operation-tag {
722
- padding: 4px 8px;
723
- border-radius: 4px;
724
- font-size: 12px;
725
- font-weight: 500;
726
- display: inline-block;
727
- }
728
-
729
- .operation-tag.start {
730
- background-color: #f3e5f5;
731
- color: #7b1fa2;
732
- }
733
-
734
- .operation-tag.approved {
735
- background-color: #e8f5e8;
736
- color: #388e3c;
737
- }
738
-
739
- .operation-tag.rejected {
740
- background-color: #ffebee;
741
- color: #d32f2f;
742
- }
743
-
744
- .operation-tag.completed {
745
- background-color: #e3f2fd;
746
- color: #1976d2;
747
- }
748
-
749
- .operation-tag.claimed {
750
- background-color: #f3e5f5;
751
- color: #7b1fa2;
752
- }
753
-
754
- .operation-tag.assigned {
755
- background-color: #e0f2f1;
756
- color: #00695c;
757
- }
758
-
759
- .operation-tag.transferred {
760
- background-color: #fff8e1;
761
- color: #f57c00;
762
- }
763
-
764
- .operation-tag.add-sign {
765
- background-color: #fce4ec;
766
- color: #c2185b;
767
- }
768
-
769
- .operation-tag.pending {
770
- background-color: #fff3e0;
771
- color: #f57c00;
772
- }
773
-
774
- .operation-tag.future {
775
- background-color: #f5f5f5;
776
- color: #757575;
777
- }
778
-
779
- .operation-tag.cc-status {
780
- background-color: #fffbe6;
781
- color: #faad14;
782
- }
783
-
784
- .operation-tag.cc-pending {
785
- background-color: #f5f5f5;
786
- color: #999999;
787
- }
788
-
789
- .step-detail {
790
- display: flex;
791
- align-items: flex-start;
792
- margin-bottom: 4px;
793
- font-size: 14px;
794
- }
795
-
796
- .step-detail:last-child {
797
- margin-bottom: 0;
798
- }
799
-
800
- .current-step {
801
- background-color: #f8f9fa;
802
- padding: 12px;
803
- border-radius: 8px;
804
- border-left: 4px solid #1976d2;
805
- }
806
-
807
- .future-step {
808
- opacity: 0.7;
809
- }
810
-
811
- .multi-instance-details {
812
- margin-top: 8px;
813
- }
814
-
815
- .instance-list {
816
- margin-top: 12px;
817
- }
818
-
819
- .instance-item {
820
- background-color: #ffffff;
821
- border: 1px solid #e0e0e0;
822
- border-radius: 6px;
823
- padding: 8px;
824
- margin-bottom: 8px;
825
- }
826
-
827
- .instance-item:last-child {
828
- margin-bottom: 0;
829
- }
830
-
831
- .instance-header {
832
- display: flex;
833
- justify-content: space-between;
834
- align-items: center;
835
- margin-bottom: 4px;
836
- }
837
-
838
- .instance-status {
839
- padding: 2px 6px;
840
- border-radius: 3px;
841
- font-size: 11px;
842
- font-weight: 500;
843
- }
844
-
845
- .instance-approved {
846
- background-color: #e8f5e8;
847
- color: #388e3c;
848
- }
849
-
850
- .instance-rejected {
851
- background-color: #ffebee;
852
- color: #d32f2f;
853
- }
854
-
855
- .instance-completed {
856
- background-color: #e3f2fd;
857
- color: #1976d2;
858
- }
859
-
860
- .instance-pending {
861
- background-color: #fff3e0;
862
- color: #f57c00;
863
- }
864
-
865
- .instance-comment {
866
- font-size: 12px;
867
- color: #666;
868
- margin-bottom: 4px;
869
- line-height: 1.4;
870
- }
871
-
872
- .instance-time {
873
- font-size: 11px;
874
- color: #999;
875
- }
876
-
877
- .multi-instance-badge {
878
- background-color: #e3f2fd;
879
- color: #1976d2;
880
- padding: 2px 6px;
881
- border-radius: 3px;
882
- font-size: 11px;
883
- font-weight: 500;
884
- }
885
-
886
- .instance-section {
887
- margin-bottom: 12px;
888
- }
889
-
890
- .instance-section h5 {
891
- font-size: 12px;
892
- color: #666;
893
- margin: 0 0 8px 0;
894
- font-weight: 500;
895
- }
896
-
897
- .instance-section:last-child {
898
- margin-bottom: 0;
899
- }
900
-
901
- /* 用户信息相关样式 */
902
- .assignee-info {
903
- display: flex;
904
- align-items: center;
905
- }
906
-
907
- .approver-info {
908
- margin-bottom: 12px;
909
- padding-bottom: 8px;
910
- border-bottom: 1px solid #f0f0f0;
911
- }
912
-
913
- .approver-info:last-child {
914
- margin-bottom: 0;
915
- border-bottom: none;
916
- }
917
-
918
- .candidate-users {
919
- display: flex;
920
- flex-wrap: wrap;
921
- gap: 8px;
922
- align-items: center;
923
- }
924
-
925
- .candidate-user {
926
- background-color: #f8f9fa;
927
- padding: 4px 8px;
928
- border-radius: 12px;
929
- border: 1px solid #e9ecef;
930
- }
931
-
932
- /* 抄送人相关样式 */
933
- .cc-users {
934
- display: flex;
935
- flex-wrap: wrap;
936
- gap: 6px;
937
- align-items: center;
938
- }
939
-
940
- .cc-user {
941
- background-color: #f6ffed;
942
- padding: 3px 6px;
943
- border-radius: 10px;
944
- border: 1px solid #d9f7be;
945
- font-size: 12px;
946
- }
947
-
948
- .cc-users-list {
949
- display: flex;
950
- flex-direction: column;
951
- gap: 8px;
952
- }
953
-
954
- .cc-user-item {
955
- background-color: #fffbe6;
956
- padding: 8px;
957
- border-radius: 8px;
958
- border: 1px solid #ffd666;
959
- }
960
-
961
- /* 简化审批步骤样式 */
962
- .simple-approval-steps {
963
- background-color: #ffffff;
964
- padding: 12px 16px;
965
- }
966
-
967
- .simple-step-item {
968
- position: relative;
969
- padding-bottom: 16px;
970
- border-left: 2px solid #e8e8e8;
971
- margin-left: 10px;
972
- }
973
-
974
- .simple-step-item:last-child {
975
- border-left: none;
976
- padding-bottom: 0;
977
- }
978
-
979
- .step-header {
980
- display: flex;
981
- align-items: center;
982
- margin-bottom: 8px;
983
- margin-left: -11px;
984
- }
985
-
986
- .step-icon {
987
- width: 20px;
988
- height: 20px;
989
- border-radius: 50%;
990
- display: flex;
991
- align-items: center;
992
- justify-content: center;
993
- margin-right: 10px;
994
- flex-shrink: 0;
995
- position: relative;
996
- z-index: 1;
997
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
998
- }
999
-
1000
- .step-icon-completed {
1001
- background: linear-gradient(135deg, #52c41a, #73d13d);
1002
- }
1003
-
1004
- .step-icon-active {
1005
- background: linear-gradient(135deg, #1890ff, #40a9ff);
1006
- }
1007
-
1008
- .step-icon-waiting {
1009
- background: linear-gradient(135deg, #d9d9d9, #f0f0f0);
1010
- }
1011
-
1012
- .step-info {
1013
- flex: 1;
1014
- }
1015
-
1016
- .step-title-row {
1017
- display: flex;
1018
- align-items: center;
1019
- justify-content: space-between;
1020
- width: 100%;
1021
- }
1022
-
1023
- .step-title {
1024
- font-size: 15px;
1025
- font-weight: 600;
1026
- color: #262626;
1027
- line-height: 1.3;
1028
- flex: 1;
1029
- margin-right: 8px;
1030
- }
1031
-
1032
- .step-status {
1033
- margin-left: 8px;
1034
- white-space: nowrap;
1035
- }
1036
-
1037
- .step-content {
1038
- margin-left: 19px;
1039
- padding-bottom: 4px;
1040
- }
1041
-
1042
- .simple-approver-wrapper {
1043
- padding: 6px 0;
1044
- margin: 6px 0;
1045
- border-bottom: 1px solid #f5f5f5;
1046
- }
1047
-
1048
- .simple-approver-wrapper:last-child {
1049
- border-bottom: none;
1050
- padding-bottom: 0;
1051
- }
1052
-
1053
- .simple-approver {
1054
- display: flex;
1055
- align-items: flex-start;
1056
- justify-content: space-between;
1057
- min-height: 40px;
1058
- }
1059
-
1060
- .approver-user-info {
1061
- flex: 1;
1062
- margin-right: 8px;
1063
- }
1064
-
1065
- .approver-action-info {
1066
- display: flex;
1067
- flex-direction: column;
1068
- align-items: flex-end;
1069
- white-space: nowrap;
1070
- }
1071
-
1072
- .action-type {
1073
- font-size: 12px;
1074
- font-weight: 500;
1075
- color: #1890ff;
1076
- margin-bottom: 2px;
1077
- }
1078
-
1079
- .target-user-info {
1080
- padding: 8px 12px;
1081
- background-color: #f8f9fa;
1082
- border-radius: 6px;
1083
- border-left: 3px solid #1890ff;
1084
- display: flex;
1085
- align-items: center;
1086
- gap: 8px;
1087
- }
1088
-
1089
- .target-label {
1090
- font-size: 12px;
1091
- color: #666;
1092
- font-weight: 500;
1093
- white-space: nowrap;
1094
- }
1095
-
1096
- .approver-time {
1097
- font-size: 11px;
1098
- color: #8c8c8c;
1099
- margin-left: 8px;
1100
- white-space: nowrap;
1101
- font-weight: 500;
1102
- }
1103
-
1104
- .approver-comment {
1105
- background-color: #fafafa;
1106
- padding: 8px 12px;
1107
- border-radius: 6px;
1108
- font-size: 12px;
1109
- color: #595959;
1110
- line-height: 1.4;
1111
- width: 100%;
1112
- border: 1px solid #f0f0f0;
1113
- box-sizing: border-box;
1114
- }
1115
-
1116
- /* 增加抄送人按钮样式 */
1117
- .add-cc-button-inline {
1118
- display: flex;
1119
- align-items: center;
1120
- gap: 4px;
1121
- font-size: 11px;
1122
- border-color: #d9d9d9;
1123
- color: #666;
1124
- height: 24px;
1125
- padding: 0 8px;
1126
- white-space: nowrap;
1127
- }
1128
-
1129
- .add-cc-button-inline:hover {
1130
- border-color: #1890ff;
1131
- color: #1890ff;
1132
- }
1
+ <template>
2
+ <div class="ebiz-approval-process">
3
+ <div class="block-base-style">
4
+ <ebiz-tdesign-loading v-if="state.loading" :loading="true" theme="circular" size="small" text=""
5
+ :reverse="false" layout="vertical" :delay="0" class="component-base-style">
6
+ <span style="display: inline-block" class="component-base-style">加载中</span>
7
+ </ebiz-tdesign-loading>
8
+
9
+ <div v-if="!state.loading" class="card">
10
+ <div style="margin: 10px 0" class="component-base-style">
11
+ <span style="display: inline-block" class="title">审批流程</span>
12
+ </div>
13
+
14
+ <!-- 流程基本信息 -->
15
+ <div v-if="state.processInfo" class="process-info">
16
+ <div class="info-item" v-if="getStartUserInfo().userId">
17
+ <span class="label">发起人:</span>
18
+ <user-info :userId="getStartUserInfo().userId" :userInfo="getStartUserInfo().userInfo"
19
+ avatar-size="small" name-size="small" :show-job-number="true" :show-department="false" />
20
+ </div>
21
+ <div class="info-item">
22
+ <span class="label">发起时间:</span>
23
+ <span>{{ state.processInfo.startTimeStr || '--' }}</span>
24
+ </div>
25
+ <div class="info-item">
26
+ <span class="label">流程状态:</span>
27
+ <t-tag :theme="getProcessStatusTheme(state.processInfo.processStatus)" size="small">
28
+ {{ getProcessStatusText(state.processInfo.processStatus) }}
29
+ </t-tag>
30
+ </div>
31
+ <!-- 抄送人信息 -->
32
+ </div>
33
+
34
+ <!-- 审批步骤 -->
35
+ <div class="simple-approval-steps">
36
+ <!-- 节点步骤 -->
37
+ <div v-for="(node, index) in state.nodes" :key="'node-' + index" class="simple-step-item">
38
+ <div class="step-header">
39
+ <div class="step-icon" :class="getSimpleStepIconClass(node)">
40
+ <t-icon name="user" size="12px" color="white" />
41
+ </div>
42
+ <div class="step-info">
43
+ <div class="step-title-row">
44
+ <span class="step-title">{{ node.nodeName }}</span>
45
+ <t-tag :theme="getNodeStatusTheme(node)" size="small">
46
+ {{ getNodeStatusText(node) }}
47
+ </t-tag>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- 简化的审批人信息 -->
53
+ <div class="step-content">
54
+ <!-- 已完成节点 -->
55
+ <div v-for="approver in getCompletedApprovers(node)"
56
+ :key="approver.nodeUser?.userId || approver.userId" class="simple-approver-wrapper">
57
+ <div class="simple-approver">
58
+ <!-- 操作用户信息 -->
59
+ <div class="approver-user-info">
60
+ <user-info :userId="approver.nodeUser?.userId || approver.userId"
61
+ :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
62
+ :show-department="false" />
63
+ </div>
64
+
65
+ <!-- 操作类型和时间 -->
66
+ <div class="approver-action-info">
67
+ <div class="action-type">{{ getOperationTypeText(approver) }}</div>
68
+ <div class="approver-time">{{ formatTime(approver.operation?.operationTime ||
69
+ approver.completedTime || approver.endTime) }}</div>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- 目标用户信息(转审、加签等操作) -->
74
+ <div v-if="approver.targetUser && needShowTargetUser(approver)"
75
+ class="target-user-info">
76
+ <div class="target-label">{{ getTargetUserLabel(approver) }}</div>
77
+ <user-info :userId="approver.targetUser.userId"
78
+ :userInfo="getTargetUserInfo(approver)" avatar-size="small" name-size="small"
79
+ :show-department="false" />
80
+ </div>
81
+
82
+ <!-- 评论信息 -->
83
+ <div v-if="approver.comment" class="approver-comment">{{ approver.comment }}</div>
84
+ </div>
85
+
86
+ <!-- 进行中节点 -->
87
+ <!-- 当前处理人 -->
88
+ <div v-if="getCurrentApprover(node)" class="simple-approver">
89
+ <user-info
90
+ :userId="getCurrentApprover(node).nodeUser?.userId || getCurrentApprover(node).userId"
91
+ :userInfo="getUserInfo(getCurrentApprover(node))" avatar-size="small"
92
+ name-size="small" :show-department="false" />
93
+ <div class="approver-time">处理中</div>
94
+ </div>
95
+
96
+ <!-- 待处理人 -->
97
+ <div v-for="approver in getPendingApprovers(node)"
98
+ :key="'pending-' + (approver.nodeUser?.userId || approver.userId)"
99
+ class="simple-approver">
100
+ <user-info :userId="approver.nodeUser?.userId || approver.userId"
101
+ :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
102
+ :show-department="false" />
103
+ <!-- <div class="approver-time">待处理</div> -->
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- 抄送人步骤 -->
109
+ <div v-if="state.processInfo" class="simple-step-item">
110
+ <div class="step-header">
111
+ <div class="step-icon" :class="getCcSimpleIconClass()">
112
+ <t-icon name="mail" size="12px" color="white" />
113
+ </div>
114
+ <div class="step-info">
115
+ <div class="step-title-row">
116
+ <span class="step-title">抄送</span>
117
+ <!-- 增加抄送人按钮 -->
118
+ <ebiz-employee-selector content="增加抄送人" :single="false" defaultTab="organization"
119
+ class="component-base-style" v-model="state.selectedCCList"
120
+ @click="handleAddCcUser" @confirm="onAddCcListConfirm">
121
+ </ebiz-employee-selector>
122
+ <!-- <t-button theme="default" variant="outline" size="mini" @click="handleAddCcUser"
123
+ class="add-cc-button-inline">
124
+ <t-icon name="add" size="12px" />
125
+ 增加抄送人
126
+ </t-button> -->
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <div class="step-content">
132
+ <div v-for="ccUser in state.processInfo.ccUserList"
133
+ :key="ccUser.nodeUser?.userId || ccUser.userId" class="simple-approver">
134
+ <user-info :userId="ccUser.nodeUser?.userId || ccUser.userId"
135
+ :userInfo="ccUser.nodeUser || ccUser" avatar-size="small" name-size="small"
136
+ :show-department="false" />
137
+ <div class="approver-time">{{ getCcTimeText() }}</div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ </div>
145
+ </div>
146
+ </template>
147
+
148
+ <script>
149
+ /**
150
+ * @displayName 审批流程
151
+ * @description 审批流程组件,用于展示审批流程的详细信息
152
+ * @category 业务组件
153
+ * @name EbizApprovalProcess
154
+ */
155
+ export default {
156
+ name: "EbizApprovalProcess",
157
+ components: {
158
+ 't-tag': Tag,
159
+ 't-icon': Icon,
160
+ 't-button': Button
161
+ }
162
+ }
163
+ </script>
164
+
165
+ <script setup>
166
+ import { reactive, onMounted } from 'vue'
167
+ import { defineProps, defineEmits } from 'vue'
168
+ import { Tag, Icon, Button, MessagePlugin as message } from 'tdesign-vue-next'
169
+ import EbizEmployeeSelector from './EbizEmployeeSelector.vue'
170
+ import EbizTdesignLoading from './EbizTdesignLoading.vue'
171
+ import dataService from '../apiService/simpleDataService.js'
172
+ import UserInfo from './mItems/UserInfo.vue'
173
+
174
+ // 定义 props
175
+ const props = defineProps({
176
+ /**
177
+ * 业务键
178
+ */
179
+ businessKey: {
180
+ type: String,
181
+ default: ''
182
+ },
183
+ /**
184
+ * 业务类型
185
+ */
186
+ type: {
187
+ type: String,
188
+ default: ''
189
+ }
190
+ })
191
+
192
+ // 定义 emits
193
+ const emit = defineEmits(['load-success', 'add-cc-user'])
194
+
195
+ // 响应式状态
196
+ const state = reactive({
197
+ loading: false,
198
+ processInfo: null,
199
+ nodes: [], // 直接保存节点数据
200
+ historyRecords: [],
201
+ currentNodes: [],
202
+ futureNodes: [],
203
+ approvalDetail: {},
204
+ selectedCCList: [],
205
+ showCcListSelector: false
206
+ })
207
+
208
+ // 处理审批详情响应
209
+ const handleApprovalDetailResponse = (data) => {
210
+ // 构建流程信息
211
+ state.processInfo = {
212
+ processInstanceId: data.processInstanceId,
213
+ businessKey: data.businessKey,
214
+ processDefinitionKey: data.processDefinitionKey,
215
+ processDefinitionName: data.processDefinitionName,
216
+ startTime: data.startTime,
217
+ startTimeStr: data.startTime, // 使用startTime作为显示时间
218
+ endTime: data.endTime,
219
+ startUserId: data.startUserId,
220
+ startUserName: data.startUserName,
221
+ startUserInfo: data.startUserInfo,
222
+ processStatus: data.processStatus,
223
+ processStatusDesc: data.processStatusDesc,
224
+ ccUserList: data.ccUserList || []
225
+ }
226
+
227
+ // 直接保存节点数据,不进行拆分
228
+ state.nodes = data.nodes || []
229
+
230
+ // 清空原有的分类数据
231
+ state.historyRecords = []
232
+ state.currentNodes = []
233
+ state.futureNodes = []
234
+
235
+ state.approvalDetail = data
236
+ state.loading = false
237
+
238
+ emit('load-success', data)
239
+ }
240
+
241
+ // 处理审批详情错误
242
+ const handleApprovalDetailError = (err) => {
243
+ state.loading = false
244
+ message.error(err.message || '获取审批信息失败')
245
+ }
246
+
247
+ // 请求审批详情
248
+ const requestApprovalDetail = (businessKey, type) => {
249
+ state.loading = true
250
+
251
+ const requestData = {
252
+ businessKey: businessKey,
253
+ businessType: type
254
+ }
255
+
256
+ // 使用新的接口获取审批详情
257
+ dataService.fetch(
258
+ requestData,
259
+ {},
260
+ '/tasks/approval-detail'
261
+ ).then((res) => {
262
+ console.log("approval-detail res", res)
263
+ handleApprovalDetailResponse(res)
264
+ }).catch((err) => {
265
+ handleApprovalDetailError(err)
266
+ })
267
+
268
+ // 临时测试数据(可以删除)
269
+ // const testData = {
270
+ // "processInstanceId": "130953",
271
+ // "businessKey": "357",
272
+ // "processDefinitionKey": "Out_Visitor_Application",
273
+ // "processDefinitionName": "访客自助登记",
274
+ // "processStatus": "ACTIVE",
275
+ // "processStatusDesc": "进行中",
276
+ // "startTime": "2025-06-13 16:04:52",
277
+ // "startUserId": null,
278
+ // "endTime": null,
279
+ // "ccUserList": [
280
+ // {
281
+ // "userId": "3180",
282
+ // "name": "邓强杰",
283
+ // "employeeNo": "4879",
284
+ // "photo": "http://oas.guokeai.cn:9003/upload/20250529/20250529_26f77dfaf1324a96b341e4b9b142ae23.jpg",
285
+ // "deptName": "技术部"
286
+ // }
287
+ // ],
288
+ // "nodes": [
289
+ // {
290
+ // "nodeId": "receiver",
291
+ // "nodeName": "接待人审批",
292
+ // "nodeStatus": "COMPLETED",
293
+ // "isMultiInstance": false,
294
+ // "approvers": [
295
+ // {
296
+ // "userId": "3176",
297
+ // "userName": "吴跃忠",
298
+ // "employeeNo": "LaiRiFangZhang",
299
+ // "deptName": "技术部",
300
+ // "approvalStatus": "COMPLETED",
301
+ // "completedTime": "2025-06-13 16:06:11"
302
+ // }
303
+ // ]
304
+ // }
305
+ // ]
306
+ // }
307
+ // handleApprovalDetailResponse(testData)
308
+ }
309
+
310
+ // 获取操作类型的样式类
311
+ const getOperationClass = (operationType, operationResult) => {
312
+ const typeMap = {
313
+ 'START': 'start',
314
+ 'COMPLETE': operationResult === '通过' ? 'approved' : operationResult === '拒绝' ? 'rejected' : 'completed',
315
+ 'CLAIM': 'claimed',
316
+ 'ASSIGN': 'assigned',
317
+ 'TRANSFER': 'transferred',
318
+ 'REJECT': 'rejected',
319
+ 'ADD_SIGN': 'add-sign'
320
+ }
321
+ return typeMap[operationType] || 'default'
322
+ }
323
+
324
+ // 获取操作类型的文本
325
+ const getOperationTypeText = (approver) => {
326
+ if (!approver.operation) {
327
+ return getSimpleStatusText(null, approver)
328
+ }
329
+
330
+ const operationType = approver.operation.operationType
331
+ switch (operationType) {
332
+ case 'COMPLETE':
333
+ // 从operationData中获取审批结果
334
+ try {
335
+ const operationData = JSON.parse(approver.operation.operationData || '{}')
336
+ if (operationData.approved === true) {
337
+ return '已同意'
338
+ } else if (operationData.approved === false) {
339
+ return '已拒绝'
340
+ }
341
+ } catch (e) {
342
+ // 解析失败,使用默认逻辑
343
+ }
344
+ return '已完成'
345
+ case 'TRANSFER':
346
+ return '转审'
347
+ case 'ADD_SIGN':
348
+ return '加签'
349
+ case 'REJECT':
350
+ return '退回'
351
+ case 'CLAIM':
352
+ return '认领'
353
+ case 'ASSIGN':
354
+ return '分配'
355
+ default:
356
+ return '已处理'
357
+ }
358
+ }
359
+
360
+ // 获取流程状态主题
361
+ const getProcessStatusTheme = (status) => {
362
+ const statusMap = {
363
+ 'ACTIVE': 'primary',
364
+ 'COMPLETED': 'success',
365
+ 'SUSPENDED': 'warning'
366
+ }
367
+ return statusMap[status] || 'default'
368
+ }
369
+
370
+ // 获取流程状态文本
371
+ const getProcessStatusText = (status) => {
372
+ const statusMap = {
373
+ 'REJECT': '已拒绝',
374
+ 'APPROVED': "已通过",
375
+ 'ACTIVE': '进行中',
376
+ 'COMPLETED': '已完成',
377
+ 'SUSPENDED': '已暂停'
378
+ }
379
+ return statusMap[status] || status
380
+ }
381
+
382
+ // 获取当前步骤索引
383
+ const getCurrentStepIndex = () => {
384
+ // 已完成节点数量
385
+ const completedNodes = state.nodes.filter(node => node.nodeStatus === 'COMPLETED').length
386
+ return completedNodes
387
+ }
388
+
389
+ // 获取节点步骤状态
390
+ const getNodeStepStatus = (node) => {
391
+ switch (node.nodeStatus) {
392
+ case 'COMPLETED':
393
+ return 'finish'
394
+ case 'ACTIVE':
395
+ return 'process'
396
+ default:
397
+ return 'wait'
398
+ }
399
+ }
400
+
401
+ // 获取节点步骤样式类
402
+ const getNodeStepClass = (node) => {
403
+ switch (node.nodeStatus) {
404
+ case 'ACTIVE':
405
+ return 'current-step'
406
+ case 'PENDING':
407
+ case 'WAITING':
408
+ return 'future-step'
409
+ default:
410
+ return ''
411
+ }
412
+ }
413
+
414
+ // 获取节点状态主题
415
+ const getNodeStatusTheme = (node) => {
416
+ switch (node.nodeStatus) {
417
+ case 'COMPLETED':
418
+ return 'success'
419
+ case 'ACTIVE':
420
+ return 'primary'
421
+ default:
422
+ return 'default'
423
+ }
424
+ }
425
+
426
+ // 获取节点状态文本
427
+ const getNodeStatusText = (node) => {
428
+ switch (node.nodeStatus) {
429
+ case 'COMPLETED':
430
+ return '已完成'
431
+ case 'ACTIVE':
432
+ return '进行中'
433
+ default:
434
+ return '未开始'
435
+ }
436
+ }
437
+
438
+ // 获取已完成的审批人
439
+ const getCompletedApprovers = (node) => {
440
+ if (!node.approvers) return []
441
+ return node.approvers.filter(approver => approver.approvalStatus === 'COMPLETED')
442
+ }
443
+
444
+ // 获取当前审批人
445
+ const getCurrentApprover = (node) => {
446
+ if (!node.approvers) return null
447
+ return node.approvers.find(approver => approver.approvalStatus === 'ACTIVE' && approver.isCurrent)
448
+ }
449
+
450
+ // 获取待处理审批人
451
+ const getPendingApprovers = (node) => {
452
+ if (!node.approvers) return []
453
+ return node.approvers.filter(approver => approver.approvalStatus === 'PENDING')
454
+ }
455
+
456
+ // 获取用户信息
457
+ const getUserInfo = (approver) => {
458
+ // 新数据结构:用户信息在nodeUser对象中
459
+ const userInfo = approver.nodeUser || approver
460
+ return {
461
+ userId: userInfo.userId,
462
+ name: userInfo.userName,
463
+ employeeNo: userInfo.employeeNo,
464
+ photo: userInfo.photo,
465
+ departmentName: userInfo.deptName,
466
+ position: userInfo.position,
467
+ phone: userInfo.phone,
468
+ email: userInfo.email
469
+ }
470
+ }
471
+
472
+ // 判断流程是否已完成
473
+ const isProcessCompleted = () => {
474
+ return state.processInfo && state.processInfo.processStatus === 'COMPLETED'
475
+ }
476
+
477
+ // 获取抄送步骤状态
478
+ const getCcStepStatus = () => {
479
+ return isProcessCompleted() ? 'finish' : 'wait'
480
+ }
481
+
482
+ // 获取抄送步骤样式类
483
+ const getCcStepClass = () => {
484
+ return isProcessCompleted() ? '' : 'future-step'
485
+ }
486
+
487
+ // 获取抄送状态样式类
488
+ const getCcStatusClass = () => {
489
+ return isProcessCompleted() ? 'cc-status' : 'cc-pending'
490
+ }
491
+
492
+ // 获取抄送状态文本
493
+ const getCcStatusText = () => {
494
+ return isProcessCompleted() ? '已抄送' : '待抄送'
495
+ }
496
+
497
+ // 获取简单步骤图标类
498
+ const getSimpleStepIconClass = (node) => {
499
+ switch (node.nodeStatus) {
500
+ case 'COMPLETED':
501
+ return 'step-icon-completed'
502
+ case 'ACTIVE':
503
+ return 'step-icon-active'
504
+ default:
505
+ return 'step-icon-waiting'
506
+ }
507
+ }
508
+
509
+ // 获取抄送简单图标类
510
+ const getCcSimpleIconClass = () => {
511
+ return isProcessCompleted() ? 'step-icon-completed' : 'step-icon-waiting'
512
+ }
513
+
514
+ // 获取简单状态文本
515
+ const getSimpleStatusText = (node, approver) => {
516
+ // 优先使用approvalResult
517
+ if (approver.approvalResult) {
518
+ return approver.approvalResult === '通过' ? '已同意' : approver.approvalResult === '拒绝' ? '已拒绝' : '已完成'
519
+ }
520
+ // 如果没有approvalResult,使用approved字段
521
+ if (approver.approved !== null && approver.approved !== undefined) {
522
+ return approver.approved ? '已同意' : '已拒绝'
523
+ }
524
+ // 默认返回已完成
525
+ return '已完成'
526
+ }
527
+
528
+ // 格式化时间
529
+ const formatTime = (timeStr) => {
530
+ if (!timeStr) return '--'
531
+
532
+ let date
533
+ // 如果是数字(时间戳),直接使用
534
+ if (typeof timeStr === 'number') {
535
+ date = new Date(timeStr)
536
+ } else {
537
+ // 如果是字符串,尝试解析
538
+ date = new Date(timeStr)
539
+ }
540
+
541
+ if (isNaN(date.getTime())) return timeStr
542
+
543
+ const month = date.getMonth() + 1
544
+ const day = date.getDate()
545
+ const hours = date.getHours().toString().padStart(2, '0')
546
+ const minutes = date.getMinutes().toString().padStart(2, '0')
547
+
548
+ return `${month}/${day} ${hours}:${minutes}`
549
+ }
550
+
551
+ // 获取抄送时间文本
552
+ const getCcTimeText = () => {
553
+ if (isProcessCompleted()) {
554
+ return `已抄送 · ${formatTime(state.processInfo.endTime || state.processInfo.startTimeStr)}`
555
+ }
556
+ return ''
557
+ }
558
+
559
+ // 获取发起人信息
560
+ const getStartUserInfo = () => {
561
+ // 优先从历史记录中查找发起人
562
+ const startRecord = state.historyRecords.find(record => record.operationType === 'START')
563
+ if (startRecord) {
564
+ return {
565
+ userId: startRecord.operatorId,
566
+ userInfo: startRecord.assigneeInfo
567
+ }
568
+ }
569
+
570
+ // 如果没有发起记录,从流程信息中获取
571
+ if (state.processInfo && state.processInfo.startUserId) {
572
+ return {
573
+ userId: state.processInfo.startUserId,
574
+ userInfo: state.processInfo.startUserInfo || {
575
+ name: state.processInfo.startUserName || '未知用户'
576
+ }
577
+ }
578
+ }
579
+
580
+ // 如果都没有,尝试从第一个已完成节点的审批人获取(可能是发起人)
581
+ if (state.historyRecords.length > 0) {
582
+ const firstRecord = state.historyRecords[0]
583
+ return {
584
+ userId: firstRecord.operatorId,
585
+ userInfo: firstRecord.assigneeInfo
586
+ }
587
+ }
588
+
589
+ return { userId: '', userInfo: {} }
590
+ }
591
+
592
+ // 处理增加抄送人
593
+ const handleAddCcUser = () => {
594
+ state.showCcListSelector = true
595
+ emit('add-cc-user', {
596
+ processInstanceId: state.processInfo?.processInstanceId,
597
+ businessKey: state.processInfo?.businessKey,
598
+ processDefinitionKey: state.processInfo?.processDefinitionKey
599
+ })
600
+ }
601
+
602
+ function onAddCcListConfirm(event) {
603
+ if (state.selectedCCList?.length === 0) return
604
+ state.showCcListSelector = false
605
+ dataService.fetch(
606
+ {
607
+ businessKey: state.processInfo?.businessKey,
608
+ type: state.processInfo?.processDefinitionKey,
609
+ processInstanceId: state.processInfo?.processInstanceId,
610
+ ccUsers: state.selectedCCList.map((item) => Number(item))
611
+ },
612
+ {},
613
+ '/tasks/batchAddCcInfo'
614
+ )
615
+ .then((res) => { })
616
+ .catch((err) => {
617
+ message.error(err.message)
618
+ })
619
+ }
620
+
621
+ // 判断是否需要显示目标用户
622
+ const needShowTargetUser = (approver) => {
623
+ if (!approver.operation || !approver.targetUser) return false
624
+
625
+ const operationType = approver.operation.operationType
626
+ return ['TRANSFER', 'ADD_SIGN'].includes(operationType)
627
+ }
628
+
629
+ // 获取目标用户标签
630
+ const getTargetUserLabel = (approver) => {
631
+ if (!approver.operation) return ''
632
+
633
+ const operationType = approver.operation.operationType
634
+ switch (operationType) {
635
+ case 'TRANSFER':
636
+ return '转审给:'
637
+ case 'ADD_SIGN':
638
+ return '加签:'
639
+ default:
640
+ return '目标:'
641
+ }
642
+ }
643
+
644
+ // 获取目标用户信息
645
+ const getTargetUserInfo = (approver) => {
646
+ const targetUser = approver.targetUser
647
+ return {
648
+ userId: targetUser.userId,
649
+ name: targetUser.userName,
650
+ employeeNo: targetUser.employeeNo,
651
+ photo: targetUser.photo,
652
+ departmentName: targetUser.deptName,
653
+ position: targetUser.position,
654
+ phone: targetUser.phone,
655
+ email: targetUser.email
656
+ }
657
+ }
658
+
659
+ // 组件挂载时请求数据
660
+ onMounted(() => {
661
+ if (props.businessKey && props.type) {
662
+ requestApprovalDetail(String(props.businessKey), props.type)
663
+ }
664
+ })
665
+
666
+ // 暴露方法供外部调用
667
+ defineExpose({
668
+ requestApprovalDetail,
669
+ state
670
+ })
671
+ </script>
672
+
673
+ <style scoped>
674
+ .ebiz-approval-process {
675
+ width: 100%;
676
+ }
677
+
678
+ .card {
679
+ background-color: #ffffff;
680
+ padding: 10px;
681
+ }
682
+
683
+ .title {
684
+ font-size: 16px;
685
+ font-weight: bold;
686
+ color: #333;
687
+ }
688
+
689
+ .process-info {
690
+ background-color: #fafafa;
691
+ padding: 10px 12px;
692
+ border-radius: 6px;
693
+ margin-bottom: 12px;
694
+ border: 1px solid #f0f0f0;
695
+ }
696
+
697
+ .info-item {
698
+ display: flex;
699
+ align-items: center;
700
+ margin-bottom: 6px;
701
+ }
702
+
703
+ .info-item:last-child {
704
+ margin-bottom: 0;
705
+ }
706
+
707
+ .label {
708
+ font-weight: 500;
709
+ color: #595959;
710
+ margin-right: 8px;
711
+ min-width: 70px;
712
+ font-size: 13px;
713
+ }
714
+
715
+ .step-content {
716
+ /* padding: 8px 0; */
717
+ }
718
+
719
+ .operation-info {
720
+ margin-bottom: 8px;
721
+ }
722
+
723
+ .operation-tag {
724
+ padding: 4px 8px;
725
+ border-radius: 4px;
726
+ font-size: 12px;
727
+ font-weight: 500;
728
+ display: inline-block;
729
+ }
730
+
731
+ .operation-tag.start {
732
+ background-color: #f3e5f5;
733
+ color: #7b1fa2;
734
+ }
735
+
736
+ .operation-tag.approved {
737
+ background-color: #e8f5e8;
738
+ color: #388e3c;
739
+ }
740
+
741
+ .operation-tag.rejected {
742
+ background-color: #ffebee;
743
+ color: #d32f2f;
744
+ }
745
+
746
+ .operation-tag.completed {
747
+ background-color: #e3f2fd;
748
+ color: #1976d2;
749
+ }
750
+
751
+ .operation-tag.claimed {
752
+ background-color: #f3e5f5;
753
+ color: #7b1fa2;
754
+ }
755
+
756
+ .operation-tag.assigned {
757
+ background-color: #e0f2f1;
758
+ color: #00695c;
759
+ }
760
+
761
+ .operation-tag.transferred {
762
+ background-color: #fff8e1;
763
+ color: #f57c00;
764
+ }
765
+
766
+ .operation-tag.add-sign {
767
+ background-color: #fce4ec;
768
+ color: #c2185b;
769
+ }
770
+
771
+ .operation-tag.pending {
772
+ background-color: #fff3e0;
773
+ color: #f57c00;
774
+ }
775
+
776
+ .operation-tag.future {
777
+ background-color: #f5f5f5;
778
+ color: #757575;
779
+ }
780
+
781
+ .operation-tag.cc-status {
782
+ background-color: #fffbe6;
783
+ color: #faad14;
784
+ }
785
+
786
+ .operation-tag.cc-pending {
787
+ background-color: #f5f5f5;
788
+ color: #999999;
789
+ }
790
+
791
+ .step-detail {
792
+ display: flex;
793
+ align-items: flex-start;
794
+ margin-bottom: 4px;
795
+ font-size: 14px;
796
+ }
797
+
798
+ .step-detail:last-child {
799
+ margin-bottom: 0;
800
+ }
801
+
802
+ .current-step {
803
+ background-color: #f8f9fa;
804
+ padding: 12px;
805
+ border-radius: 8px;
806
+ border-left: 4px solid #1976d2;
807
+ }
808
+
809
+ .future-step {
810
+ opacity: 0.7;
811
+ }
812
+
813
+ .multi-instance-details {
814
+ margin-top: 8px;
815
+ }
816
+
817
+ .instance-list {
818
+ margin-top: 12px;
819
+ }
820
+
821
+ .instance-item {
822
+ background-color: #ffffff;
823
+ border: 1px solid #e0e0e0;
824
+ border-radius: 6px;
825
+ padding: 8px;
826
+ margin-bottom: 8px;
827
+ }
828
+
829
+ .instance-item:last-child {
830
+ margin-bottom: 0;
831
+ }
832
+
833
+ .instance-header {
834
+ display: flex;
835
+ justify-content: space-between;
836
+ align-items: center;
837
+ margin-bottom: 4px;
838
+ }
839
+
840
+ .instance-status {
841
+ padding: 2px 6px;
842
+ border-radius: 3px;
843
+ font-size: 11px;
844
+ font-weight: 500;
845
+ }
846
+
847
+ .instance-approved {
848
+ background-color: #e8f5e8;
849
+ color: #388e3c;
850
+ }
851
+
852
+ .instance-rejected {
853
+ background-color: #ffebee;
854
+ color: #d32f2f;
855
+ }
856
+
857
+ .instance-completed {
858
+ background-color: #e3f2fd;
859
+ color: #1976d2;
860
+ }
861
+
862
+ .instance-pending {
863
+ background-color: #fff3e0;
864
+ color: #f57c00;
865
+ }
866
+
867
+ .instance-comment {
868
+ font-size: 12px;
869
+ color: #666;
870
+ margin-bottom: 4px;
871
+ line-height: 1.4;
872
+ }
873
+
874
+ .instance-time {
875
+ font-size: 11px;
876
+ color: #999;
877
+ }
878
+
879
+ .multi-instance-badge {
880
+ background-color: #e3f2fd;
881
+ color: #1976d2;
882
+ padding: 2px 6px;
883
+ border-radius: 3px;
884
+ font-size: 11px;
885
+ font-weight: 500;
886
+ }
887
+
888
+ .instance-section {
889
+ margin-bottom: 12px;
890
+ }
891
+
892
+ .instance-section h5 {
893
+ font-size: 12px;
894
+ color: #666;
895
+ margin: 0 0 8px 0;
896
+ font-weight: 500;
897
+ }
898
+
899
+ .instance-section:last-child {
900
+ margin-bottom: 0;
901
+ }
902
+
903
+ /* 用户信息相关样式 */
904
+ .assignee-info {
905
+ display: flex;
906
+ align-items: center;
907
+ }
908
+
909
+ .approver-info {
910
+ margin-bottom: 12px;
911
+ padding-bottom: 8px;
912
+ border-bottom: 1px solid #f0f0f0;
913
+ }
914
+
915
+ .approver-info:last-child {
916
+ margin-bottom: 0;
917
+ border-bottom: none;
918
+ }
919
+
920
+ .candidate-users {
921
+ display: flex;
922
+ flex-wrap: wrap;
923
+ gap: 8px;
924
+ align-items: center;
925
+ }
926
+
927
+ .candidate-user {
928
+ background-color: #f8f9fa;
929
+ padding: 4px 8px;
930
+ border-radius: 12px;
931
+ border: 1px solid #e9ecef;
932
+ }
933
+
934
+ /* 抄送人相关样式 */
935
+ .cc-users {
936
+ display: flex;
937
+ flex-wrap: wrap;
938
+ gap: 6px;
939
+ align-items: center;
940
+ }
941
+
942
+ .cc-user {
943
+ background-color: #f6ffed;
944
+ padding: 3px 6px;
945
+ border-radius: 10px;
946
+ border: 1px solid #d9f7be;
947
+ font-size: 12px;
948
+ }
949
+
950
+ .cc-users-list {
951
+ display: flex;
952
+ flex-direction: column;
953
+ gap: 8px;
954
+ }
955
+
956
+ .cc-user-item {
957
+ background-color: #fffbe6;
958
+ padding: 8px;
959
+ border-radius: 8px;
960
+ border: 1px solid #ffd666;
961
+ }
962
+
963
+ /* 简化审批步骤样式 */
964
+ .simple-approval-steps {
965
+ background-color: #ffffff;
966
+ padding: 12px 16px;
967
+ }
968
+
969
+ .simple-step-item {
970
+ position: relative;
971
+ padding-bottom: 16px;
972
+ border-left: 2px solid #e8e8e8;
973
+ margin-left: 10px;
974
+ }
975
+
976
+ .simple-step-item:last-child {
977
+ border-left: none;
978
+ padding-bottom: 0;
979
+ }
980
+
981
+ .step-header {
982
+ display: flex;
983
+ align-items: center;
984
+ margin-bottom: 8px;
985
+ margin-left: -11px;
986
+ }
987
+
988
+ .step-icon {
989
+ width: 20px;
990
+ height: 20px;
991
+ border-radius: 50%;
992
+ display: flex;
993
+ align-items: center;
994
+ justify-content: center;
995
+ margin-right: 10px;
996
+ flex-shrink: 0;
997
+ position: relative;
998
+ z-index: 1;
999
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1000
+ }
1001
+
1002
+ .step-icon-completed {
1003
+ background: linear-gradient(135deg, #52c41a, #73d13d);
1004
+ }
1005
+
1006
+ .step-icon-active {
1007
+ background: linear-gradient(135deg, #1890ff, #40a9ff);
1008
+ }
1009
+
1010
+ .step-icon-waiting {
1011
+ background: linear-gradient(135deg, #d9d9d9, #f0f0f0);
1012
+ }
1013
+
1014
+ .step-info {
1015
+ flex: 1;
1016
+ }
1017
+
1018
+ .step-title-row {
1019
+ display: flex;
1020
+ align-items: center;
1021
+ justify-content: space-between;
1022
+ width: 100%;
1023
+ }
1024
+
1025
+ .step-title {
1026
+ font-size: 15px;
1027
+ font-weight: 600;
1028
+ color: #262626;
1029
+ line-height: 1.3;
1030
+ flex: 1;
1031
+ margin-right: 8px;
1032
+ }
1033
+
1034
+ .step-status {
1035
+ margin-left: 8px;
1036
+ white-space: nowrap;
1037
+ }
1038
+
1039
+ .step-content {
1040
+ margin-left: 19px;
1041
+ padding-bottom: 4px;
1042
+ }
1043
+
1044
+ .simple-approver-wrapper {
1045
+ padding: 6px 0;
1046
+ margin: 6px 0;
1047
+ border-bottom: 1px solid #f5f5f5;
1048
+ }
1049
+
1050
+ .simple-approver-wrapper:last-child {
1051
+ border-bottom: none;
1052
+ padding-bottom: 0;
1053
+ }
1054
+
1055
+ .simple-approver {
1056
+ display: flex;
1057
+ align-items: flex-start;
1058
+ justify-content: space-between;
1059
+ min-height: 40px;
1060
+ }
1061
+
1062
+ .approver-user-info {
1063
+ flex: 1;
1064
+ margin-right: 8px;
1065
+ }
1066
+
1067
+ .approver-action-info {
1068
+ display: flex;
1069
+ flex-direction: column;
1070
+ align-items: flex-end;
1071
+ white-space: nowrap;
1072
+ }
1073
+
1074
+ .action-type {
1075
+ font-size: 12px;
1076
+ font-weight: 500;
1077
+ color: #1890ff;
1078
+ margin-bottom: 2px;
1079
+ }
1080
+
1081
+ .target-user-info {
1082
+ padding: 8px 12px;
1083
+ background-color: #f8f9fa;
1084
+ border-radius: 6px;
1085
+ border-left: 3px solid #1890ff;
1086
+ display: flex;
1087
+ align-items: center;
1088
+ gap: 8px;
1089
+ }
1090
+
1091
+ .target-label {
1092
+ font-size: 12px;
1093
+ color: #666;
1094
+ font-weight: 500;
1095
+ white-space: nowrap;
1096
+ }
1097
+
1098
+ .approver-time {
1099
+ font-size: 11px;
1100
+ color: #8c8c8c;
1101
+ margin-left: 8px;
1102
+ white-space: nowrap;
1103
+ font-weight: 500;
1104
+ }
1105
+
1106
+ .approver-comment {
1107
+ background-color: #fafafa;
1108
+ padding: 8px 12px;
1109
+ border-radius: 6px;
1110
+ font-size: 12px;
1111
+ color: #595959;
1112
+ line-height: 1.4;
1113
+ width: 100%;
1114
+ border: 1px solid #f0f0f0;
1115
+ box-sizing: border-box;
1116
+ }
1117
+
1118
+ /* 增加抄送人按钮样式 */
1119
+ .add-cc-button-inline {
1120
+ display: flex;
1121
+ align-items: center;
1122
+ gap: 4px;
1123
+ font-size: 11px;
1124
+ border-color: #d9d9d9;
1125
+ color: #666;
1126
+ height: 24px;
1127
+ padding: 0 8px;
1128
+ white-space: nowrap;
1129
+ }
1130
+
1131
+ .add-cc-button-inline:hover {
1132
+ border-color: #1890ff;
1133
+ color: #1890ff;
1134
+ }
1133
1135
  </style>