@ebiz/designer-components 0.1.124 → 0.1.126

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 (230) hide show
  1. package/README.md +29 -29
  2. package/dist/designer-components.css +1 -1
  3. package/dist/index.mjs +35548 -33511
  4. package/dist/{pdf-C9oIcL2N.js → pdf-KomaE0u5.js} +891 -891
  5. package/package.json +1 -1
  6. package/src/App.vue +26 -26
  7. package/src/apiService/SIMPLE_DATA_SERVICE.md +284 -284
  8. package/src/apiService/mockDataService.js +115 -115
  9. package/src/apiService/simpleDataService.js +312 -312
  10. package/src/assets/base.css +86 -86
  11. package/src/assets/logo.svg +1 -1
  12. package/src/components/Button.vue +149 -149
  13. package/src/components/DataContainer.vue +40 -40
  14. package/src/components/EbizApproval.vue +366 -366
  15. package/src/components/EbizApprovalDetail.vue +169 -169
  16. package/src/components/EbizApprovalForm.vue +534 -534
  17. package/src/components/EbizApprovalV2.vue +713 -713
  18. package/src/components/EbizAutoForm.vue +596 -596
  19. package/src/components/EbizAvatar.vue +115 -115
  20. package/src/components/EbizCheckbox.vue +93 -93
  21. package/src/components/EbizCheckboxGroup.vue +69 -69
  22. package/src/components/EbizDepartmentSelector.vue +148 -148
  23. package/src/components/EbizDescriptions.vue +340 -340
  24. package/src/components/EbizDescriptionsItem.vue +47 -47
  25. package/src/components/EbizDetailBlock.vue +81 -81
  26. package/src/components/EbizDetailItem.vue +559 -559
  27. package/src/components/EbizDetailView.md +438 -438
  28. package/src/components/EbizDetailView.vue +355 -355
  29. package/src/components/EbizDialog.vue +260 -260
  30. package/src/components/EbizDictionarySelect.vue +229 -229
  31. package/src/components/EbizDiv.vue +40 -40
  32. package/src/components/EbizDivider.vue +96 -96
  33. package/src/components/EbizDormDashboard.vue +314 -314
  34. package/src/components/EbizDropdown.vue +135 -135
  35. package/src/components/EbizDropdownItem.vue +85 -85
  36. package/src/components/EbizEmployeeInfo.vue +144 -144
  37. package/src/components/EbizEmployeeSelector.vue +1160 -1160
  38. package/src/components/EbizFileList.vue +502 -502
  39. package/src/components/EbizMap.vue +541 -541
  40. package/src/components/EbizMeetingRoomSelector.vue +1149 -1149
  41. package/src/components/EbizMobileMeetingRoomSelector.vue +727 -727
  42. package/src/components/EbizOkrTree.vue +99 -99
  43. package/src/components/EbizPageHeader.vue +98 -98
  44. package/src/components/EbizPagination.vue +162 -162
  45. package/src/components/EbizParkingDashboard.vue +152 -152
  46. package/src/components/EbizPdfViewer.vue +540 -540
  47. package/src/components/EbizPopconfirm.vue +47 -47
  48. package/src/components/EbizQrCode.vue +167 -167
  49. package/src/components/EbizRadio.vue +86 -86
  50. package/src/components/EbizRadioGroup.vue +83 -83
  51. package/src/components/EbizRemoteSelect.vue +232 -232
  52. package/src/components/EbizRichTextEditor.vue +338 -338
  53. package/src/components/EbizRouteBreadcrumb.vue +46 -46
  54. package/src/components/EbizSApprovalProcess.vue +1456 -1426
  55. package/src/components/EbizSelect.vue +85 -85
  56. package/src/components/EbizSpace.vue +100 -100
  57. package/src/components/EbizStatistic.vue +149 -149
  58. package/src/components/EbizStatsCard.vue +113 -113
  59. package/src/components/EbizSwiper.vue +113 -113
  60. package/src/components/EbizSwiperItem.vue +13 -13
  61. package/src/components/EbizSwitch.vue +85 -85
  62. package/src/components/EbizTabHeader.vue +132 -132
  63. package/src/components/EbizTabPanel.vue +22 -22
  64. package/src/components/EbizTable.vue +469 -469
  65. package/src/components/EbizTableColumn.vue +116 -116
  66. package/src/components/EbizTableSort.vue +179 -179
  67. package/src/components/EbizTabs.vue +142 -142
  68. package/src/components/EbizTdesignButtonDialog.vue +332 -332
  69. package/src/components/EbizTdesignLoading.vue +107 -107
  70. package/src/components/EbizTimePicker.vue +143 -143
  71. package/src/components/EbizTitle.vue +91 -91
  72. package/src/components/EbizTree.vue +141 -141
  73. package/src/components/EbizTreeMergeTable.vue +1494 -1494
  74. package/src/components/EbizTreeSelector.vue +469 -469
  75. package/src/components/EbizVideo.vue +553 -553
  76. package/src/components/EbizVxeTable.vue +290 -290
  77. package/src/components/Form.vue +28 -28
  78. package/src/components/Home.vue +7 -7
  79. package/src/components/LaunchInterview.vue +526 -526
  80. package/src/components/MyComponent.vue +39 -39
  81. package/src/components/Table.vue +45 -45
  82. package/src/components/TdesignAlert.vue +115 -115
  83. package/src/components/TdesignButton.vue +135 -135
  84. package/src/components/TdesignCalendar/index.vue +145 -145
  85. package/src/components/TdesignCard.vue +195 -195
  86. package/src/components/TdesignCol.vue +101 -101
  87. package/src/components/TdesignCollapse.vue +142 -142
  88. package/src/components/TdesignCollapsePanel.vue +79 -79
  89. package/src/components/TdesignDatePicker.vue +124 -124
  90. package/src/components/TdesignDescriptions.vue +74 -74
  91. package/src/components/TdesignDescriptionsItem.vue +50 -50
  92. package/src/components/TdesignDialog.vue +225 -225
  93. package/src/components/TdesignForm.vue +138 -138
  94. package/src/components/TdesignFormItem.vue +105 -105
  95. package/src/components/TdesignGrid.vue +55 -55
  96. package/src/components/TdesignIcon.vue +67 -67
  97. package/src/components/TdesignImage.vue +162 -162
  98. package/src/components/TdesignImageViewer.vue +200 -200
  99. package/src/components/TdesignInput.vue +242 -242
  100. package/src/components/TdesignSelect.vue +446 -446
  101. package/src/components/TdesignTag.vue +117 -117
  102. package/src/components/TdesignTextarea.vue +142 -142
  103. package/src/components/TdesignTimeline.vue +58 -58
  104. package/src/components/TdesignTimelineItem.vue +71 -71
  105. package/src/components/TdesignUpload.vue +414 -414
  106. package/src/components/TdesignWatermark.vue +107 -107
  107. package/src/components/ebiz-form/components/cascader.vue +61 -61
  108. package/src/components/ebiz-form/components/checkbox.vue +37 -37
  109. package/src/components/ebiz-form/components/city.vue +137 -137
  110. package/src/components/ebiz-form/components/date-panel.vue +52 -52
  111. package/src/components/ebiz-form/components/date-range-panel.vue +52 -52
  112. package/src/components/ebiz-form/components/date-range.vue +56 -56
  113. package/src/components/ebiz-form/components/date.vue +52 -52
  114. package/src/components/ebiz-form/components/editor-multi-language.vue +47 -47
  115. package/src/components/ebiz-form/components/editor.vue +78 -78
  116. package/src/components/ebiz-form/components/file-multi-language.vue +52 -52
  117. package/src/components/ebiz-form/components/file.vue +149 -149
  118. package/src/components/ebiz-form/components/images-multi-language.vue +52 -52
  119. package/src/components/ebiz-form/components/images.vue +129 -129
  120. package/src/components/ebiz-form/components/img-multi-language.vue +51 -51
  121. package/src/components/ebiz-form/components/img.vue +129 -129
  122. package/src/components/ebiz-form/components/number.vue +50 -50
  123. package/src/components/ebiz-form/components/radio.vue +28 -28
  124. package/src/components/ebiz-form/components/select.vue +119 -119
  125. package/src/components/ebiz-form/components/switch.vue +23 -23
  126. package/src/components/ebiz-form/components/text-multi-language.vue +47 -47
  127. package/src/components/ebiz-form/components/text.vue +52 -52
  128. package/src/components/ebiz-form/components/textarea-multi-language.vue +48 -48
  129. package/src/components/ebiz-form/components/textarea.vue +29 -29
  130. package/src/components/ebiz-form/components/video-multi-language.vue +51 -51
  131. package/src/components/ebiz-form/components/video.vue +97 -97
  132. package/src/components/ebiz-form/index.vue +157 -157
  133. package/src/components/examples/PopconfirmExample.vue +149 -149
  134. package/src/components/icons/IconCommunity.vue +7 -7
  135. package/src/components/icons/IconDocumentation.vue +7 -7
  136. package/src/components/icons/IconEcosystem.vue +7 -7
  137. package/src/components/icons/IconSupport.vue +7 -7
  138. package/src/components/icons/IconTooling.vue +19 -19
  139. package/src/components/mItems/UserInfo.vue +349 -349
  140. package/src/components/senior/EbizApprovalList/ApprovalList.vue +127 -127
  141. package/src/components/senior/EbizSData/index.vue +280 -280
  142. package/src/components/senior/EbizSDialog/index.vue +776 -776
  143. package/src/components/senior/EbizSForm/README.md +157 -157
  144. package/src/components/senior/EbizSForm/index.vue +667 -667
  145. package/src/components/senior/EbizSForm/item.vue +1011 -1011
  146. package/src/components/senior/EbizSForm/mItems/DateTimePicker.vue +51 -51
  147. package/src/components/senior/EbizSForm/mItems/Picker.vue +63 -63
  148. package/src/index.js +327 -327
  149. package/src/main.js +55 -55
  150. package/src/router/index.js +436 -436
  151. package/src/utils/formatCode.js +24 -24
  152. package/src/utils/generateImportStatement.js +52 -52
  153. package/src/utils/hasJsx.js +25 -25
  154. package/src/utils/index.js +166 -166
  155. package/src/utils/mergeOptions.js +29 -29
  156. package/src/utils/parseRequiredBlocks.js +18 -18
  157. package/src/utils/vue-sfc-validator.js +155 -155
  158. package/src/views/Button.vue +23 -23
  159. package/src/views/CheckboxDemo.vue +104 -104
  160. package/src/views/DataContainer.vue +19 -19
  161. package/src/views/DialogDemo.vue +125 -125
  162. package/src/views/EbizApprovalDemo.vue +87 -87
  163. package/src/views/EbizApprovalFormDemo.vue +207 -207
  164. package/src/views/EbizAutoFormDemo.vue +129 -129
  165. package/src/views/EbizAvatar.vue +223 -223
  166. package/src/views/EbizDepartmentSelectorDemo.vue +169 -169
  167. package/src/views/EbizDetailBlockDemo.vue +30 -30
  168. package/src/views/EbizDetailViewDemo.vue +412 -412
  169. package/src/views/EbizDormDashboardDemo.vue +87 -87
  170. package/src/views/EbizEmployeeInfo.vue +249 -249
  171. package/src/views/EbizEmployeeSelector.vue +85 -85
  172. package/src/views/EbizFileListDemo.vue +339 -339
  173. package/src/views/EbizMap.vue +201 -201
  174. package/src/views/EbizMeetingRoomSelectorDemo.vue +293 -293
  175. package/src/views/EbizMobileMeetingRoomSelectorDemo.vue +566 -566
  176. package/src/views/EbizParkingDashboardDemo.vue +28 -28
  177. package/src/views/EbizRadioDemo.vue +151 -151
  178. package/src/views/EbizSDataDemo.vue +136 -136
  179. package/src/views/EbizSDialogDemo.vue +303 -303
  180. package/src/views/EbizSForm/index.vue +352 -352
  181. package/src/views/EbizSFormDemo.vue +420 -420
  182. package/src/views/EbizSpace.vue +185 -185
  183. package/src/views/EbizSwiper.vue +157 -157
  184. package/src/views/EbizTdesignButtonDialogExample.vue +437 -437
  185. package/src/views/Form.vue +19 -19
  186. package/src/views/GridDemo.vue +238 -238
  187. package/src/views/Home.vue +156 -156
  188. package/src/views/LaunchInterviewDemo.vue +111 -111
  189. package/src/views/Mindmap.vue +17 -17
  190. package/src/views/MyComponent.vue +19 -19
  191. package/src/views/OkrTree.vue +19 -19
  192. package/src/views/PageHeaderDemo.vue +104 -104
  193. package/src/views/PaginationDemo.vue +96 -96
  194. package/src/views/PdfViewerDemo.vue +433 -433
  195. package/src/views/PermissionBoxDemo.vue +85 -85
  196. package/src/views/PopconfirmDemo.vue +80 -80
  197. package/src/views/RemoteSelect.vue +350 -350
  198. package/src/views/StatisticDemo.vue +190 -190
  199. package/src/views/SwitchDemo.vue +79 -79
  200. package/src/views/Table.vue +19 -19
  201. package/src/views/TableDemo.vue +334 -334
  202. package/src/views/TableSortDemo.vue +143 -143
  203. package/src/views/TableView.vue +68 -68
  204. package/src/views/TabsDemo.vue +282 -282
  205. package/src/views/TagDemo.vue +101 -101
  206. package/src/views/TdesignAlert.vue +98 -98
  207. package/src/views/TdesignButton.vue +190 -190
  208. package/src/views/TdesignCalendar.vue +94 -94
  209. package/src/views/TdesignCard.vue +296 -296
  210. package/src/views/TdesignCollapse.vue +293 -293
  211. package/src/views/TdesignDatePicker.vue +187 -187
  212. package/src/views/TdesignDescriptions.vue +101 -101
  213. package/src/views/TdesignForm.vue +248 -248
  214. package/src/views/TdesignIcon.vue +203 -203
  215. package/src/views/TdesignImage.vue +215 -215
  216. package/src/views/TdesignImageViewer.vue +198 -198
  217. package/src/views/TdesignInput.vue +252 -252
  218. package/src/views/TdesignSelect.vue +473 -473
  219. package/src/views/TdesignSwiper.vue +157 -157
  220. package/src/views/TextareaDemo.vue +93 -93
  221. package/src/views/TimePickerDemo.vue +146 -146
  222. package/src/views/TimelineDemo.vue +160 -160
  223. package/src/views/Title.vue +19 -19
  224. package/src/views/TreeDemo.vue +254 -254
  225. package/src/views/TreeMergeTableDemo.vue +239 -239
  226. package/src/views/TreeSelectorDemo.vue +245 -245
  227. package/src/views/UploadDemo.vue +128 -128
  228. package/src/views/VideoDemo.vue +245 -245
  229. package/src/views/VxeTableDemo.vue +279 -279
  230. package/src/views/WatermarkDemo.vue +85 -85
@@ -1,1427 +1,1457 @@
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 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">
17
- <span class="label">发起时间:</span>
18
- <span>{{ state.processInfo.startTimeStr || '--' }}</span>
19
- </div>
20
- <div class="info-item">
21
- <span class="label">流程状态:</span>
22
- <t-tag :theme="getProcessStatusTheme(state.processInfo.processStatus)" size="small">
23
- {{ getProcessStatusText(state.processInfo.processStatus) }}
24
- </t-tag>
25
- </div>
26
- <!-- 抄送人信息 -->
27
- </div>
28
-
29
- <!-- 审批步骤 -->
30
- <div class="simple-approval-steps">
31
- <!-- 节点步骤 -->
32
- <div v-for="(node, index) in state.nodes" :key="'node-' + index" class="simple-step-item">
33
-
34
- <div
35
- v-if="node.nodeStatus == 'PENDING' && getPendingApprovers(node).length === 0 && state.processInfo.processStatus != 'ACTIVE'">
36
- </div>
37
- <div v-else>
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
- ||
70
- approver.completedTime || approver.endTime) }}</div>
71
- </div>
72
- </div>
73
-
74
- <!-- 目标用户信息(转审、加签等操作) -->
75
- <div v-if="approver.targetUsers && needShowTargetUser(approver)"
76
- class="target-user-info">
77
- <div class="target-label">{{ getTargetUserLabel(approver) }}</div>
78
- <div style="display: flex; flex-direction: column; gap: 4px;">
79
- <div v-for="targetUser in approver.targetUsers" :key="targetUser.userId">
80
- <user-info :userId="targetUser.userId"
81
- :userInfo="getTargetUserInfo(targetUser)" avatar-size="small"
82
- name-size="small" :show-department="false" />
83
- </div>
84
- </div>
85
- </div>
86
-
87
-
88
- <!-- 评论信息 -->
89
- <div v-if="approver.comment || (approver.operation.attachmentsList && approver.operation.attachmentsList.length > 0)"
90
- class="approver-comment">
91
- <div v-if="approver.comment">{{ approver.comment }}</div>
92
- <div
93
- v-if="approver.operation.attachmentsList && approver.operation.attachmentsList.length > 0">
94
- <ebiz-file-list size="mini" :files="approver.operation.attachmentsList" />
95
- </div>
96
- </div>
97
- <!-- <div v-if="approver.comment" class="approver-comment">{{ approver.comment }}</div> -->
98
- </div>
99
-
100
- <!-- 进行中节点 -->
101
- <!-- 当前处理人 -->
102
- <div v-if="getCurrentApprover(node)" class="simple-approver">
103
- <user-info
104
- :userId="getCurrentApprover(node).nodeUser?.userId || getCurrentApprover(node).userId"
105
- :userInfo="getUserInfo(getCurrentApprover(node))" avatar-size="small"
106
- name-size="small" :show-department="false" />
107
- <div class="approver-time">处理中</div>
108
- </div>
109
-
110
- <!-- 待处理人 -->
111
- <div v-for="approver in getPendingApprovers(node)"
112
- :key="'pending-' + (approver.nodeUser?.userId || approver.userId)"
113
- class="simple-approver">
114
- <user-info :userId="approver.nodeUser?.userId || approver.userId"
115
- :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
116
- :show-department="false" />
117
- <!-- <div class="approver-time">待处理</div> -->
118
- </div>
119
- </div>
120
- </div>
121
- </div>
122
-
123
- <!-- 抄送人步骤 -->
124
- <div v-if="state.processInfo" class="simple-step-item">
125
- <div class="step-header">
126
- <div class="step-icon" :class="getCcSimpleIconClass()">
127
- <t-icon name="mail" size="12px" color="white" />
128
- </div>
129
- <div class="step-info">
130
- <div class="step-title-row">
131
- <span class="step-title">抄送</span>
132
- <!-- 增加抄送人按钮 -->
133
- <div v-if="state.processInfo.processStatus === 'ACTIVE'">
134
- <ebiz-employee-selector content="增加抄送人" :single="false"
135
- defaultTab="organization" class="component-base-style"
136
- @click="handleAddCcUser" @confirm="onAddCcListConfirm">
137
- <template #selected-items="{ event, data }">
138
- <div></div>
139
- </template>
140
- </ebiz-employee-selector>
141
- </div>
142
- </div>
143
- </div>
144
- </div>
145
-
146
- <div class="step-content">
147
- <div v-for="ccUser in state.processInfo.ccUserList"
148
- :key="ccUser.nodeUser?.userId || ccUser.userId" class="simple-approver">
149
- <user-info :userId="ccUser.nodeUser?.userId || ccUser.userId"
150
- :userInfo="ccUser.nodeUser || ccUser" avatar-size="small" name-size="small"
151
- :show-department="false" />
152
- <div class="approver-time">{{ getCcTimeText() }}</div>
153
- </div>
154
- </div>
155
- </div>
156
- </div>
157
- </div>
158
-
159
- <!-- 备注部分 -->
160
- <div class="block-base-style">
161
- <div class="card">
162
- <div class="card-header">
163
- <span class="title">备注 <span v-if="state.remarks.length > 0">({{ state.remarks.length
164
- }})</span></span>
165
- <div class="card-actions">
166
- <t-button variant="text" @click="showAddRemarkDialog">添加备注</t-button>
167
- </div>
168
- </div>
169
-
170
- <!-- 备注列表 -->
171
- <div class="remark-list" v-if="state.remarks.length > 0">
172
- <div v-for="remark in state.remarks" :key="remark.id" class="remark-item">
173
- <div class="remark-header">
174
- <user-info :userId="remark.userId" :userInfo="remark.userDetail" avatar-size="small"
175
- name-size="small" :show-job-number="true" :show-department="false" />
176
- <div class="remark-actions">
177
- <div class="remark-time">{{ remark.createdTimeStr }}</div>
178
- <t-button v-if="canDeleteRemark(remark)" variant="text" theme="danger" size="small"
179
- class="delete-btn" @click="confirmDeleteRemark(remark)">
180
- <t-icon name="delete" />
181
- </t-button>
182
- </div>
183
- </div>
184
- <div class="remark-content">{{ remark.content }}</div>
185
- <div class="remark-attachments" v-if="remark.attachments && remark.attachments.length > 0">
186
- <ebiz-file-list size="mini" :files="remark.attachments" />
187
- </div>
188
- </div>
189
- </div>
190
-
191
- <!-- 无备注时的提示 -->
192
- <div class="no-remarks" v-else>
193
- <div class="no-remarks-tip">暂无备注信息</div>
194
- </div>
195
- </div>
196
- </div>
197
-
198
- <!-- 添加备注对话框 -->
199
- <t-dialog v-model:visible="state.showRemarkDialog" header="添加备注" :width="500" :footer="false" attach="body"
200
- :close-on-overlay-click="false" :close-btn="true">
201
- <div class="remark-form">
202
- <t-textarea v-model="state.newRemark.content" placeholder="请输入备注内容" :maxlength="500"
203
- :autosize="{ minRows: 3, maxRows: 5 }" />
204
-
205
- <div>
206
- <ebiz-upload v-model="state.newRemark.attachments" :multiple="true" theme="file">
207
- <template #trigger>
208
- <t-button variant="outline">
209
- <template #icon>
210
- <t-icon name="attach" />
211
- </template>
212
- 上传附件
213
- </t-button>
214
- </template>
215
- </ebiz-upload>
216
- </div>
217
- <div class="remark-form-footer">
218
- <div></div>
219
- <div class="remark-form-actions">
220
- <t-button theme="default" @click="cancelAddRemark">取消</t-button>
221
- <t-button theme="primary" :loading="state.submittingRemark"
222
- @click="submitRemark">提交</t-button>
223
- </div>
224
- </div>
225
- </div>
226
- </t-dialog>
227
-
228
- <!-- 删除确认对话框 -->
229
- <t-dialog v-model:visible="state.showDeleteConfirmDialog" header="删除确认" :width="400"
230
- :confirm-btn="{ content: '确认', theme: 'danger', loading: state.deletingRemark }"
231
- :cancel-btn="{ content: '取消', theme: 'default' }" @confirm="deleteRemark" @cancel="cancelDeleteRemark">
232
- <div class="delete-confirm-content">
233
- <t-icon name="error-circle-filled" size="large" style="color: #E34D59; margin-right: 8px;" />
234
- <span>确定要删除此备注吗?此操作不可恢复。</span>
235
- </div>
236
- </t-dialog>
237
- </div>
238
- </div>
239
- </template>
240
-
241
- <script>
242
- /**
243
- * @displayName 审批流程
244
- * @description 审批流程组件,用于展示审批流程的详细信息
245
- * @category 业务组件
246
- * @name EbizApprovalProcess
247
- */
248
- export default {
249
- name: "EbizApprovalProcess",
250
- components: {
251
- 't-tag': Tag,
252
- 't-icon': Icon,
253
- 't-button': Button,
254
- 't-dialog': Dialog,
255
- 't-textarea': Textarea,
256
- 't-upload': Upload
257
- }
258
- }
259
- </script>
260
-
261
- <script setup>
262
- import { reactive, onMounted, ref } from 'vue'
263
- import { defineProps, defineEmits } from 'vue'
264
- import { Tag, Icon, Button, Dialog, Textarea, Upload, MessagePlugin as message } from 'tdesign-vue-next'
265
- import EbizEmployeeSelector from './EbizEmployeeSelector.vue'
266
- import EbizTdesignLoading from './EbizTdesignLoading.vue'
267
- import dataService from '../apiService/simpleDataService.js'
268
- import UserInfo from './mItems/UserInfo.vue'
269
- import EbizFileList from './EbizFileList.vue'
270
- import { EbizUpload } from '../index.js'
271
-
272
- // 定义 props
273
- const props = defineProps({
274
- /**
275
- * 业务键
276
- */
277
- businessKey: {
278
- type: String,
279
- default: ''
280
- },
281
- /**
282
- * 业务类型
283
- */
284
- type: {
285
- type: String,
286
- default: ''
287
- }
288
- })
289
-
290
- // 定义 emits
291
- const emit = defineEmits(['load-success', 'add-cc-user'])
292
-
293
- // 响应式状态
294
- const state = reactive({
295
- loading: false,
296
- processInfo: null,
297
- nodes: [], // 直接保存节点数据
298
- historyRecords: [],
299
- currentNodes: [],
300
- futureNodes: [],
301
- approvalDetail: {},
302
- selectedCCList: [],
303
- showCcListSelector: false,
304
-
305
- // 备注相关
306
- remarks: [],
307
- remarksLoading: false,
308
- showRemarkDialog: false,
309
- submittingRemark: false,
310
- newRemark: {
311
- content: '',
312
- attachments: []
313
- },
314
- showDeleteConfirmDialog: false,
315
- deletingRemark: false,
316
- remarkToDelete: null
317
- })
318
-
319
- // 处理审批详情响应
320
- const handleApprovalDetailResponse = (data) => {
321
- // 构建流程信息
322
- state.processInfo = {
323
- processInstanceId: data.processInstanceId,
324
- businessKey: data.businessKey,
325
- processDefinitionKey: data.processDefinitionKey,
326
- processDefinitionName: data.processDefinitionName,
327
- startTime: data.startTime,
328
- startTimeStr: data.startTime, // 使用startTime作为显示时间
329
- endTime: data.endTime,
330
- startUserId: data.startUserId,
331
- startUserName: data.startUserName,
332
- startUserInfo: data.startUserInfo,
333
- processStatus: data.processStatus,
334
- processStatusDesc: data.processStatusDesc,
335
- ccUserList: data.ccUserList || []
336
- }
337
-
338
- // 直接保存节点数据,不进行拆分
339
- state.nodes = data.nodes || []
340
-
341
- // 清空原有的分类数据
342
- state.historyRecords = []
343
- state.currentNodes = []
344
- state.futureNodes = []
345
-
346
- state.approvalDetail = data
347
- state.loading = false
348
-
349
- emit('load-success', data)
350
- }
351
-
352
- // 判断是否可以删除备注(只能删除自己的备注)
353
- const canDeleteRemark = (remark) => {
354
- if (!state.currentUser) {
355
- try {
356
- const userStr = localStorage.getItem('user')
357
- if (userStr) {
358
- state.currentUser = JSON.parse(userStr)
359
- }
360
- } catch (e) {
361
- console.error('解析用户信息失败', e)
362
- return false
363
- }
364
- }
365
-
366
- // 如果没有获取到当前用户信息,则不允许删除
367
- if (!state.currentUser || !state.currentUser.userId) {
368
- return false
369
- }
370
-
371
- // 判断备注的用户ID是否与当前用户ID一致
372
- const remarkUserId = remark.userId || remark.userDetail?.userId
373
- return remarkUserId && String(remarkUserId) === String(state.currentUser.userId)
374
- }
375
-
376
- // 处理审批详情错误
377
- const handleApprovalDetailError = (err) => {
378
- state.loading = false
379
- message.error(err.message || '获取审批信息失败')
380
- }
381
-
382
- // 请求审批详情
383
- const requestApprovalDetail = (businessKey, type) => {
384
- state.loading = true
385
-
386
- const requestData = {
387
- businessKey: businessKey,
388
- businessType: type
389
- }
390
-
391
- // 使用新的接口获取审批详情
392
- dataService.fetch(
393
- requestData,
394
- {},
395
- '/tasks/approval-detail'
396
- ).then((res) => {
397
- console.log("approval-detail res", res)
398
- handleApprovalDetailResponse(res)
399
- }).catch((err) => {
400
- handleApprovalDetailError(err)
401
- })
402
- }
403
-
404
- // 获取操作类型的样式类
405
- const getOperationClass = (operationType, operationResult) => {
406
- const typeMap = {
407
- 'START': 'start',
408
- 'COMPLETE': operationResult === '通过' ? 'approved' : operationResult === '拒绝' ? 'rejected' : 'completed',
409
- 'CLAIM': 'claimed',
410
- 'ASSIGN': 'assigned',
411
- 'TRANSFER': 'transferred',
412
- 'REJECT': 'rejected',
413
- 'ADD_SIGN': 'add-sign'
414
- }
415
- return typeMap[operationType] || 'default'
416
- }
417
-
418
- // 获取操作类型的文本
419
- const getOperationTypeText = (approver) => {
420
- if (!approver.operation) {
421
- return getSimpleStatusText(null, approver)
422
- }
423
-
424
- const operationType = approver.operation.operationType
425
- switch (operationType) {
426
- case 'COMPLETE':
427
- // 从operationData中获取审批结果
428
- try {
429
- const operationData = JSON.parse(approver.operation.operationData || '{}')
430
- if (operationData.approved === true) {
431
- return '已同意'
432
- } else if (operationData.approved === false) {
433
- return '已拒绝'
434
- }
435
- } catch (e) {
436
- // 解析失败,使用默认逻辑
437
- }
438
- return '已完成'
439
- case 'TRANSFER':
440
- return '转审'
441
- case 'ADD_SIGN':
442
- const operationData = JSON.parse(approver.operation.operationData || '{}')
443
- if (operationData.position == 'before') {
444
- return "向前加签";
445
- }
446
- return '已同意并加签'
447
- // return '加签'
448
- case 'REJECT':
449
- return '驳回'
450
- case 'RETURN':
451
- return '退回'
452
- case 'CLAIM':
453
- return '认领'
454
- case 'ASSIGN':
455
- return '分配'
456
- default:
457
- return '已处理'
458
- }
459
- }
460
-
461
- // 获取流程状态主题
462
- const getProcessStatusTheme = (status) => {
463
- const statusMap = {
464
- 'CANCELED': 'default',
465
- 'REJECTED': 'danger',
466
- 'APPROVED': "success",
467
- 'ACTIVE': 'primary',
468
- 'COMPLETED': 'success',
469
- 'SUSPENDED': 'warning',
470
- 'REVOKE': 'default'
471
- }
472
- return statusMap[status] || 'default'
473
- }
474
-
475
- // 获取流程状态文本
476
- const getProcessStatusText = (status) => {
477
- const statusMap = {
478
- 'CANCELED': '已取消',
479
- 'REJECTED': '已驳回',
480
- 'APPROVED': "已通过",
481
- 'ACTIVE': '进行中',
482
- 'COMPLETED': '已完成',
483
- 'SUSPENDED': '已暂停',
484
- 'REVOKE': '已撤销'
485
- }
486
- return statusMap[status] || status
487
- }
488
-
489
- // 获取节点状态主题
490
- const getNodeStatusTheme = (node) => {
491
- switch (node.nodeStatus) {
492
- case 'COMPLETED':
493
- return 'success'
494
- case 'ACTIVE':
495
- return 'primary'
496
- default:
497
- return 'default'
498
- }
499
- }
500
-
501
- // 获取节点状态文本
502
- const getNodeStatusText = (node) => {
503
- switch (node.nodeStatus) {
504
- case 'COMPLETED':
505
- return '已完成'
506
- case 'ACTIVE':
507
- return '进行中'
508
- default:
509
- return '未开始'
510
- }
511
- }
512
-
513
- // 获取已完成的审批人
514
- const getCompletedApprovers = (node) => {
515
- if (!node.approvers) return []
516
- return node.approvers.filter(approver => approver.approvalStatus === 'COMPLETED')
517
- }
518
-
519
- // 获取当前审批人
520
- const getCurrentApprover = (node) => {
521
- if (!node.approvers) return null
522
- return node.approvers.find(approver => approver.approvalStatus === 'ACTIVE' && approver.isCurrent)
523
- }
524
-
525
- // 获取待处理审批人
526
- const getPendingApprovers = (node) => {
527
- if (!node.approvers) return []
528
- return node.approvers.filter(approver => approver.approvalStatus === 'PENDING')
529
- }
530
-
531
- // 获取用户信息
532
- const getUserInfo = (approver) => {
533
- // 新数据结构:用户信息在nodeUser对象中
534
- const userInfo = approver.nodeUser || approver
535
- return {
536
- userId: userInfo.userId,
537
- name: userInfo.userName,
538
- employeeNo: userInfo.employeeNo,
539
- photo: userInfo.photo,
540
- departmentName: userInfo.deptName,
541
- position: userInfo.position,
542
- phone: userInfo.phone,
543
- email: userInfo.email
544
- }
545
- }
546
-
547
- // 判断流程是否已完成
548
- const isProcessCompleted = () => {
549
- return state.processInfo && state.processInfo.processStatus === 'COMPLETED'
550
- }
551
-
552
- // 获取简单步骤图标类
553
- const getSimpleStepIconClass = (node) => {
554
- switch (node.nodeStatus) {
555
- case 'COMPLETED':
556
- return 'step-icon-completed'
557
- case 'ACTIVE':
558
- return 'step-icon-active'
559
- default:
560
- return 'step-icon-waiting'
561
- }
562
- }
563
-
564
- // 获取抄送简单图标类
565
- const getCcSimpleIconClass = () => {
566
- return isProcessCompleted() ? 'step-icon-completed' : 'step-icon-waiting'
567
- }
568
-
569
- // 获取简单状态文本
570
- const getSimpleStatusText = (node, approver) => {
571
- // 优先使用approvalResult
572
- if (approver.approvalResult) {
573
- return approver.approvalResult === '通过' ? '已同意' : approver.approvalResult === '拒绝' ? '已拒绝' : '已完成'
574
- }
575
- // 如果没有approvalResult,使用approved字段
576
- if (approver.approved !== null && approver.approved !== undefined) {
577
- return approver.approved ? '已同意' : '已拒绝'
578
- }
579
- // 默认返回已完成
580
- return '已完成'
581
- }
582
-
583
- // 格式化时间
584
- const formatTime = (timeStr) => {
585
- if (!timeStr) return '--'
586
-
587
- let date
588
- // 如果是数字(时间戳),直接使用
589
- if (typeof timeStr === 'number') {
590
- date = new Date(timeStr)
591
- } else {
592
- // 如果是字符串,尝试解析
593
- date = new Date(timeStr)
594
- }
595
-
596
- if (isNaN(date.getTime())) return timeStr
597
-
598
- const month = date.getMonth() + 1
599
- const day = date.getDate()
600
- const hours = date.getHours().toString().padStart(2, '0')
601
- const minutes = date.getMinutes().toString().padStart(2, '0')
602
-
603
- return `${month}/${day} ${hours}:${minutes}`
604
- }
605
-
606
- // 获取抄送时间文本
607
- const getCcTimeText = () => {
608
- if (isProcessCompleted()) {
609
- return `已抄送 · ${formatTime(state.processInfo.endTime || state.processInfo.startTimeStr)}`
610
- }
611
- return ''
612
- }
613
-
614
- // 获取发起人信息
615
- const getStartUserInfo = () => {
616
- // 优先从历史记录中查找发起人
617
- const startRecord = state.historyRecords.find(record => record.operationType === 'START')
618
- if (startRecord) {
619
- return {
620
- userId: startRecord.operatorId,
621
- userInfo: startRecord.assigneeInfo
622
- }
623
- }
624
-
625
- // 如果没有发起记录,从流程信息中获取
626
- if (state.processInfo && state.processInfo.startUserId) {
627
- return {
628
- userId: state.processInfo.startUserId,
629
- userInfo: state.processInfo.startUserInfo || {
630
- name: state.processInfo.startUserName || '未知用户'
631
- }
632
- }
633
- }
634
-
635
- // 如果都没有,尝试从第一个已完成节点的审批人获取(可能是发起人)
636
- if (state.historyRecords.length > 0) {
637
- const firstRecord = state.historyRecords[0]
638
- return {
639
- userId: firstRecord.operatorId,
640
- userInfo: firstRecord.assigneeInfo
641
- }
642
- }
643
-
644
- return { userId: '', userInfo: {} }
645
- }
646
-
647
- // 处理增加抄送人
648
- const handleAddCcUser = () => {
649
- console.log("handleAddCcUser")
650
- state.showCcListSelector = true
651
- emit('add-cc-user', {
652
- processInstanceId: state.processInfo?.processInstanceId,
653
- businessKey: state.processInfo?.businessKey,
654
- processDefinitionKey: state.processInfo?.processDefinitionKey
655
- })
656
- }
657
-
658
- function onAddCcListConfirm(event) {
659
- let selectedCCList = event.map(item => Number(item))
660
- if (selectedCCList?.length === 0) return
661
- state.showCcListSelector = false
662
- dataService.fetch(
663
- {
664
- businessKey: state.processInfo?.businessKey,
665
- type: state.processInfo?.processDefinitionKey,
666
- processInstanceId: state.processInfo?.processInstanceId,
667
- ccUsers: selectedCCList
668
- },
669
- {},
670
- '/tasks/batchAddCcInfo'
671
- )
672
- .then((res) => {
673
- // 重新加载数据
674
- requestApprovalDetail(String(props.businessKey), props.type)
675
- message.success("添加成功")
676
- })
677
- .catch((err) => {
678
- message.error(err.message)
679
- })
680
- }
681
-
682
- // 判断是否需要显示目标用户
683
- const needShowTargetUser = (approver) => {
684
- if (!approver.operation || !approver.targetUsers) return false
685
-
686
- return true;
687
- // const operationType = approver.operation.operationType
688
- // return ['TRANSFER', 'ADD_SIGN', 'RETURN'].includes(operationType)
689
- }
690
-
691
- // 获取目标用户标签
692
- const getTargetUserLabel = (approver) => {
693
- if (!approver.operation) return ''
694
-
695
- const operationType = approver.operation.operationType
696
- switch (operationType) {
697
- case 'TRANSFER':
698
- return '转审给:'
699
- case 'ADD_SIGN':
700
- return '加签:'
701
- case 'RETURN':
702
- return '退回给:'
703
- default:
704
- return '目标:'
705
- }
706
- }
707
-
708
- // 获取目标用户信息
709
- const getTargetUserInfo = (targetUser) => {
710
- // const targetUser = approver.targetUser
711
- return {
712
- userId: targetUser.userId,
713
- name: targetUser.userName,
714
- employeeNo: targetUser.employeeNo,
715
- photo: targetUser.photo,
716
- departmentName: targetUser.deptName,
717
- position: targetUser.position,
718
- phone: targetUser.phone,
719
- email: targetUser.email
720
- }
721
- }
722
-
723
- // 获取流程备注列表
724
- const fetchProcessRemarks = () => {
725
- if (!props.businessKey || !props.type) return
726
-
727
- state.remarksLoading = true
728
- dataService.fetch(
729
- {
730
- businessKey: props.businessKey,
731
- processKey: props.type
732
- },
733
- {},
734
- '/process/remark/list'
735
- ).then(res => {
736
- // API 返回的数据结构是 { code, message, data },data 才是实际的备注数组
737
- state.remarks = res || []
738
- state.remarksLoading = false
739
- }).catch(err => {
740
- message.error('获取备注失败:' + (err.message || '未知错误'))
741
- state.remarksLoading = false
742
- state.remarks = []
743
- })
744
- }
745
-
746
- // 确认删除备注
747
- const confirmDeleteRemark = (remark) => {
748
- state.remarkToDelete = remark
749
- state.showDeleteConfirmDialog = true
750
- }
751
-
752
- // 取消删除备注
753
- const cancelDeleteRemark = () => {
754
- state.showDeleteConfirmDialog = false
755
- state.remarkToDelete = null
756
- }
757
-
758
- // 删除备注
759
- const deleteRemark = () => {
760
- if (!state.remarkToDelete || !state.remarkToDelete.id) {
761
- message.error('备注信息不完整,无法删除')
762
- state.showDeleteConfirmDialog = false
763
- return
764
- }
765
-
766
- state.deletingRemark = true
767
-
768
- dataService.fetch(
769
- {},
770
- {},
771
- '/process/remark/delete/' + state.remarkToDelete.id,
772
- 'POST'
773
- ).then(res => {
774
- message.success('删除备注成功')
775
- state.deletingRemark = false
776
- state.showDeleteConfirmDialog = false
777
- state.remarkToDelete = null
778
-
779
- // 重新获取备注列表
780
- fetchProcessRemarks()
781
- }).catch(err => {
782
- message.error('删除备注失败:' + (err.message || '未知错误'))
783
- state.deletingRemark = false
784
- })
785
- }
786
-
787
- // 显示添加备注对话框
788
- const showAddRemarkDialog = () => {
789
- state.newRemark = {
790
- content: '',
791
- attachments: []
792
- }
793
- state.showRemarkDialog = true
794
- }
795
-
796
- // 取消添加备注
797
- const cancelAddRemark = () => {
798
- state.showRemarkDialog = false
799
- }
800
-
801
- // 处理附件上传成功
802
- const handleUploadSuccess = (file, fileList) => {
803
- // 这里可以处理文件上传后的回调,如果需要的话
804
- console.log('文件上传成功', file, fileList)
805
- }
806
-
807
- // 提交备注
808
- const submitRemark = () => {
809
- if (!state.newRemark.content.trim()) {
810
- message.warning('请输入备注内容')
811
- return
812
- }
813
-
814
- state.submittingRemark = true
815
-
816
- // 准备附件数据
817
- const attachments = state.newRemark.attachments.map(file => ({
818
- url: file.url || file.response?.url,
819
- name: file.name,
820
- size: file.size,
821
- type: file.raw?.type || file.type
822
- }))
823
-
824
- // 提交备注请求
825
- dataService.fetch(
826
- {
827
- businessKey: props.businessKey,
828
- processKey: props.type,
829
- content: state.newRemark.content,
830
- attachments: attachments
831
- },
832
- {},
833
- '/process/remark/add',
834
- 'POST'
835
- ).then(res => {
836
- message.success('添加备注成功')
837
- state.submittingRemark = false
838
- state.showRemarkDialog = false
839
-
840
- // 重新获取备注列表
841
- fetchProcessRemarks()
842
- }).catch(err => {
843
- message.error('添加备注失败:' + (err.message || '未知错误'))
844
- state.submittingRemark = false
845
- })
846
- }
847
-
848
- // 组件挂载时请求数据
849
- onMounted(() => {
850
- if (props.businessKey && props.type) {
851
- requestApprovalDetail(String(props.businessKey), props.type)
852
- fetchProcessRemarks()
853
- }
854
- })
855
-
856
- // 暴露方法供外部调用
857
- defineExpose({
858
- requestApprovalDetail,
859
- fetchProcessRemarks,
860
- state
861
- })
862
- </script>
863
-
864
- <style scoped>
865
- .ebiz-approval-process {
866
- width: 100%;
867
- }
868
-
869
- .card {
870
- background-color: #ffffff;
871
- padding: 10px;
872
- }
873
-
874
- .title {
875
- font-size: 16px;
876
- font-weight: bold;
877
- color: #333;
878
- }
879
-
880
- .process-info {
881
- background-color: #fafafa;
882
- padding: 10px 12px;
883
- border-radius: 6px;
884
- margin-bottom: 12px;
885
- border: 1px solid #f0f0f0;
886
- }
887
-
888
- .info-item {
889
- display: flex;
890
- align-items: center;
891
- margin-bottom: 6px;
892
- }
893
-
894
- .info-item:last-child {
895
- margin-bottom: 0;
896
- }
897
-
898
- .label {
899
- font-weight: 500;
900
- color: #595959;
901
- margin-right: 8px;
902
- min-width: 70px;
903
- font-size: 13px;
904
- }
905
-
906
- .step-content {
907
- /* padding: 8px 0; */
908
- }
909
-
910
- .operation-info {
911
- margin-bottom: 8px;
912
- }
913
-
914
- .operation-tag {
915
- padding: 4px 8px;
916
- border-radius: 4px;
917
- font-size: 12px;
918
- font-weight: 500;
919
- display: inline-block;
920
- }
921
-
922
- .operation-tag.start {
923
- background-color: #f3e5f5;
924
- color: #7b1fa2;
925
- }
926
-
927
- .operation-tag.approved {
928
- background-color: #e8f5e8;
929
- color: #388e3c;
930
- }
931
-
932
- .operation-tag.rejected {
933
- background-color: #ffebee;
934
- color: #d32f2f;
935
- }
936
-
937
- .operation-tag.completed {
938
- background-color: #e3f2fd;
939
- color: #1976d2;
940
- }
941
-
942
- .operation-tag.claimed {
943
- background-color: #f3e5f5;
944
- color: #7b1fa2;
945
- }
946
-
947
- .operation-tag.assigned {
948
- background-color: #e0f2f1;
949
- color: #00695c;
950
- }
951
-
952
- .operation-tag.transferred {
953
- background-color: #fff8e1;
954
- color: #f57c00;
955
- }
956
-
957
- .operation-tag.add-sign {
958
- background-color: #fce4ec;
959
- color: #c2185b;
960
- }
961
-
962
- .operation-tag.pending {
963
- background-color: #fff3e0;
964
- color: #f57c00;
965
- }
966
-
967
- .operation-tag.future {
968
- background-color: #f5f5f5;
969
- color: #757575;
970
- }
971
-
972
- .operation-tag.cc-status {
973
- background-color: #fffbe6;
974
- color: #faad14;
975
- }
976
-
977
- .operation-tag.cc-pending {
978
- background-color: #f5f5f5;
979
- color: #999999;
980
- }
981
-
982
- .step-detail {
983
- display: flex;
984
- align-items: flex-start;
985
- margin-bottom: 4px;
986
- font-size: 14px;
987
- }
988
-
989
- .step-detail:last-child {
990
- margin-bottom: 0;
991
- }
992
-
993
- .current-step {
994
- background-color: #f8f9fa;
995
- padding: 12px;
996
- border-radius: 8px;
997
- border-left: 4px solid #1976d2;
998
- }
999
-
1000
- .future-step {
1001
- opacity: 0.7;
1002
- }
1003
-
1004
- .multi-instance-details {
1005
- margin-top: 8px;
1006
- }
1007
-
1008
- .instance-list {
1009
- margin-top: 12px;
1010
- }
1011
-
1012
- .instance-item {
1013
- background-color: #ffffff;
1014
- border: 1px solid #e0e0e0;
1015
- border-radius: 6px;
1016
- padding: 8px;
1017
- margin-bottom: 8px;
1018
- }
1019
-
1020
- .instance-item:last-child {
1021
- margin-bottom: 0;
1022
- }
1023
-
1024
- .instance-header {
1025
- display: flex;
1026
- justify-content: space-between;
1027
- align-items: center;
1028
- margin-bottom: 4px;
1029
- }
1030
-
1031
- .instance-status {
1032
- padding: 2px 6px;
1033
- border-radius: 3px;
1034
- font-size: 11px;
1035
- font-weight: 500;
1036
- }
1037
-
1038
- .instance-approved {
1039
- background-color: #e8f5e8;
1040
- color: #388e3c;
1041
- }
1042
-
1043
- .instance-rejected {
1044
- background-color: #ffebee;
1045
- color: #d32f2f;
1046
- }
1047
-
1048
- .instance-completed {
1049
- background-color: #e3f2fd;
1050
- color: #1976d2;
1051
- }
1052
-
1053
- .instance-pending {
1054
- background-color: #fff3e0;
1055
- color: #f57c00;
1056
- }
1057
-
1058
- .instance-comment {
1059
- font-size: 12px;
1060
- color: #666;
1061
- margin-bottom: 4px;
1062
- line-height: 1.4;
1063
- }
1064
-
1065
- .instance-time {
1066
- font-size: 11px;
1067
- color: #999;
1068
- }
1069
-
1070
- .multi-instance-badge {
1071
- background-color: #e3f2fd;
1072
- color: #1976d2;
1073
- padding: 2px 6px;
1074
- border-radius: 3px;
1075
- font-size: 11px;
1076
- font-weight: 500;
1077
- }
1078
-
1079
- .instance-section {
1080
- margin-bottom: 12px;
1081
- }
1082
-
1083
- .instance-section h5 {
1084
- font-size: 12px;
1085
- color: #666;
1086
- margin: 0 0 8px 0;
1087
- font-weight: 500;
1088
- }
1089
-
1090
- .instance-section:last-child {
1091
- margin-bottom: 0;
1092
- }
1093
-
1094
- /* 用户信息相关样式 */
1095
- .assignee-info {
1096
- display: flex;
1097
- align-items: center;
1098
- }
1099
-
1100
- .approver-info {
1101
- margin-bottom: 12px;
1102
- padding-bottom: 8px;
1103
- border-bottom: 1px solid #f0f0f0;
1104
- }
1105
-
1106
- .approver-info:last-child {
1107
- margin-bottom: 0;
1108
- border-bottom: none;
1109
- }
1110
-
1111
- .candidate-users {
1112
- display: flex;
1113
- flex-wrap: wrap;
1114
- gap: 8px;
1115
- align-items: center;
1116
- }
1117
-
1118
- .candidate-user {
1119
- background-color: #f8f9fa;
1120
- padding: 4px 8px;
1121
- border-radius: 12px;
1122
- border: 1px solid #e9ecef;
1123
- }
1124
-
1125
- /* 抄送人相关样式 */
1126
- .cc-users {
1127
- display: flex;
1128
- flex-wrap: wrap;
1129
- gap: 6px;
1130
- align-items: center;
1131
- }
1132
-
1133
- .cc-user {
1134
- background-color: #f6ffed;
1135
- padding: 3px 6px;
1136
- border-radius: 10px;
1137
- border: 1px solid #d9f7be;
1138
- font-size: 12px;
1139
- }
1140
-
1141
- .cc-users-list {
1142
- display: flex;
1143
- flex-direction: column;
1144
- gap: 8px;
1145
- }
1146
-
1147
- .cc-user-item {
1148
- background-color: #fffbe6;
1149
- padding: 8px;
1150
- border-radius: 8px;
1151
- border: 1px solid #ffd666;
1152
- }
1153
-
1154
- /* 简化审批步骤样式 */
1155
- .simple-approval-steps {
1156
- background-color: #ffffff;
1157
- padding: 12px 16px;
1158
- }
1159
-
1160
- .simple-step-item {
1161
- position: relative;
1162
- padding-bottom: 16px;
1163
- border-left: 2px solid #e8e8e8;
1164
- margin-left: 10px;
1165
- }
1166
-
1167
- .simple-step-item:last-child {
1168
- border-left: none;
1169
- padding-bottom: 0;
1170
- }
1171
-
1172
- .step-header {
1173
- display: flex;
1174
- align-items: center;
1175
- margin-bottom: 8px;
1176
- margin-left: -11px;
1177
- }
1178
-
1179
- .step-icon {
1180
- width: 20px;
1181
- height: 20px;
1182
- border-radius: 50%;
1183
- display: flex;
1184
- align-items: center;
1185
- justify-content: center;
1186
- margin-right: 10px;
1187
- flex-shrink: 0;
1188
- position: relative;
1189
- z-index: 1;
1190
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1191
- }
1192
-
1193
- .step-icon-completed {
1194
- background: linear-gradient(135deg, #52c41a, #73d13d);
1195
- }
1196
-
1197
- .step-icon-active {
1198
- background: linear-gradient(135deg, #1890ff, #40a9ff);
1199
- }
1200
-
1201
- .step-icon-waiting {
1202
- background: linear-gradient(135deg, #d9d9d9, #f0f0f0);
1203
- }
1204
-
1205
- .step-info {
1206
- flex: 1;
1207
- }
1208
-
1209
- .step-title-row {
1210
- display: flex;
1211
- align-items: center;
1212
- justify-content: flex-start;
1213
- width: 100%;
1214
- }
1215
-
1216
- .step-title {
1217
- font-size: 15px;
1218
- font-weight: 600;
1219
- color: #262626;
1220
- line-height: 1.3;
1221
- flex: 1;
1222
- margin-right: 8px;
1223
- }
1224
-
1225
- .step-status {
1226
- margin-left: 8px;
1227
- white-space: nowrap;
1228
- }
1229
-
1230
- .step-content {
1231
- margin-left: 19px;
1232
- padding-bottom: 4px;
1233
- }
1234
-
1235
- .simple-approver-wrapper {
1236
- padding: 6px 0;
1237
- margin: 6px 0;
1238
- border-bottom: 1px solid #f5f5f5;
1239
- }
1240
-
1241
- .simple-approver-wrapper:last-child {
1242
- border-bottom: none;
1243
- padding-bottom: 0;
1244
- }
1245
-
1246
- .simple-approver {
1247
- display: flex;
1248
- align-items: flex-start;
1249
- justify-content: space-between;
1250
- min-height: 40px;
1251
- }
1252
-
1253
- .approver-user-info {
1254
- flex: 1;
1255
- margin-right: 8px;
1256
- }
1257
-
1258
- .approver-action-info {
1259
- display: flex;
1260
- flex-direction: column;
1261
- align-items: flex-end;
1262
- white-space: nowrap;
1263
- }
1264
-
1265
- .action-type {
1266
- font-size: 12px;
1267
- font-weight: 500;
1268
- color: #1890ff;
1269
- margin-bottom: 2px;
1270
- }
1271
-
1272
- .target-user-info {
1273
- padding: 8px 12px;
1274
- background-color: #f8f9fa;
1275
- border-radius: 6px;
1276
- border-left: 3px solid #1890ff;
1277
- display: flex;
1278
- align-items: center;
1279
- gap: 8px;
1280
- }
1281
-
1282
- .target-label {
1283
- font-size: 12px;
1284
- color: #666;
1285
- font-weight: 500;
1286
- white-space: nowrap;
1287
- }
1288
-
1289
- .approver-time {
1290
- font-size: 11px;
1291
- color: #8c8c8c;
1292
- margin-left: 8px;
1293
- white-space: nowrap;
1294
- font-weight: 500;
1295
- }
1296
-
1297
- .approver-comment {
1298
- background-color: #fafafa;
1299
- padding: 8px 12px;
1300
- border-radius: 6px;
1301
- font-size: 12px;
1302
- color: #595959;
1303
- line-height: 1.4;
1304
- width: 100%;
1305
- border: 1px solid #f0f0f0;
1306
- box-sizing: border-box;
1307
- }
1308
-
1309
- /* 增加抄送人按钮样式 */
1310
- .add-cc-button-inline {
1311
- display: flex;
1312
- align-items: center;
1313
- gap: 4px;
1314
- font-size: 11px;
1315
- border-color: #d9d9d9;
1316
- color: #666;
1317
- height: 24px;
1318
- padding: 0 8px;
1319
- white-space: nowrap;
1320
- }
1321
-
1322
- .add-cc-button-inline:hover {
1323
- border-color: #1890ff;
1324
- color: #1890ff;
1325
- }
1326
-
1327
- /* 备注相关样式 */
1328
- .card-header {
1329
- display: flex;
1330
- justify-content: space-between;
1331
- align-items: center;
1332
- margin-bottom: 12px;
1333
- }
1334
-
1335
- .card-actions {
1336
- display: flex;
1337
- align-items: center;
1338
- }
1339
-
1340
- .remark-list {
1341
- padding: 0 4px;
1342
- }
1343
-
1344
- .remark-item {
1345
- padding: 12px 0;
1346
- border-bottom: 1px solid #f0f0f0;
1347
- }
1348
-
1349
- .remark-item:last-child {
1350
- border-bottom: none;
1351
- }
1352
-
1353
- .remark-header {
1354
- display: flex;
1355
- justify-content: space-between;
1356
- align-items: center;
1357
- margin-bottom: 8px;
1358
- }
1359
-
1360
- .remark-actions {
1361
- display: flex;
1362
- align-items: center;
1363
- gap: 8px;
1364
- }
1365
-
1366
- .remark-time {
1367
- font-size: 12px;
1368
- color: #8c8c8c;
1369
- }
1370
-
1371
- .delete-btn {
1372
- opacity: 0.6;
1373
- padding: 0 4px;
1374
- height: 24px;
1375
- min-width: 24px;
1376
- }
1377
-
1378
- .delete-btn:hover {
1379
- opacity: 1;
1380
- }
1381
-
1382
- .delete-confirm-content {
1383
- display: flex;
1384
- align-items: center;
1385
- padding: 16px 0;
1386
- }
1387
-
1388
- .remark-content {
1389
- margin-bottom: 10px;
1390
- font-size: 14px;
1391
- line-height: 1.6;
1392
- color: #333;
1393
- word-break: break-word;
1394
- }
1395
-
1396
- .remark-attachments {
1397
- background-color: #f9f9f9;
1398
- padding: 8px;
1399
- border-radius: 4px;
1400
- }
1401
-
1402
- .no-remarks {
1403
- padding: 24px 0;
1404
- text-align: center;
1405
- }
1406
-
1407
- .no-remarks-tip {
1408
- color: #999;
1409
- font-size: 14px;
1410
- }
1411
-
1412
- .remark-form {
1413
- padding: 10px 0;
1414
- }
1415
-
1416
- .remark-form-footer {
1417
- margin-top: 16px;
1418
- display: flex;
1419
- justify-content: space-between;
1420
- align-items: center;
1421
- }
1422
-
1423
- .remark-form-actions {
1424
- display: flex;
1425
- gap: 8px;
1426
- }
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 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">
17
+ <span class="label">发起时间:</span>
18
+ <span>{{ state.processInfo.startTimeStr || '--' }}</span>
19
+ </div>
20
+ <div class="info-item">
21
+ <span class="label">流程状态:</span>
22
+ <t-tag :theme="getProcessStatusTheme(state.processInfo.processStatus)" size="small">
23
+ {{ getProcessStatusText(state.processInfo.processStatus) }}
24
+ </t-tag>
25
+ </div>
26
+ 抄送人信息
27
+ </div> -->
28
+
29
+ <!-- 审批步骤 -->
30
+ <div class="simple-approval-steps">
31
+ <!-- 节点步骤 -->
32
+ <div v-for="(node, index) in state.nodes" :key="'node-' + index" class="simple-step-item">
33
+
34
+ <div
35
+ v-if="node.nodeStatus == 'PENDING' && getPendingApprovers(node).length === 0 && state.processInfo.processStatus != 'ACTIVE'">
36
+ </div>
37
+ <div v-else>
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
+ <div>
45
+ <span class="step-title">{{ node.nodeName }}</span>
46
+ <span class="node-type-badge" :class="{ 'parallel': isParallelNode(node) }">
47
+ {{ node.nodeTypeDesc || node.multiInstanceTypeDesc }}
48
+ </span>
49
+ </div>
50
+ <t-tag v-if="node.nodeStatus != 'PENDING'" :theme="getNodeStatusTheme(node)" size="small">
51
+ {{ getNodeStatusText(node) }}
52
+ </t-tag>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- 简化的审批人信息 -->
58
+ <div class="step-content">
59
+ <!-- 已完成节点 -->
60
+ <div v-for="approver in getCompletedApprovers(node)"
61
+ :key="approver.nodeUser?.userId || approver.userId" class="simple-approver-wrapper">
62
+ <div class="simple-approver">
63
+ <!-- 操作用户信息 -->
64
+ <div class="approver-user-info">
65
+ <user-info :userId="approver.nodeUser?.userId || approver.userId"
66
+ :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
67
+ :show-department="false" />
68
+ </div>
69
+
70
+ <!-- 操作类型和时间 -->
71
+ <div class="approver-action-info">
72
+ <div class="action-type">{{ getOperationTypeText(approver) }}</div>
73
+ <div class="approver-time">{{ formatTime(approver.operation?.operationTime
74
+ ||
75
+ approver.completedTime || approver.endTime) }}</div>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- 目标用户信息(转审、加签等操作) -->
80
+ <div v-if="approver.targetUsers && needShowTargetUser(approver)"
81
+ class="target-user-info">
82
+ <div class="target-label">{{ getTargetUserLabel(approver) }}</div>
83
+ <div style="display: flex; flex-direction: column; gap: 4px;">
84
+ <div v-for="targetUser in approver.targetUsers" :key="targetUser.userId">
85
+ <user-info :userId="targetUser.userId"
86
+ :userInfo="getTargetUserInfo(targetUser)" avatar-size="small"
87
+ name-size="small" :show-department="false" />
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+
93
+ <!-- 评论信息 -->
94
+ <div v-if="approver.comment || (approver.operation.attachmentsList && approver.operation.attachmentsList.length > 0)"
95
+ class="approver-comment">
96
+ <div v-if="approver.comment">{{ approver.comment }}</div>
97
+ <div
98
+ v-if="approver.operation.attachmentsList && approver.operation.attachmentsList.length > 0">
99
+ <ebiz-file-list size="mini" :files="approver.operation.attachmentsList" />
100
+ </div>
101
+ </div>
102
+ <!-- <div v-if="approver.comment" class="approver-comment">{{ approver.comment }}</div> -->
103
+ </div>
104
+
105
+ <!-- 进行中节点 -->
106
+ <!-- 当前处理人 -->
107
+ <div v-for="approver in getCurrentApprovers(node)"
108
+ :key="'pending-' + (approver.nodeUser?.userId || approver.userId)"
109
+ class="simple-approver">
110
+ <user-info :userId="approver.nodeUser?.userId || approver.userId"
111
+ :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
112
+ :show-department="false" />
113
+
114
+ <div v-if="isParallelNode(node) && getCurrentApprovers(node).length > 1" class="approver-time">审批中</div>
115
+ </div>
116
+
117
+ <!-- 待处理人 -->
118
+ <div v-for="approver in getPendingApprovers(node)"
119
+ :key="'pending-' + (approver.nodeUser?.userId || approver.userId)"
120
+ class="simple-approver">
121
+ <user-info :userId="approver.nodeUser?.userId || approver.userId"
122
+ :userInfo="getUserInfo(approver)" avatar-size="small" name-size="small"
123
+ :show-department="false" />
124
+ <!-- <div class="approver-time">待处理</div> -->
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <!-- 抄送人步骤 -->
131
+ <div v-if="state.processInfo" class="simple-step-item">
132
+ <div class="step-header">
133
+ <div class="step-icon" :class="getCcSimpleIconClass()">
134
+ <t-icon name="mail" size="12px" color="white" />
135
+ </div>
136
+ <div class="step-info">
137
+ <div class="step-title-row">
138
+ <span class="step-title">抄送</span>
139
+ <!-- 增加抄送人按钮 -->
140
+ <div v-if="state.processInfo.processStatus === 'ACTIVE'">
141
+ <ebiz-employee-selector content="增加抄送人" :single="false"
142
+ defaultTab="organization" class="component-base-style"
143
+ @click="handleAddCcUser" @confirm="onAddCcListConfirm">
144
+ <template #selected-items="{ event, data }">
145
+ <div></div>
146
+ </template>
147
+ </ebiz-employee-selector>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div class="step-content">
154
+ <div v-for="ccUser in state.processInfo.ccUserList"
155
+ :key="ccUser.nodeUser?.userId || ccUser.userId" class="simple-approver">
156
+ <user-info :userId="ccUser.nodeUser?.userId || ccUser.userId"
157
+ :userInfo="ccUser.nodeUser || ccUser" avatar-size="small" name-size="small"
158
+ :show-department="false" />
159
+ <div class="approver-time">{{ getCcTimeText() }}</div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <!-- 备注部分 -->
167
+ <div class="block-base-style">
168
+ <div class="card">
169
+ <div class="card-header">
170
+ <span class="title">备注 <span v-if="state.remarks.length > 0">({{ state.remarks.length
171
+ }})</span></span>
172
+ <div class="card-actions">
173
+ <t-button variant="text" @click="showAddRemarkDialog">添加备注</t-button>
174
+ </div>
175
+ </div>
176
+
177
+ <!-- 备注列表 -->
178
+ <div class="remark-list" v-if="state.remarks.length > 0">
179
+ <div v-for="remark in state.remarks" :key="remark.id" class="remark-item">
180
+ <div class="remark-header">
181
+ <user-info :userId="remark.userId" :userInfo="remark.userDetail" avatar-size="small"
182
+ name-size="small" :show-job-number="true" :show-department="false" />
183
+ <div class="remark-actions">
184
+ <div class="remark-time">{{ remark.createdTimeStr }}</div>
185
+ <t-button v-if="canDeleteRemark(remark)" variant="text" theme="danger" size="small"
186
+ class="delete-btn" @click="confirmDeleteRemark(remark)">
187
+ <t-icon name="delete" />
188
+ </t-button>
189
+ </div>
190
+ </div>
191
+ <div class="remark-content">{{ remark.content }}</div>
192
+ <div class="remark-attachments" v-if="remark.attachments && remark.attachments.length > 0">
193
+ <ebiz-file-list size="mini" :files="remark.attachments" />
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <!-- 无备注时的提示 -->
199
+ <div class="no-remarks" v-else>
200
+ <div class="no-remarks-tip">暂无备注信息</div>
201
+ </div>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- 添加备注对话框 -->
206
+ <t-dialog v-model:visible="state.showRemarkDialog" header="添加备注" :width="500" :footer="false" attach="body"
207
+ :close-on-overlay-click="false" :close-btn="true">
208
+ <div class="remark-form">
209
+ <t-textarea v-model="state.newRemark.content" placeholder="请输入备注内容" :maxlength="500"
210
+ :autosize="{ minRows: 3, maxRows: 5 }" />
211
+
212
+ <div>
213
+ <ebiz-upload v-model="state.newRemark.attachments" :multiple="true" theme="file">
214
+ <template #trigger>
215
+ <t-button variant="outline">
216
+ <template #icon>
217
+ <t-icon name="attach" />
218
+ </template>
219
+ 上传附件
220
+ </t-button>
221
+ </template>
222
+ </ebiz-upload>
223
+ </div>
224
+ <div class="remark-form-footer">
225
+ <div></div>
226
+ <div class="remark-form-actions">
227
+ <t-button theme="default" @click="cancelAddRemark">取消</t-button>
228
+ <t-button theme="primary" :loading="state.submittingRemark"
229
+ @click="submitRemark">提交</t-button>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </t-dialog>
234
+
235
+ <!-- 删除确认对话框 -->
236
+ <t-dialog v-model:visible="state.showDeleteConfirmDialog" header="删除确认" :width="400"
237
+ :confirm-btn="{ content: '确认', theme: 'danger', loading: state.deletingRemark }"
238
+ :cancel-btn="{ content: '取消', theme: 'default' }" @confirm="deleteRemark" @cancel="cancelDeleteRemark">
239
+ <div class="delete-confirm-content">
240
+ <t-icon name="error-circle-filled" size="large" style="color: #E34D59; margin-right: 8px;" />
241
+ <span>确定要删除此备注吗?此操作不可恢复。</span>
242
+ </div>
243
+ </t-dialog>
244
+ </div>
245
+ </div>
246
+ </template>
247
+
248
+ <script>
249
+ /**
250
+ * @displayName 审批流程
251
+ * @description 审批流程组件,用于展示审批流程的详细信息
252
+ * @category 业务组件
253
+ * @name EbizApprovalProcess
254
+ */
255
+ export default {
256
+ name: "EbizApprovalProcess",
257
+ components: {
258
+ 't-tag': Tag,
259
+ 't-icon': Icon,
260
+ 't-button': Button,
261
+ 't-dialog': Dialog,
262
+ 't-textarea': Textarea,
263
+ 't-upload': Upload
264
+ }
265
+ }
266
+ </script>
267
+
268
+ <script setup>
269
+ import { reactive, onMounted, ref } from 'vue'
270
+ import { defineProps, defineEmits } from 'vue'
271
+ import { Tag, Icon, Button, Dialog, Textarea, Upload, MessagePlugin as message } from 'tdesign-vue-next'
272
+ import EbizEmployeeSelector from './EbizEmployeeSelector.vue'
273
+ import EbizTdesignLoading from './EbizTdesignLoading.vue'
274
+ import dataService from '../apiService/simpleDataService.js'
275
+ import UserInfo from './mItems/UserInfo.vue'
276
+ import EbizFileList from './EbizFileList.vue'
277
+ import { EbizUpload } from '../index.js'
278
+
279
+ // 定义 props
280
+ const props = defineProps({
281
+ /**
282
+ * 业务键
283
+ */
284
+ businessKey: {
285
+ type: String,
286
+ default: ''
287
+ },
288
+ /**
289
+ * 业务类型
290
+ */
291
+ type: {
292
+ type: String,
293
+ default: ''
294
+ }
295
+ })
296
+
297
+ // 定义 emits
298
+ const emit = defineEmits(['load-success', 'add-cc-user'])
299
+
300
+ // 响应式状态
301
+ const state = reactive({
302
+ loading: false,
303
+ processInfo: null,
304
+ nodes: [], // 直接保存节点数据
305
+ historyRecords: [],
306
+ currentNodes: [],
307
+ futureNodes: [],
308
+ approvalDetail: {},
309
+ selectedCCList: [],
310
+ showCcListSelector: false,
311
+
312
+ // 备注相关
313
+ remarks: [],
314
+ remarksLoading: false,
315
+ showRemarkDialog: false,
316
+ submittingRemark: false,
317
+ newRemark: {
318
+ content: '',
319
+ attachments: []
320
+ },
321
+ showDeleteConfirmDialog: false,
322
+ deletingRemark: false,
323
+ remarkToDelete: null
324
+ })
325
+
326
+ // 处理审批详情响应
327
+ const handleApprovalDetailResponse = (data) => {
328
+ // 构建流程信息
329
+ state.processInfo = {
330
+ processInstanceId: data.processInstanceId,
331
+ businessKey: data.businessKey,
332
+ processDefinitionKey: data.processDefinitionKey,
333
+ processDefinitionName: data.processDefinitionName,
334
+ startTime: data.startTime,
335
+ startTimeStr: data.startTime, // 使用startTime作为显示时间
336
+ endTime: data.endTime,
337
+ startUserId: data.startUserId,
338
+ startUserName: data.startUserName,
339
+ startUserInfo: data.startUserInfo,
340
+ processStatus: data.processStatus,
341
+ processStatusDesc: data.processStatusDesc,
342
+ ccUserList: data.ccUserList || []
343
+ }
344
+
345
+ // 直接保存节点数据,不进行拆分
346
+ state.nodes = data.nodes || []
347
+
348
+ // 清空原有的分类数据
349
+ state.historyRecords = []
350
+ state.currentNodes = []
351
+ state.futureNodes = []
352
+
353
+ state.approvalDetail = data
354
+ state.loading = false
355
+
356
+ emit('load-success', data)
357
+ }
358
+
359
+ // 判断是否可以删除备注(只能删除自己的备注)
360
+ const canDeleteRemark = (remark) => {
361
+ if (!state.currentUser) {
362
+ try {
363
+ const userStr = localStorage.getItem('user')
364
+ if (userStr) {
365
+ state.currentUser = JSON.parse(userStr)
366
+ }
367
+ } catch (e) {
368
+ console.error('解析用户信息失败', e)
369
+ return false
370
+ }
371
+ }
372
+
373
+ // 如果没有获取到当前用户信息,则不允许删除
374
+ if (!state.currentUser || !state.currentUser.userId) {
375
+ return false
376
+ }
377
+
378
+ // 判断备注的用户ID是否与当前用户ID一致
379
+ const remarkUserId = remark.userId || remark.userDetail?.userId
380
+ return remarkUserId && String(remarkUserId) === String(state.currentUser.userId)
381
+ }
382
+
383
+ // 处理审批详情错误
384
+ const handleApprovalDetailError = (err) => {
385
+ state.loading = false
386
+ message.error(err.message || '获取审批信息失败')
387
+ }
388
+
389
+ // 请求审批详情
390
+ const requestApprovalDetail = (businessKey, type) => {
391
+ state.loading = true
392
+
393
+ const requestData = {
394
+ businessKey: businessKey,
395
+ businessType: type
396
+ }
397
+
398
+ // 使用新的接口获取审批详情
399
+ dataService.fetch(
400
+ requestData,
401
+ {},
402
+ '/tasks/approval-detail'
403
+ ).then((res) => {
404
+ console.log("approval-detail res", res)
405
+ handleApprovalDetailResponse(res)
406
+ }).catch((err) => {
407
+ handleApprovalDetailError(err)
408
+ })
409
+ }
410
+
411
+ // 获取操作类型的样式类
412
+ const getOperationClass = (operationType, operationResult) => {
413
+ const typeMap = {
414
+ 'START': 'start',
415
+ 'COMPLETE': operationResult === '通过' ? 'approved' : operationResult === '拒绝' ? 'rejected' : 'completed',
416
+ 'CLAIM': 'claimed',
417
+ 'ASSIGN': 'assigned',
418
+ 'TRANSFER': 'transferred',
419
+ 'REJECT': 'rejected',
420
+ 'ADD_SIGN': 'add-sign'
421
+ }
422
+ return typeMap[operationType] || 'default'
423
+ }
424
+
425
+ // 获取操作类型的文本
426
+ const getOperationTypeText = (approver) => {
427
+ if (!approver.operation) {
428
+ return getSimpleStatusText(null, approver)
429
+ }
430
+
431
+ const operationType = approver.operation.operationType
432
+ switch (operationType) {
433
+ case 'COMPLETE':
434
+ // 从operationData中获取审批结果
435
+ try {
436
+ const operationData = JSON.parse(approver.operation.operationData || '{}')
437
+ if (operationData.approved === true) {
438
+ return '已同意'
439
+ } else if (operationData.approved === false) {
440
+ return '已拒绝'
441
+ }
442
+ } catch (e) {
443
+ // 解析失败,使用默认逻辑
444
+ }
445
+ return '已完成'
446
+ case 'TRANSFER':
447
+ return '转审'
448
+ case 'ADD_SIGN':
449
+ const operationData = JSON.parse(approver.operation.operationData || '{}')
450
+ if (operationData.position == 'before') {
451
+ return "向前加签";
452
+ }
453
+ return '已同意并加签'
454
+ // return '加签'
455
+ case 'REJECT':
456
+ return '驳回'
457
+ case 'RETURN':
458
+ return '退回'
459
+ case 'CLAIM':
460
+ return '认领'
461
+ case 'ASSIGN':
462
+ return '分配'
463
+ default:
464
+ return '已处理'
465
+ }
466
+ }
467
+
468
+ // 获取流程状态主题
469
+ const getProcessStatusTheme = (status) => {
470
+ const statusMap = {
471
+ 'CANCELED': 'default',
472
+ 'REJECTED': 'danger',
473
+ 'APPROVED': "success",
474
+ 'ACTIVE': 'primary',
475
+ 'COMPLETED': 'success',
476
+ 'SUSPENDED': 'warning',
477
+ 'REVOKE': 'default'
478
+ }
479
+ return statusMap[status] || 'default'
480
+ }
481
+
482
+ // 获取流程状态文本
483
+ const getProcessStatusText = (status) => {
484
+ const statusMap = {
485
+ 'CANCELED': '已取消',
486
+ 'REJECTED': '已驳回',
487
+ 'APPROVED': "已通过",
488
+ 'ACTIVE': '进行中',
489
+ 'COMPLETED': '已完成',
490
+ 'SUSPENDED': '已暂停',
491
+ 'REVOKE': '已撤销'
492
+ }
493
+ return statusMap[status] || status
494
+ }
495
+
496
+ // 获取节点状态主题
497
+ const getNodeStatusTheme = (node) => {
498
+ switch (node.nodeStatus) {
499
+ case 'COMPLETED':
500
+ return 'success'
501
+ case 'ACTIVE':
502
+ return 'primary'
503
+ default:
504
+ return 'default'
505
+ }
506
+ }
507
+
508
+ // 获取节点状态文本
509
+ const getNodeStatusText = (node) => {
510
+ switch (node.nodeStatus) {
511
+ case 'COMPLETED':
512
+ return '已完成'
513
+ case 'ACTIVE':
514
+ return '进行中'
515
+ default:
516
+ return '未开始'
517
+ }
518
+ }
519
+
520
+ // 获取已完成的审批人
521
+ const getCompletedApprovers = (node) => {
522
+ if (!node.approvers) return []
523
+ return node.approvers.filter(approver => approver.approvalStatus === 'COMPLETED')
524
+ }
525
+
526
+ // 获取当前审批人
527
+ const getCurrentApprover = (node) => {
528
+ if (!node.approvers) return null
529
+ return node.approvers.find(approver => approver.approvalStatus === 'ACTIVE' && approver.isCurrent)
530
+ }
531
+
532
+ // 获取当前审批人
533
+ const getCurrentApprovers = (node) => {
534
+ if (!node.approvers) return []
535
+ return node.approvers.filter(approver => approver.approvalStatus === 'ACTIVE' && approver.isCurrent)
536
+ }
537
+
538
+ // 检查节点是否为会签节点
539
+ const isParallelNode = (node) => {
540
+ return node.nodeType === 'PARALLEL'
541
+ }
542
+
543
+ // 获取待处理审批人
544
+ const getPendingApprovers = (node) => {
545
+ if (!node.approvers) return []
546
+ return node.approvers.filter(approver => approver.approvalStatus === 'PENDING')
547
+ }
548
+
549
+ // 获取用户信息
550
+ const getUserInfo = (approver) => {
551
+ // 新数据结构:用户信息在nodeUser对象中
552
+ const userInfo = approver.nodeUser || approver
553
+ return {
554
+ userId: userInfo.userId,
555
+ name: userInfo.userName,
556
+ employeeNo: userInfo.employeeNo,
557
+ photo: userInfo.photo,
558
+ departmentName: userInfo.deptName,
559
+ position: userInfo.position,
560
+ phone: userInfo.phone,
561
+ email: userInfo.email
562
+ }
563
+ }
564
+
565
+ // 判断流程是否已完成
566
+ const isProcessCompleted = () => {
567
+ return state.processInfo && state.processInfo.processStatus === 'COMPLETED'
568
+ }
569
+
570
+ // 获取简单步骤图标类
571
+ const getSimpleStepIconClass = (node) => {
572
+ switch (node.nodeStatus) {
573
+ case 'COMPLETED':
574
+ return 'step-icon-completed'
575
+ case 'ACTIVE':
576
+ return 'step-icon-active'
577
+ default:
578
+ return 'step-icon-waiting'
579
+ }
580
+ }
581
+
582
+ // 获取抄送简单图标类
583
+ const getCcSimpleIconClass = () => {
584
+ return isProcessCompleted() ? 'step-icon-completed' : 'step-icon-waiting'
585
+ }
586
+
587
+ // 获取简单状态文本
588
+ const getSimpleStatusText = (node, approver) => {
589
+ // 优先使用approvalResult
590
+ if (approver.approvalResult) {
591
+ return approver.approvalResult === '通过' ? '已同意' : approver.approvalResult === '拒绝' ? '已拒绝' : '已完成'
592
+ }
593
+ // 如果没有approvalResult,使用approved字段
594
+ if (approver.approved !== null && approver.approved !== undefined) {
595
+ return approver.approved ? '已同意' : '已拒绝'
596
+ }
597
+ // 默认返回已完成
598
+ return '已完成'
599
+ }
600
+
601
+ // 格式化时间
602
+ const formatTime = (timeStr) => {
603
+ if (!timeStr) return '--'
604
+
605
+ let date
606
+ // 如果是数字(时间戳),直接使用
607
+ if (typeof timeStr === 'number') {
608
+ date = new Date(timeStr)
609
+ } else {
610
+ // 如果是字符串,尝试解析
611
+ date = new Date(timeStr)
612
+ }
613
+
614
+ if (isNaN(date.getTime())) return timeStr
615
+
616
+ const month = date.getMonth() + 1
617
+ const day = date.getDate()
618
+ const hours = date.getHours().toString().padStart(2, '0')
619
+ const minutes = date.getMinutes().toString().padStart(2, '0')
620
+
621
+ return `${month}/${day} ${hours}:${minutes}`
622
+ }
623
+
624
+ // 获取抄送时间文本
625
+ const getCcTimeText = () => {
626
+ if (isProcessCompleted()) {
627
+ return `已抄送 · ${formatTime(state.processInfo.endTime || state.processInfo.startTimeStr)}`
628
+ }
629
+ return ''
630
+ }
631
+
632
+ // 获取发起人信息
633
+ const getStartUserInfo = () => {
634
+ // 优先从历史记录中查找发起人
635
+ const startRecord = state.historyRecords.find(record => record.operationType === 'START')
636
+ if (startRecord) {
637
+ return {
638
+ userId: startRecord.operatorId,
639
+ userInfo: startRecord.assigneeInfo
640
+ }
641
+ }
642
+
643
+ // 如果没有发起记录,从流程信息中获取
644
+ if (state.processInfo && state.processInfo.startUserId) {
645
+ return {
646
+ userId: state.processInfo.startUserId,
647
+ userInfo: state.processInfo.startUserInfo || {
648
+ name: state.processInfo.startUserName || '未知用户'
649
+ }
650
+ }
651
+ }
652
+
653
+ // 如果都没有,尝试从第一个已完成节点的审批人获取(可能是发起人)
654
+ if (state.historyRecords.length > 0) {
655
+ const firstRecord = state.historyRecords[0]
656
+ return {
657
+ userId: firstRecord.operatorId,
658
+ userInfo: firstRecord.assigneeInfo
659
+ }
660
+ }
661
+
662
+ return { userId: '', userInfo: {} }
663
+ }
664
+
665
+ // 处理增加抄送人
666
+ const handleAddCcUser = () => {
667
+ console.log("handleAddCcUser")
668
+ state.showCcListSelector = true
669
+ emit('add-cc-user', {
670
+ processInstanceId: state.processInfo?.processInstanceId,
671
+ businessKey: state.processInfo?.businessKey,
672
+ processDefinitionKey: state.processInfo?.processDefinitionKey
673
+ })
674
+ }
675
+
676
+ function onAddCcListConfirm(event) {
677
+ let selectedCCList = event.map(item => Number(item))
678
+ if (selectedCCList?.length === 0) return
679
+ state.showCcListSelector = false
680
+ dataService.fetch(
681
+ {
682
+ businessKey: state.processInfo?.businessKey,
683
+ type: state.processInfo?.processDefinitionKey,
684
+ processInstanceId: state.processInfo?.processInstanceId,
685
+ ccUsers: selectedCCList
686
+ },
687
+ {},
688
+ '/tasks/batchAddCcInfo'
689
+ )
690
+ .then((res) => {
691
+ // 重新加载数据
692
+ requestApprovalDetail(String(props.businessKey), props.type)
693
+ message.success("添加成功")
694
+ })
695
+ .catch((err) => {
696
+ message.error(err.message)
697
+ })
698
+ }
699
+
700
+ // 判断是否需要显示目标用户
701
+ const needShowTargetUser = (approver) => {
702
+ if (!approver.operation || !approver.targetUsers) return false
703
+
704
+ return true;
705
+ // const operationType = approver.operation.operationType
706
+ // return ['TRANSFER', 'ADD_SIGN', 'RETURN'].includes(operationType)
707
+ }
708
+
709
+ // 获取目标用户标签
710
+ const getTargetUserLabel = (approver) => {
711
+ if (!approver.operation) return ''
712
+
713
+ const operationType = approver.operation.operationType
714
+ switch (operationType) {
715
+ case 'TRANSFER':
716
+ return '转审给:'
717
+ case 'ADD_SIGN':
718
+ return '加签:'
719
+ case 'RETURN':
720
+ return '退回给:'
721
+ default:
722
+ return '目标:'
723
+ }
724
+ }
725
+
726
+ // 获取目标用户信息
727
+ const getTargetUserInfo = (targetUser) => {
728
+ // const targetUser = approver.targetUser
729
+ return {
730
+ userId: targetUser.userId,
731
+ name: targetUser.userName,
732
+ employeeNo: targetUser.employeeNo,
733
+ photo: targetUser.photo,
734
+ departmentName: targetUser.deptName,
735
+ position: targetUser.position,
736
+ phone: targetUser.phone,
737
+ email: targetUser.email
738
+ }
739
+ }
740
+
741
+ // 获取流程备注列表
742
+ const fetchProcessRemarks = () => {
743
+ if (!props.businessKey || !props.type) return
744
+
745
+ state.remarksLoading = true
746
+ dataService.fetch(
747
+ {
748
+ businessKey: props.businessKey,
749
+ processKey: props.type
750
+ },
751
+ {},
752
+ '/process/remark/list'
753
+ ).then(res => {
754
+ // API 返回的数据结构是 { code, message, data },data 才是实际的备注数组
755
+ state.remarks = res || []
756
+ state.remarksLoading = false
757
+ }).catch(err => {
758
+ message.error('获取备注失败:' + (err.message || '未知错误'))
759
+ state.remarksLoading = false
760
+ state.remarks = []
761
+ })
762
+ }
763
+
764
+ // 确认删除备注
765
+ const confirmDeleteRemark = (remark) => {
766
+ state.remarkToDelete = remark
767
+ state.showDeleteConfirmDialog = true
768
+ }
769
+
770
+ // 取消删除备注
771
+ const cancelDeleteRemark = () => {
772
+ state.showDeleteConfirmDialog = false
773
+ state.remarkToDelete = null
774
+ }
775
+
776
+ // 删除备注
777
+ const deleteRemark = () => {
778
+ if (!state.remarkToDelete || !state.remarkToDelete.id) {
779
+ message.error('备注信息不完整,无法删除')
780
+ state.showDeleteConfirmDialog = false
781
+ return
782
+ }
783
+
784
+ state.deletingRemark = true
785
+
786
+ dataService.fetch(
787
+ {},
788
+ {},
789
+ '/process/remark/delete/' + state.remarkToDelete.id,
790
+ 'POST'
791
+ ).then(res => {
792
+ message.success('删除备注成功')
793
+ state.deletingRemark = false
794
+ state.showDeleteConfirmDialog = false
795
+ state.remarkToDelete = null
796
+
797
+ // 重新获取备注列表
798
+ fetchProcessRemarks()
799
+ }).catch(err => {
800
+ message.error('删除备注失败:' + (err.message || '未知错误'))
801
+ state.deletingRemark = false
802
+ })
803
+ }
804
+
805
+ // 显示添加备注对话框
806
+ const showAddRemarkDialog = () => {
807
+ state.newRemark = {
808
+ content: '',
809
+ attachments: []
810
+ }
811
+ state.showRemarkDialog = true
812
+ }
813
+
814
+ // 取消添加备注
815
+ const cancelAddRemark = () => {
816
+ state.showRemarkDialog = false
817
+ }
818
+
819
+ // 处理附件上传成功
820
+ const handleUploadSuccess = (file, fileList) => {
821
+ // 这里可以处理文件上传后的回调,如果需要的话
822
+ console.log('文件上传成功', file, fileList)
823
+ }
824
+
825
+ // 提交备注
826
+ const submitRemark = () => {
827
+ if (!state.newRemark.content.trim()) {
828
+ message.warning('请输入备注内容')
829
+ return
830
+ }
831
+
832
+ state.submittingRemark = true
833
+
834
+ // 准备附件数据
835
+ const attachments = state.newRemark.attachments.map(file => ({
836
+ url: file.url || file.response?.url,
837
+ name: file.name,
838
+ size: file.size,
839
+ type: file.raw?.type || file.type
840
+ }))
841
+
842
+ // 提交备注请求
843
+ dataService.fetch(
844
+ {
845
+ businessKey: props.businessKey,
846
+ processKey: props.type,
847
+ content: state.newRemark.content,
848
+ attachments: attachments
849
+ },
850
+ {},
851
+ '/process/remark/add',
852
+ 'POST'
853
+ ).then(res => {
854
+ message.success('添加备注成功')
855
+ state.submittingRemark = false
856
+ state.showRemarkDialog = false
857
+
858
+ // 重新获取备注列表
859
+ fetchProcessRemarks()
860
+ }).catch(err => {
861
+ message.error('添加备注失败:' + (err.message || '未知错误'))
862
+ state.submittingRemark = false
863
+ })
864
+ }
865
+
866
+ // 组件挂载时请求数据
867
+ onMounted(() => {
868
+ if (props.businessKey && props.type) {
869
+ requestApprovalDetail(String(props.businessKey), props.type)
870
+ fetchProcessRemarks()
871
+ }
872
+ })
873
+
874
+ // 暴露方法供外部调用
875
+ defineExpose({
876
+ requestApprovalDetail,
877
+ fetchProcessRemarks,
878
+ state
879
+ })
880
+ </script>
881
+
882
+ <style scoped>
883
+ .ebiz-approval-process {
884
+ width: 100%;
885
+ }
886
+
887
+ .card {
888
+ background-color: #ffffff;
889
+ padding: 10px;
890
+ }
891
+
892
+ .title {
893
+ font-size: 16px;
894
+ font-weight: bold;
895
+ color: #333;
896
+ }
897
+
898
+ .process-info {
899
+ background-color: #fafafa;
900
+ padding: 10px 12px;
901
+ border-radius: 6px;
902
+ margin-bottom: 12px;
903
+ border: 1px solid #f0f0f0;
904
+ }
905
+
906
+ .info-item {
907
+ display: flex;
908
+ align-items: center;
909
+ margin-bottom: 6px;
910
+ }
911
+
912
+ .info-item:last-child {
913
+ margin-bottom: 0;
914
+ }
915
+
916
+ .label {
917
+ font-weight: 500;
918
+ color: #595959;
919
+ margin-right: 8px;
920
+ min-width: 70px;
921
+ font-size: 13px;
922
+ }
923
+
924
+ .step-content {
925
+ /* padding: 8px 0; */
926
+ }
927
+
928
+ .operation-info {
929
+ margin-bottom: 8px;
930
+ }
931
+
932
+ .operation-tag {
933
+ padding: 4px 8px;
934
+ border-radius: 4px;
935
+ font-size: 12px;
936
+ font-weight: 500;
937
+ display: inline-block;
938
+ }
939
+
940
+ .operation-tag.start {
941
+ background-color: #f3e5f5;
942
+ color: #7b1fa2;
943
+ }
944
+
945
+ .operation-tag.approved {
946
+ background-color: #e8f5e8;
947
+ color: #388e3c;
948
+ }
949
+
950
+ .operation-tag.rejected {
951
+ background-color: #ffebee;
952
+ color: #d32f2f;
953
+ }
954
+
955
+ .operation-tag.completed {
956
+ background-color: #e3f2fd;
957
+ color: #1976d2;
958
+ }
959
+
960
+ .operation-tag.claimed {
961
+ background-color: #f3e5f5;
962
+ color: #7b1fa2;
963
+ }
964
+
965
+ .operation-tag.assigned {
966
+ background-color: #e0f2f1;
967
+ color: #00695c;
968
+ }
969
+
970
+ .operation-tag.transferred {
971
+ background-color: #fff8e1;
972
+ color: #f57c00;
973
+ }
974
+
975
+ .operation-tag.add-sign {
976
+ background-color: #fce4ec;
977
+ color: #c2185b;
978
+ }
979
+
980
+ .operation-tag.pending {
981
+ background-color: #fff3e0;
982
+ color: #f57c00;
983
+ }
984
+
985
+ .operation-tag.future {
986
+ background-color: #f5f5f5;
987
+ color: #757575;
988
+ }
989
+
990
+ .operation-tag.cc-status {
991
+ background-color: #fffbe6;
992
+ color: #faad14;
993
+ }
994
+
995
+ .operation-tag.cc-pending {
996
+ background-color: #f5f5f5;
997
+ color: #999999;
998
+ }
999
+
1000
+ .step-detail {
1001
+ display: flex;
1002
+ align-items: flex-start;
1003
+ margin-bottom: 4px;
1004
+ font-size: 14px;
1005
+ }
1006
+
1007
+ .step-detail:last-child {
1008
+ margin-bottom: 0;
1009
+ }
1010
+
1011
+ .current-step {
1012
+ background-color: #f8f9fa;
1013
+ padding: 12px;
1014
+ border-radius: 8px;
1015
+ border-left: 4px solid #1976d2;
1016
+ }
1017
+
1018
+ .future-step {
1019
+ opacity: 0.7;
1020
+ }
1021
+
1022
+ .multi-instance-details {
1023
+ margin-top: 8px;
1024
+ }
1025
+
1026
+ .instance-list {
1027
+ margin-top: 12px;
1028
+ }
1029
+
1030
+ .instance-item {
1031
+ background-color: #ffffff;
1032
+ border: 1px solid #e0e0e0;
1033
+ border-radius: 6px;
1034
+ padding: 8px;
1035
+ margin-bottom: 8px;
1036
+ }
1037
+
1038
+ .instance-item:last-child {
1039
+ margin-bottom: 0;
1040
+ }
1041
+
1042
+ .instance-header {
1043
+ display: flex;
1044
+ justify-content: space-between;
1045
+ align-items: center;
1046
+ margin-bottom: 4px;
1047
+ }
1048
+
1049
+ .instance-status {
1050
+ padding: 2px 6px;
1051
+ border-radius: 3px;
1052
+ font-size: 11px;
1053
+ font-weight: 500;
1054
+ }
1055
+
1056
+ .instance-approved {
1057
+ background-color: #e8f5e8;
1058
+ color: #388e3c;
1059
+ }
1060
+
1061
+ .instance-rejected {
1062
+ background-color: #ffebee;
1063
+ color: #d32f2f;
1064
+ }
1065
+
1066
+ .instance-completed {
1067
+ background-color: #e3f2fd;
1068
+ color: #1976d2;
1069
+ }
1070
+
1071
+ .instance-pending {
1072
+ background-color: #fff3e0;
1073
+ color: #f57c00;
1074
+ }
1075
+
1076
+ .instance-comment {
1077
+ font-size: 12px;
1078
+ color: #666;
1079
+ margin-bottom: 4px;
1080
+ line-height: 1.4;
1081
+ }
1082
+
1083
+ .instance-time {
1084
+ font-size: 11px;
1085
+ color: #999;
1086
+ }
1087
+
1088
+ .multi-instance-badge {
1089
+ background-color: #e3f2fd;
1090
+ color: #1976d2;
1091
+ padding: 2px 6px;
1092
+ border-radius: 3px;
1093
+ font-size: 11px;
1094
+ font-weight: 500;
1095
+ }
1096
+
1097
+ .instance-section {
1098
+ margin-bottom: 12px;
1099
+ }
1100
+
1101
+ .instance-section h5 {
1102
+ font-size: 12px;
1103
+ color: #666;
1104
+ margin: 0 0 8px 0;
1105
+ font-weight: 500;
1106
+ }
1107
+
1108
+ .instance-section:last-child {
1109
+ margin-bottom: 0;
1110
+ }
1111
+
1112
+ /* 用户信息相关样式 */
1113
+ .assignee-info {
1114
+ display: flex;
1115
+ align-items: center;
1116
+ }
1117
+
1118
+ .approver-info {
1119
+ margin-bottom: 12px;
1120
+ padding-bottom: 8px;
1121
+ border-bottom: 1px solid #f0f0f0;
1122
+ }
1123
+
1124
+ .approver-info:last-child {
1125
+ margin-bottom: 0;
1126
+ border-bottom: none;
1127
+ }
1128
+
1129
+ .candidate-users {
1130
+ display: flex;
1131
+ flex-wrap: wrap;
1132
+ gap: 8px;
1133
+ align-items: center;
1134
+ }
1135
+
1136
+ .candidate-user {
1137
+ background-color: #f8f9fa;
1138
+ padding: 4px 8px;
1139
+ border-radius: 12px;
1140
+ border: 1px solid #e9ecef;
1141
+ }
1142
+
1143
+ /* 抄送人相关样式 */
1144
+ .cc-users {
1145
+ display: flex;
1146
+ flex-wrap: wrap;
1147
+ gap: 6px;
1148
+ align-items: center;
1149
+ }
1150
+
1151
+ .cc-user {
1152
+ background-color: #f6ffed;
1153
+ padding: 3px 6px;
1154
+ border-radius: 10px;
1155
+ border: 1px solid #d9f7be;
1156
+ font-size: 12px;
1157
+ }
1158
+
1159
+ .cc-users-list {
1160
+ display: flex;
1161
+ flex-direction: column;
1162
+ gap: 8px;
1163
+ }
1164
+
1165
+ .cc-user-item {
1166
+ background-color: #fffbe6;
1167
+ padding: 8px;
1168
+ border-radius: 8px;
1169
+ border: 1px solid #ffd666;
1170
+ }
1171
+
1172
+ /* 简化审批步骤样式 */
1173
+ .simple-approval-steps {
1174
+ background-color: #ffffff;
1175
+ padding: 12px 16px;
1176
+ }
1177
+
1178
+ .simple-step-item {
1179
+ position: relative;
1180
+ padding-bottom: 16px;
1181
+ border-left: 2px solid #e8e8e8;
1182
+ margin-left: 10px;
1183
+ }
1184
+
1185
+ .simple-step-item:last-child {
1186
+ border-left: none;
1187
+ padding-bottom: 0;
1188
+ }
1189
+
1190
+ .step-header {
1191
+ display: flex;
1192
+ align-items: center;
1193
+ margin-bottom: 8px;
1194
+ margin-left: -11px;
1195
+ }
1196
+
1197
+ .step-icon {
1198
+ width: 20px;
1199
+ height: 20px;
1200
+ border-radius: 50%;
1201
+ display: flex;
1202
+ align-items: center;
1203
+ justify-content: center;
1204
+ margin-right: 10px;
1205
+ flex-shrink: 0;
1206
+ position: relative;
1207
+ z-index: 1;
1208
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1209
+ }
1210
+
1211
+ .step-icon-completed {
1212
+ background: linear-gradient(135deg, #52c41a, #73d13d);
1213
+ }
1214
+
1215
+ .step-icon-active {
1216
+ background: linear-gradient(135deg, #1890ff, #40a9ff);
1217
+ }
1218
+
1219
+ .step-icon-waiting {
1220
+ background: linear-gradient(135deg, #d9d9d9, #f0f0f0);
1221
+ }
1222
+
1223
+ .step-info {
1224
+ flex: 1;
1225
+ }
1226
+
1227
+ .step-title-row {
1228
+ display: flex;
1229
+ align-items: center;
1230
+ justify-content: space-between;
1231
+ width: 100%;
1232
+ }
1233
+
1234
+ .step-title {
1235
+ font-size: 15px;
1236
+ font-weight: 600;
1237
+ color: #262626;
1238
+ line-height: 1.3;
1239
+ flex: 1;
1240
+ margin-right: 8px;
1241
+ }
1242
+
1243
+ .step-status {
1244
+ margin-left: 8px;
1245
+ white-space: nowrap;
1246
+ }
1247
+
1248
+ .step-content {
1249
+ margin-left: 19px;
1250
+ padding-bottom: 4px;
1251
+ }
1252
+
1253
+ .simple-approver-wrapper {
1254
+ padding: 6px 0;
1255
+ margin: 6px 0;
1256
+ border-bottom: 1px solid #f5f5f5;
1257
+ }
1258
+
1259
+ .simple-approver-wrapper:last-child {
1260
+ border-bottom: none;
1261
+ padding-bottom: 0;
1262
+ }
1263
+
1264
+ .simple-approver {
1265
+ display: flex;
1266
+ align-items: flex-start;
1267
+ justify-content: space-between;
1268
+ min-height: 40px;
1269
+ }
1270
+
1271
+ .approver-user-info {
1272
+ flex: 1;
1273
+ margin-right: 8px;
1274
+ }
1275
+
1276
+ .approver-action-info {
1277
+ display: flex;
1278
+ flex-direction: column;
1279
+ align-items: flex-end;
1280
+ white-space: nowrap;
1281
+ }
1282
+
1283
+ .action-type {
1284
+ font-size: 12px;
1285
+ font-weight: 500;
1286
+ color: #1890ff;
1287
+ margin-bottom: 2px;
1288
+ }
1289
+
1290
+ .target-user-info {
1291
+ padding: 8px 12px;
1292
+ background-color: #f8f9fa;
1293
+ border-radius: 6px;
1294
+ border-left: 3px solid #1890ff;
1295
+ display: flex;
1296
+ align-items: center;
1297
+ gap: 8px;
1298
+ }
1299
+
1300
+ .target-label {
1301
+ font-size: 12px;
1302
+ color: #666;
1303
+ font-weight: 500;
1304
+ white-space: nowrap;
1305
+ }
1306
+
1307
+ .approver-time {
1308
+ font-size: 11px;
1309
+ color: #8c8c8c;
1310
+ margin-left: 8px;
1311
+ white-space: nowrap;
1312
+ font-weight: 500;
1313
+ }
1314
+
1315
+ .approver-comment {
1316
+ background-color: #fafafa;
1317
+ padding: 8px 12px;
1318
+ border-radius: 6px;
1319
+ font-size: 12px;
1320
+ color: #595959;
1321
+ line-height: 1.4;
1322
+ width: 100%;
1323
+ border: 1px solid #f0f0f0;
1324
+ box-sizing: border-box;
1325
+ }
1326
+
1327
+ /* 增加抄送人按钮样式 */
1328
+ .add-cc-button-inline {
1329
+ display: flex;
1330
+ align-items: center;
1331
+ gap: 4px;
1332
+ font-size: 11px;
1333
+ border-color: #d9d9d9;
1334
+ color: #666;
1335
+ height: 24px;
1336
+ padding: 0 8px;
1337
+ white-space: nowrap;
1338
+ }
1339
+
1340
+ .add-cc-button-inline:hover {
1341
+ border-color: #1890ff;
1342
+ color: #1890ff;
1343
+ }
1344
+
1345
+ /* 备注相关样式 */
1346
+ .card-header {
1347
+ display: flex;
1348
+ justify-content: space-between;
1349
+ align-items: center;
1350
+ margin-bottom: 12px;
1351
+ }
1352
+
1353
+ .card-actions {
1354
+ display: flex;
1355
+ align-items: center;
1356
+ }
1357
+
1358
+ .remark-list {
1359
+ padding: 0 4px;
1360
+ }
1361
+
1362
+ .remark-item {
1363
+ padding: 12px 0;
1364
+ border-bottom: 1px solid #f0f0f0;
1365
+ }
1366
+
1367
+ .remark-item:last-child {
1368
+ border-bottom: none;
1369
+ }
1370
+
1371
+ .remark-header {
1372
+ display: flex;
1373
+ justify-content: space-between;
1374
+ align-items: center;
1375
+ margin-bottom: 8px;
1376
+ }
1377
+
1378
+ .remark-actions {
1379
+ display: flex;
1380
+ align-items: center;
1381
+ gap: 8px;
1382
+ }
1383
+
1384
+ .remark-time {
1385
+ font-size: 12px;
1386
+ color: #8c8c8c;
1387
+ }
1388
+
1389
+ .delete-btn {
1390
+ opacity: 0.6;
1391
+ padding: 0 4px;
1392
+ height: 24px;
1393
+ min-width: 24px;
1394
+ }
1395
+
1396
+ .delete-btn:hover {
1397
+ opacity: 1;
1398
+ }
1399
+
1400
+ .delete-confirm-content {
1401
+ display: flex;
1402
+ align-items: center;
1403
+ padding: 16px 0;
1404
+ }
1405
+
1406
+ .remark-content {
1407
+ margin-bottom: 10px;
1408
+ font-size: 14px;
1409
+ line-height: 1.6;
1410
+ color: #333;
1411
+ word-break: break-word;
1412
+ }
1413
+
1414
+ .remark-attachments {
1415
+ background-color: #f9f9f9;
1416
+ padding: 8px;
1417
+ border-radius: 4px;
1418
+ }
1419
+
1420
+ .no-remarks {
1421
+ padding: 24px 0;
1422
+ text-align: center;
1423
+ }
1424
+
1425
+ .no-remarks-tip {
1426
+ color: #999;
1427
+ font-size: 14px;
1428
+ }
1429
+
1430
+ .remark-form {
1431
+ padding: 10px 0;
1432
+ }
1433
+
1434
+ .remark-form-footer {
1435
+ margin-top: 16px;
1436
+ display: flex;
1437
+ justify-content: space-between;
1438
+ align-items: center;
1439
+ }
1440
+
1441
+ .remark-form-actions {
1442
+ display: flex;
1443
+ gap: 8px;
1444
+ }
1445
+ .node-type-badge {
1446
+ padding: 2px 6px;
1447
+ font-size: 10px;
1448
+ background: #e6f7ff;
1449
+ color: #1890ff;
1450
+ border-radius: 4px;
1451
+ }
1452
+ .parallel {
1453
+ background: #fff2e8;
1454
+ color: #fa8c16;
1455
+ }
1456
+
1427
1457
  </style>