@ebiz/designer-components 0.1.78 → 0.1.80

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