@lambo-design/workflow-approve 1.0.0-beta.14 → 1.0.0-beta.141

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.
@@ -1,382 +1,1181 @@
1
- <template>
2
- <div>
3
-
4
- <Timeline class="portrait-timeline">
5
- <TimelineItem v-for="(items,index) in itemList" :key="index" class="portrait-timeline-item">
6
- <template #dot>
7
- <div v-if="items[0].auditResult === '30' || items[0].auditResult === '80'" class="cicle" style="background-color: #005aff"></div>
8
- <div v-else-if="items[0].auditResult === '40' || items[0].auditResult === '50'" class="cicle" style="background-color: #ffcc66"></div>
9
- <div v-else-if="items[0].auditResult === '60'" class="cicle" style="background-color: #ed4014"></div>
10
- <div v-else class="cicle" style="background-color: #ff9900"></div>
11
- </template>
12
- <Collapse v-model="activeName" simple style="margin: -10px 0 0 -2px;border: none">
13
- <Panel :name="'panel' + index" hide-arrow>
14
- <span class="history-title"> {{ items[0].taskName }}</span>
15
- <template #content>
16
- <div v-for="(item, index) in items" :key="index">
17
- <div v-for="(itemListItem, itemListIndex) in item.auditName" :key="itemListIndex">
18
- <Card dis-hover
19
- class="portrait-card"
20
- :bordered="false">
21
- <List item-layout="vertical">
22
- <ListItem>
23
- <Row style="display: flex; align-items: center;">
24
- <!-- 左边:Avatar -->
25
- <Col>
26
- <avatar
27
- :class="portraitWidth >= 600 ? 'portrait-avatar-large' : 'portrait-avatar-small'"
28
- size="small">
29
- {{ getFirstName(itemListItem) }}</avatar>
30
- </Col>
31
- <!-- 右边:审批信息 -->
32
- <Col span="17">
33
- <Row>
34
- <tooltip>
35
- <span class="audit-name-style"> {{ itemListItem }}</span>
36
- <div slot="content" style="white-space: normal"> {{ itemListItem }}</div>
37
- </tooltip>
38
- <Divider style="background-color:#808695;height: 1em;margin: 4px 9px 0 6px;"
39
- type="vertical"/>
40
- <span v-if="item.auditOrganName"
41
- class="audit-name-style"> {{ item.auditOrganName[itemListIndex] }}</span>
42
- <span v-if="portraitWidth === 0 || portraitWidth >= 600" class="audit-date-style">
43
- {{ item.startDate }}{{ item.auditDate }}
44
- </span>
45
- </Row>
46
- <Row v-if="item.auditComment">
47
- <tooltip max-width="200px" placement="bottom-start">
48
- <span class="audit-name-style">{{ item.auditComment }}</span>
49
- <div slot="content" style="white-space: normal"> {{ item.auditComment }}</div>
50
- </tooltip>
51
- </Row>
52
- <Row v-if="portraitWidth < 600" class="portrait-audit-date-style">{{showDate(item)}}{{ item.startDate }}{{ item.auditDate }}</Row>
53
- <Row style="margin-top: 10px">
54
- <Col span="12">
55
- <span style="color: #005aff;; font-size: 13px;"
56
- v-if="item.auditResult === '30'">通过</span>
57
- <span style="color: #ed4014; font-size: 13px;"
58
- v-else-if="item.auditResult === '40'">驳回到上一节点</span>
59
- <span style="color: #ed4014; font-size: 13px;"
60
- v-else-if="item.auditResult === '50'">驳回到原点</span>
61
- <span style="color: #ed4014; font-size: 13px;"
62
- v-else-if="item.auditResult === '60'">撤回</span>
63
- <span style="color: #19be6b; font-size: 13px;"
64
- v-else-if="item.auditResult === '80'">跳转到指定节点</span>
65
- <span style="color: #19be6b; font-size: 13px;"
66
- v-else-if="item.auditResult === '90'">驳回到指定节点</span>
67
- <span style="color: #ff9900; font-size: 13px;" v-else>待审批</span>
68
- </Col>
69
- <Col span="12" style="text-align: right">
70
- <Button v-if="item.fileList && item.fileList.length > 0" size="small" type="primary" ghost class="urging" @click="openModal(item)">查看附件</Button>
71
- <Button v-if="displayPushButton && item.auditResult !== '30'&&item.auditResult !== '40'&&item.auditResult !== '50'&&item.auditResult !== '60'&&item.auditResult!=='80'&&item.auditResult!=='90'"
72
- size="small" type="primary" ghost class="urging"
73
- @click="pushHim(item,itemListItem,itemListIndex)">催一下
74
- </Button>
75
- </Col>
76
- </Row>
77
- </Col>
78
- </Row>
79
-
80
- </ListItem>
81
- </List>
82
- </Card>
83
- </div>
84
- </div>
85
- </template>
86
- </Panel>
87
- </Collapse>
88
- </TimelineItem>
89
- </Timeline>
90
-
91
- <Modal
92
- v-model="showModal"
93
- title="查看附件"
94
- >
95
- <Table border
96
- :columns="attachmentColumn"
97
- :data="fileList"
98
- >
99
- </Table>
100
- </Modal>
101
- <div v-if="0 < allGroupKeys.length">
102
- <div v-show="arrowShow" class="arrow">
103
- <a @click="arrowClick">
104
- <Icon type="ios-arrow-down"/>
105
- 展开显示更多
106
- </a>
107
- </div>
108
- <div v-show="!arrowShow" class="arrow">
109
- <a @click="arrowClick">
110
- <Icon type="ios-arrow-up"/>
111
- 收缩
112
- </a>
113
- </div>
114
- </div>
115
- <Modal title="查看附件" v-model="modalImg" fullscreen scrollable :mask="false">
116
- <img :src="image" v-if="modalImg" alt="" style="width: 100%">
117
- <div slot="footer">
118
- <Button type="primary" @click="modalImg = false">关闭</Button>
119
- </div>
120
- </Modal>
121
- <Modal title="查看附件" v-model="modalDocx" fullscreen scrollable :mask="false">
122
- <div ref="file"></div>
123
- </Modal>
124
- </div>
125
- </template>
126
-
127
- <script>
128
- import ajax from "@lambo-design/shared/utils/ajax";
129
- import { operateHref } from '@lambo-design/shared/utils/assist'
130
- import axios from 'axios';
131
- // 引入docx-preview插件
132
- let docx = require('docx-preview');
133
- export default {
134
- components: {},
135
- props: {
136
- list: {
137
- type: Array,
138
- default: () => []
139
- },
140
- applyId: {
141
- type: String,
142
- default: ""
143
- },
144
- donePage: {
145
- type: Boolean,
146
- default: false
147
- },
148
- //横竖版样式不同
149
- portraitWidth: {
150
- type: Number,
151
- default: 0
152
- },
153
- smartFlowServerContext: {
154
- type: String,
155
- default: '/api/smart-flow-server',
156
- },
157
- },
158
- data() {
159
- return {
160
- allGroupKeys: [],
161
- displayedList: [],
162
- arrowShow: true, // 控制加载更多按钮的显示
163
- activeName: 'panel0',
164
- modalDocx: false,
165
- setModals: {
166
- modals: []
167
- },
168
- showModal: false,
169
- fileList: [],
170
- modalImg: false,
171
- image: "",
172
- itemList: {},
173
- auditNameList: [],
174
- displayPushButton: false,
175
- }
176
- },
177
- computed: {
178
- attachmentColumn: function () {
179
- let column = [];
180
- column.push({title: '序号', type: 'index', width: 70, align: 'center', fixed: 'left'});
181
- column.push({
182
- title: '附件名称', key: 'fileName', minWidth: 150, align: 'center', fixed: 'left',
183
- render(h, {row}) {
184
- const index = row.fileName.lastIndexOf(".");
185
- const fileName = row.fileName.substr(0, index)
186
- return h("span", fileName)
187
-
188
- }
189
- });
190
- column.push({
191
- title: '附件类型', width: 100, align: 'center', fixed: 'left',
192
- render: (h, {row}) => {
193
- const index = row.fileName.lastIndexOf(".");
194
- const fileType = row.fileName.substr(index + 1)
195
- return h("span", fileType)
196
- }
197
- });
198
-
199
- column.push({
200
- title: "操作", width: 160, align: 'center', tooltip: true,
201
- render: (h, {row}) => {
202
- const index = row.fileName.lastIndexOf(".");
203
- const fileType = row.fileName.substr(index + 1).toLowerCase()
204
- const typeList=['jpg','gif','png','docx']
205
- if (typeList.indexOf(fileType) !== -1) {
206
- return h("div", [
207
- operateHref(this, h, row, "下载", () => {
208
- this.getAttach(row);
209
- }, "primary"),
210
- operateHref(this, h, row, "预览", () => {
211
- this.preViewAttach(row);
212
- }, "primary")
213
- ]);
214
- } else {
215
- return h("div", [
216
- operateHref(this, h, row, "下载", () => {
217
- this.getAttach(row);
218
- }, "primary")
219
- ]);
220
- }
221
-
222
- }
223
-
224
- });
225
- return column;
226
- }
227
- },
228
- methods: {
229
- pushHim(item, auditName, index) {
230
- if (this.isDebouncing) {
231
- return;
232
- }
233
-
234
- this.isDebouncing = true;
235
- // 设置一个延时,例如 1 秒,之后重置防抖状态
236
- setTimeout(() => {
237
- this.isDebouncing = false;
238
- }, 2000);
239
-
240
- let self = this;
241
- let applyUserName = item.applyUser.split(":")[1];
242
- let params = {
243
- applyUserName: applyUserName,
244
- auditName: auditName,
245
- auditId: item.auditId[index],
246
- procName: item.procName,
247
- effectTime: item.startDate,
248
- expireTime: item.warningDate,
249
- applyId: item.applyId,
250
- };
251
- let url = self.smartFlowServerContext + "/manage/processTodo/sendMessage";
252
- ajax.post(url, params).then(function (resp) {
253
- console.log("message ", resp);
254
- if (resp.data.code == 200) {
255
- self.$Message.success("催办成功");
256
- } else {
257
- self.$Message.error("催办失败");
258
- }
259
- });
260
- },
261
- getAttach(row) {
262
- window.open(this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId, "_blank");
263
- },
264
- preViewAttach(row) {
265
- let reg = /\.(gif|jpg|jpeg|bmp|png|PNG)$/
266
- let regs = /\.(pdf)$/
267
- if (reg.test(row.fileName)) {
268
- let url = this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId;
269
- this.imgPreview(url);
270
- } else if (regs.test(row.fileName)) {
271
- window.open(this.smartFlowServerContext + "/manage/oss/file/getFileStream?fileId=" + row.fileId, "_blank");
272
- } else {
273
- this.modalDocx = true
274
- axios({
275
- method: 'get',
276
- responseType: 'blob', // 因为是流文件,所以要指定blob类型
277
- url: this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId// 一个word下载文件的接口
278
- }).then(({data}) => {
279
- docx.renderAsync(data, this.$refs.file, null, {
280
- className: "docx", //默认和文档样式类的类名/前缀
281
- inWrapper: true, //启用围绕文档内容呈现包装器
282
- ignoreWidth: false, //禁用页面的渲染宽度
283
- ignoreHeight: false, //禁用页面的渲染高度
284
- ignoreFonts: false, //禁用字体渲染
285
- breakPages: true, //在分页符上启用分页
286
- ignoreLastRenderedPageBreak: true, //在lastRenderedPageBreak元素上禁用分页
287
- experimental: false, //启用实验功能(制表符停止计算)
288
- trimXmlDeclaration: true, //如果为true,则在解析之前将从xml文档中删除xml声明
289
- useBase64URL: false, //如果为true,图像、字体等将转换为base 64 URL,否则使用URL.createObjectURL
290
- useMathMLPolyfill: false, //包括用于铬、边等的MathML多填充。
291
- showChanges: false, //启用文档更改的实验渲染(插入/删除)
292
- debug: false, //启用额外的日志记录
293
- })
294
- }
295
- )
296
- }
297
- },
298
- //查看附件
299
- imgPreview: function (img) {
300
- this.modalImg = true
301
- this.image = img;
302
- },
303
- arrowClick() {
304
- if (this.itemList.length > 0) {
305
- if (this.arrowShow) {
306
- // 显示全部
307
- this.displayedList = this.itemList;
308
- this.arrowShow = false;
309
- } else {
310
- // 只显示两个
311
- this.displayedList = this.itemList.slice(0, 2);
312
- this.arrowShow = true;
313
- }
314
- }
315
- },
316
- openModal(item) {
317
- this.fileList = item.fileList
318
- this.showModal = true
319
- },
320
- getFirstName(item) {
321
- return item ? item[0] : ''
322
- },
323
- showDate(item) {
324
- if (item.auditResult){
325
- return '处理时间:'
326
- }
327
- return '接收时间:'
328
- }
329
- },
330
- mounted() {
331
- this.displayPushButton = this.donePage;
332
- this.itemList = this.list;
333
- if (this.itemList.length > 0) {
334
- this.displayedList = this.itemList.slice(0, 2);
335
- // 初始化modals
336
- this.setModals.modals = this.itemList.map(() => ({value: false}));
337
- this.activeName = 'panel0';
338
- }
339
- },
340
- watch: {
341
- list: function (newValue) {
342
- this.$nextTick(() => {
343
- this.itemList = newValue;
344
- if (this.itemList.length > 0) {
345
- this.displayedList = this.itemList.slice(0, 2);
346
- this.setModals.modals = this.itemList.map(() => ({value: false}));
347
- }
348
-
349
- });
350
- }
351
- }
352
-
353
-
354
- }
355
- </script>
356
-
357
- <style scoped>
358
- @import "../styles/css/index.less";
359
- /deep/ .docx {
360
- width: 100% !important;
361
- }
362
-
363
- /deep/ .ivu-timeline-item-tail {
364
- border-style: solid;
365
- border-left: none;
366
- border-color: #005aff;
367
- border-width: thin;
368
- }
369
-
370
- /deep/ .ivu-list-item {
371
- padding-bottom: 0 !important;
372
- }
373
-
374
- /deep/ .ivu-collapse-content-box {
375
- padding: 0 !important;
376
- }
377
-
378
- /deep/ .ivu-card-body {
379
- padding: 5px !important;
380
- }
381
-
382
- </style>
1
+ <template>
2
+ <div>
3
+ <!-- 添加排序和分组切换按钮 -->
4
+ <div class="history-controls" style="margin-bottom: 16px;">
5
+ <ButtonGroup>
6
+ <Button
7
+ :type="sortBy === 'time' ? 'primary' : 'default'"
8
+ size="small"
9
+ @click="toggleSortBy('time')">
10
+ <Icon type="md-time" />
11
+ 按时间排序
12
+ </Button>
13
+ <Button
14
+ :type="sortBy === 'node' ? 'primary' : 'default'"
15
+ size="small"
16
+ @click="toggleSortBy('node')">
17
+ <Icon type="md-git-network" />
18
+ 按节点分组
19
+ </Button>
20
+ </ButtonGroup>
21
+ </div>
22
+
23
+ <Timeline class="portrait-timeline">
24
+ <TimelineItem v-for="(items,index) in processedItemList" :key="index" class="portrait-timeline-item">
25
+ <template #dot>
26
+ <div class="cicle" :style="{backgroundColor: getTimelineDotColor(items[0].auditResult)}"></div>
27
+ </template>
28
+ <!-- 按时间排序时:不显示标题,自动展开 -->
29
+ <template v-if="sortBy === 'time'">
30
+ <!-- 检查是否有待审批记录 -->
31
+ <template v-if="items.some(item => isPendingAudit(item))">
32
+ <!-- 待审批的记录:使用折叠面板 -->
33
+ <Collapse v-model="pendingActiveName" simple style="margin: -10px 0 0 20px;border: none">
34
+ <Panel :name="'pending-panel' + index" hide-arrow>
35
+ <span class="pending-audit-title">
36
+ <Icon type="md-time" style="color: orange; margin-right: 5px;" />
37
+ 待{{ handleName }}
38
+ </span>
39
+ <template #content>
40
+ <!-- 遍历所有待审批的记录 -->
41
+ <div v-for="(item, itemIndex) in items.filter(item => isPendingAudit(item))" :key="itemIndex">
42
+ <div v-for="(itemListItem, itemListIndex) in item.auditName" :key="itemListIndex">
43
+ <Card dis-hover
44
+ class="portrait-card portrait-card-time"
45
+ :bordered="false">
46
+ <List item-layout="vertical">
47
+ <ListItem>
48
+ <Row style="display: flex; align-items: center;">
49
+ <!-- 左边:Avatar -->
50
+ <Col>
51
+ <avatar
52
+ :class="portraitWidth >= 600 ? 'portrait-avatar-large' : 'portrait-avatar-small'"
53
+ size="small">
54
+ {{ getFirstName(itemListItem) }}</avatar>
55
+ </Col>
56
+ <!-- 右边:审批信息 -->
57
+ <Col span="17">
58
+ <Row>
59
+ <tooltip>
60
+ <span class="audit-name-style"> {{ itemListItem }}</span>
61
+ <div slot="content" style="white-space: normal"> {{ itemListItem }}</div>
62
+ </tooltip>
63
+ <Divider style="background-color:#808695;height: 1em;margin: 4px 9px 0 6px;"
64
+ type="vertical"/>
65
+ <tooltip v-if="item.auditOrganName">
66
+ <span class="audit-name-style"> {{ item.auditOrganName[itemListIndex] }}</span>
67
+ <div slot="content" style="white-space: normal"> {{ item.auditOrganName[itemListIndex] }}</div>
68
+ </tooltip>
69
+ <span v-if="portraitWidth === 0 || portraitWidth >= 600" class="audit-date-style">
70
+ {{showDate(item)}}
71
+ </span>
72
+ </Row>
73
+ <Row v-if="item.auditComment">
74
+ <tooltip max-width="200px" placement="bottom-start">
75
+ <span class="audit-comment-style">{{ item.auditComment }}</span>
76
+ <div slot="content" style="white-space: normal"> {{ item.auditComment }}</div>
77
+ </tooltip>
78
+ </Row>
79
+ <Row v-if="portraitWidth < 600" class="portrait-audit-date-style">{{showDate(item)}}</Row>
80
+ <Row style="margin-top: 10px">
81
+ <Col span="12">
82
+ <span style="color: orange; font-size: 13px;">{{ '待' + handleName }}</span>
83
+ </Col>
84
+ <Col span="12" style="text-align: right">
85
+ <Button v-if="item.fileList && item.fileList.length > 0" size="small" type="primary" ghost class="urging" @click="openModal(item)">查看附件</Button>
86
+ <Button v-if="displayPushButton"
87
+ size="small" type="primary" ghost class="urging" v-permission="'swf-work-hurry'"
88
+ @click="pushHim(item,itemListItem,itemListIndex)">催一下
89
+ </Button>
90
+ </Col>
91
+ </Row>
92
+ </Col>
93
+ </Row>
94
+ </ListItem>
95
+ </List>
96
+ </Card>
97
+ </div>
98
+ </div>
99
+ </template>
100
+ </Panel>
101
+ </Collapse>
102
+ </template>
103
+
104
+ <!-- 已审批的记录:直接显示 -->
105
+ <div v-for="(item, itemIndex) in items.filter(item => !isPendingAudit(item))" :key="'approved-' + itemIndex">
106
+ <div v-for="(itemListItem, itemListIndex) in item.auditName" :key="itemListIndex">
107
+ <Card dis-hover
108
+ class="portrait-card portrait-card-time"
109
+ :bordered="false">
110
+ <List item-layout="vertical">
111
+ <ListItem>
112
+ <Row style="display: flex; align-items: center;">
113
+ <!-- 左边:Avatar -->
114
+ <Col>
115
+ <avatar
116
+ :class="portraitWidth >= 600 ? 'portrait-avatar-large' : 'portrait-avatar-small'"
117
+ size="small">
118
+ {{ getFirstName(itemListItem) }}</avatar>
119
+ </Col>
120
+ <!-- 右边:审批信息 -->
121
+ <Col span="17">
122
+ <Row>
123
+ <tooltip>
124
+ <span class="audit-name-style"> {{ itemListItem }}</span>
125
+ <div slot="content" style="white-space: normal"> {{ itemListItem }}</div>
126
+ </tooltip>
127
+ <Divider style="background-color:#808695;height: 1em;margin: 4px 9px 0 6px;"
128
+ type="vertical"/>
129
+ <tooltip v-if="item.auditOrganName">
130
+ <span class="audit-name-style"> {{ item.auditOrganName[itemListIndex] }}</span>
131
+ <div slot="content" style="white-space: normal"> {{ item.auditOrganName[itemListIndex] }}</div>
132
+ </tooltip>
133
+ <span v-if="portraitWidth === 0 || portraitWidth >= 600" class="audit-date-style">
134
+ {{showDate(item)}}
135
+ </span>
136
+ </Row>
137
+ <Row v-if="item.auditComment">
138
+ <tooltip max-width="200px" placement="bottom-start">
139
+ <span class="audit-comment-style">{{ item.auditComment }}</span>
140
+ <div slot="content" style="white-space: normal"> {{ item.auditComment }}</div>
141
+ </tooltip>
142
+ </Row>
143
+ <Row v-if="portraitWidth < 600" class="portrait-audit-date-style">{{showDate(item)}}</Row>
144
+ <Row style="margin-top: 10px">
145
+ <Col span="12">
146
+ <!-- green 52c41a // 绿色-->
147
+ <span style="color: #52c41a; font-size: 13px;"
148
+ v-if="item.auditResult === '00'">流程发起</span>
149
+ <span style="color: #52c41a; font-size: 13px;"
150
+ v-else-if="item.auditResult === '10'">首节点自动跳过</span>
151
+ <span style="color: #52c41a; font-size: 13px;"
152
+ v-else-if="item.auditResult === '11'">与上一环节办理人相同自动跳过</span>
153
+ <span style="color: #52c41a; font-size: 13px;"
154
+ v-else-if="item.auditResult === '12'">与发起人相同自动跳过</span>
155
+ <span style="color: #52c41a; font-size: 13px;"
156
+ v-else-if="item.auditResult === '13'">办理人为空自动跳过</span>
157
+ <span style="color: #52c41a; font-size: 13px;"
158
+ v-else-if="item.auditResult === '14'">符合流程变量条件自动跳过</span>
159
+ <span style="color: #52c41a; font-size: 13px;"
160
+ v-else-if="item.auditResult === '30'">通过</span>
161
+ <span style="color: #52c41a; font-size: 13px;"
162
+ v-else-if="item.auditResult === '31'">{{item.passName ? item.passName : '通过'}}</span>
163
+ <span style="color: #fa541c; font-size: 13px;"
164
+ v-else-if="item.auditResult === '32'">不{{item.passName ? item.passName : '通过'}}</span>
165
+ <!-- volcano fa541c // 火红色-->
166
+ <span style="color: #fa541c; font-size: 13px;"
167
+ v-else-if="item.auditResult === '40'">{{item.rejectName ? item.rejectName : '驳回'}}上一节点</span>
168
+ <!-- red f5222d // 红色-->
169
+ <span style="color: #f5222d; font-size: 13px;"
170
+ v-else-if="item.auditResult === '50'">{{item.rejectName ? item.rejectName : '驳回'}}到原点</span>
171
+ <!-- purple 722ed1 // 紫色-->
172
+ <span style="color: #722ed1; font-size: 13px;"
173
+ v-else-if="item.auditResult === '51'">流程终止</span>
174
+ <!-- blue 1890ff // 蓝色-->
175
+ <span style="color: #1890ff; font-size: 13px;"
176
+ v-else-if="item.auditResult === '60'">撤回</span>
177
+ <span style="color: #52c41a; font-size: 13px;"
178
+ v-else-if="item.auditResult === '61'">交回委派任务</span>
179
+ <span style="color: #1890ff; font-size: 13px;"
180
+ v-else-if="item.auditResult === '62'">委派任务被撤回</span>
181
+ <!-- orange fa8c16 // 橙色-->
182
+ <span style="color: #fa8c16; font-size: 13px;"
183
+ v-else-if="item.auditResult === '63'">委派任务</span>
184
+ <!-- cyan 13c2c2 // 青色-->
185
+ <span style="color: #13c2c2; font-size: 13px;"
186
+ v-else-if="item.auditResult === '80'">跳转指定节点</span>
187
+ <span style="color: #13c2c2; font-size: 13px;"
188
+ v-else-if="item.auditResult === '82'">指定他人处理</span>
189
+ <span style="color: #1890ff; font-size: 13px;"
190
+ v-else-if="item.auditResult === '83'">会签减签</span>
191
+ <!-- magenta eb2f96 // 品红色-->
192
+ <span style="color: #eb2f96; font-size: 13px;"
193
+ v-else-if="item.auditResult === '90'">{{item.rejectName ? item.rejectName : '驳回'}}指定节点</span>
194
+ <!-- orange fa8c16 // 默认橙色-->
195
+ <span style="color: #fa8c16; font-size: 13px;" v-else>{{ '待' + handleName }}</span>
196
+ </Col>
197
+ <Col span="12" style="text-align: right">
198
+ <Button v-if="item.fileList && item.fileList.length > 0" size="small" type="primary" ghost class="urging" @click="openModal(item)">查看附件</Button>
199
+ </Col>
200
+ </Row>
201
+ </Col>
202
+ </Row>
203
+ </ListItem>
204
+ </List>
205
+ </Card>
206
+ </div>
207
+ </div>
208
+ </template>
209
+
210
+ <!-- 按节点分组时:显示折叠面板 -->
211
+ <template v-else>
212
+ <Collapse v-model="activeName" simple style="margin: -10px 0 0 -2px;border: none">
213
+ <Panel :name="'panel' + index" hide-arrow>
214
+ <span class="history-title">{{ items[0].taskName }}</span>
215
+ <template #content>
216
+ <div v-for="(item, index) in items" :key="index">
217
+ <div v-for="(itemListItem, itemListIndex) in item.auditName" :key="itemListIndex">
218
+ <Card dis-hover
219
+ class="portrait-card"
220
+ :bordered="false">
221
+ <List item-layout="vertical">
222
+ <ListItem>
223
+ <Row style="display: flex; align-items: center;">
224
+ <!-- 左边:Avatar -->
225
+ <Col>
226
+ <avatar
227
+ :class="portraitWidth >= 600 ? 'portrait-avatar-large' : 'portrait-avatar-small'"
228
+ size="small">
229
+ {{ getFirstName(itemListItem) }}</avatar>
230
+ </Col>
231
+ <!-- 右边:审批信息 -->
232
+ <Col span="17">
233
+ <Row>
234
+ <tooltip>
235
+ <span class="audit-name-style"> {{ itemListItem }}</span>
236
+ <div slot="content" style="white-space: normal"> {{ itemListItem }}</div>
237
+ </tooltip>
238
+ <Divider style="background-color:#808695;height: 1em;margin: 4px 9px 0 6px;"
239
+ type="vertical"/>
240
+ <tooltip v-if="item.auditOrganName">
241
+ <span class="audit-name-style"> {{ item.auditOrganName[itemListIndex] }}</span>
242
+ <div slot="content" style="white-space: normal"> {{ item.auditOrganName[itemListIndex] }}</div>
243
+ </tooltip>
244
+ <span v-if="portraitWidth === 0 || portraitWidth >= 600" class="audit-date-style">
245
+ {{showDate(item)}}
246
+ </span>
247
+ </Row>
248
+ <Row v-if="item.auditComment">
249
+ <tooltip max-width="200px" placement="bottom-start">
250
+ <span class="audit-comment-style">{{ item.auditComment }}</span>
251
+ <div slot="content" style="white-space: normal"> {{ item.auditComment }}</div>
252
+ </tooltip>
253
+ </Row>
254
+ <Row v-if="portraitWidth < 600" class="portrait-audit-date-style">{{showDate(item)}}</Row>
255
+ <Row style="margin-top: 10px">
256
+ <Col span="12">
257
+ <!-- green 52c41a // 绿色-->
258
+ <span style="color: #52c41a; font-size: 13px;"
259
+ v-if="item.auditResult === '00'">流程发起</span>
260
+ <span style="color: #52c41a; font-size: 13px;"
261
+ v-else-if="item.auditResult === '10'">首节点自动跳过</span>
262
+ <span style="color: #52c41a; font-size: 13px;"
263
+ v-else-if="item.auditResult === '11'">与上一环节办理人相同自动跳过</span>
264
+ <span style="color: #52c41a; font-size: 13px;"
265
+ v-else-if="item.auditResult === '12'">与发起人相同自动跳过</span>
266
+ <span style="color: #52c41a; font-size: 13px;"
267
+ v-else-if="item.auditResult === '13'">办理人为空自动跳过</span>
268
+ <span style="color: #52c41a; font-size: 13px;"
269
+ v-else-if="item.auditResult === '14'">符合流程变量条件自动跳过</span>
270
+ <span style="color: #52c41a; font-size: 13px;"
271
+ v-else-if="item.auditResult === '30'">通过</span>
272
+ <span style="color: #52c41a; font-size: 13px;"
273
+ v-else-if="item.auditResult === '31'">会签表决{{item.passName ? item.passName : '通过'}}</span>
274
+ <span style="color: #fa541c; font-size: 13px;"
275
+ v-else-if="item.auditResult === '32'">会签表决不{{item.passName ? item.passName : '通过'}}</span>
276
+ <!-- volcano fa541c // 火红色-->
277
+ <span style="color: #fa541c; font-size: 13px;"
278
+ v-else-if="item.auditResult === '40'">{{item.rejectName ? item.rejectName : '驳回'}}上一节点</span>
279
+ <!-- red f5222d // 红色-->
280
+ <span style="color: #f5222d; font-size: 13px;"
281
+ v-else-if="item.auditResult === '50'">{{item.rejectName ? item.rejectName : '驳回'}}到原点</span>
282
+ <!-- purple 722ed1 // 紫色-->
283
+ <span style="color: #722ed1; font-size: 13px;"
284
+ v-else-if="item.auditResult === '51'">流程终止</span>
285
+ <!-- blue 1890ff // 蓝色-->
286
+ <span style="color: #1890ff; font-size: 13px;"
287
+ v-else-if="item.auditResult === '60'">撤回</span>
288
+ <span style="color: #52c41a; font-size: 13px;"
289
+ v-else-if="item.auditResult === '61'">交回委派任务</span>
290
+ <span style="color: #1890ff; font-size: 13px;"
291
+ v-else-if="item.auditResult === '62'">委派任务被撤回</span>
292
+ <!-- orange fa8c16 // 橙色-->
293
+ <span style="color: #fa8c16; font-size: 13px;"
294
+ v-else-if="item.auditResult === '63'">委派任务</span>
295
+ <!-- cyan 13c2c2 // 青色-->
296
+ <span style="color: #13c2c2; font-size: 13px;"
297
+ v-else-if="item.auditResult === '80'">跳转指定节点</span>
298
+ <span style="color: #13c2c2; font-size: 13px;"
299
+ v-else-if="item.auditResult === '82'">指定他人处理</span>
300
+ <span style="color: #1890ff; font-size: 13px;"
301
+ v-else-if="item.auditResult === '83'">会签减签</span>
302
+ <!-- magenta eb2f96 // 品红色-->
303
+ <span style="color: #eb2f96; font-size: 13px;"
304
+ v-else-if="item.auditResult === '90'">{{item.rejectName ? item.rejectName : '驳回'}}指定节点</span>
305
+ <!-- orange fa8c16 // 默认橙色-->
306
+ <span style="color: #fa8c16; font-size: 13px;" v-else>{{ '待' + handleName }}</span>
307
+ </Col>
308
+ <Col span="12" style="text-align: right">
309
+ <Button v-if="item.fileList && item.fileList.length > 0" size="small" type="primary" ghost class="urging" @click="openModal(item)">查看附件</Button>
310
+ <Button v-if="displayPushButton && !auditPassStatus.includes(item.auditResult)"
311
+ size="small" type="primary" ghost class="urging" v-permission="'swf-work-hurry'"
312
+ @click="pushHim(item,itemListItem,itemListIndex)">催一下
313
+ </Button>
314
+ </Col>
315
+ </Row>
316
+ </Col>
317
+ </Row>
318
+
319
+ </ListItem>
320
+ </List>
321
+ </Card>
322
+ </div>
323
+ </div>
324
+ </template>
325
+ </Panel>
326
+ </Collapse>
327
+ </template>
328
+ </TimelineItem>
329
+ </Timeline>
330
+
331
+ <Modal
332
+ v-model="showModal"
333
+ title="查看附件"
334
+ >
335
+ <Table border
336
+ :columns="attachmentColumn"
337
+ :data="fileList"
338
+ >
339
+ </Table>
340
+ </Modal>
341
+ <div v-if="0 < allGroupKeys.length">
342
+ <div v-show="arrowShow" class="arrow">
343
+ <a @click="arrowClick">
344
+ <Icon type="ios-arrow-down"/>
345
+ 展开显示更多
346
+ </a>
347
+ </div>
348
+ <div v-show="!arrowShow" class="arrow">
349
+ <a @click="arrowClick">
350
+ <Icon type="ios-arrow-up"/>
351
+ 收缩
352
+ </a>
353
+ </div>
354
+ </div>
355
+ <Modal title="查看附件" v-model="modalImg" fullscreen scrollable :mask="false">
356
+ <img :src="image" v-if="modalImg" alt="" style="width: 100%">
357
+ <div slot="footer">
358
+ <Button type="primary" @click="modalImg = false">关闭</Button>
359
+ </div>
360
+ </Modal>
361
+ <Modal title="查看附件" v-model="modalDocx" fullscreen scrollable :mask="false">
362
+ <div ref="file"></div>
363
+ </Modal>
364
+ <Modal
365
+ v-model="remindModalVisible"
366
+ title="催办提醒"
367
+ width="640"
368
+ class-name="todo-remind-modal"
369
+ @on-cancel="closeRemindModal"
370
+ >
371
+ <div class="todo-remind-dialog">
372
+ <div class="todo-remind-row">
373
+ <div class="todo-remind-label">催办类型:</div>
374
+ <CheckboxGroup v-model="remindForm.types" class="todo-remind-type-group">
375
+ <Checkbox label="MESSAGE">系统消息</Checkbox>
376
+ <Checkbox label="SMS">短信提醒</Checkbox>
377
+ </CheckboxGroup>
378
+ </div>
379
+ <div class="todo-remind-row todo-remind-row-top">
380
+ <div class="todo-remind-label">催办内容:</div>
381
+ <div class="todo-remind-body">
382
+ <Input
383
+ ref="remindContent"
384
+ v-model="remindForm.content"
385
+ type="textarea"
386
+ :rows="6"
387
+ :maxlength="500"
388
+ show-word-limit
389
+ placeholder="请输入催办内容"
390
+ class="todo-remind-textarea"
391
+ @on-focus="syncRemindCursor"
392
+ @on-click="syncRemindCursor"
393
+ @on-keyup="syncRemindCursor"
394
+ @on-select="syncRemindCursor"
395
+ />
396
+ </div>
397
+ </div>
398
+ <div class="todo-remind-row todo-remind-row-top">
399
+ <div class="todo-remind-label">占位变量:</div>
400
+ <div class="todo-remind-body todo-remind-variable-list">
401
+ <button
402
+ v-for="item in remindVariableList"
403
+ :key="item.key"
404
+ type="button"
405
+ class="todo-remind-variable"
406
+ @mousedown.prevent="insertRemindVariable(item)"
407
+ >
408
+ {{ item.label }}
409
+ </button>
410
+ </div>
411
+ </div>
412
+ </div>
413
+ <template #footer>
414
+ <Button type="primary" :loading="remindSubmitting" @click="submitRemind">催办</Button>
415
+ </template>
416
+ </Modal>
417
+ </div>
418
+ </template>
419
+
420
+ <script>
421
+ import ajax from "@lambo-design/shared/utils/ajax";
422
+ import { operateHref } from '@lambo-design/shared/utils/assist'
423
+ import axios from 'axios';
424
+ // 引入docx-preview插件
425
+ let docx = require('docx-preview');
426
+ export default {
427
+ components: {},
428
+ props: {
429
+ list: {
430
+ type: Array,
431
+ default: () => []
432
+ },
433
+ applyId: {
434
+ type: String,
435
+ default: ""
436
+ },
437
+ donePage: {
438
+ type: Boolean,
439
+ default: false
440
+ },
441
+ pushButton: {
442
+ type: Boolean,
443
+ default: true
444
+ },
445
+ handleName: {
446
+ type: String,
447
+ default: "审批"
448
+ },
449
+ historySortBy: {
450
+ type: String,
451
+ default: "time"
452
+ },
453
+ //横竖版样式不同
454
+ portraitWidth: {
455
+ type: Number,
456
+ default: 400
457
+ },
458
+ smartFlowServerContext: {
459
+ type: String,
460
+ default: '/api/smart-flow-server',
461
+ },
462
+ },
463
+ data() {
464
+ return {
465
+ allGroupKeys: [],
466
+ displayedList: [],
467
+ arrowShow: true, // 控制加载更多按钮的显示
468
+ activeName: '',
469
+ modalDocx: false,
470
+ setModals: {
471
+ modals: []
472
+ },
473
+ auditPassStatus:['00', '10', '11', '12', '13', '14', '30', '31', '32', '40', '50', '51', '60', '61', '62', '80', '82', '83', '90'],
474
+ showModal: false,
475
+ fileList: [],
476
+ modalImg: false,
477
+ image: "",
478
+ itemList: [],
479
+ auditNameList: [],
480
+ displayPushButton: false,
481
+ remindModalVisible: false,
482
+ remindSubmitting: false,
483
+ currentRemindRow: null,
484
+ remindCursorStart: 0,
485
+ remindCursorEnd: 0,
486
+ remindForm: {
487
+ types: ['MESSAGE'],
488
+ content: ''
489
+ },
490
+ remindVariableList: [
491
+ {
492
+ key: 'applyId',
493
+ label: '申请编码'
494
+ },
495
+ {
496
+ key: 'applyUserName',
497
+ label: '发起人'
498
+ },
499
+ {
500
+ key: 'procName',
501
+ label: '流程名称'
502
+ },
503
+ {
504
+ key: 'procTypeName',
505
+ label: '流程类型'
506
+ },
507
+ {
508
+ key: 'startTime',
509
+ label: '发起时间'
510
+ },
511
+ {
512
+ key: 'businessTopic',
513
+ label: '业务主题'
514
+ },
515
+ {
516
+ key: 'busKeyDes',
517
+ label: '业务描述'
518
+ },
519
+ {
520
+ key: 'taskName',
521
+ label: '环节名称'
522
+ },
523
+ {
524
+ key: 'orgName',
525
+ label: '区域名称'
526
+ }
527
+ ],
528
+ sortBy: this.historySortBy,
529
+ pendingActiveName: [], // 控制待审批折叠面板的展开状态
530
+ }
531
+ },
532
+ computed: {
533
+ processedItemList() {
534
+ // 确保 itemList 存在且是数组
535
+ if (!this.itemList || !Array.isArray(this.itemList) || this.itemList.length === 0) {
536
+ return [];
537
+ }
538
+
539
+ if (this.sortBy === 'time') {
540
+ // 按时间排序:将所有审批记录平铺,按时间排序,然后重新分组
541
+ let allRecords = [];
542
+
543
+ // 平铺所有审批记录
544
+ this.itemList.forEach(nodeGroup => {
545
+ // 确保 nodeGroup 是数组
546
+ if (Array.isArray(nodeGroup)) {
547
+ nodeGroup.forEach(record => {
548
+ if (record && typeof record === 'object') {
549
+ // 获取准确的时间用于排序
550
+ let sortTime = this.getRecordTime(record);
551
+ let timestamp = new Date(sortTime).getTime();
552
+
553
+ // 验证时间戳是否有效
554
+ if (isNaN(timestamp)) {
555
+ console.warn('时间戳解析失败:', {
556
+ record: record,
557
+ sortTime: sortTime,
558
+ auditDate: record.auditDate,
559
+ createTime: record.createTime
560
+ });
561
+ // 使用当前时间作为后备
562
+ timestamp = new Date().getTime();
563
+ sortTime = new Date().toISOString();
564
+ }
565
+
566
+ allRecords.push({
567
+ record: record,
568
+ sortTime: sortTime,
569
+ timestamp: timestamp
570
+ });
571
+ }
572
+ });
573
+ }
574
+ });
575
+
576
+ // 调试信息:打印排序前的数据
577
+ console.log('排序前的记录:', allRecords.map(item => ({
578
+ auditResult: item.record.auditResult,
579
+ auditDate: item.record.auditDate,
580
+ createTime: item.record.createTime,
581
+ sortTime: item.sortTime,
582
+ timestamp: item.timestamp,
583
+ date: new Date(item.timestamp)
584
+ })));
585
+
586
+ // 按时间戳排序(倒序,最新在前)
587
+ allRecords.sort((a, b) => {
588
+ // 使用时间戳进行排序,避免字符串比较问题
589
+ return b.timestamp - a.timestamp;
590
+ });
591
+
592
+ // 调试信息:打印排序后的数据
593
+ console.log('排序后的记录:', allRecords.map(item => ({
594
+ auditResult: item.record.auditResult,
595
+ auditDate: item.record.auditDate,
596
+ createTime: item.record.createTime,
597
+ sortTime: item.sortTime,
598
+ timestamp: item.timestamp,
599
+ date: new Date(item.timestamp)
600
+ })));
601
+
602
+ // 重新分组:相同状态且时间相近的记录分为一组
603
+ let groupedRecords = [];
604
+ let currentGroup = [];
605
+
606
+ allRecords.forEach((item, index) => {
607
+ if (currentGroup.length === 0) {
608
+ // 第一个记录,直接加入
609
+ currentGroup.push(item.record);
610
+ } else {
611
+ // 检查是否应该与当前组合并
612
+ let shouldGroup = false;
613
+
614
+ // 如果都是待审批状态且是同一个节点,则分为一组
615
+ if (this.isPendingAudit(item.record) &&
616
+ currentGroup.some(r => this.isPendingAudit(r) && r.taskName === item.record.taskName)) {
617
+ shouldGroup = true;
618
+ }
619
+ // 如果都是已审批状态且时间非常接近(同一天或几分钟内),可以考虑分组,但通常按个人分组
620
+ else if (!this.isPendingAudit(item.record) &&
621
+ currentGroup.length === 1 &&
622
+ !this.isPendingAudit(currentGroup[0]) &&
623
+ currentGroup[0].taskName === item.record.taskName) {
624
+ // 暂时不分组已审批记录,每个人一组
625
+ shouldGroup = false;
626
+ }
627
+
628
+ if (shouldGroup) {
629
+ currentGroup.push(item.record);
630
+ } else {
631
+ // 开始新的分组
632
+ if (currentGroup.length > 0) {
633
+ groupedRecords.push([...currentGroup]);
634
+ }
635
+ currentGroup = [item.record];
636
+ }
637
+ }
638
+ });
639
+
640
+ // 添加最后一组
641
+ if (currentGroup.length > 0) {
642
+ groupedRecords.push(currentGroup);
643
+ }
644
+
645
+ return groupedRecords;
646
+ } else {
647
+ this.activeName = ''
648
+ // 按节点分组:保持原有的节点分组结构
649
+ return this.itemList;
650
+ }
651
+ },
652
+
653
+ attachmentColumn: function () {
654
+ let column = [];
655
+ column.push({title: '序号', type: 'index', width: 70, align: 'center', fixed: 'left'});
656
+ column.push({
657
+ title: '附件名称', key: 'fileName', minWidth: 150, align: 'center', fixed: 'left',
658
+ render(h, {row}) {
659
+ const index = row.fileName.lastIndexOf(".");
660
+ const fileName = row.fileName.substr(0, index)
661
+ return h("span", fileName)
662
+
663
+ }
664
+ });
665
+ column.push({
666
+ title: '附件类型', width: 100, align: 'center', fixed: 'left',
667
+ render: (h, {row}) => {
668
+ const index = row.fileName.lastIndexOf(".");
669
+ const fileType = row.fileName.substr(index + 1)
670
+ return h("span", fileType)
671
+ }
672
+ });
673
+
674
+ column.push({
675
+ title: "操作", width: 160, align: 'center', tooltip: true,
676
+ render: (h, {row}) => {
677
+ const index = row.fileName.lastIndexOf(".");
678
+ const fileType = row.fileName.substr(index + 1).toLowerCase()
679
+ const typeList=['jpg','gif','png','docx']
680
+ if (typeList.indexOf(fileType) !== -1) {
681
+ return h("div", [
682
+ operateHref(this, h, row, "下载", () => {
683
+ this.getAttach(row);
684
+ }, "primary"),
685
+ operateHref(this, h, row, "预览", () => {
686
+ this.preViewAttach(row);
687
+ }, "primary")
688
+ ]);
689
+ } else {
690
+ return h("div", [
691
+ operateHref(this, h, row, "下载", () => {
692
+ this.getAttach(row);
693
+ }, "primary")
694
+ ]);
695
+ }
696
+
697
+ }
698
+
699
+ });
700
+ return column;
701
+ }
702
+ },
703
+ methods: {
704
+ pushHim(item, auditName, index) {
705
+ this.openRemindModal(item, auditName, index)
706
+ },
707
+ getRemindContextRow(item, auditName, index) {
708
+ const applyUser = item && item.applyUser ? String(item.applyUser) : ''
709
+ const applyUserName = applyUser && applyUser.indexOf(':') > -1 ? applyUser.split(':')[1] : applyUser
710
+ const auditIds = item && item.auditId
711
+ const auditId = Array.isArray(auditIds) ? auditIds[index] : auditIds
712
+ return Object.assign({}, item, {
713
+ applyUserName,
714
+ auditName,
715
+ auditId,
716
+ startTime: this.formatDateTime(item && item.startDate),
717
+ procInstanceId: item && (item.procInstanceId || item.instanceId) ? (item.procInstanceId || item.instanceId) : ''
718
+ })
719
+ },
720
+ openRemindModal(item, auditName, index) {
721
+ this.currentRemindRow = this.getRemindContextRow(item || {}, auditName, index)
722
+ this.remindModalVisible = true
723
+ },
724
+ closeRemindModal() {
725
+ this.remindModalVisible = false
726
+ this.$nextTick(() => {
727
+ this.resetRemindForm()
728
+ })
729
+ },
730
+ resetRemindForm() {
731
+ this.currentRemindRow = null
732
+ this.remindCursorStart = 0
733
+ this.remindCursorEnd = 0
734
+ this.remindSubmitting = false
735
+ this.remindForm = {
736
+ types: ['MESSAGE'],
737
+ content: ''
738
+ }
739
+ },
740
+ syncRemindCursor() {
741
+ this.$nextTick(() => {
742
+ const inputRef = this.$refs.remindContent
743
+ const textarea = inputRef && inputRef.$refs ? inputRef.$refs.textarea : null
744
+ if (!textarea) {
745
+ return
746
+ }
747
+ this.remindCursorStart = typeof textarea.selectionStart === 'number' ? textarea.selectionStart : 0
748
+ this.remindCursorEnd = typeof textarea.selectionEnd === 'number' ? textarea.selectionEnd : this.remindCursorStart
749
+ })
750
+ },
751
+ insertRemindVariable(variable) {
752
+ const key = variable && variable.key ? variable.key : ''
753
+ const rawValue = key && this.currentRemindRow ? this.currentRemindRow[key] : ''
754
+ const value = rawValue === null || rawValue === undefined || rawValue === '--' ? '' : String(rawValue)
755
+ if (!value) {
756
+ return
757
+ }
758
+ const currentContent = this.remindForm.content || ''
759
+ const inputRef = this.$refs.remindContent
760
+ const textarea = inputRef && inputRef.$refs ? inputRef.$refs.textarea : null
761
+ const selectionStart = textarea && typeof textarea.selectionStart === 'number' ? textarea.selectionStart : this.remindCursorStart
762
+ const selectionEnd = textarea && typeof textarea.selectionEnd === 'number' ? textarea.selectionEnd : this.remindCursorEnd
763
+ const start = Math.max(0, Math.min(selectionStart, currentContent.length))
764
+ const end = Math.max(start, Math.min(selectionEnd, currentContent.length))
765
+ this.remindForm.content = `${currentContent.slice(0, start)}${value}${currentContent.slice(end)}`
766
+ this.$nextTick(() => {
767
+ const currentInputRef = this.$refs.remindContent
768
+ const currentTextarea = currentInputRef && currentInputRef.$refs ? currentInputRef.$refs.textarea : null
769
+ if (currentTextarea && typeof currentTextarea.focus === 'function') {
770
+ currentTextarea.focus()
771
+ const nextPosition = start + value.length
772
+ if (typeof currentTextarea.setSelectionRange === 'function') {
773
+ currentTextarea.setSelectionRange(nextPosition, nextPosition)
774
+ }
775
+ this.remindCursorStart = nextPosition
776
+ this.remindCursorEnd = nextPosition
777
+ }
778
+ })
779
+ },
780
+ submitRemind() {
781
+ if (this.remindSubmitting) {
782
+ return
783
+ }
784
+ if (!this.remindForm.types.length) {
785
+ this.$Message.warning('请选择至少一种催办类型')
786
+ return
787
+ }
788
+ const messageContent = (this.remindForm.content || '').trim()
789
+ if (!messageContent) {
790
+ this.$Message.warning('请输入催办内容')
791
+ return
792
+ }
793
+ const remindRow = this.currentRemindRow || {}
794
+ const taskNode = remindRow.taskNode
795
+ const procInstanceId = remindRow.procInstanceId
796
+ if (!taskNode || !procInstanceId) {
797
+ this.$Message.error('催办参数缺失,无法提交')
798
+ return
799
+ }
800
+ const actionTypes = this.remindForm.types.filter(Boolean)
801
+ if (!actionTypes.length) {
802
+ this.$Message.error('催办类型转换失败')
803
+ return
804
+ }
805
+ const requestUrl = this.smartFlowServerContext + '/manage/processTodo/urgeTask'
806
+ const actionType = actionTypes.join(',')
807
+ this.remindSubmitting = true
808
+ this.$Spin.show()
809
+ ajax.post(requestUrl, { taskNode, procInstanceId, actionType, messageContent }).then((resp) => {
810
+ const successCodes = ['200', 200]
811
+ if (!resp || !resp.data || successCodes.indexOf(resp.data.code) === -1) {
812
+ const message = resp && resp.data && resp.data.message ? resp.data.message : '催办失败'
813
+ this.$Message.error(message)
814
+ this.closeRemindModal()
815
+ return
816
+ }
817
+ this.$Message.success('催办成功')
818
+ this.closeRemindModal()
819
+ }).catch(() => {
820
+ this.$Message.error('催办失败')
821
+ }).finally(() => {
822
+ this.remindSubmitting = false
823
+ this.$Spin.hide()
824
+ })
825
+ },
826
+ formatDateTime(value) {
827
+ if (!value) {
828
+ return '--'
829
+ }
830
+ const date = new Date(value)
831
+ if (Number.isNaN(date.getTime())) {
832
+ return '--'
833
+ }
834
+ const year = date.getFullYear()
835
+ const month = String(date.getMonth() + 1).padStart(2, '0')
836
+ const day = String(date.getDate()).padStart(2, '0')
837
+ const hours = String(date.getHours()).padStart(2, '0')
838
+ const minutes = String(date.getMinutes()).padStart(2, '0')
839
+ const seconds = String(date.getSeconds()).padStart(2, '0')
840
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
841
+ },
842
+ getAttach(row) {
843
+ window.open(this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId, "_blank");
844
+ },
845
+ preViewAttach(row) {
846
+ let reg = /\.(gif|jpg|jpeg|bmp|png|PNG)$/
847
+ let regs = /\.(pdf)$/
848
+ if (reg.test(row.fileName)) {
849
+ let url = this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId;
850
+ this.imgPreview(url);
851
+ } else if (regs.test(row.fileName)) {
852
+ window.open(this.smartFlowServerContext + "/manage/oss/file/getFileStream?fileId=" + row.fileId, "_blank");
853
+ } else {
854
+ this.modalDocx = true
855
+ axios({
856
+ method: 'get',
857
+ responseType: 'blob', // 因为是流文件,所以要指定blob类型
858
+ url: this.smartFlowServerContext + "/manage/oss/file/get/" + row.fileId// 一个word下载文件的接口
859
+ }).then(({data}) => {
860
+ docx.renderAsync(data, this.$refs.file, null, {
861
+ className: "docx", //默认和文档样式类的类名/前缀
862
+ inWrapper: true, //启用围绕文档内容呈现包装器
863
+ ignoreWidth: false, //禁用页面的渲染宽度
864
+ ignoreHeight: false, //禁用页面的渲染高度
865
+ ignoreFonts: false, //禁用字体渲染
866
+ breakPages: true, //在分页符上启用分页
867
+ ignoreLastRenderedPageBreak: true, //在lastRenderedPageBreak元素上禁用分页
868
+ experimental: false, //启用实验功能(制表符停止计算)
869
+ trimXmlDeclaration: true, //如果为true,则在解析之前将从xml文档中删除xml声明
870
+ useBase64URL: false, //如果为true,图像、字体等将转换为base 64 URL,否则使用URL.createObjectURL
871
+ useMathMLPolyfill: false, //包括用于铬、边等的MathML多填充。
872
+ showChanges: false, //启用文档更改的实验渲染(插入/删除)
873
+ debug: false, //启用额外的日志记录
874
+ })
875
+ }
876
+ )
877
+ }
878
+ },
879
+ //查看附件
880
+ imgPreview: function (img) {
881
+ this.modalImg = true
882
+ this.image = img;
883
+ },
884
+ arrowClick() {
885
+ if (this.itemList.length > 0) {
886
+ if (this.arrowShow) {
887
+ // 显示全部
888
+ this.displayedList = this.itemList;
889
+ this.arrowShow = false;
890
+ } else {
891
+ // 只显示两个
892
+ this.displayedList = this.itemList.slice(0, 2);
893
+ this.arrowShow = true;
894
+ }
895
+ }
896
+ },
897
+ openModal(item) {
898
+ this.fileList = item.fileList
899
+ this.showModal = true
900
+ },
901
+ getFirstName(item) {
902
+ return item ? item[0] : ''
903
+ },
904
+ showDate(item) {
905
+ if (item.auditResult){
906
+ if (item.auditResult === '00'){
907
+ return `发起时间:${item.auditDate}`
908
+ }
909
+ return `处理时间:${item.auditDate}`
910
+ }
911
+ return `接收时间:${item.createTime}`
912
+ },
913
+ getRecordTime(item) {
914
+ // 获取记录的时间用于排序
915
+ let timeStr = '';
916
+
917
+ if (item.auditResult && item.auditResult !== '') {
918
+ // 已审批记录:优先使用审批时间
919
+ timeStr = item.auditDate || item.createTime;
920
+ } else {
921
+ // 待审批记录:使用创建时间
922
+ timeStr = item.createTime || item.auditDate;
923
+ }
924
+
925
+ // 如果没有时间,使用当前时间
926
+ if (!timeStr) {
927
+ return new Date().toISOString();
928
+ }
929
+
930
+ // 标准化时间格式
931
+ try {
932
+ // 处理中国标准时间格式 "YYYY-MM-DD HH:mm:ss"
933
+ if (typeof timeStr === 'string') {
934
+ // 移除可能的多余空格
935
+ timeStr = timeStr.trim();
936
+
937
+ // 检查是否是标准格式 "YYYY-MM-DD HH:mm:ss"
938
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(timeStr)) {
939
+ // 直接使用 new Date() 解析,这样会按本地时区处理
940
+ let date = new Date(timeStr);
941
+ if (!isNaN(date.getTime())) {
942
+ return date.toISOString();
943
+ }
944
+ }
945
+
946
+ // 如果包含 T 但没有时区信息,添加本地时区
947
+ if (timeStr.includes('T') && !timeStr.includes('Z') && !timeStr.includes('+')) {
948
+ timeStr += 'Z';
949
+ }
950
+ }
951
+
952
+ // 验证时间是否有效
953
+ let date = new Date(timeStr);
954
+ if (isNaN(date.getTime())) {
955
+ console.warn('Invalid date format:', timeStr);
956
+ return new Date().toISOString();
957
+ }
958
+
959
+ return date.toISOString();
960
+ } catch (error) {
961
+ console.warn('Error parsing date:', timeStr, error);
962
+ return new Date().toISOString();
963
+ }
964
+ },
965
+ getTimelineDotColor(auditResult) {
966
+ if (this.auditPassStatus.includes(auditResult)) {
967
+ return '#005aff'; // 蓝色:通过状态
968
+ } else if (auditResult === '40' || auditResult === '50' ) {
969
+ return '#ffcc66'; // 黄色:驳回状态
970
+ } else if (auditResult === '60' || auditResult === '62' || auditResult === '83') {
971
+ return '#ed4014'; // 红色:撤回/委派状态
972
+ } else {
973
+ return '#ff9900'; // 橙色:其他状态
974
+ }
975
+ // if (auditResult === '00' || auditResult === '10' || auditResult === '11' || auditResult === '12' || auditResult === '13' || auditResult === '14'
976
+ // || auditResult === '30' || auditResult === '61') {
977
+ // return 'green'; // 绿色:通过/跳过/交回委派状态
978
+ // } else if (auditResult === '40') {
979
+ // return 'volcano'; // 火红色:驳回上一节点状态
980
+ // } else if (auditResult === '50' || auditResult === '83') {
981
+ // return 'red'; // 红色:驳回到原点/会签减签状态
982
+ // } else if (auditResult === '90') {
983
+ // return 'magenta'; // 品红色:驳回指定节点状态
984
+ // } else if (auditResult === '51') {
985
+ // return 'purple'; // 紫色:流程终止状态
986
+ // } else if (auditResult === '60' || auditResult === '62') {
987
+ // return 'blue'; // 蓝色:撤回/委派撤回状态
988
+ // } else if (auditResult === '80' || auditResult === '82') {
989
+ // return 'cyan'; // 青色:跳转/交接状态
990
+ // } else {
991
+ // return 'orange'; // 橙色:其他状态
992
+ // }
993
+ },
994
+ toggleSortBy(by) {
995
+ this.sortBy = by;
996
+ },
997
+ isPendingAudit(item) {
998
+ // 判断是否为待审批状态
999
+ // auditResult为空、null、undefined或者不在已审批状态列表中时,认为是待审批
1000
+ return !item.auditResult || !this.auditPassStatus.includes(item.auditResult);
1001
+ }
1002
+ },
1003
+ mounted() {
1004
+ this.displayPushButton = this.donePage && this.pushButton;
1005
+ this.itemList = this.list;
1006
+ if (this.itemList.length > 0) {
1007
+ // 初始化modals
1008
+ this.setModals.modals = this.itemList.map(() => ({value: false}));
1009
+ this.activeName = 'panel0';
1010
+ }
1011
+ },
1012
+ watch: {
1013
+ list: function (newValue) {
1014
+ this.$nextTick(() => {
1015
+ this.itemList = newValue;
1016
+ if (this.itemList.length > 0) {
1017
+ this.setModals.modals = this.itemList.map(() => ({value: false}));
1018
+ }
1019
+ });
1020
+ }
1021
+ }
1022
+
1023
+
1024
+ }
1025
+ </script>
1026
+
1027
+ <style scoped>
1028
+ @import "../styles/css/index.less";
1029
+
1030
+ .history-controls {
1031
+ padding: 12px;
1032
+ background-color: #f8f9fa;
1033
+ border-radius: 6px;
1034
+ border: 1px solid #e8eaec;
1035
+ text-align: center;
1036
+ }
1037
+
1038
+ .history-controls .ivu-btn-group .ivu-btn {
1039
+ margin-right: 0;
1040
+ }
1041
+
1042
+ .history-controls .ivu-btn-group .ivu-btn + .ivu-btn {
1043
+ margin-left: -1px;
1044
+ }
1045
+
1046
+ .history-title {
1047
+ font-weight: 600;
1048
+ color: #2c3e50;
1049
+ }
1050
+
1051
+ .pending-audit-title {
1052
+ font-weight: 600;
1053
+ color: orange;
1054
+ font-size: 14px;
1055
+ display: flex;
1056
+ align-items: center;
1057
+ }
1058
+
1059
+ /* 按时间排序的卡片样式 - 更小,不遮挡时间轴圆点 */
1060
+ .portrait-card-time {
1061
+ margin-left: 20px !important;
1062
+ margin-right: 10px !important;
1063
+ margin-bottom: 8px !important;
1064
+ background-color: #fafafa !important;
1065
+ }
1066
+
1067
+ :deep(.portrait-card-time .ivu-card-body) {
1068
+ padding: 8px 12px !important;
1069
+ }
1070
+
1071
+ :deep(.portrait-card-time .ivu-list-item) {
1072
+ padding: 6px 0 !important;
1073
+ }
1074
+
1075
+ :deep(.docx) {
1076
+ width: 100% !important;
1077
+ }
1078
+
1079
+ :deep(.ivu-timeline-item-tail) {
1080
+ border-style: solid;
1081
+ border-left: none;
1082
+ border-color: #005aff;
1083
+ border-width: thin;
1084
+ }
1085
+
1086
+ :deep(.ivu-list-item) {
1087
+ padding-bottom: 0 !important;
1088
+ }
1089
+
1090
+ :deep(.ivu-collapse-content-box) {
1091
+ padding: 0 !important;
1092
+ }
1093
+
1094
+ :deep(.ivu-card-body) {
1095
+ padding: 5px !important;
1096
+ }
1097
+
1098
+ .todo-remind-dialog {
1099
+ padding: 8px 8px 0;
1100
+ }
1101
+
1102
+ .todo-remind-row {
1103
+ display: flex;
1104
+ align-items: center;
1105
+ margin-bottom: 20px;
1106
+ }
1107
+
1108
+ .todo-remind-row-top {
1109
+ align-items: flex-start;
1110
+ }
1111
+
1112
+ .todo-remind-label {
1113
+ width: 96px;
1114
+ flex-shrink: 0;
1115
+ color: #17233d;
1116
+ line-height: 32px;
1117
+ text-align: right;
1118
+ }
1119
+
1120
+ .todo-remind-body {
1121
+ flex: 1;
1122
+ min-width: 0;
1123
+ }
1124
+
1125
+ .todo-remind-type-group {
1126
+ display: flex;
1127
+ align-items: center;
1128
+ flex-wrap: wrap;
1129
+ gap: 20px;
1130
+ min-height: 32px;
1131
+ }
1132
+
1133
+ .todo-remind-type-group .ivu-checkbox-wrapper {
1134
+ margin-right: 0;
1135
+ }
1136
+
1137
+ .todo-remind-textarea textarea {
1138
+ resize: none;
1139
+ min-height: 132px;
1140
+ }
1141
+
1142
+ .todo-remind-variable-list {
1143
+ display: flex;
1144
+ flex-wrap: wrap;
1145
+ gap: 12px;
1146
+ }
1147
+
1148
+ .todo-remind-variable {
1149
+ min-width: 72px;
1150
+ height: 28px;
1151
+ padding: 0 10px;
1152
+ border: 1px solid #dcdfe6;
1153
+ border-radius: 4px;
1154
+ background: #fff;
1155
+ color: #515a6e;
1156
+ cursor: pointer;
1157
+ transition: all 0.2s ease;
1158
+ font-size: 12px;
1159
+ line-height: 28px;
1160
+ }
1161
+
1162
+ .todo-remind-variable:hover {
1163
+ color: #2d8cf0;
1164
+ border-color: #2d8cf0;
1165
+ background: #f0faff;
1166
+ }
1167
+
1168
+ .todo-remind-modal .ivu-modal-header {
1169
+ padding: 16px 24px;
1170
+ }
1171
+
1172
+ .todo-remind-modal .ivu-modal-body {
1173
+ padding: 20px 24px 12px;
1174
+ }
1175
+
1176
+ .todo-remind-modal .ivu-modal-footer {
1177
+ padding: 10px 24px 20px;
1178
+ border-top: none;
1179
+ }
1180
+
1181
+ </style>