wui-components-v2 1.0.13 → 1.0.14
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/api/core/index.ts +74 -0
- package/api/index.ts +2 -13
- package/api/login.ts +51 -0
- package/api/menu.ts +7 -0
- package/components/demo-block/demo-block.vue +66 -0
- package/components/global-loading/GlobalLoading.md +366 -0
- package/components/global-loading/global-loading.vue +55 -0
- package/components/global-message/GlobalMessage.md +570 -0
- package/components/global-message/global-message.vue +64 -0
- package/components/global-toast/GlobalToast.md +261 -0
- package/components/global-toast/global-toast.vue +56 -0
- package/components/login/login.vue +22 -0
- package/components/login-form/login-form.vue +106 -0
- package/components/menus/menus.vue +180 -0
- package/components/privacy-popup/privacy-popup.vue +178 -0
- package/components/{system-settings.vue → system-settings/system-settings.vue} +3 -2
- package/composables/types/user.ts +5 -0
- package/composables/useGlobalLoading.ts +46 -0
- package/composables/useGlobalMessage.ts +54 -0
- package/composables/useGlobalToast.ts +62 -0
- package/composables/useTheme.ts +1 -1
- package/composables/useUser.ts +21 -0
- package/index.ts +3 -2
- package/package.json +6 -1
- package/store/userStore.ts +22 -0
- package/utils/index.ts +9 -0
- package/utils/rsaEncrypt.ts +10 -0
- package/api/core/handlers.ts +0 -106
- package/api/core/instance.ts +0 -60
- package/api/core/middleware.ts +0 -92
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 封装uni.request为Promise形式
|
|
3
|
+
* @param {object} options 请求配置
|
|
4
|
+
* @param {string} options.url 请求地址
|
|
5
|
+
* @param {string} [options.method] 请求方法
|
|
6
|
+
* @param {object} [options.data] 请求数据
|
|
7
|
+
* @param {object} [options.header] 请求头
|
|
8
|
+
* @param {number} [options.timeout] 超时时间(毫秒)
|
|
9
|
+
* @returns {Promise} 返回Promise对象
|
|
10
|
+
*/
|
|
11
|
+
interface RequestOptions {
|
|
12
|
+
url: string
|
|
13
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE' | 'CONNECT'
|
|
14
|
+
data?: any
|
|
15
|
+
header?: any
|
|
16
|
+
timeout?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function request(options: RequestOptions) {
|
|
20
|
+
const baseUrl = uni.getStorageSync('BASE_URL') || '/'
|
|
21
|
+
const hydrocarbonProgramToken = uni.getStorageSync('HYDROCARBON_PROGRAM_TOKEN') || ''
|
|
22
|
+
const token = uni.getStorageSync('TOKEN') || ''
|
|
23
|
+
const hydrocarbonClient = uni.getStorageSync('HYDROCARBON_CLIENT') || 'MOBILE'
|
|
24
|
+
console.log('request', baseUrl + options.url, options.data)
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
// 设置默认值
|
|
27
|
+
const defaultOptions = {
|
|
28
|
+
method: 'GET' as const,
|
|
29
|
+
data: {},
|
|
30
|
+
header: {
|
|
31
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
32
|
+
'hydrocarbon-program-token': hydrocarbonProgramToken, // 项目code
|
|
33
|
+
'hydrocarbon-token': token, // 登录token
|
|
34
|
+
'hydrocarbon-client': hydrocarbonClient, // 端类型
|
|
35
|
+
// #ifdef APP-PLUS
|
|
36
|
+
'hydrocarbon-app-version': '1.0.0',
|
|
37
|
+
// #endif
|
|
38
|
+
...options.header,
|
|
39
|
+
},
|
|
40
|
+
timeout: options.timeout || 60000,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 合并配置
|
|
44
|
+
const requestOptions = {
|
|
45
|
+
...defaultOptions,
|
|
46
|
+
...options,
|
|
47
|
+
url: baseUrl + options.url,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
uni.request({
|
|
51
|
+
...requestOptions,
|
|
52
|
+
success: (res: any) => {
|
|
53
|
+
// 可以根据实际业务需求调整这里的判断条件
|
|
54
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
55
|
+
if (res.data.message === '权限不足或没有资源') {
|
|
56
|
+
uni.showToast({ title: res.data.message, icon: 'none' })
|
|
57
|
+
uni.removeStorageSync('token')
|
|
58
|
+
reject(res)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
resolve(res.data)
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
reject(res)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
fail: (err: any) => {
|
|
68
|
+
reject(err)
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default request
|
package/api/index.ts
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
// 导入核心alova实例
|
|
2
|
-
import
|
|
2
|
+
import req from './core/index'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export { alovaInstance }
|
|
6
|
-
|
|
7
|
-
// 为特定API配置方法选项
|
|
8
|
-
export const $$userConfigMap = withConfigType({})
|
|
9
|
-
|
|
10
|
-
// 创建全局Apis对象
|
|
11
|
-
const Apis = createApis(alovaInstance, $$userConfigMap)
|
|
12
|
-
|
|
13
|
-
// 导出默认导出和命名导出以进行自动导入
|
|
14
|
-
export default Apis
|
|
15
|
-
export { Apis }
|
|
4
|
+
export { req }
|
package/api/login.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// 导入核心alova实例
|
|
2
|
+
import dayjs from 'dayjs/esm/index'
|
|
3
|
+
import timezone from 'dayjs/esm/plugin/timezone'
|
|
4
|
+
import utc from 'dayjs/esm/plugin/utc'
|
|
5
|
+
import rsaEncrypt from '../utils/rsaEncrypt'
|
|
6
|
+
import req from './core/index'
|
|
7
|
+
// 获取公钥
|
|
8
|
+
export function pubKey(): Promise<any> {
|
|
9
|
+
return req({
|
|
10
|
+
url: `/v3/auth/ras-pubkey`,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 登录
|
|
15
|
+
export async function login(username: string, password: string, kaptchaToken: string = '', kaptchaText: string = ''): Promise<any> {
|
|
16
|
+
dayjs.extend(utc)
|
|
17
|
+
dayjs.extend(timezone)
|
|
18
|
+
const datetime = dayjs(new Date())
|
|
19
|
+
.tz('Europe/London')
|
|
20
|
+
.format('YYYY-MM-DD HH:mm:ss')
|
|
21
|
+
const json = {
|
|
22
|
+
username,
|
|
23
|
+
password,
|
|
24
|
+
datetime,
|
|
25
|
+
}
|
|
26
|
+
const res = await pubKey()
|
|
27
|
+
const pubkeystr = res.rasPubkey
|
|
28
|
+
const userInfo = rsaEncrypt(JSON.stringify(json), pubkeystr)
|
|
29
|
+
return req({
|
|
30
|
+
url: `/v3/auth/hctoken`,
|
|
31
|
+
data: {
|
|
32
|
+
userInfo,
|
|
33
|
+
kaptchaToken,
|
|
34
|
+
kaptchaText,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
// 获取验证码
|
|
39
|
+
export function getKaptchaToken(): Promise<any> {
|
|
40
|
+
return req({
|
|
41
|
+
url: `/v3/auth/kaptcha`,
|
|
42
|
+
method: 'POST',
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 获取登录用户权限配置信息
|
|
47
|
+
export function getUserConfig(): Promise<any> {
|
|
48
|
+
return req({
|
|
49
|
+
url: `/v3/current-user`,
|
|
50
|
+
})
|
|
51
|
+
}
|
package/api/menu.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed, defineOptions, defineProps } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
name: 'DemoBlock',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
// 标题
|
|
10
|
+
title: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: '',
|
|
13
|
+
},
|
|
14
|
+
// 自定义类名
|
|
15
|
+
customClass: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: '',
|
|
18
|
+
},
|
|
19
|
+
// 垂直间距
|
|
20
|
+
ver: {
|
|
21
|
+
type: [Number, String],
|
|
22
|
+
default: 10,
|
|
23
|
+
},
|
|
24
|
+
// 水平间距
|
|
25
|
+
hor: {
|
|
26
|
+
type: [Number, String],
|
|
27
|
+
default: 15,
|
|
28
|
+
},
|
|
29
|
+
// 是否透明
|
|
30
|
+
transparent: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const style = computed(() => {
|
|
37
|
+
return `margin: 0 ${props.hor}px;padding:${props.ver}px 0;`
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<script lang="ts">
|
|
42
|
+
export default {
|
|
43
|
+
options: {
|
|
44
|
+
addGlobalClass: true,
|
|
45
|
+
virtualHost: true,
|
|
46
|
+
styleIsolation: 'shared',
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<view
|
|
53
|
+
class="mb-3 box-border w-full px-3 text-gray-500 last:mb-0 dark:text-gray-300"
|
|
54
|
+
:class="[
|
|
55
|
+
transparent ? '' : 'bg-white dark:bg-[var(--wot-dark-background2)]',
|
|
56
|
+
customClass,
|
|
57
|
+
]"
|
|
58
|
+
>
|
|
59
|
+
<view v-if="title" class="px-4 py-3 text-26rpx">
|
|
60
|
+
{{ title }}
|
|
61
|
+
</view>
|
|
62
|
+
<view :style="transparent ? '' : style">
|
|
63
|
+
<slot />
|
|
64
|
+
</view>
|
|
65
|
+
</view>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# GlobalLoading 全局加载组件
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
GlobalLoading 是基于 `wot-design-uni` 的 `wd-toast` 组件封装的全局加载组件,可以结合`axios`、`alova`等请求库使用,显示加载状态,通过 Pinia 状态管理实现全局调用,用于显示数据加载、操作处理等场景的等待状态。
|
|
6
|
+
|
|
7
|
+
## 组件特性
|
|
8
|
+
|
|
9
|
+
- 基于 `wd-toast` 组件封装
|
|
10
|
+
- 显示加载动画,默认不自动关闭
|
|
11
|
+
- 支持遮罩层覆盖,防止用户操作
|
|
12
|
+
- 自动页面路径检测,确保在正确页面显示
|
|
13
|
+
- 虚拟节点设计,不在 DOM 中创建额外层级
|
|
14
|
+
|
|
15
|
+
## 安装使用
|
|
16
|
+
|
|
17
|
+
### 1. 注册全局组件
|
|
18
|
+
|
|
19
|
+
在 `App.vue` 或根组件中注册:
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<div>
|
|
24
|
+
<!-- 其他内容 -->
|
|
25
|
+
<GlobalLoading />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. 在组件中使用
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { useGlobalLoading } from '@/composables/useGlobalLoading'
|
|
34
|
+
|
|
35
|
+
const loading = useGlobalLoading()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API 文档
|
|
39
|
+
|
|
40
|
+
### 基础用法
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// 显示加载(字符串参数)
|
|
44
|
+
loading.loading('加载中...')
|
|
45
|
+
|
|
46
|
+
// 显示加载(配置对象)
|
|
47
|
+
loading.loading({
|
|
48
|
+
msg: '数据加载中',
|
|
49
|
+
cover: true
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// 关闭加载
|
|
53
|
+
loading.close()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 方法说明
|
|
57
|
+
|
|
58
|
+
#### loading(option)
|
|
59
|
+
显示加载状态
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// 简单文本
|
|
63
|
+
loading.loading('请稍候...')
|
|
64
|
+
|
|
65
|
+
// 自定义配置
|
|
66
|
+
loading.loading({
|
|
67
|
+
msg: '数据处理中,请稍候',
|
|
68
|
+
cover: false,
|
|
69
|
+
position: 'middle'
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**默认配置:**
|
|
74
|
+
- 图标:loading(旋转动画)
|
|
75
|
+
- 持续时间:0(不自动关闭)
|
|
76
|
+
- 遮罩:true
|
|
77
|
+
- 位置:middle
|
|
78
|
+
- 显示:true
|
|
79
|
+
|
|
80
|
+
#### close()
|
|
81
|
+
关闭加载状态
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
loading.close()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 参数说明
|
|
88
|
+
|
|
89
|
+
### ToastOptions
|
|
90
|
+
|
|
91
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
92
|
+
|------|------|--------|------|
|
|
93
|
+
| msg | string | - | 加载提示文本 |
|
|
94
|
+
| iconName | string | 'loading' | 图标名称(固定为loading) |
|
|
95
|
+
| duration | number | 0 | 持续时间(ms),0表示不自动关闭 |
|
|
96
|
+
| cover | boolean | true | 是否显示遮罩层 |
|
|
97
|
+
| position | string | 'middle' | 显示位置:'top' \| 'middle' \| 'bottom' |
|
|
98
|
+
| show | boolean | - | 是否显示(内部使用) |
|
|
99
|
+
|
|
100
|
+
### 方法参数
|
|
101
|
+
|
|
102
|
+
支持两种参数类型:
|
|
103
|
+
|
|
104
|
+
1. **字符串参数**:直接传入加载提示文本
|
|
105
|
+
2. **选项对象**:传入完整的 ToastOptions 配置
|
|
106
|
+
|
|
107
|
+
## 使用示例
|
|
108
|
+
|
|
109
|
+
### 基础示例
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
export default {
|
|
113
|
+
setup() {
|
|
114
|
+
const loading = useGlobalLoading()
|
|
115
|
+
|
|
116
|
+
const handleLoad = async () => {
|
|
117
|
+
loading.loading('加载中...')
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// 模拟异步操作
|
|
121
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
122
|
+
console.log('加载完成')
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
loading.close()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
handleLoad
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 网络请求示例
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
async function fetchData() {
|
|
140
|
+
const loading = useGlobalLoading()
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
loading.loading('获取数据中...')
|
|
144
|
+
const response = await api.getData()
|
|
145
|
+
return response.data
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error('获取数据失败:', error)
|
|
149
|
+
throw error
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
loading.close()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 表单提交示例
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
async function handleSubmit(formData) {
|
|
161
|
+
const loading = useGlobalLoading()
|
|
162
|
+
const toast = useGlobalToast()
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
loading.loading('提交中,请稍候...')
|
|
166
|
+
await submitForm(formData)
|
|
167
|
+
toast.success('提交成功')
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
toast.error(`提交失败:${error.message}`)
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
loading.close()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 文件上传示例
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
async function uploadFile(file) {
|
|
182
|
+
const loading = useGlobalLoading()
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
loading.loading({
|
|
186
|
+
msg: '文件上传中...',
|
|
187
|
+
cover: true // 防止用户重复操作
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const result = await uploadToServer(file)
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
loading.close()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 批量操作示例
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
async function batchProcess(items) {
|
|
203
|
+
const loading = useGlobalLoading()
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
loading.loading(`处理中 (0/${items.length})`)
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < items.length; i++) {
|
|
209
|
+
// 更新进度提示
|
|
210
|
+
loading.loading(`处理中 (${i + 1}/${items.length})`)
|
|
211
|
+
await processItem(items[i])
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
loading.close()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 自定义样式示例
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
function showCustomLoading() {
|
|
224
|
+
loading.loading({
|
|
225
|
+
msg: '自定义加载样式',
|
|
226
|
+
cover: false, // 不显示遮罩
|
|
227
|
+
position: 'top' // 显示在顶部
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 实现原理
|
|
233
|
+
|
|
234
|
+
### 状态管理
|
|
235
|
+
- 使用 Pinia 管理全局状态
|
|
236
|
+
- 状态包含:`loadingOptions`(加载配置)、`currentPage`(当前页面路径)
|
|
237
|
+
|
|
238
|
+
### 页面检测机制
|
|
239
|
+
- 调用加载方法时记录当前页面路径
|
|
240
|
+
- 组件监听状态变化时验证页面路径
|
|
241
|
+
- 确保加载状态在正确的页面显示
|
|
242
|
+
|
|
243
|
+
### 组件配置
|
|
244
|
+
```typescript
|
|
245
|
+
export default {
|
|
246
|
+
options: {
|
|
247
|
+
virtualHost: true, // 虚拟节点
|
|
248
|
+
addGlobalClass: true, // 支持全局样式
|
|
249
|
+
styleIsolation: 'shared' // 样式隔离共享
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 最佳实践
|
|
255
|
+
|
|
256
|
+
1. **及时关闭**
|
|
257
|
+
```typescript
|
|
258
|
+
// ✅ 推荐:使用 try-finally 确保关闭
|
|
259
|
+
try {
|
|
260
|
+
loading.loading('处理中...')
|
|
261
|
+
await doSomething()
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
loading.close()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ❌ 避免:可能忘记关闭
|
|
268
|
+
loading.loading('处理中...')
|
|
269
|
+
await doSomething()
|
|
270
|
+
loading.close() // 如果上面抛出异常,此行不会执行
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
2. **合理使用遮罩**
|
|
274
|
+
```typescript
|
|
275
|
+
// 重要操作,防止用户干扰
|
|
276
|
+
loading.loading({
|
|
277
|
+
msg: '正在保存...',
|
|
278
|
+
cover: true
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// 轻量操作,允许用户其他操作
|
|
282
|
+
loading.loading({
|
|
283
|
+
msg: '加载中...',
|
|
284
|
+
cover: false
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
3. **提供清晰的提示文本**
|
|
289
|
+
```typescript
|
|
290
|
+
// ✅ 清晰明确
|
|
291
|
+
loading.loading('正在上传文件...')
|
|
292
|
+
loading.loading('正在保存数据...')
|
|
293
|
+
|
|
294
|
+
// ❌ 模糊不清
|
|
295
|
+
loading.loading('请稍候...')
|
|
296
|
+
loading.loading('处理中...')
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
4. **长时间操作显示进度**
|
|
300
|
+
```typescript
|
|
301
|
+
// 对于耗时较长的操作,提供进度信息
|
|
302
|
+
for (let i = 0; i < items.length; i++) {
|
|
303
|
+
loading.loading(`处理进度:${i + 1}/${items.length}`)
|
|
304
|
+
await processItem(items[i])
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
5. **避免嵌套调用**
|
|
309
|
+
```typescript
|
|
310
|
+
// ❌ 避免嵌套
|
|
311
|
+
loading.loading('外层操作...')
|
|
312
|
+
await outerOperation()
|
|
313
|
+
loading.loading('内层操作...') // 会覆盖外层
|
|
314
|
+
await innerOperation()
|
|
315
|
+
loading.close() // 只关闭了内层
|
|
316
|
+
|
|
317
|
+
// ✅ 推荐:合并操作或使用状态管理
|
|
318
|
+
loading.loading('正在执行操作...')
|
|
319
|
+
try {
|
|
320
|
+
await outerOperation()
|
|
321
|
+
await innerOperation()
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
loading.close()
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## 使用场景
|
|
329
|
+
|
|
330
|
+
1. **数据加载**:页面初始化、列表刷新
|
|
331
|
+
2. **表单提交**:用户操作提交、数据保存
|
|
332
|
+
3. **文件操作**:上传、下载、删除文件
|
|
333
|
+
4. **网络请求**:API 调用、数据同步
|
|
334
|
+
5. **批量处理**:多个项目的批量操作
|
|
335
|
+
6. **页面跳转**:路由切换前的准备工作
|
|
336
|
+
|
|
337
|
+
## 注意事项
|
|
338
|
+
|
|
339
|
+
1. **必须手动关闭**:加载组件不会自动关闭,务必在操作完成后调用 `close()`
|
|
340
|
+
2. **防止内存泄漏**:在组件销毁前确保关闭加载状态
|
|
341
|
+
3. **单例模式**:同时只能显示一个加载状态,新的调用会覆盖旧的
|
|
342
|
+
4. **页面路径检测**:组件会自动在对应页面显示,切换页面时会自动关闭
|
|
343
|
+
5. **遮罩层影响**:开启遮罩时会阻止用户操作,谨慎使用
|
|
344
|
+
|
|
345
|
+
## 错误处理
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// 推荐的错误处理模式
|
|
349
|
+
async function safeOperation() {
|
|
350
|
+
const loading = useGlobalLoading()
|
|
351
|
+
const toast = useGlobalToast()
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
loading.loading('处理中...')
|
|
355
|
+
await riskyOperation()
|
|
356
|
+
toast.success('操作成功')
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
console.error('操作失败:', error)
|
|
360
|
+
toast.error('操作失败,请重试')
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
// 无论成功或失败都要关闭加载
|
|
364
|
+
loading.close()
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { storeToRefs } from 'pinia'
|
|
3
|
+
import { useToast } from 'wot-design-uni'
|
|
4
|
+
import { defineOptions, nextTick, ref, watch } from 'vue'
|
|
5
|
+
import { useGlobalLoading } from '../../composables/useGlobalLoading'
|
|
6
|
+
import { getCurrentPath } from '../../utils/index'
|
|
7
|
+
|
|
8
|
+
defineOptions({
|
|
9
|
+
name: 'GlobalLoading',
|
|
10
|
+
})
|
|
11
|
+
const { loadingOptions, currentPage } = storeToRefs(useGlobalLoading())
|
|
12
|
+
|
|
13
|
+
const { close: closeGlobalLoading } = useGlobalLoading()
|
|
14
|
+
|
|
15
|
+
const loading = useToast('globalLoading')
|
|
16
|
+
const currentPath = getCurrentPath()
|
|
17
|
+
|
|
18
|
+
// #ifdef MP-ALIPAY
|
|
19
|
+
const hackAlipayVisible = ref(false)
|
|
20
|
+
|
|
21
|
+
nextTick(() => {
|
|
22
|
+
hackAlipayVisible.value = true
|
|
23
|
+
})
|
|
24
|
+
// #endif
|
|
25
|
+
|
|
26
|
+
watch(() => loadingOptions.value, (newVal) => {
|
|
27
|
+
if (newVal && newVal.show) {
|
|
28
|
+
if (currentPage.value === currentPath) {
|
|
29
|
+
loading.loading(loadingOptions.value)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
loading.close()
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<script lang="ts">
|
|
39
|
+
export default {
|
|
40
|
+
options: {
|
|
41
|
+
virtualHost: true,
|
|
42
|
+
addGlobalClass: true,
|
|
43
|
+
styleIsolation: 'shared',
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<!-- #ifdef MP-ALIPAY -->
|
|
50
|
+
<wd-toast v-if="hackAlipayVisible" selector="globalLoading" :closed="closeGlobalLoading" />
|
|
51
|
+
<!-- #endif -->
|
|
52
|
+
<!-- #ifndef MP-ALIPAY -->
|
|
53
|
+
<wd-toast selector="globalLoading" :closed="closeGlobalLoading" />
|
|
54
|
+
<!-- #endif -->
|
|
55
|
+
</template>
|