@ebiz/designer-components 0.1.80 → 0.1.81

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 (220) hide show
  1. package/README.md +29 -29
  2. package/dist/designer-components.css +1 -1
  3. package/dist/index.mjs +26608 -24285
  4. package/package.json +2 -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/EbizDictionarySelect.vue +229 -0
  28. package/src/components/EbizDiv.vue +40 -40
  29. package/src/components/EbizDivider.vue +96 -96
  30. package/src/components/EbizDropdown.vue +135 -135
  31. package/src/components/EbizDropdownItem.vue +85 -85
  32. package/src/components/EbizEmployeeInfo.vue +145 -139
  33. package/src/components/EbizEmployeeSelector.vue +1159 -1159
  34. package/src/components/EbizFileList.vue +466 -466
  35. package/src/components/EbizMap.vue +541 -541
  36. package/src/components/EbizMeetingRoomSelector.vue +664 -664
  37. package/src/components/EbizMobileMeetingRoomSelector.vue +727 -727
  38. package/src/components/EbizOkrTree.vue +99 -99
  39. package/src/components/EbizPageHeader.vue +95 -95
  40. package/src/components/EbizPagination.vue +162 -162
  41. package/src/components/EbizPdfViewer.vue +540 -540
  42. package/src/components/EbizPopconfirm.vue +47 -47
  43. package/src/components/EbizQrCode.vue +73 -73
  44. package/src/components/EbizRadio.vue +86 -86
  45. package/src/components/EbizRadioGroup.vue +83 -83
  46. package/src/components/EbizRemoteSelect.vue +232 -232
  47. package/src/components/EbizRichTextEditor.vue +275 -275
  48. package/src/components/EbizRouteBreadcrumb.vue +46 -46
  49. package/src/components/EbizSApprovalProcess.vue +1429 -1429
  50. package/src/components/EbizSelect.vue +85 -85
  51. package/src/components/EbizSpace.vue +100 -100
  52. package/src/components/EbizStatistic.vue +149 -149
  53. package/src/components/EbizStatsCard.vue +113 -113
  54. package/src/components/EbizSwiper.vue +113 -113
  55. package/src/components/EbizSwiperItem.vue +13 -13
  56. package/src/components/EbizSwitch.vue +85 -85
  57. package/src/components/EbizTabHeader.vue +132 -132
  58. package/src/components/EbizTabPanel.vue +22 -22
  59. package/src/components/EbizTable.vue +469 -469
  60. package/src/components/EbizTableColumn.vue +116 -116
  61. package/src/components/EbizTableSort.vue +179 -179
  62. package/src/components/EbizTabs.vue +142 -142
  63. package/src/components/EbizTdesignButtonDialog.vue +332 -332
  64. package/src/components/EbizTdesignLoading.vue +107 -107
  65. package/src/components/EbizTimePicker.vue +143 -143
  66. package/src/components/EbizTitle.vue +91 -91
  67. package/src/components/EbizTree.vue +141 -141
  68. package/src/components/EbizTreeMergeTable.vue +1494 -1494
  69. package/src/components/EbizTreeSelector.vue +418 -418
  70. package/src/components/EbizVideo.vue +553 -553
  71. package/src/components/EbizVxeTable.vue +290 -290
  72. package/src/components/Form.vue +28 -28
  73. package/src/components/Home.vue +7 -7
  74. package/src/components/MyComponent.vue +39 -39
  75. package/src/components/Table.vue +45 -45
  76. package/src/components/TdesignAlert.vue +115 -115
  77. package/src/components/TdesignButton.vue +135 -135
  78. package/src/components/TdesignCalendar/index.vue +145 -145
  79. package/src/components/TdesignCard.vue +195 -195
  80. package/src/components/TdesignCol.vue +101 -101
  81. package/src/components/TdesignCollapse.vue +142 -142
  82. package/src/components/TdesignCollapsePanel.vue +79 -79
  83. package/src/components/TdesignDatePicker.vue +124 -124
  84. package/src/components/TdesignDescriptions.vue +74 -74
  85. package/src/components/TdesignDescriptionsItem.vue +50 -50
  86. package/src/components/TdesignDialog.vue +225 -225
  87. package/src/components/TdesignForm.vue +138 -138
  88. package/src/components/TdesignFormItem.vue +105 -105
  89. package/src/components/TdesignGrid.vue +55 -55
  90. package/src/components/TdesignIcon.vue +67 -67
  91. package/src/components/TdesignImage.vue +162 -162
  92. package/src/components/TdesignImageViewer.vue +200 -200
  93. package/src/components/TdesignInput.vue +242 -242
  94. package/src/components/TdesignSelect.vue +446 -446
  95. package/src/components/TdesignTag.vue +117 -117
  96. package/src/components/TdesignTextarea.vue +142 -142
  97. package/src/components/TdesignTimeline.vue +58 -58
  98. package/src/components/TdesignTimelineItem.vue +71 -71
  99. package/src/components/TdesignUpload.vue +414 -414
  100. package/src/components/TdesignWatermark.vue +107 -107
  101. package/src/components/ebiz-form/components/cascader.vue +61 -61
  102. package/src/components/ebiz-form/components/checkbox.vue +37 -37
  103. package/src/components/ebiz-form/components/city.vue +137 -137
  104. package/src/components/ebiz-form/components/date-panel.vue +52 -52
  105. package/src/components/ebiz-form/components/date-range-panel.vue +52 -52
  106. package/src/components/ebiz-form/components/date-range.vue +56 -56
  107. package/src/components/ebiz-form/components/date.vue +52 -52
  108. package/src/components/ebiz-form/components/editor-multi-language.vue +47 -47
  109. package/src/components/ebiz-form/components/editor.vue +78 -78
  110. package/src/components/ebiz-form/components/file-multi-language.vue +52 -52
  111. package/src/components/ebiz-form/components/file.vue +149 -149
  112. package/src/components/ebiz-form/components/images-multi-language.vue +52 -52
  113. package/src/components/ebiz-form/components/images.vue +129 -129
  114. package/src/components/ebiz-form/components/img-multi-language.vue +51 -51
  115. package/src/components/ebiz-form/components/img.vue +129 -129
  116. package/src/components/ebiz-form/components/number.vue +50 -50
  117. package/src/components/ebiz-form/components/radio.vue +28 -28
  118. package/src/components/ebiz-form/components/select.vue +119 -119
  119. package/src/components/ebiz-form/components/switch.vue +23 -23
  120. package/src/components/ebiz-form/components/text-multi-language.vue +47 -47
  121. package/src/components/ebiz-form/components/text.vue +52 -52
  122. package/src/components/ebiz-form/components/textarea-multi-language.vue +48 -48
  123. package/src/components/ebiz-form/components/textarea.vue +29 -29
  124. package/src/components/ebiz-form/components/video-multi-language.vue +51 -51
  125. package/src/components/ebiz-form/components/video.vue +97 -97
  126. package/src/components/ebiz-form/index.vue +157 -157
  127. package/src/components/examples/PopconfirmExample.vue +149 -149
  128. package/src/components/icons/IconCommunity.vue +7 -7
  129. package/src/components/icons/IconDocumentation.vue +7 -7
  130. package/src/components/icons/IconEcosystem.vue +7 -7
  131. package/src/components/icons/IconSupport.vue +7 -7
  132. package/src/components/icons/IconTooling.vue +19 -19
  133. package/src/components/mItems/UserInfo.vue +342 -342
  134. package/src/components/senior/EbizSData/index.vue +273 -273
  135. package/src/components/senior/EbizSDialog/index.vue +770 -770
  136. package/src/components/senior/EbizSForm/README.md +157 -157
  137. package/src/components/senior/EbizSForm/index.vue +667 -667
  138. package/src/components/senior/EbizSForm/item.vue +986 -971
  139. package/src/components/senior/EbizSForm/mItems/DateTimePicker.vue +51 -51
  140. package/src/components/senior/EbizSForm/mItems/Picker.vue +63 -63
  141. package/src/index.js +313 -255
  142. package/src/main.js +55 -55
  143. package/src/router/index.js +415 -415
  144. package/src/utils/formatCode.js +24 -24
  145. package/src/utils/generateImportStatement.js +52 -52
  146. package/src/utils/hasJsx.js +25 -25
  147. package/src/utils/index.js +166 -166
  148. package/src/utils/mergeOptions.js +29 -29
  149. package/src/utils/parseRequiredBlocks.js +18 -18
  150. package/src/utils/vue-sfc-validator.js +155 -155
  151. package/src/views/Button.vue +23 -23
  152. package/src/views/CheckboxDemo.vue +104 -104
  153. package/src/views/DataContainer.vue +19 -19
  154. package/src/views/DialogDemo.vue +125 -125
  155. package/src/views/EbizApprovalDemo.vue +87 -87
  156. package/src/views/EbizApprovalFormDemo.vue +207 -207
  157. package/src/views/EbizAutoFormDemo.vue +129 -129
  158. package/src/views/EbizAvatar.vue +223 -223
  159. package/src/views/EbizDepartmentSelectorDemo.vue +169 -169
  160. package/src/views/EbizDetailBlockDemo.vue +30 -30
  161. package/src/views/EbizDetailViewDemo.vue +412 -412
  162. package/src/views/EbizEmployeeInfo.vue +249 -249
  163. package/src/views/EbizEmployeeSelector.vue +85 -85
  164. package/src/views/EbizFileListDemo.vue +339 -339
  165. package/src/views/EbizMap.vue +201 -201
  166. package/src/views/EbizMeetingRoomSelectorDemo.vue +293 -293
  167. package/src/views/EbizMobileMeetingRoomSelectorDemo.vue +566 -566
  168. package/src/views/EbizRadioDemo.vue +151 -151
  169. package/src/views/EbizSDataDemo.vue +136 -136
  170. package/src/views/EbizSDialogDemo.vue +303 -303
  171. package/src/views/EbizSForm/index.vue +352 -351
  172. package/src/views/EbizSFormDemo.vue +420 -420
  173. package/src/views/EbizSpace.vue +185 -185
  174. package/src/views/EbizSwiper.vue +157 -157
  175. package/src/views/EbizTdesignButtonDialogExample.vue +437 -437
  176. package/src/views/Form.vue +19 -19
  177. package/src/views/GridDemo.vue +238 -238
  178. package/src/views/Home.vue +153 -153
  179. package/src/views/Mindmap.vue +17 -17
  180. package/src/views/MyComponent.vue +19 -19
  181. package/src/views/OkrTree.vue +19 -19
  182. package/src/views/PageHeaderDemo.vue +104 -104
  183. package/src/views/PaginationDemo.vue +96 -96
  184. package/src/views/PdfViewerDemo.vue +433 -433
  185. package/src/views/PermissionBoxDemo.vue +85 -85
  186. package/src/views/PopconfirmDemo.vue +80 -80
  187. package/src/views/RemoteSelect.vue +350 -350
  188. package/src/views/StatisticDemo.vue +190 -190
  189. package/src/views/SwitchDemo.vue +79 -79
  190. package/src/views/Table.vue +19 -19
  191. package/src/views/TableDemo.vue +334 -334
  192. package/src/views/TableSortDemo.vue +143 -143
  193. package/src/views/TableView.vue +68 -68
  194. package/src/views/TabsDemo.vue +282 -282
  195. package/src/views/TagDemo.vue +101 -101
  196. package/src/views/TdesignAlert.vue +98 -98
  197. package/src/views/TdesignButton.vue +190 -190
  198. package/src/views/TdesignCalendar.vue +94 -94
  199. package/src/views/TdesignCard.vue +296 -296
  200. package/src/views/TdesignCollapse.vue +293 -293
  201. package/src/views/TdesignDatePicker.vue +187 -187
  202. package/src/views/TdesignDescriptions.vue +101 -101
  203. package/src/views/TdesignForm.vue +248 -248
  204. package/src/views/TdesignIcon.vue +203 -203
  205. package/src/views/TdesignImage.vue +215 -215
  206. package/src/views/TdesignImageViewer.vue +198 -198
  207. package/src/views/TdesignInput.vue +252 -252
  208. package/src/views/TdesignSelect.vue +473 -473
  209. package/src/views/TdesignSwiper.vue +157 -157
  210. package/src/views/TextareaDemo.vue +93 -93
  211. package/src/views/TimePickerDemo.vue +146 -146
  212. package/src/views/TimelineDemo.vue +160 -160
  213. package/src/views/Title.vue +19 -19
  214. package/src/views/TreeDemo.vue +254 -254
  215. package/src/views/TreeMergeTableDemo.vue +239 -239
  216. package/src/views/TreeSelectorDemo.vue +245 -245
  217. package/src/views/UploadDemo.vue +128 -128
  218. package/src/views/VideoDemo.vue +245 -245
  219. package/src/views/VxeTableDemo.vue +279 -279
  220. 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>