vue2-client 1.18.46 → 1.18.49

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.
@@ -14,12 +14,10 @@
14
14
  :defaultQueryForm="{
15
15
  s_f_user_name: '张三'
16
16
  }"
17
- serviceName="af-safecheck"
17
+ serviceName="af-system"
18
18
  ref="xFormTable"
19
- >
20
- </x-form-table>
21
- <div style="height: 100px; width: 100%; background-color: #f0f2f5;">
22
- </div>
19
+ ></x-form-table>
20
+ <div style="height: 100px; width: 100%; background-color: #f0f2f5"></div>
23
21
  </a-card>
24
22
  </template>
25
23
 
@@ -29,12 +27,12 @@ import { microDispatch } from '@vue2-client/utils/microAppUtils'
29
27
  export default {
30
28
  name: 'Demo',
31
29
  components: {
32
- XFormTable,
30
+ XFormTable
33
31
  },
34
- data () {
32
+ data() {
35
33
  return {
36
34
  // 查询配置文件名
37
- queryParamsName: 'checkPlanListCRUD',
35
+ queryParamsName: 'ceshiCRUD',
38
36
  // 查询配置左侧tree
39
37
  xTreeConfigName: 'addressType',
40
38
  // 新增表单固定值
@@ -47,30 +45,21 @@ export default {
47
45
  selectedKeys: [],
48
46
  selected: {
49
47
  keys: [],
50
- rows: [],
51
- },
48
+ rows: []
49
+ }
52
50
  }
53
51
  },
54
52
 
55
53
  methods: {
56
54
  // ========== 原有方法 ==========
57
- rowDblClick (record) {
55
+ rowDblClick(record) {
58
56
  console.log('rowDblClick', record)
59
57
  },
60
58
  // input框 行編輯參數傳遞
61
- ceshi (...args) {
59
+ ceshi(...args) {
62
60
  // attr, value, currentRecord, currentIndex, nextRecord, nextIndex
63
- const [attr, value, currentRecord, currentIndex, nextRecord, nextIndex] =
64
- args
65
- console.log(
66
- 'ceshi',
67
- attr,
68
- value,
69
- currentRecord,
70
- currentIndex,
71
- nextRecord,
72
- nextIndex
73
- )
61
+ const [attr, value, currentRecord, currentIndex, nextRecord, nextIndex] = args
62
+ console.log('ceshi', attr, value, currentRecord, currentIndex, nextRecord, nextIndex)
74
63
  // 示例:当按下 Enter 键且有下一行时,跳转到下一行的同一列
75
64
  // 可以在这里执行业务逻辑(例如:保存数据)
76
65
  console.log('当前行:', currentIndex, '下一行:', nextIndex)
@@ -81,42 +70,42 @@ export default {
81
70
  // 跳转到下一行的同一列
82
71
  this.$refs.xFormTable.$refs.xTable.focusInput(nextIndex, attr.model)
83
72
  },
84
- test () {
73
+ test() {
85
74
  this.$refs.xFormTable.setTableData([])
86
75
  },
87
- defaultF () {
76
+ defaultF() {
88
77
  this.$refs.xFormTable.setTableSize('default')
89
78
  },
90
- middleF () {
79
+ middleF() {
91
80
  this.$refs.xFormTable.setTableSize('middle')
92
81
  },
93
- smallF () {
82
+ smallF() {
94
83
  this.$refs.xFormTable.setTableSize('small')
95
84
  },
96
- columnClick (key, value, record) {
85
+ columnClick(key, value, record) {
97
86
  microDispatch({
98
87
  type: 'v3route',
99
88
  path: '/bingliguanli/dianzibingliluru',
100
- props: { selected: arguments[0].his_f_admission_id },
89
+ props: { selected: arguments[0].his_f_admission_id }
101
90
  })
102
91
  },
103
- action (record, id, actionType) {
92
+ action(record, id, actionType) {
104
93
  this.detailVisible = true
105
94
  console.log('触发了详情操作', record, id, actionType)
106
95
  },
107
- onClose () {
96
+ onClose() {
108
97
  this.detailVisible = false
109
98
  // 关闭详情之后重新查询表单
110
99
  this.$refs.xFormTable.refreshTable(true)
111
100
  },
112
- selectRow (selectedRowKeys, selectedRows) {
101
+ selectRow(selectedRowKeys, selectedRows) {
113
102
  this.selected = {
114
103
  keys: selectedRowKeys,
115
- rows: selectedRows,
104
+ rows: selectedRows
116
105
  }
117
106
  console.log('selectedDemo', this.selected)
118
- },
119
- },
107
+ }
108
+ }
120
109
  }
121
110
  </script>
122
111
 
@@ -200,6 +200,33 @@ export default {
200
200
  if (initStatus) {
201
201
  this.activeKey = this.defaultActiveKey
202
202
  }
203
+
204
+ // 每次切换后,重新应用单个页签的显示/隐藏规则
205
+ this.applyTabHeaderVisibility()
206
+ },
207
+ applyTabHeaderVisibility () {
208
+ // 根级 showTabBar 为 false 时,整个头都不显示,就不再处理单个页签
209
+ if (!this.showTabBar || !this.config || !Array.isArray(this.config.data)) return
210
+
211
+ this.$nextTick(() => {
212
+ try {
213
+ const navTabs = this.$el.querySelectorAll('.ant-tabs-nav .ant-tabs-tab')
214
+ if (!navTabs || !navTabs.length) return
215
+
216
+ this.config.data.forEach((tab, index) => {
217
+ const tabNode = navTabs[index]
218
+ if (!tabNode) return
219
+ if (tab && tab.showTabBar === false) {
220
+ tabNode.style.display = 'none'
221
+ } else {
222
+ tabNode.style.display = ''
223
+ }
224
+ })
225
+ } catch (e) {
226
+ // DOM 结构异常时不影响主流程
227
+ console.error('applyTabHeaderVisibility error', e)
228
+ }
229
+ })
203
230
  },
204
231
 
205
232
  // 新增:等待组件就绪的方法
@@ -331,6 +358,8 @@ export default {
331
358
  if (this.config && this.config.data) {
332
359
  this.markTabAsLoaded(this.defaultActiveKey)
333
360
  }
361
+ // 初始化完成后,根据每个 tab 的 showTabBar 设置头部显示
362
+ this.applyTabHeaderVisibility()
334
363
  },
335
364
  getConfig () {
336
365
  getConfigByName(this.configName, this.serverName, res => {
@@ -341,6 +370,8 @@ export default {
341
370
  if (this.config && this.config.data) {
342
371
  this.markTabAsLoaded(this.defaultActiveKey)
343
372
  }
373
+ // 从服务端配置加载完成后,应用单个页签的显示/隐藏
374
+ this.applyTabHeaderVisibility()
344
375
  }, this.env === 'dev')
345
376
  },
346
377
  getEventHandlers (tab, index) {
@@ -614,9 +614,10 @@ export default {
614
614
  f_file_name: this.fileName
615
615
  }
616
616
  // 保存HTML文档和结构化数据到后端服务
617
+ const queryParams = {admissionId: this.admissionId, f_template_id: this.templateId}
617
618
  runLogic(this.saveDataLogicName, data, this.serviceName).then(res => {
618
619
  this.$message.success('保存成功')
619
- this.changeRes(res.currResData.id)
620
+ this.reload({...queryParams,resId: res.currResData.id})
620
621
  this.$emit('saveafter', data.dataObject)
621
622
  }).finally(() => {
622
623
  this.resDataModalVisible = false
@@ -25,8 +25,7 @@ routerResource.chargeQuery = () => import('@vue2-client/base-client/components/c
25
25
  // --------------------------------------仪表盘--------------------------------------
26
26
  routerResource.dashboard = view.blank
27
27
  // 工作台
28
- routerResource.workplace = () =>
29
- import('@vue2-client/pages/dashboard/workplace')
28
+ routerResource.workplace = () => import('@vue2-client/pages/dashboard/workplace')
30
29
  // --------------------------------------系统配置--------------------------------------
31
30
  routerResource.system = view.blank
32
31
  // 字典管理
@@ -51,13 +50,13 @@ routerResource.dynamicStatistics = () => import('@vue2-client/pages/DynamicStati
51
50
  routerResource.newDynamicStatistics = () => import('@vue2-client/pages/NewDynamicStatistics')
52
51
  // 示例页面
53
52
  routerResource.example = {
54
- path: 'example',
53
+ path: 'example',
55
54
  name: '示例主页面',
56
55
  // component: () => import('@vue2-client/base-client/components/common/XDescriptions/demo.vue'),
57
56
  // component: () => import('@vue2-client/base-client/components/his/HChart/demo.vue'),
58
57
  // component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
59
- // component: () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue'),
60
- component: () => import('@vue2-client/base-client/components/common/ImagePreviewModal/demo.vue'),
58
+ component: () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue')
59
+ // component: () => import('@vue2-client/base-client/components/common/ImagePreviewModal/demo.vue'),
61
60
  // component: () => import('@vue2-client/components/xScrollBox/example.vue'),
62
61
  // component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
63
62
  // component: () => import('@vue2-client/pages/addressSelect/addressDemo.vue'),
@@ -103,34 +102,30 @@ const routerMap = {
103
102
  login: {
104
103
  authority: '*',
105
104
  path: '/login',
106
- component: process.env.VUE_APP_LOGIN_VERSION === 'V3'
107
- ? view.loginv3 : view.login
105
+ component: process.env.VUE_APP_LOGIN_VERSION === 'V3' ? view.loginv3 : view.login
108
106
  },
109
107
  root: {
110
108
  path: '/',
111
109
  name: '首页',
112
110
  // 只有在非微前端环境下才进行重定向,或者通过环境变量控制
113
111
  redirect: window.__MICRO_APP_ENVIRONMENT__ ? undefined : homePage,
114
- component: process.env.VUE_APP_SINGLE_PAPER === 'TRUE' ? view.blank : view.tabs,
112
+ component: process.env.VUE_APP_SINGLE_PAPER === 'TRUE' ? view.blank : view.tabs
115
113
  },
116
114
  exp403: {
117
115
  authority: '*',
118
116
  name: 'exp403',
119
117
  path: '403',
120
- component: () =>
121
- import('@vue2-client/pages/exception/403')
118
+ component: () => import('@vue2-client/pages/exception/403')
122
119
  },
123
120
  exp404: {
124
121
  name: 'exp404',
125
122
  path: '404',
126
- component: () =>
127
- import('@vue2-client/pages/exception/404')
123
+ component: () => import('@vue2-client/pages/exception/404')
128
124
  },
129
125
  exp500: {
130
126
  name: 'exp500',
131
127
  path: '500',
132
- component: () =>
133
- import('@vue2-client/pages/exception/500')
128
+ component: () => import('@vue2-client/pages/exception/500')
134
129
  }
135
130
  }
136
131
  Object.assign(routerMap, routerResource)
@@ -141,13 +141,21 @@ export function parseConfig (configContent, configType) {
141
141
 
142
142
  /**
143
143
  * 通用执行业务逻辑
144
+ * @param {string} logicName - 业务逻辑名称
145
+ * @param {object} parameter - 请求参数
146
+ * @param {string} serviceName - 服务名称
147
+ * @param {boolean} isDev - 是否开发环境
148
+ * @param {object} config - 请求配置
149
+ * @param {boolean} config.dedupe - 是否启用去重(默认 true)
150
+ * @param {string} config.dedupeStrategy - 去重策略:'reject'(拒绝)或 'reuse'(复用结果)
151
+ * @param {boolean|string} config.globalLoading - 是否显示全局 Loading
144
152
  */
145
- export function runLogic (logicName, parameter, serviceName = process.env.VUE_APP_SYSTEM_NAME, isDev) {
153
+ export function runLogic (logicName, parameter, serviceName = process.env.VUE_APP_SYSTEM_NAME, isDev, config = {}) {
146
154
  let apiPre = '/api/'
147
155
  if (isDev) {
148
156
  apiPre = '/devApi/'
149
157
  }
150
- return post(apiPre + serviceName + '/logic/' + logicName, parameter)
158
+ return post(apiPre + serviceName + '/logic/' + logicName, parameter, config)
151
159
  }
152
160
 
153
161
  /**
@@ -19,7 +19,18 @@ function get (url, parameter) {
19
19
  * @param config 配置项
20
20
  * @param {boolean|string} config.globalLoading - 是否显示全局 Loading,可传入字符串作为提示文字
21
21
  * @param {boolean} config.dedupe - 是否启用请求去重(POST 默认开启,设为 false 可关闭)
22
+ * @param {string} config.dedupeStrategy - 去重策略:'reject'(拒绝,默认)或 'reuse'(复用结果)
22
23
  * @returns {Promise<AxiosResponse<T>>}
24
+ *
25
+ * @example
26
+ * // 表单提交 - 默认拒绝重复(防止重复提交)
27
+ * post('/api/save', formData)
28
+ *
29
+ * // 下拉框数据 - 复用结果(多处同时请求共享结果)
30
+ * post('/api/options', params, { dedupe: true, dedupeStrategy: 'reuse' })
31
+ *
32
+ * // 列表查询 - 关闭去重(允许重复查询)
33
+ * post('/api/list', params, { dedupe: false })
23
34
  */
24
35
  function post (url, parameter, config = {}) {
25
36
  // 兼容 config 为 null 的情况
@@ -1,7 +1,7 @@
1
1
  import { METHOD, request } from '@vue2-client/utils/request'
2
2
  import { runLogic } from '@vue2-client/services/api/common'
3
3
 
4
- function getLeafNodes (nodes) {
4
+ function getLeafNodes(nodes) {
5
5
  // 确保 nodes 是数组
6
6
  const nodeArray = Array.isArray(nodes) ? nodes : [nodes]
7
7
  return nodeArray.reduce((leaves, node) => {
@@ -16,7 +16,7 @@ function getLeafNodes (nodes) {
16
16
  }, [])
17
17
  }
18
18
 
19
- function getLeafNodesByCondition (type, nodes, parent = null) {
19
+ function getLeafNodesByCondition(type, nodes, parent = null) {
20
20
  // 确保 nodes 是数组
21
21
  const nodeArray = Array.isArray(nodes) ? nodes : [nodes]
22
22
 
@@ -29,7 +29,7 @@ function getLeafNodesByCondition (type, nodes, parent = null) {
29
29
  if (isValidNode) {
30
30
  leaves.push({
31
31
  ...node,
32
- name: parent ? `${node.name}-${parent}` : node.name,
32
+ name: parent ? `${node.name}-${parent}` : node.name
33
33
  })
34
34
  }
35
35
  // 递归处理子节点
@@ -38,7 +38,7 @@ function getLeafNodesByCondition (type, nodes, parent = null) {
38
38
  // 无子节点但符合条件时,直接加入叶子节点列表
39
39
  leaves.push({
40
40
  ...node,
41
- name: parent ? `${node.name}-${parent}` : node.name,
41
+ name: parent ? `${node.name}-${parent}` : node.name
42
42
  })
43
43
  }
44
44
 
@@ -46,8 +46,8 @@ function getLeafNodesByCondition (type, nodes, parent = null) {
46
46
  }, [])
47
47
  }
48
48
 
49
- function transformData (inputData) {
50
- function transform (node) {
49
+ function transformData(inputData) {
50
+ function transform(node) {
51
51
  return {
52
52
  label: node.name,
53
53
  value: node.id,
@@ -61,23 +61,31 @@ function transformData (inputData) {
61
61
  return inputData.map(transform)
62
62
  }
63
63
 
64
- function getResData (params, toCallback) {
65
- const data = { userId: params.userid, roleName: params.roleName, filter: params.filter, filterType: params.filterType }
64
+ // 复用策略配置:相同请求共享结果,避免重复网络请求
65
+ const REUSE_CONFIG = { dedupe: true, dedupeStrategy: 'reuse' }
66
+
67
+ function getResData(params, toCallback) {
68
+ const data = {
69
+ userId: params.userid,
70
+ roleName: params.roleName,
71
+ filter: params.filter,
72
+ filterType: params.filterType
73
+ }
66
74
  if (params.source === '获取分公司') {
67
- runLogic('getOrgBySearch', data, 'af-system').then(res => toCallback(res))
75
+ runLogic('getOrgBySearch', data, 'af-system', false, REUSE_CONFIG).then(res => toCallback(res))
68
76
  } else if (params.source === '获取部门') {
69
- runLogic('getDepBySearch', data, 'af-system').then(res => toCallback(res))
77
+ runLogic('getDepBySearch', data, 'af-system', false, REUSE_CONFIG).then(res => toCallback(res))
70
78
  } else if (params.source === '获取人员') {
71
- runLogic('getUserBySearch', data, 'af-system').then(res => toCallback(res))
79
+ runLogic('getUserBySearch', data, 'af-system', false, REUSE_CONFIG).then(res => toCallback(res))
72
80
  } else if (params.source === '根据角色获取人员') {
73
- runLogic('getUserBySearchRole', data, 'af-system').then(res => toCallback(res))
81
+ runLogic('getUserBySearchRole', data, 'af-system', false, REUSE_CONFIG).then(res => toCallback(res))
74
82
  } else {
75
83
  return search(params).then(res => toCallback(res))
76
84
  }
77
85
  }
78
86
 
79
- export async function searchToOption (params, callback) {
80
- function toCallback (res) {
87
+ export async function searchToOption(params, callback) {
88
+ function toCallback(res) {
81
89
  if (res.length) {
82
90
  if (res[0].children && res[0].children.length) {
83
91
  if (res[0].children[0].children) {
@@ -96,8 +104,8 @@ export async function searchToOption (params, callback) {
96
104
  await getResData(params, toCallback)
97
105
  }
98
106
 
99
- export async function searchToListOption (params, callback) {
100
- function toCallback (res) {
107
+ export async function searchToListOption(params, callback) {
108
+ function toCallback(res) {
101
109
  if (params.source.includes('人员')) {
102
110
  callback(transformData(getLeafNodes(res)))
103
111
  } else {
@@ -109,7 +117,7 @@ export async function searchToListOption (params, callback) {
109
117
  await getResData(params, toCallback)
110
118
  }
111
119
 
112
- export async function search (params) {
120
+ export async function search(params) {
113
121
  return request('/rs/search', METHOD.POST, params)
114
122
  }
115
123
 
@@ -12,81 +12,34 @@ import { logout, V4RefreshToken } from '@vue2-client/services/user'
12
12
  import { LOGIN, SEARCH, V4_LOGIN } from '@vue2-client/services/apiService'
13
13
  import { setV4AccessToken } from '@vue2-client/utils/login'
14
14
  import EncryptUtil from '@vue2-client/utils/EncryptUtil'
15
+ // 引入请求去重模块
16
+ import {
17
+ DEDUPE_STRATEGY,
18
+ generateRequestKey,
19
+ hasPendingRequest,
20
+ addPendingRequest,
21
+ handleDuplicateRequest,
22
+ resolvePendingRequest,
23
+ rejectPendingRequest,
24
+ clearPendingRequests as clearAllPendingRequests,
25
+ shouldEnableDedupe,
26
+ getDedupeStrategy
27
+ } from '@vue2-client/utils/requestDedupe'
15
28
 
16
29
  // 是否显示重新登录
17
30
  let isReloginShow
18
31
 
19
- // ============ 请求去重管理 ============
20
- // 存储进行中的请求 Map<requestKey, { promise, controller }>
21
- const pendingRequests = new Map()
22
-
23
- /**
24
- * 生成请求唯一标识
25
- * @param {object} config - axios 请求配置
26
- * @returns {string} 请求标识
27
- */
28
- function generateRequestKey (config) {
29
- const { method, url, data, params } = config
30
- let dataStr = ''
31
- let paramsStr = ''
32
- try {
33
- dataStr = data ? JSON.stringify(data) : ''
34
- paramsStr = params ? JSON.stringify(params) : ''
35
- } catch (e) {
36
- // 循环引用或其他序列化问题,使用时间戳保证唯一性
37
- console.warn('[请求去重] 参数序列化失败,跳过去重')
38
- dataStr = `_${Date.now()}_${Math.random()}`
39
- }
40
- return `${method}:${url}:${dataStr}:${paramsStr}`
41
- }
42
-
43
- /**
44
- * 添加请求到 pending 列表
45
- * @param {object} config - axios 请求配置
46
- */
47
- function addPendingRequest (config) {
48
- const requestKey = generateRequestKey(config)
49
- config._requestKey = requestKey
50
-
51
- if (!pendingRequests.has(requestKey)) {
52
- // 创建 AbortController 用于取消请求
53
- const controller = new AbortController()
54
- config.signal = controller.signal
55
- pendingRequests.set(requestKey, { controller, config })
56
- }
57
- }
58
-
59
- /**
60
- * 移除已完成的请求
61
- * @param {object} config - axios 请求配置
62
- */
63
- function removePendingRequest (config) {
64
- const requestKey = config._requestKey || generateRequestKey(config)
65
- if (pendingRequests.has(requestKey)) {
66
- pendingRequests.delete(requestKey)
67
- }
68
- }
69
-
70
- /**
71
- * 检查是否有相同的请求正在进行
72
- * @param {object} config - axios 请求配置
73
- * @returns {boolean}
74
- */
75
- function hasPendingRequest (config) {
76
- const requestKey = generateRequestKey(config)
77
- return pendingRequests.has(requestKey)
78
- }
79
-
80
32
  /**
81
33
  * 清空所有 pending 请求
34
+ * @description 页面切换或需要取消所有请求时调用
82
35
  */
83
36
  export function clearPendingRequests () {
84
- pendingRequests.forEach(({ controller }) => {
85
- controller.abort()
86
- })
87
- pendingRequests.clear()
37
+ clearAllPendingRequests()
88
38
  }
89
39
 
40
+ // 导出去重策略常量,方便外部使用
41
+ export { DEDUPE_STRATEGY }
42
+
90
43
  axios.defaults.timeout = 50000
91
44
  axios.defaults.withCredentials = true
92
45
  // 如果是microapp
@@ -219,19 +172,35 @@ function loadInterceptors () {
219
172
  axios.interceptors.request.use(config => {
220
173
  // ============ 请求去重逻辑 ============
221
174
  // POST 请求默认开启去重,可通过 config.dedupe = false 关闭
222
- const shouldDedupe = config.method?.toLowerCase() === 'post' && config.dedupe !== false
175
+ // 支持两种策略:reject(拒绝,默认)和 reuse(复用结果)
176
+ const enableDedupe = shouldEnableDedupe(config)
223
177
 
224
- if (shouldDedupe && hasPendingRequest(config)) {
225
- // 相同请求正在进行中,直接拒绝,不发起请求
226
- console.warn(`重复请求被拦截: ${config.url}`)
227
- const error = new Error('重复请求被取消')
228
- error.code = 'ERR_DUPLICATE_REQUEST'
229
- error.config = config
230
- return Promise.reject(error)
231
- }
178
+ if (enableDedupe) {
179
+ const requestKey = generateRequestKey(config)
180
+ config._requestKey = requestKey
232
181
 
233
- if (shouldDedupe) {
234
- addPendingRequest(config)
182
+ // 检查是否有相同请求正在进行
183
+ if (hasPendingRequest(requestKey)) {
184
+ const result = handleDuplicateRequest(requestKey, config)
185
+
186
+ if (!result.shouldProceed) {
187
+ if (result.isReuse && result.promise) {
188
+ // 复用策略:返回相同的 Promise,标记为复用请求
189
+ config._isReuseRequest = true
190
+ config._reusePromise = result.promise
191
+ // 返回一个特殊标记,让后续逻辑知道这是复用请求
192
+ return config
193
+ } else {
194
+ // 拒绝策略:直接返回错误
195
+ return Promise.reject(result.error)
196
+ }
197
+ }
198
+ }
199
+
200
+ // 添加到 pending 列表
201
+ const strategy = getDedupeStrategy(config)
202
+ const { signal } = addPendingRequest(requestKey, config, strategy)
203
+ config.signal = signal
235
204
  }
236
205
  // ============ 去重逻辑结束 ============
237
206
 
@@ -279,8 +248,12 @@ function loadInterceptors () {
279
248
  }, errorHandler)
280
249
  // 加载响应拦截器
281
250
  axios.interceptors.response.use((res) => {
282
- // 请求完成,移除 pending 记录
283
- removePendingRequest(res.config)
251
+ const requestKey = res.config._requestKey
252
+
253
+ // 如果是复用请求,直接返回复用的 Promise 结果
254
+ if (res.config._isReuseRequest && res.config._reusePromise) {
255
+ return res.config._reusePromise
256
+ }
284
257
 
285
258
  // 判断是否需要解密
286
259
  if (res.headers && res.headers['x-encrypted'] === '1') {
@@ -358,10 +331,22 @@ function loadInterceptors () {
358
331
  description: msg
359
332
  })
360
333
  } else {
334
+ // 请求成功,通知复用的请求并返回数据
335
+ if (requestKey) {
336
+ resolvePendingRequest(requestKey, res.data)
337
+ }
361
338
  return res.data
362
339
  }
340
+ // 请求失败,通知复用的请求
341
+ if (requestKey) {
342
+ rejectPendingRequest(requestKey, new Error(msg))
343
+ }
363
344
  return Promise.reject(msg)
364
345
  } else {
346
+ // 请求成功,通知复用的请求并返回数据
347
+ if (requestKey) {
348
+ resolvePendingRequest(requestKey, res.data)
349
+ }
365
350
  return res.data
366
351
  }
367
352
  }, errorHandler)
@@ -408,9 +393,12 @@ function loginExpire () {
408
393
  }
409
394
  // 异常拦截处理器
410
395
  const errorHandler = (error) => {
411
- // 请求失败,移除 pending 记录
396
+ // 请求失败,通知复用的请求并移除 pending 记录
412
397
  if (error.config) {
413
- removePendingRequest(error.config)
398
+ const requestKey = error.config._requestKey
399
+ if (requestKey) {
400
+ rejectPendingRequest(requestKey, error)
401
+ }
414
402
  }
415
403
 
416
404
  // 如果是被取消的请求(去重导致或 AbortController),静默处理