vue2-client 1.18.38 → 1.18.40
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.
- package/docs//350/257/267/346/261/202/345/267/245/345/205/267/344/275/277/347/224/250/350/257/264/346/230/216.md +353 -0
- package/package.json +1 -1
- package/src/base-client/components/common/XDatePicker/demo.vue +31 -0
- package/src/base-client/components/common/XDatePicker/index.vue +61 -0
- package/src/base-client/components/common/XForm/XForm.vue +5 -3
- package/src/base-client/components/common/XForm/XFormItem.vue +1 -1
- package/src/base-client/components/common/XFormTable/demo.vue +123 -125
- package/src/base-client/components/common/XReportGrid/XReportTrGroup.vue +1 -1
- package/src/base-client/components/common/XTable/XTableWrapper.vue +18 -1
- package/src/composables/demo/UseRequestDemo.vue +175 -0
- package/src/composables/index.js +6 -0
- package/src/composables/useGlobalLoading.js +206 -0
- package/src/composables/usePost.js +221 -0
- package/src/services/api/restTools.js +34 -46
- package/src/utils/request.js +103 -0
- package/vue.config.js +5 -0
|
@@ -1,125 +1,123 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<a-card :bordered="false">
|
|
3
|
-
<x-form-table
|
|
4
|
-
title="示例表单"
|
|
5
|
-
:queryParamsName="queryParamsName"
|
|
6
|
-
:fixedAddForm="fixedAddForm"
|
|
7
|
-
:externalSelectedRowKeys="selectedKeys"
|
|
8
|
-
@action="action"
|
|
9
|
-
@selectRow="selectRow"
|
|
10
|
-
@columnClick="columnClick"
|
|
11
|
-
@ceshi="ceshi"
|
|
12
|
-
@rowDblClick="rowDblClick"
|
|
13
|
-
:defaultPageSize="5"
|
|
14
|
-
:defaultQueryForm="{
|
|
15
|
-
s_f_user_name: '张三'
|
|
16
|
-
}"
|
|
17
|
-
serviceName="af-safecheck"
|
|
18
|
-
ref="xFormTable"
|
|
19
|
-
>
|
|
20
|
-
</x-form-table>
|
|
21
|
-
<div style="height: 100px;width: 100%; background-color: #
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<style scoped></style>
|
|
1
|
+
<template>
|
|
2
|
+
<a-card :bordered="false">
|
|
3
|
+
<x-form-table
|
|
4
|
+
title="示例表单"
|
|
5
|
+
:queryParamsName="queryParamsName"
|
|
6
|
+
:fixedAddForm="fixedAddForm"
|
|
7
|
+
:externalSelectedRowKeys="selectedKeys"
|
|
8
|
+
@action="action"
|
|
9
|
+
@selectRow="selectRow"
|
|
10
|
+
@columnClick="columnClick"
|
|
11
|
+
@ceshi="ceshi"
|
|
12
|
+
@rowDblClick="rowDblClick"
|
|
13
|
+
:defaultPageSize="5"
|
|
14
|
+
:defaultQueryForm="{
|
|
15
|
+
s_f_user_name: '张三'
|
|
16
|
+
}"
|
|
17
|
+
serviceName="af-safecheck"
|
|
18
|
+
ref="xFormTable"
|
|
19
|
+
>
|
|
20
|
+
</x-form-table>
|
|
21
|
+
<div style="height: 100px; width: 100%; background-color: #f0f2f5;">
|
|
22
|
+
</div>
|
|
23
|
+
</a-card>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
import XFormTable from '@vue2-client/base-client/components/common/XFormTable/XFormTable.vue'
|
|
28
|
+
import { microDispatch } from '@vue2-client/utils/microAppUtils'
|
|
29
|
+
export default {
|
|
30
|
+
name: 'Demo',
|
|
31
|
+
components: {
|
|
32
|
+
XFormTable,
|
|
33
|
+
},
|
|
34
|
+
data () {
|
|
35
|
+
return {
|
|
36
|
+
// 查询配置文件名
|
|
37
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
38
|
+
// 查询配置左侧tree
|
|
39
|
+
xTreeConfigName: 'addressType',
|
|
40
|
+
// 新增表单固定值
|
|
41
|
+
fixedAddForm: {},
|
|
42
|
+
// 是否显示详情抽屉
|
|
43
|
+
detailVisible: false,
|
|
44
|
+
// 当前记录
|
|
45
|
+
record: {},
|
|
46
|
+
// 选中的行keys
|
|
47
|
+
selectedKeys: [],
|
|
48
|
+
selected: {
|
|
49
|
+
keys: [],
|
|
50
|
+
rows: [],
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
methods: {
|
|
56
|
+
// ========== 原有方法 ==========
|
|
57
|
+
rowDblClick (record) {
|
|
58
|
+
console.log('rowDblClick', record)
|
|
59
|
+
},
|
|
60
|
+
// input框 行編輯參數傳遞
|
|
61
|
+
ceshi (...args) {
|
|
62
|
+
// 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
|
+
)
|
|
74
|
+
// 示例:当按下 Enter 键且有下一行时,跳转到下一行的同一列
|
|
75
|
+
// 可以在这里执行业务逻辑(例如:保存数据)
|
|
76
|
+
console.log('当前行:', currentIndex, '下一行:', nextIndex)
|
|
77
|
+
if (!nextIndex) {
|
|
78
|
+
this.$message.warning('没有下一行')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
// 跳转到下一行的同一列
|
|
82
|
+
this.$refs.xFormTable.$refs.xTable.focusInput(nextIndex, attr.model)
|
|
83
|
+
},
|
|
84
|
+
test () {
|
|
85
|
+
this.$refs.xFormTable.setTableData([])
|
|
86
|
+
},
|
|
87
|
+
defaultF () {
|
|
88
|
+
this.$refs.xFormTable.setTableSize('default')
|
|
89
|
+
},
|
|
90
|
+
middleF () {
|
|
91
|
+
this.$refs.xFormTable.setTableSize('middle')
|
|
92
|
+
},
|
|
93
|
+
smallF () {
|
|
94
|
+
this.$refs.xFormTable.setTableSize('small')
|
|
95
|
+
},
|
|
96
|
+
columnClick (key, value, record) {
|
|
97
|
+
microDispatch({
|
|
98
|
+
type: 'v3route',
|
|
99
|
+
path: '/bingliguanli/dianzibingliluru',
|
|
100
|
+
props: { selected: arguments[0].his_f_admission_id },
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
action (record, id, actionType) {
|
|
104
|
+
this.detailVisible = true
|
|
105
|
+
console.log('触发了详情操作', record, id, actionType)
|
|
106
|
+
},
|
|
107
|
+
onClose () {
|
|
108
|
+
this.detailVisible = false
|
|
109
|
+
// 关闭详情之后重新查询表单
|
|
110
|
+
this.$refs.xFormTable.refreshTable(true)
|
|
111
|
+
},
|
|
112
|
+
selectRow (selectedRowKeys, selectedRows) {
|
|
113
|
+
this.selected = {
|
|
114
|
+
keys: selectedRowKeys,
|
|
115
|
+
rows: selectedRows,
|
|
116
|
+
}
|
|
117
|
+
console.log('selectedDemo', this.selected)
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<style scoped></style>
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
</span>
|
|
108
108
|
<span v-else-if="item.slotType === 'action'" :key="'action-' + c_index">
|
|
109
109
|
<template v-if="item.actionArr && item.actionArr.length > 0">
|
|
110
|
-
<a-dropdown placement="bottomCenter" :getPopupContainer="
|
|
110
|
+
<a-dropdown placement="bottomCenter" :getPopupContainer="getPopupContainer">
|
|
111
111
|
<a class="ant-dropdown-link" @click="e => e.preventDefault()">
|
|
112
112
|
{{ item.scopedSlots?.customRender || item.slotValue }} <a-icon type="down"/>
|
|
113
113
|
</a>
|
|
@@ -271,6 +271,7 @@
|
|
|
271
271
|
<a-dropdown
|
|
272
272
|
placement="bottomRight"
|
|
273
273
|
overlayClassName="x-table-action-dropdown"
|
|
274
|
+
:getPopupContainer="getPopupContainer"
|
|
274
275
|
:overlayStyle="{
|
|
275
276
|
whiteSpace: 'nowrap',
|
|
276
277
|
maxWidth: '250px',
|
|
@@ -523,6 +524,22 @@ export default {
|
|
|
523
524
|
},
|
|
524
525
|
inject: ['tableContext'],
|
|
525
526
|
methods: {
|
|
527
|
+
getPopupContainer (triggerNode) {
|
|
528
|
+
// 限制最多向上查找5层
|
|
529
|
+
let parent = triggerNode.parentNode
|
|
530
|
+
let depth = 0
|
|
531
|
+
const maxDepth = 10
|
|
532
|
+
|
|
533
|
+
while (parent && parent !== document.body && depth < maxDepth) {
|
|
534
|
+
if (parent.tagName === 'TBODY') {
|
|
535
|
+
return parent
|
|
536
|
+
}
|
|
537
|
+
parent = parent.parentNode
|
|
538
|
+
depth++
|
|
539
|
+
}
|
|
540
|
+
// 如果5层内没找到,回退到body
|
|
541
|
+
return document.body
|
|
542
|
+
},
|
|
526
543
|
handleRowClick (record) {
|
|
527
544
|
this.$emit('rowClick', record)
|
|
528
545
|
},
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a-card title="useRunLogic / usePost 请求工具示例" :bordered="false">
|
|
3
|
+
<a-space direction="vertical" style="width: 100%">
|
|
4
|
+
<!-- 示例1: 基础用法 - 组件级 loading -->
|
|
5
|
+
<a-button
|
|
6
|
+
type="primary"
|
|
7
|
+
:loading="basicRequest.loading"
|
|
8
|
+
@click="handleBasicRequest"
|
|
9
|
+
>
|
|
10
|
+
基础请求 (组件级 loading)
|
|
11
|
+
</a-button>
|
|
12
|
+
|
|
13
|
+
<!-- 示例2: 全局 Loading -->
|
|
14
|
+
<a-button
|
|
15
|
+
type="danger"
|
|
16
|
+
:loading="globalLoadingRequest.loading"
|
|
17
|
+
@click="handleGlobalLoadingRequest"
|
|
18
|
+
>
|
|
19
|
+
全局 Loading 请求
|
|
20
|
+
</a-button>
|
|
21
|
+
|
|
22
|
+
<!-- 示例3: 请求去重 -->
|
|
23
|
+
<a-button
|
|
24
|
+
type="dashed"
|
|
25
|
+
@click="handleDedupeRequest"
|
|
26
|
+
>
|
|
27
|
+
去重请求 (快速点击试试)
|
|
28
|
+
</a-button>
|
|
29
|
+
|
|
30
|
+
<!-- 示例4: 组合使用 -->
|
|
31
|
+
<a-button
|
|
32
|
+
:loading="comboRequest.loading"
|
|
33
|
+
@click="handleComboRequest"
|
|
34
|
+
>
|
|
35
|
+
组合使用 (去重 + 全局 Loading)
|
|
36
|
+
</a-button>
|
|
37
|
+
|
|
38
|
+
<!-- 示例5: usePost 基础用法 -->
|
|
39
|
+
<a-button
|
|
40
|
+
type="primary"
|
|
41
|
+
ghost
|
|
42
|
+
:loading="postRequest.loading"
|
|
43
|
+
@click="handlePostRequest"
|
|
44
|
+
>
|
|
45
|
+
usePost 示例
|
|
46
|
+
</a-button>
|
|
47
|
+
|
|
48
|
+
<!-- 显示请求结果 -->
|
|
49
|
+
<a-card v-if="requestResult" size="small" title="请求结果">
|
|
50
|
+
<pre style="margin: 0; font-size: 12px; max-height: 200px; overflow: auto;">{{ JSON.stringify(requestResult, null, 2) }}</pre>
|
|
51
|
+
</a-card>
|
|
52
|
+
</a-space>
|
|
53
|
+
</a-card>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
import { useRunLogic, usePost } from '@vue2-client/composables'
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
name: 'UseRequestDemo',
|
|
61
|
+
|
|
62
|
+
data () {
|
|
63
|
+
return {
|
|
64
|
+
// 请求结果展示
|
|
65
|
+
requestResult: null,
|
|
66
|
+
|
|
67
|
+
// ========== useRunLogic 实例 ==========
|
|
68
|
+
// 示例1: 基础用法
|
|
69
|
+
basicRequest: useRunLogic('test'),
|
|
70
|
+
|
|
71
|
+
// 示例2: 全局 Loading
|
|
72
|
+
globalLoadingRequest: useRunLogic('test', {
|
|
73
|
+
globalLoading: '正在加载数据...'
|
|
74
|
+
}),
|
|
75
|
+
|
|
76
|
+
// 示例3: 请求去重
|
|
77
|
+
dedupeRequest: useRunLogic('test', {
|
|
78
|
+
dedupe: true
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
// 示例4: 组合使用
|
|
82
|
+
comboRequest: useRunLogic('test', {
|
|
83
|
+
dedupe: true,
|
|
84
|
+
globalLoading: '处理中,请勿重复操作...'
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
// ========== usePost 实例 ==========
|
|
88
|
+
postRequest: usePost('/api/af-safecheck/logic/test', {
|
|
89
|
+
globalLoading: '请求中...'
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
methods: {
|
|
95
|
+
// 示例1: 基础请求
|
|
96
|
+
async handleBasicRequest () {
|
|
97
|
+
const { success, data, error } = await this.basicRequest.execute({
|
|
98
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
99
|
+
pageNum: 1,
|
|
100
|
+
pageSize: 5
|
|
101
|
+
})
|
|
102
|
+
if (success) {
|
|
103
|
+
this.requestResult = data
|
|
104
|
+
this.$message.success('基础请求成功')
|
|
105
|
+
} else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
|
|
106
|
+
this.$message.error('请求失败: ' + error?.message)
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// 示例2: 全局 Loading 请求
|
|
111
|
+
async handleGlobalLoadingRequest () {
|
|
112
|
+
const { success, data, error } = await this.globalLoadingRequest.execute({
|
|
113
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
114
|
+
pageNum: 1,
|
|
115
|
+
pageSize: 5
|
|
116
|
+
})
|
|
117
|
+
if (success) {
|
|
118
|
+
this.requestResult = data
|
|
119
|
+
this.$message.success('全局 Loading 请求成功')
|
|
120
|
+
} else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
|
|
121
|
+
this.$message.error('请求失败: ' + error?.message)
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// 示例3: 去重请求
|
|
126
|
+
async handleDedupeRequest () {
|
|
127
|
+
// 快速点击多次,只会发送一次请求
|
|
128
|
+
const { success, data, error } = await this.dedupeRequest.execute({
|
|
129
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
130
|
+
pageNum: 1,
|
|
131
|
+
pageSize: 5
|
|
132
|
+
})
|
|
133
|
+
if (success) {
|
|
134
|
+
this.requestResult = data
|
|
135
|
+
this.$message.success('去重请求成功 (查看控制台是否有去重警告)')
|
|
136
|
+
} else if (error?.code === 'ERR_DUPLICATE_REQUEST') {
|
|
137
|
+
// 重复请求被拦截,静默处理或提示
|
|
138
|
+
console.log('重复请求已被拦截')
|
|
139
|
+
} else {
|
|
140
|
+
this.$message.error('请求失败: ' + error?.message)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// 示例4: 组合请求
|
|
145
|
+
async handleComboRequest () {
|
|
146
|
+
const { success, data, error } = await this.comboRequest.execute({
|
|
147
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
148
|
+
pageNum: 1,
|
|
149
|
+
pageSize: 5
|
|
150
|
+
})
|
|
151
|
+
if (success) {
|
|
152
|
+
this.requestResult = data
|
|
153
|
+
this.$message.success('组合请求成功')
|
|
154
|
+
} else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
|
|
155
|
+
this.$message.error('请求失败: ' + error?.message)
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// 示例5: usePost 请求
|
|
160
|
+
async handlePostRequest () {
|
|
161
|
+
const { success, data, error } = await this.postRequest.execute({
|
|
162
|
+
queryParamsName: 'checkPlanListCRUD',
|
|
163
|
+
pageNum: 1,
|
|
164
|
+
pageSize: 5
|
|
165
|
+
})
|
|
166
|
+
if (success) {
|
|
167
|
+
this.requestResult = data
|
|
168
|
+
this.$message.success('usePost 请求成功')
|
|
169
|
+
} else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
|
|
170
|
+
this.$message.error('请求失败: ' + error?.message)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局 Loading 管理
|
|
3
|
+
* 纯 JS 实现,直接操作 DOM,不依赖 Vue 组件
|
|
4
|
+
* 支持引用计数,多个并发请求时正确处理显示/隐藏
|
|
5
|
+
* 支持超时自动隐藏保护机制
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// 全局状态
|
|
9
|
+
let isLoading = false
|
|
10
|
+
let loadingElement = null
|
|
11
|
+
let messageText = '加载中...'
|
|
12
|
+
let loadingCount = 0 // 引用计数,支持并发请求
|
|
13
|
+
let autoHideTimer = null // 超时自动隐藏定时器
|
|
14
|
+
|
|
15
|
+
// 配置
|
|
16
|
+
const DEFAULT_TIMEOUT = 30000 // 默认超时时间 30 秒
|
|
17
|
+
const CONTAINER_ID = 'global-loading-container'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 创建 Loading DOM 元素
|
|
21
|
+
*/
|
|
22
|
+
function createLoadingElement (message) {
|
|
23
|
+
const container = document.createElement('div')
|
|
24
|
+
container.id = CONTAINER_ID
|
|
25
|
+
container.innerHTML = `
|
|
26
|
+
<div class="global-loading-mask">
|
|
27
|
+
<div class="global-loading-content">
|
|
28
|
+
<div class="global-loading-spinner">
|
|
29
|
+
<div class="ant-spin ant-spin-lg ant-spin-spinning">
|
|
30
|
+
<span class="ant-spin-dot ant-spin-dot-spin">
|
|
31
|
+
<i class="ant-spin-dot-item"></i>
|
|
32
|
+
<i class="ant-spin-dot-item"></i>
|
|
33
|
+
<i class="ant-spin-dot-item"></i>
|
|
34
|
+
<i class="ant-spin-dot-item"></i>
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<p class="global-loading-message">${message}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
`
|
|
42
|
+
return container
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 注入全局样式(只注入一次)
|
|
47
|
+
*/
|
|
48
|
+
function injectStyles () {
|
|
49
|
+
if (document.getElementById('global-loading-styles')) return
|
|
50
|
+
|
|
51
|
+
const style = document.createElement('style')
|
|
52
|
+
style.id = 'global-loading-styles'
|
|
53
|
+
style.textContent = `
|
|
54
|
+
.global-loading-mask {
|
|
55
|
+
position: fixed;
|
|
56
|
+
top: 0;
|
|
57
|
+
left: 0;
|
|
58
|
+
right: 0;
|
|
59
|
+
bottom: 0;
|
|
60
|
+
background: rgba(0, 0, 0, 0.45);
|
|
61
|
+
z-index: 99999;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
}
|
|
66
|
+
.global-loading-content {
|
|
67
|
+
text-align: center;
|
|
68
|
+
}
|
|
69
|
+
.global-loading-message {
|
|
70
|
+
margin-top: 16px;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
color: #fff;
|
|
73
|
+
}
|
|
74
|
+
.global-loading-content .ant-spin-dot-item {
|
|
75
|
+
background-color: #fff !important;
|
|
76
|
+
}
|
|
77
|
+
`
|
|
78
|
+
document.head.appendChild(style)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 清除超时定时器
|
|
83
|
+
*/
|
|
84
|
+
function clearAutoHideTimer () {
|
|
85
|
+
if (autoHideTimer) {
|
|
86
|
+
clearTimeout(autoHideTimer)
|
|
87
|
+
autoHideTimer = null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 设置超时自动隐藏
|
|
93
|
+
* @param {number} timeout - 超时时间(毫秒)
|
|
94
|
+
*/
|
|
95
|
+
function setAutoHideTimer (timeout) {
|
|
96
|
+
clearAutoHideTimer()
|
|
97
|
+
if (timeout > 0) {
|
|
98
|
+
autoHideTimer = setTimeout(() => {
|
|
99
|
+
console.warn(`[GlobalLoading] 超时自动隐藏 (${timeout}ms)`)
|
|
100
|
+
forceHideGlobalLoading()
|
|
101
|
+
}, timeout)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 显示全局 Loading
|
|
107
|
+
* 支持引用计数,多次调用只显示一个 Loading
|
|
108
|
+
* @param {string|boolean} message - 提示信息,传 true 使用默认文字
|
|
109
|
+
* @param {number} timeout - 超时自动隐藏时间(毫秒),默认 30 秒,传 0 禁用
|
|
110
|
+
*/
|
|
111
|
+
export function showGlobalLoading (message = '加载中...', timeout = DEFAULT_TIMEOUT) {
|
|
112
|
+
loadingCount++
|
|
113
|
+
|
|
114
|
+
// 已经在显示,只增加计数,重置超时
|
|
115
|
+
if (isLoading) {
|
|
116
|
+
setAutoHideTimer(timeout)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isLoading = true
|
|
121
|
+
messageText = typeof message === 'string' ? message : '加载中...'
|
|
122
|
+
|
|
123
|
+
// 注入样式
|
|
124
|
+
injectStyles()
|
|
125
|
+
|
|
126
|
+
// 创建并插入 DOM
|
|
127
|
+
loadingElement = createLoadingElement(messageText)
|
|
128
|
+
document.body.appendChild(loadingElement)
|
|
129
|
+
|
|
130
|
+
// 设置超时自动隐藏
|
|
131
|
+
setAutoHideTimer(timeout)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 隐藏全局 Loading
|
|
136
|
+
* 只有当所有请求都完成时才真正隐藏
|
|
137
|
+
*/
|
|
138
|
+
export function hideGlobalLoading () {
|
|
139
|
+
loadingCount = Math.max(0, loadingCount - 1)
|
|
140
|
+
|
|
141
|
+
// 还有其他请求在进行中,不隐藏
|
|
142
|
+
if (loadingCount > 0) return
|
|
143
|
+
|
|
144
|
+
doHide()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 强制隐藏全局 Loading(重置计数器)
|
|
149
|
+
* 用于异常情况下的强制重置
|
|
150
|
+
*/
|
|
151
|
+
export function forceHideGlobalLoading () {
|
|
152
|
+
loadingCount = 0
|
|
153
|
+
doHide()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 执行隐藏操作
|
|
158
|
+
*/
|
|
159
|
+
function doHide () {
|
|
160
|
+
if (!isLoading) return
|
|
161
|
+
|
|
162
|
+
isLoading = false
|
|
163
|
+
clearAutoHideTimer()
|
|
164
|
+
|
|
165
|
+
// 移除 DOM
|
|
166
|
+
if (loadingElement && loadingElement.parentNode) {
|
|
167
|
+
loadingElement.parentNode.removeChild(loadingElement)
|
|
168
|
+
}
|
|
169
|
+
loadingElement = null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取当前 Loading 状态
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
export function isGlobalLoadingVisible () {
|
|
177
|
+
return isLoading
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 获取当前 Loading 计数
|
|
182
|
+
* @returns {number}
|
|
183
|
+
*/
|
|
184
|
+
export function getLoadingCount () {
|
|
185
|
+
return loadingCount
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* useGlobalLoading Hook(Vue Composition API 风格)
|
|
190
|
+
* @returns {{ show: Function, hide: Function, forceHide: Function, isVisible: Function }}
|
|
191
|
+
*/
|
|
192
|
+
export function useGlobalLoading () {
|
|
193
|
+
return {
|
|
194
|
+
show: showGlobalLoading,
|
|
195
|
+
hide: hideGlobalLoading,
|
|
196
|
+
forceHide: forceHideGlobalLoading,
|
|
197
|
+
isVisible: isGlobalLoadingVisible
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default {
|
|
202
|
+
show: showGlobalLoading,
|
|
203
|
+
hide: hideGlobalLoading,
|
|
204
|
+
forceHide: forceHideGlobalLoading,
|
|
205
|
+
isVisible: isGlobalLoadingVisible
|
|
206
|
+
}
|