vue2-client 1.14.55 → 1.14.57

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,261 +1,261 @@
1
- <template>
2
- <div class="timeline" ref="timelineContent" style="white-space: nowrap; overflow-x: auto">
3
- <a-steps
4
- :current="currentStepId"
5
- :initial="1">
6
- <template #progressDot="{index}">
7
- <span v-if="!changeAble" class="step-label in-steps"><i :class="['step-icon', getStepIconColor(index)]"></i></span>
8
- <span v-else :key="displayStepId" :class="['step-icon-filled digital', getStepIconColor(index)]">
9
- <a-icon v-if="index < currentStepId || state" type="check-circle" theme="filled" :class="['step-icon-filled icon', getStepIconColor(index)]" />
10
- <span v-else :class="['step-icon-filled digital', getStepIconColor(index)]">{{ index }}</span>
11
- </span>
12
- </template>
13
- <a-step
14
- v-for="item in steps"
15
- :key="item.id"
16
- class="step-item"
17
- @click.stop="onStepClick(item.id)"
18
- >
19
- <template #title>
20
- <h3 v-if="item.name">{{ item.name }}</h3>
21
- </template>
22
- <template #description>
23
- <div>
24
- <p v-if="item.handler">负责人:
25
- <trim-text-tail :text="item.handler" :length="14"/>
26
- </p>
27
- <p v-if="item.date">填报时间:{{ formatDate(item.date, 'yyyy-MM-dd hh:mm') }}</p>
28
- <p v-if="item.deadline" :class="{ 'text-red': item.id === currentStepId && isOverdue }">截止时间:{{ formatDate(item.deadline, 'yyyy-MM-dd hh:mm') }}</p>
29
- <p v-if="item.note">备注:
30
- <trim-text-tail :text="item.note" :length="15"/>
31
- </p>
32
- </div>
33
- </template>
34
- </a-step>
35
- </a-steps>
36
- </div>
37
- </template>
38
-
39
- <script>
40
- import { formatDate } from '@vue2-client/utils/util'
41
- import TrimTextTail from './TrimTextTail'
42
-
43
- export default {
44
- components: { TrimTextTail },
45
- name: 'WorkFlowTimeline',
46
- props: {
47
- workflowId: {
48
- type: String,
49
- required: true
50
- },
51
- currentStepId: {
52
- type: Number,
53
- required: false,
54
- default: 1
55
- },
56
- activeStepId: {
57
- type: Number,
58
- required: false,
59
- default: 1
60
- },
61
- state: {
62
- type: [Boolean, Number],
63
- required: false,
64
- default: false
65
- },
66
- steps: {
67
- type: Array,
68
- required: true
69
- },
70
- changeAble: {
71
- type: Boolean,
72
- required: false,
73
- default: false
74
- }
75
- },
76
- data () {
77
- return {
78
- // 当前显示的步骤 id
79
- displayStepId: 1,
80
- // 当前任务是否逾期
81
- isOverdue: false
82
- }
83
- },
84
- mounted () {
85
- this.init()
86
- const timelineContent = this.$refs.timelineContent
87
- timelineContent.onmousewheel = function (e) {
88
- timelineContent.scrollLeft -= e.wheelDelta
89
- e.preventDefault()
90
- }
91
- },
92
- beforeDestroy () {
93
- this.$refs.timelineContent.onmousewheel = null
94
- },
95
- methods: {
96
- init () {
97
- this.displayStepId = this.activeStepId
98
- const deadline = this.steps.find(step => step.id === this.currentStepId).deadline
99
- this.isOverdue = !this.state && this.formatDate(new Date(), 'yyyy-MM-dd hh:mm') > deadline
100
- this.$emit('activeStep', this.displayStepId)
101
- },
102
- // 判断id是否为流程中最后一个
103
- isLastStep (stepId) {
104
- return stepId >= this.steps.length
105
- },
106
- // 动态展示时间线节点颜色
107
- getStepIconColor (stepId) {
108
- if (this.changeAble && stepId === this.displayStepId) {
109
- return 'yellow'
110
- } else if (!this.changeAble && stepId === this.currentStepId) {
111
- return this.state ? 'blue' : 'green'
112
- }
113
- if (stepId < this.currentStepId || this.state) {
114
- return 'blue'
115
- }
116
- if (stepId === this.currentStepId) {
117
- return this.isOverdue ? 'red' : 'blue'
118
- }
119
- return 'gray'
120
- },
121
- onStepClick (stepId) {
122
- if (!this.changeAble || stepId === this.displayStepId) {
123
- return
124
- }
125
- if (stepId > this.currentStepId) {
126
- return this.$message.warn('请先完成当前步骤')
127
- }
128
- this.$emit('activeStep', stepId)
129
- this.displayStepId = stepId
130
- },
131
- formatDate,
132
- },
133
- watch: {
134
- activeStepId: function (newVal) {
135
- this.displayStepId = newVal
136
- }
137
- }
138
- }
139
- </script>
140
-
141
- <style lang="less" scoped>
142
- .timeline {
143
- /deep/ .ant-steps-dot {
144
- margin-bottom: 6px;
145
- .ant-steps-item-tail {
146
- top: 7px;
147
- margin-left: 106px;
148
- width: calc(100% - 23px);
149
- }
150
- .ant-steps-item-icon {
151
- margin-left: 90px;
152
- width: 8px;
153
- height: 8px;
154
- line-height: 8px;
155
- }
156
- .ant-steps-item-content {
157
- min-width: 200px;
158
- margin-top: 24px;
159
- .ant-steps-item-description {
160
- text-align: left;
161
- background: #fff;
162
- border-radius: 4px;
163
- padding: 12px;
164
- position: relative;
165
-
166
- p {
167
- position: relative;
168
- padding: 8px 0;
169
- margin: 0;
170
- font-size: 13px;
171
- color: #595959;
172
- line-height: 1.5;
173
- white-space: nowrap;
174
- overflow: visible;
175
-
176
- &:first-child {
177
- border-top: 1px dotted #e8e8e8;
178
- }
179
-
180
- &:not(:last-child) {
181
- border-bottom: 1px dashed #f0f0f0;
182
- }
183
-
184
- &:last-child {
185
- padding-bottom: 0;
186
- }
187
- }
188
- }
189
- }
190
- &.ant-steps {
191
- padding-top: 8px;
192
- }
193
- }
194
- /deep/ .ant-steps-item-title {
195
- font-size: 14px;
196
- h3 {
197
- margin-bottom: 0;
198
- }
199
- }
200
- @red: rgb(255, 77, 79);
201
- .step-icon-filled {
202
- position: absolute;
203
- top: -2px;
204
- left: -6px;
205
- @blue: rgb(24, 144, 255);
206
- @yellow: rgb(255, 164, 39);
207
- @gray: rgb(191, 191, 191);
208
- &.digital {
209
- width: 28px;
210
- line-height: 28px;
211
- border-radius: 50%;
212
- color: #ffffff;
213
- &.blue {
214
- background: @blue;
215
- }
216
- &.yellow {
217
- background: @yellow;
218
- }
219
- &.gray {
220
- background: @gray;
221
- }
222
- &.red {
223
- background: @red;
224
- }
225
- }
226
- &.icon {
227
- font-size: 28px;
228
- &.blue {
229
- color: @blue;
230
- }
231
- &.yellow {
232
- color: @yellow;
233
- }
234
- &.gray {
235
- color: @gray;
236
- }
237
- }
238
- }
239
- .step-item {
240
- cursor: pointer;
241
- &:hover {
242
- .ant-steps-item-description {
243
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
244
- transform: translateY(-1px);
245
- transition: all 0.3s ease;
246
- }
247
- p {
248
- color: #262626;
249
- }
250
- }
251
- .text-red {
252
- color: @red !important;
253
- }
254
- }
255
- .next-step-overdue {
256
- /deep/ .ant-steps-item-tail::after {
257
- background: @red;
258
- }
259
- }
260
- }
261
- </style>
1
+ <template>
2
+ <div class="timeline" ref="timelineContent" style="white-space: nowrap; overflow-x: auto">
3
+ <a-steps
4
+ :current="currentStepId"
5
+ :initial="1">
6
+ <template #progressDot="{index}">
7
+ <span v-if="!changeAble" class="step-label in-steps"><i :class="['step-icon', getStepIconColor(index)]"></i></span>
8
+ <span v-else :key="displayStepId" :class="['step-icon-filled digital', getStepIconColor(index)]">
9
+ <a-icon v-if="index < currentStepId || state" type="check-circle" theme="filled" :class="['step-icon-filled icon', getStepIconColor(index)]" />
10
+ <span v-else :class="['step-icon-filled digital', getStepIconColor(index)]">{{ index }}</span>
11
+ </span>
12
+ </template>
13
+ <a-step
14
+ v-for="item in steps"
15
+ :key="item.id"
16
+ class="step-item"
17
+ @click.stop="onStepClick(item.id)"
18
+ >
19
+ <template #title>
20
+ <h3 v-if="item.name">{{ item.name }}</h3>
21
+ </template>
22
+ <template #description>
23
+ <div>
24
+ <p v-if="item.handler">负责人:
25
+ <trim-text-tail :text="item.handler" :length="14"/>
26
+ </p>
27
+ <p v-if="item.date">填报时间:{{ formatDate(item.date, 'yyyy-MM-dd hh:mm') }}</p>
28
+ <p v-if="item.deadline" :class="{ 'text-red': item.id === currentStepId && isOverdue }">截止时间:{{ formatDate(item.deadline, 'yyyy-MM-dd hh:mm') }}</p>
29
+ <p v-if="item.note">备注:
30
+ <trim-text-tail :text="item.note" :length="15"/>
31
+ </p>
32
+ </div>
33
+ </template>
34
+ </a-step>
35
+ </a-steps>
36
+ </div>
37
+ </template>
38
+
39
+ <script>
40
+ import { formatDate } from '@vue2-client/utils/util'
41
+ import TrimTextTail from './TrimTextTail'
42
+
43
+ export default {
44
+ components: { TrimTextTail },
45
+ name: 'WorkFlowTimeline',
46
+ props: {
47
+ workflowId: {
48
+ type: String,
49
+ required: true
50
+ },
51
+ currentStepId: {
52
+ type: Number,
53
+ required: false,
54
+ default: 1
55
+ },
56
+ activeStepId: {
57
+ type: Number,
58
+ required: false,
59
+ default: 1
60
+ },
61
+ state: {
62
+ type: [Boolean, Number],
63
+ required: false,
64
+ default: false
65
+ },
66
+ steps: {
67
+ type: Array,
68
+ required: true
69
+ },
70
+ changeAble: {
71
+ type: Boolean,
72
+ required: false,
73
+ default: false
74
+ }
75
+ },
76
+ data () {
77
+ return {
78
+ // 当前显示的步骤 id
79
+ displayStepId: 1,
80
+ // 当前任务是否逾期
81
+ isOverdue: false
82
+ }
83
+ },
84
+ mounted () {
85
+ this.init()
86
+ const timelineContent = this.$refs.timelineContent
87
+ timelineContent.onmousewheel = function (e) {
88
+ timelineContent.scrollLeft -= e.wheelDelta
89
+ e.preventDefault()
90
+ }
91
+ },
92
+ beforeDestroy () {
93
+ this.$refs.timelineContent.onmousewheel = null
94
+ },
95
+ methods: {
96
+ init () {
97
+ this.displayStepId = this.activeStepId
98
+ const deadline = this.steps.find(step => step.id === this.currentStepId).deadline
99
+ this.isOverdue = !this.state && this.formatDate(new Date(), 'yyyy-MM-dd hh:mm') > deadline
100
+ this.$emit('activeStep', this.displayStepId)
101
+ },
102
+ // 判断id是否为流程中最后一个
103
+ isLastStep (stepId) {
104
+ return stepId >= this.steps.length
105
+ },
106
+ // 动态展示时间线节点颜色
107
+ getStepIconColor (stepId) {
108
+ if (this.changeAble && stepId === this.displayStepId) {
109
+ return 'yellow'
110
+ } else if (!this.changeAble && stepId === this.currentStepId) {
111
+ return this.state ? 'blue' : 'green'
112
+ }
113
+ if (stepId < this.currentStepId || this.state) {
114
+ return 'blue'
115
+ }
116
+ if (stepId === this.currentStepId) {
117
+ return this.isOverdue ? 'red' : 'blue'
118
+ }
119
+ return 'gray'
120
+ },
121
+ onStepClick (stepId) {
122
+ if (!this.changeAble || stepId === this.displayStepId) {
123
+ return
124
+ }
125
+ if (stepId > this.currentStepId) {
126
+ return this.$message.warn('请先完成当前步骤')
127
+ }
128
+ this.$emit('activeStep', stepId)
129
+ this.displayStepId = stepId
130
+ },
131
+ formatDate,
132
+ },
133
+ watch: {
134
+ activeStepId: function (newVal) {
135
+ this.displayStepId = newVal
136
+ }
137
+ }
138
+ }
139
+ </script>
140
+
141
+ <style lang="less" scoped>
142
+ .timeline {
143
+ /deep/ .ant-steps-dot {
144
+ margin-bottom: 6px;
145
+ .ant-steps-item-tail {
146
+ top: 7px;
147
+ margin-left: 106px;
148
+ width: calc(100% - 23px);
149
+ }
150
+ .ant-steps-item-icon {
151
+ margin-left: 90px;
152
+ width: 8px;
153
+ height: 8px;
154
+ line-height: 8px;
155
+ }
156
+ .ant-steps-item-content {
157
+ min-width: 200px;
158
+ margin-top: 24px;
159
+ .ant-steps-item-description {
160
+ text-align: left;
161
+ background: #fff;
162
+ border-radius: 4px;
163
+ padding: 12px;
164
+ position: relative;
165
+
166
+ p {
167
+ position: relative;
168
+ padding: 8px 0;
169
+ margin: 0;
170
+ font-size: 13px;
171
+ color: #595959;
172
+ line-height: 1.5;
173
+ white-space: nowrap;
174
+ overflow: visible;
175
+
176
+ &:first-child {
177
+ border-top: 1px dotted #e8e8e8;
178
+ }
179
+
180
+ &:not(:last-child) {
181
+ border-bottom: 1px dashed #f0f0f0;
182
+ }
183
+
184
+ &:last-child {
185
+ padding-bottom: 0;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ &.ant-steps {
191
+ padding-top: 8px;
192
+ }
193
+ }
194
+ /deep/ .ant-steps-item-title {
195
+ font-size: 14px;
196
+ h3 {
197
+ margin-bottom: 0;
198
+ }
199
+ }
200
+ @red: rgb(255, 77, 79);
201
+ .step-icon-filled {
202
+ position: absolute;
203
+ top: -2px;
204
+ left: -6px;
205
+ @blue: rgb(24, 144, 255);
206
+ @yellow: rgb(255, 164, 39);
207
+ @gray: rgb(191, 191, 191);
208
+ &.digital {
209
+ width: 28px;
210
+ line-height: 28px;
211
+ border-radius: 50%;
212
+ color: #ffffff;
213
+ &.blue {
214
+ background: @blue;
215
+ }
216
+ &.yellow {
217
+ background: @yellow;
218
+ }
219
+ &.gray {
220
+ background: @gray;
221
+ }
222
+ &.red {
223
+ background: @red;
224
+ }
225
+ }
226
+ &.icon {
227
+ font-size: 28px;
228
+ &.blue {
229
+ color: @blue;
230
+ }
231
+ &.yellow {
232
+ color: @yellow;
233
+ }
234
+ &.gray {
235
+ color: @gray;
236
+ }
237
+ }
238
+ }
239
+ .step-item {
240
+ cursor: pointer;
241
+ &:hover {
242
+ .ant-steps-item-description {
243
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
244
+ transform: translateY(-1px);
245
+ transition: all 0.3s ease;
246
+ }
247
+ p {
248
+ color: #262626;
249
+ }
250
+ }
251
+ .text-red {
252
+ color: @red !important;
253
+ }
254
+ }
255
+ .next-step-overdue {
256
+ /deep/ .ant-steps-item-tail::after {
257
+ background: @red;
258
+ }
259
+ }
260
+ }
261
+ </style>
@@ -64,8 +64,8 @@ routerResource.example = {
64
64
  // component: () => import('@vue2-client/base-client/components/common/XTab/XTabDemo.vue'),
65
65
  // component: () => import('@vue2-client/base-client/components/common/XRate/demo.vue'),
66
66
  // component: () => import('@vue2-client/base-client/components/common/XForm/demo.vue'),
67
- component: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelectDemo.vue'),
68
- // component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
67
+ // component: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelectDemo.vue'),
68
+ component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
69
69
  // component: () => import('@vue2-client/base-client/components/common/XConversation/XConversationDemo.vue'),
70
70
  // component: () => import('@vue2-client/base-client/components/common/XButtons/XButtonDemo.vue'),
71
71
  // component: () => import('@vue2-client/base-client/components/common/XLabelSelect/XLabelSelectDemo.vue'),
@@ -81,13 +81,23 @@ function startEventStreamPOST (url, params, headers = {}, onDataUpdate, onError,
81
81
  return startEventStream(url, params, headers, onDataUpdate, onError, serviceName, 'POST')
82
82
  }
83
83
  function startEventStream (url, params, headers = {}, onDataUpdate, onError, serviceName = process.env.VUE_APP_SYSTEM_NAME, method = 'GET') {
84
+ // let isStopped = false
84
85
  let isStopped = false
86
+ let requestCount = 0
87
+
85
88
  if (!(url.startsWith('api/') || url.startsWith('/api/') || url.startsWith('http'))) {
86
89
  if (!url.startsWith('/')) {
87
90
  url = '/' + url
88
91
  }
89
92
  url = '/api/' + serviceName + url
90
93
  }
94
+
95
+ console.log(`[EventStream] 开始新的请求: ${url}`, {
96
+ params,
97
+ headers,
98
+ method
99
+ })
100
+
91
101
  const controller = new AbortController()
92
102
  const { signal } = controller
93
103
  const token = localStorage.getItem(ACCESS_TOKEN)
@@ -101,38 +111,99 @@ function startEventStream (url, params, headers = {}, onDataUpdate, onError, ser
101
111
  headers[ACCESS_TOKEN] = token
102
112
  }
103
113
  }
104
- // 开始请求
105
- fetchEventSource(url, {
114
+ fetch(url, {
106
115
  method: method,
107
- headers: headers,
108
- body: JSON.stringify(params),
109
- signal,
110
- onopen (response) {
111
- if (!response.ok) {
112
- const error = new Error(`Error: ${response.status} ${response.statusText}`)
113
- onError?.(error)
114
- throw error
115
- }
116
- console.log('连接已打开')
116
+ headers: {
117
+ ...headers,
118
+ Accept: 'text/event-stream',
119
+ 'Cache-Control': 'no-cache',
120
+ Connection: 'keep-alive'
117
121
  },
118
- onmessage (event) {
119
- if (isStopped) return
120
- const data = event.data
121
- try {
122
- const parsedData = JSON.parse(data)
123
- onDataUpdate?.(parsedData)
124
- } catch (e) {
125
- onDataUpdate?.(data)
122
+ body: JSON.stringify(params),
123
+ signal
124
+ }).then(response => {
125
+ if (!response.ok) {
126
+ throw new Error(`HTTP error! status: ${response.status}`)
127
+ }
128
+
129
+ requestCount++
130
+ console.log(`[EventStream] 请求已打开 (第${requestCount}次): ${url}`, {
131
+ status: response.status,
132
+ statusText: response.statusText
133
+ })
134
+
135
+ const reader = response.body.getReader()
136
+ const decoder = new TextDecoder()
137
+ let buffer = ''
138
+
139
+ function processStream () {
140
+ if (isStopped) {
141
+ console.log(`[EventStream] 请求已停止,停止读取流: ${url}`)
142
+ return
126
143
  }
127
- },
128
- onerror (error) {
129
- if (isStopped) return
130
- console.error('发生错误:', error)
144
+
145
+ reader.read().then(({ done, value }) => {
146
+ if (done) {
147
+ console.log(`[EventStream] 流读取完成: ${url}`)
148
+ return
149
+ }
150
+
151
+ const chunk = decoder.decode(value, { stream: true })
152
+ buffer += chunk
153
+
154
+ // 处理接收到的数据
155
+ const lines = buffer.split('\n')
156
+ buffer = lines.pop() // 保留最后一个不完整的行
157
+
158
+ let currentEvent = null
159
+ let currentData = null
160
+
161
+ for (const line of lines) {
162
+ if (line.startsWith('event: ')) {
163
+ currentEvent = line.slice(7)
164
+ } else if (line.startsWith('data: ')) {
165
+ currentData = line.slice(6)
166
+ try {
167
+ const parsedData = JSON.parse(currentData)
168
+ if (currentEvent === 'additionalInfo') {
169
+ // 触发 additionalInfo 回调
170
+ onDataUpdate?.(parsedData, 'additionalInfo')
171
+ } else {
172
+ // 普通消息数据
173
+ onDataUpdate?.(parsedData, 'sourceInfo')
174
+ }
175
+ } catch (e) {
176
+ if (currentEvent === 'sourceInfo') {
177
+ // 触发 sourceInfo 回调
178
+ onDataUpdate?.(currentData, 'sourceInfo')
179
+ } else {
180
+ onDataUpdate?.(currentData, 'data')
181
+ }
182
+ }
183
+ // 重置当前事件和数据
184
+ currentEvent = null
185
+ currentData = null
186
+ }
187
+ }
188
+
189
+ processStream()
190
+ }).catch(error => {
191
+ console.error(`[EventStream] 流读取错误: ${url}`, error)
192
+ if (!isStopped) {
193
+ isStopped = true
194
+ onError?.(error)
195
+ }
196
+ })
197
+ }
198
+
199
+ processStream()
200
+ }).catch(error => {
201
+ console.error(`[EventStream] 请求错误: ${url}`, error)
202
+ if (!isStopped) {
131
203
  isStopped = true
132
- controller.abort()
133
204
  onError?.(error)
134
- },
135
- }).then(r => {})
205
+ }
206
+ })
136
207
 
137
208
  // 返回停止函数
138
209
  return () => {