verce-vue-test 0.0.31 → 0.0.33
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.
|
@@ -5,24 +5,30 @@
|
|
|
5
5
|
* 提供原生能力调用:UI 控制、用户信息、选人组件、原生 HTTP 请求
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { appendQuery } from '@/utils/query'
|
|
9
|
+
|
|
8
10
|
// ============================================================
|
|
9
11
|
// 类型定义
|
|
10
12
|
// ============================================================
|
|
11
13
|
|
|
12
14
|
/** 原生 AJAX 请求参数 */
|
|
13
|
-
interface MXAjaxParams {
|
|
15
|
+
export interface MXAjaxParams {
|
|
14
16
|
type: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
|
15
17
|
url: string
|
|
16
|
-
|
|
18
|
+
dataType?: string
|
|
17
19
|
async?: boolean
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
data?: unknown
|
|
21
|
+
headers?: Record<string, string>
|
|
22
|
+
complete?: () => void
|
|
23
|
+
success?: (data: unknown, status?: number, xhr?: unknown) => void
|
|
24
|
+
error?: (data: unknown, status?: number, xhr?: unknown) => void
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/** 原生 AJAX 响应 */
|
|
23
28
|
export interface MXAjaxResponse<T = unknown> {
|
|
24
29
|
data: T
|
|
25
|
-
status
|
|
30
|
+
status?: number
|
|
31
|
+
xhr?: unknown
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/** 用户信息 */
|
|
@@ -47,19 +53,27 @@ interface MXApiCallbacks {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/** 原生接口命名空间 */
|
|
50
|
-
type MXNamespace = 'MXCommon' | 'MXWebui' | 'MXContacts'
|
|
56
|
+
type MXNamespace = 'MXCommon' | 'NXCommon' | 'MXWebui' | 'MXContacts'
|
|
51
57
|
|
|
52
58
|
// ============================================================
|
|
53
59
|
// 声明全局原生接口
|
|
54
60
|
// ============================================================
|
|
55
61
|
|
|
56
62
|
declare global {
|
|
63
|
+
const MXCommon: Window['MXCommon']
|
|
64
|
+
const NXCommon: Window['NXCommon']
|
|
65
|
+
const MXWebui: Window['MXWebui']
|
|
66
|
+
const MXContacts: Window['MXContacts']
|
|
67
|
+
|
|
57
68
|
interface Window {
|
|
58
69
|
MXCommon?: {
|
|
59
70
|
getCurrentUser: (callback: (user: MXUser) => void) => void
|
|
60
71
|
getEncryptString: (callback: (secret: string) => void) => void
|
|
61
72
|
ajax: (params: MXAjaxParams) => void
|
|
62
73
|
}
|
|
74
|
+
NXCommon?: {
|
|
75
|
+
ajax: (params: MXAjaxParams) => void
|
|
76
|
+
}
|
|
63
77
|
MXWebui?: {
|
|
64
78
|
hideWebViewTitle: () => void
|
|
65
79
|
showOptionMenu: () => void
|
|
@@ -143,64 +157,33 @@ const makeApi =
|
|
|
143
157
|
(api: string, ...args: unknown[]) =>
|
|
144
158
|
applyApi(namespace, api, args)
|
|
145
159
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const
|
|
160
|
+
const callMXCommon = makeApi('MXCommon')
|
|
161
|
+
const callNXCommon = makeApi('NXCommon')
|
|
162
|
+
const callMXWebui = makeApi('MXWebui')
|
|
163
|
+
const callMXContacts = makeApi('MXContacts')
|
|
149
164
|
|
|
150
165
|
// ============================================================
|
|
151
166
|
// 导出的业务 API
|
|
152
167
|
// ============================================================
|
|
153
168
|
|
|
154
169
|
/** 隐藏 WebView 标题栏 */
|
|
155
|
-
export const hideWebViewTitle = (): void =>
|
|
170
|
+
export const hideWebViewTitle = (): void => callMXWebui('hideWebViewTitle')
|
|
156
171
|
|
|
157
172
|
/** 显示右上角菜单按钮 */
|
|
158
|
-
export const showOptionMenu = (): void =>
|
|
173
|
+
export const showOptionMenu = (): void => callMXWebui('showOptionMenu')
|
|
159
174
|
|
|
160
175
|
/** 设置自定义头部菜单 */
|
|
161
176
|
export const setCustomHeaderMenu = (...args: unknown[]): void =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/** 超时兜底:原生回调未触发时自动执行 onError */
|
|
165
|
-
function withCallbackTimeout<T extends (...args: any[]) => void>(
|
|
166
|
-
onSuccess: T,
|
|
167
|
-
onError: (reason: string) => void,
|
|
168
|
-
ms: number,
|
|
169
|
-
label: string,
|
|
170
|
-
): T {
|
|
171
|
-
const timer = setTimeout(() => onError(`[MX] ${label} 超时(${ms}ms)`), ms)
|
|
172
|
-
return ((...args: unknown[]) => {
|
|
173
|
-
clearTimeout(timer)
|
|
174
|
-
onSuccess(...args)
|
|
175
|
-
}) as unknown as T
|
|
176
|
-
}
|
|
177
|
+
callMXWebui('setCustomHeaderMenu', ...args)
|
|
177
178
|
|
|
178
179
|
/** 获取当前登录用户信息 */
|
|
179
|
-
export const getCurrentUser = (
|
|
180
|
-
onSuccess
|
|
181
|
-
onError?: (reason: string) => void,
|
|
182
|
-
timeout = 5000,
|
|
183
|
-
): void => {
|
|
184
|
-
MXCommon('getCurrentUser', withCallbackTimeout(
|
|
185
|
-
onSuccess,
|
|
186
|
-
(reason) => { console.error(reason); onError?.(reason) },
|
|
187
|
-
timeout,
|
|
188
|
-
'getCurrentUser',
|
|
189
|
-
))
|
|
180
|
+
export const getCurrentUser = (onSuccess: (user: MXUser) => void): void => {
|
|
181
|
+
MXCommon!.getCurrentUser(onSuccess)
|
|
190
182
|
}
|
|
191
183
|
|
|
192
184
|
/** 从原生客户端获取加密密钥 */
|
|
193
|
-
export const getEncryptString = (
|
|
194
|
-
onSuccess
|
|
195
|
-
onError?: (reason: string) => void,
|
|
196
|
-
timeout = 5000,
|
|
197
|
-
): void => {
|
|
198
|
-
MXCommon('getEncryptString', withCallbackTimeout(
|
|
199
|
-
onSuccess,
|
|
200
|
-
(reason) => { console.error(reason); onError?.(reason) },
|
|
201
|
-
timeout,
|
|
202
|
-
'getEncryptString',
|
|
203
|
-
))
|
|
185
|
+
export const getEncryptString = (onSuccess: (secret: string) => void): void => {
|
|
186
|
+
MXCommon!.getEncryptString(onSuccess)
|
|
204
187
|
}
|
|
205
188
|
|
|
206
189
|
/** 打开原生选人组件 */
|
|
@@ -208,7 +191,7 @@ export const MXSelectUsers = (
|
|
|
208
191
|
options: MXSelectUsersOptions = { enableSelectDept: false, canSelectSelf: true }
|
|
209
192
|
): Promise<MXUser[]> =>
|
|
210
193
|
new Promise((resolve) => {
|
|
211
|
-
|
|
194
|
+
callMXContacts('selectUsers', (result: MXUser[]) => {
|
|
212
195
|
resolve(result)
|
|
213
196
|
}, options)
|
|
214
197
|
})
|
|
@@ -217,58 +200,88 @@ export const MXSelectUsers = (
|
|
|
217
200
|
* 原生 AJAX 请求
|
|
218
201
|
*
|
|
219
202
|
* 使用原生应用的 HTTP 客户端发起请求,绕过浏览器同源策略限制。
|
|
220
|
-
* ajax
|
|
203
|
+
* NXCommon.ajax 只支持回调,不返回 Promise。
|
|
221
204
|
*/
|
|
222
|
-
export const ajax =
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const timeout = new Promise<MXAjaxResponse<T>>((_, reject) =>
|
|
240
|
-
setTimeout(() => reject(new Error(`[MX] ajax ${params.type} ${params.url} 超时`)), 15000),
|
|
241
|
-
)
|
|
205
|
+
export const ajax = (params: MXAjaxParams): void => {
|
|
206
|
+
const requestParams: MXAjaxParams = {
|
|
207
|
+
...params,
|
|
208
|
+
url: normalizeAjaxUrl(params.url),
|
|
209
|
+
async: params.async ?? true,
|
|
210
|
+
dataType: params.dataType ?? 'text',
|
|
211
|
+
complete() {
|
|
212
|
+
params.complete?.()
|
|
213
|
+
},
|
|
214
|
+
success(data, status, xhr) {
|
|
215
|
+
params.success?.(data, status, xhr)
|
|
216
|
+
},
|
|
217
|
+
error(data, status, xhr) {
|
|
218
|
+
params.error?.(data, status, xhr)
|
|
219
|
+
},
|
|
220
|
+
}
|
|
242
221
|
|
|
243
|
-
|
|
222
|
+
callNativeAjax(requestParams)
|
|
244
223
|
}
|
|
245
224
|
|
|
246
225
|
/** 原生 GET 请求 */
|
|
247
|
-
export const ajaxGet =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return ajax<T>({
|
|
226
|
+
export const ajaxGet = (
|
|
227
|
+
url: string,
|
|
228
|
+
query?: Record<string, unknown>,
|
|
229
|
+
options?: Pick<MXAjaxParams, 'headers' | 'complete' | 'dataType' | 'success' | 'error'>,
|
|
230
|
+
): void =>
|
|
231
|
+
ajax({
|
|
232
|
+
...options,
|
|
256
233
|
type: 'GET',
|
|
257
|
-
url:
|
|
234
|
+
url: appendQuery(url, query),
|
|
258
235
|
})
|
|
259
|
-
}
|
|
260
236
|
|
|
261
237
|
/** 原生 POST 请求 */
|
|
262
|
-
export const ajaxPost =
|
|
263
|
-
|
|
238
|
+
export const ajaxPost = (
|
|
239
|
+
url: string,
|
|
240
|
+
data?: unknown,
|
|
241
|
+
options?: Pick<MXAjaxParams, 'headers' | 'complete' | 'dataType' | 'success' | 'error'>,
|
|
242
|
+
): void =>
|
|
243
|
+
ajax({ ...options, type: 'POST', url, data })
|
|
264
244
|
|
|
265
245
|
/** 原生 PUT 请求 */
|
|
266
|
-
export const ajaxPut =
|
|
267
|
-
|
|
246
|
+
export const ajaxPut = (
|
|
247
|
+
url: string,
|
|
248
|
+
data?: unknown,
|
|
249
|
+
options?: Pick<MXAjaxParams, 'headers' | 'complete' | 'dataType' | 'success' | 'error'>,
|
|
250
|
+
): void =>
|
|
251
|
+
ajax({ ...options, type: 'PUT', url, data })
|
|
268
252
|
|
|
269
253
|
/** 原生 DELETE 请求 */
|
|
270
|
-
export const ajaxDelete =
|
|
271
|
-
|
|
254
|
+
export const ajaxDelete = (
|
|
255
|
+
url: string,
|
|
256
|
+
data?: unknown,
|
|
257
|
+
options?: Pick<MXAjaxParams, 'headers' | 'complete' | 'dataType' | 'success' | 'error'>,
|
|
258
|
+
): void =>
|
|
259
|
+
ajax({
|
|
260
|
+
...options,
|
|
261
|
+
type: 'DELETE',
|
|
262
|
+
url: typeof data === 'string' || typeof data === 'number'
|
|
263
|
+
? `${url.replace(/\/+$/, '')}/${encodeURIComponent(String(data))}`
|
|
264
|
+
: url,
|
|
265
|
+
data: typeof data === 'string' || typeof data === 'number' ? undefined : data,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
function callNativeAjax(params: MXAjaxParams): void {
|
|
269
|
+
if (NXCommon?.ajax) {
|
|
270
|
+
callNXCommon('ajax', params)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
callMXCommon('ajax', params)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function normalizeAjaxUrl(url: string): string {
|
|
278
|
+
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(url)) return url
|
|
279
|
+
|
|
280
|
+
const baseURL = import.meta.env.VITE_API_BASE_URL || ''
|
|
281
|
+
if (!baseURL) return url
|
|
282
|
+
|
|
283
|
+
return `${baseURL.replace(/\/+$/, '')}/${url.replace(/^\/+/, '')}`
|
|
284
|
+
}
|
|
272
285
|
|
|
273
286
|
let nativeReady = false
|
|
274
287
|
let resolveNativeReady: (() => void) | null = null
|
|
@@ -285,7 +298,7 @@ document.addEventListener('deviceready', () => {
|
|
|
285
298
|
/**
|
|
286
299
|
* 等待原生设备就绪(deviceready 事件触发后 resolve)
|
|
287
300
|
*
|
|
288
|
-
* native 环境下 deviceready 触发后
|
|
301
|
+
* native 环境下 deviceready 触发后 MXCommon 才注入完成,
|
|
289
302
|
* 之后 isNativeApp() 才能拿到正确结果。
|
|
290
303
|
*/
|
|
291
304
|
export const whenNativeReady = (): Promise<boolean> => {
|
|
@@ -299,4 +312,4 @@ export const whenNativeReady = (): Promise<boolean> => {
|
|
|
299
312
|
|
|
300
313
|
/** 检测是否在原生环境(需在 deviceready 之后调用才准确) */
|
|
301
314
|
export const isNativeApp = (): boolean =>
|
|
302
|
-
typeof
|
|
315
|
+
typeof MXCommon !== 'undefined' || typeof NXCommon !== 'undefined'
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* 根据环境自动选择 axios 或原生 AJAX
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { isNativeApp,
|
|
9
|
-
import type { MXAjaxResponse } from '@/core/mxApi'
|
|
8
|
+
import { isNativeApp, ajax } from '@/core/mxApi'
|
|
9
|
+
import type { MXAjaxParams, MXAjaxResponse } from '@/core/mxApi'
|
|
10
10
|
import { request as axiosRequest } from '@/utils/request'
|
|
11
|
+
import { appendQuery } from '@/utils/query'
|
|
11
12
|
import type { ApiResponse } from '@/types/api'
|
|
13
|
+
import { showToast } from 'vant'
|
|
14
|
+
import NProgress from 'nprogress'
|
|
15
|
+
import router from '@/router'
|
|
12
16
|
|
|
13
17
|
// ============================================================
|
|
14
18
|
// Token 管理
|
|
@@ -79,6 +83,10 @@ export async function request<R = unknown, T = unknown>(
|
|
|
79
83
|
...customHeaders,
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
if (auth && !headers.Authorization) {
|
|
87
|
+
headers.Authorization = `Basic ${window.btoa(`${auth.username}:${auth.password}`)}`
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
|
|
83
91
|
if (isNativeApp()) {
|
|
84
92
|
return await nativeRequest<R, T>({ url, method, data, params, headers })
|
|
@@ -98,32 +106,216 @@ export async function request<R = unknown, T = unknown>(
|
|
|
98
106
|
async function nativeRequest<R, T>(
|
|
99
107
|
config: RequestConfig<T>
|
|
100
108
|
): Promise<ApiResponse<R>> {
|
|
101
|
-
const { url, method, data, params } = config
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
const { url, method, data, params, headers } = config
|
|
110
|
+
|
|
111
|
+
NProgress.start()
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const options = { headers, dataType: 'text' }
|
|
115
|
+
let response: MXAjaxResponse<ApiResponse<R>>
|
|
116
|
+
|
|
117
|
+
switch (method) {
|
|
118
|
+
case 'GET':
|
|
119
|
+
response = await nativeAjax<ApiResponse<R>>({
|
|
120
|
+
...options,
|
|
121
|
+
type: 'GET',
|
|
122
|
+
url: appendQuery(url, params),
|
|
123
|
+
})
|
|
124
|
+
break
|
|
125
|
+
case 'POST':
|
|
126
|
+
response = await nativeAjax<ApiResponse<R>>({
|
|
127
|
+
...options,
|
|
128
|
+
type: 'POST',
|
|
129
|
+
url,
|
|
130
|
+
data,
|
|
131
|
+
})
|
|
132
|
+
break
|
|
133
|
+
case 'PUT':
|
|
134
|
+
response = await nativeAjax<ApiResponse<R>>({
|
|
135
|
+
...options,
|
|
136
|
+
type: 'PUT',
|
|
137
|
+
url,
|
|
138
|
+
data,
|
|
139
|
+
})
|
|
140
|
+
break
|
|
141
|
+
case 'DELETE':
|
|
142
|
+
response = await nativeAjax<ApiResponse<R>>({
|
|
143
|
+
...options,
|
|
144
|
+
type: 'DELETE',
|
|
145
|
+
url: appendDeleteParams(url, params),
|
|
146
|
+
data,
|
|
147
|
+
})
|
|
148
|
+
break
|
|
149
|
+
default:
|
|
150
|
+
throw new Error(`不支持的请求方法: ${method}`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = normalizeNativeResponse<R>(response)
|
|
154
|
+
|
|
155
|
+
if (result.code !== 0) {
|
|
156
|
+
showToast(result.message || '请求失败')
|
|
157
|
+
|
|
158
|
+
if (result.code === 401) {
|
|
159
|
+
clearToken()
|
|
160
|
+
router.push('/login')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const error = new Error(result.message || '请求失败') as Error & { handled?: boolean }
|
|
164
|
+
error.handled = true
|
|
165
|
+
throw error
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const nativeError = error as Error & { status?: number; data?: unknown; handled?: boolean }
|
|
171
|
+
if (nativeError.handled) {
|
|
172
|
+
throw error
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const status = nativeError.status
|
|
176
|
+
const statusCodeMap: Record<number, string> = {
|
|
177
|
+
400: '请求参数错误',
|
|
178
|
+
401: '未授权,请重新登录',
|
|
179
|
+
403: '拒绝访问',
|
|
180
|
+
404: '请求资源不存在',
|
|
181
|
+
500: '服务器内部错误',
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
showToast(status ? statusCodeMap[status] || `连接错误 ${status}` : nativeError.message || '网络连接异常')
|
|
185
|
+
|
|
186
|
+
if (status === 401) {
|
|
187
|
+
clearToken()
|
|
188
|
+
router.push('/login')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
throw error
|
|
192
|
+
} finally {
|
|
193
|
+
NProgress.done()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeNativeResponse<R>(response: MXAjaxResponse<unknown>): ApiResponse<R> {
|
|
198
|
+
const data = response.data
|
|
199
|
+
|
|
200
|
+
if (isApiResponse<R>(data)) {
|
|
201
|
+
return data
|
|
121
202
|
}
|
|
122
203
|
|
|
123
|
-
// 包装成与 axios 拦截器一致的响应格式
|
|
124
204
|
return {
|
|
125
205
|
code: 0,
|
|
126
206
|
message: 'success',
|
|
127
|
-
data:
|
|
128
|
-
}
|
|
207
|
+
data: data as R,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isApiResponse<R>(value: unknown): value is ApiResponse<R> {
|
|
212
|
+
return typeof value === 'object'
|
|
213
|
+
&& value !== null
|
|
214
|
+
&& typeof (value as ApiResponse<R>).code === 'number'
|
|
215
|
+
&& 'data' in value
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function appendDeleteParams(url: string, params?: Record<string, unknown>): string {
|
|
219
|
+
if (!params) return url
|
|
220
|
+
|
|
221
|
+
const { id, ...rest } = params
|
|
222
|
+
const targetUrl = id === undefined || id === null
|
|
223
|
+
? url
|
|
224
|
+
: `${url.replace(/\/+$/, '')}/${encodeURIComponent(String(id))}`
|
|
225
|
+
|
|
226
|
+
const query = new URLSearchParams()
|
|
227
|
+
Object.entries(rest).forEach(([key, value]) => {
|
|
228
|
+
if (value === undefined || value === null) return
|
|
229
|
+
query.append(key, String(value))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const queryString = query.toString()
|
|
233
|
+
if (!queryString) return targetUrl
|
|
234
|
+
|
|
235
|
+
return `${targetUrl}${targetUrl.includes('?') ? '&' : '?'}${queryString}`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function nativeAjax<T>(params: MXAjaxParams & { timeout?: number }): Promise<MXAjaxResponse<T>> {
|
|
239
|
+
const { timeout = 15000, ...ajaxParams } = params
|
|
240
|
+
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
let settled = false
|
|
243
|
+
const timer = window.setTimeout(() => {
|
|
244
|
+
if (settled) return
|
|
245
|
+
settled = true
|
|
246
|
+
reject(new Error(`[MX] ajax ${ajaxParams.type} ${ajaxParams.url} 超时(${timeout}ms)`))
|
|
247
|
+
}, timeout)
|
|
248
|
+
|
|
249
|
+
const settle = (callback: () => void) => {
|
|
250
|
+
if (settled) return
|
|
251
|
+
settled = true
|
|
252
|
+
window.clearTimeout(timer)
|
|
253
|
+
callback()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
ajax({
|
|
257
|
+
...ajaxParams,
|
|
258
|
+
success(data, status, xhr) {
|
|
259
|
+
settle(() => {
|
|
260
|
+
ajaxParams.success?.(data, status, xhr)
|
|
261
|
+
resolve({
|
|
262
|
+
data: parseAjaxData<T>(data, ajaxParams.dataType),
|
|
263
|
+
status,
|
|
264
|
+
xhr,
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
},
|
|
268
|
+
error(data, status, xhr) {
|
|
269
|
+
settle(() => {
|
|
270
|
+
ajaxParams.error?.(data, status, xhr)
|
|
271
|
+
reject(createNativeAjaxError(data, status, xhr, ajaxParams))
|
|
272
|
+
})
|
|
273
|
+
},
|
|
274
|
+
complete() {
|
|
275
|
+
ajaxParams.complete?.()
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseAjaxData<T>(data: unknown, dataType?: string): T {
|
|
282
|
+
if (dataType === 'text') {
|
|
283
|
+
return parseJsonString(data) as T
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (typeof data !== 'string') return data as T
|
|
287
|
+
|
|
288
|
+
return parseJsonString(data) as T
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function parseJsonString(data: unknown): unknown {
|
|
292
|
+
if (typeof data !== 'string') return data
|
|
293
|
+
|
|
294
|
+
const trimmed = data.trim()
|
|
295
|
+
if (!trimmed) return undefined
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
return JSON.parse(trimmed)
|
|
299
|
+
} catch {
|
|
300
|
+
return data
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createNativeAjaxError(
|
|
305
|
+
data: unknown,
|
|
306
|
+
status: number | undefined,
|
|
307
|
+
xhr: unknown,
|
|
308
|
+
params: Omit<MXAjaxParams, 'success' | 'error'>,
|
|
309
|
+
): Error & { data: unknown; status?: number; xhr?: unknown } {
|
|
310
|
+
const parsedData = parseAjaxData(data, params.dataType)
|
|
311
|
+
const message = typeof parsedData === 'object' && parsedData && 'message' in parsedData
|
|
312
|
+
? String((parsedData as { message?: unknown }).message || '请求失败')
|
|
313
|
+
: typeof parsedData === 'string'
|
|
314
|
+
? parsedData
|
|
315
|
+
: '请求失败'
|
|
316
|
+
const error = new Error(message) as Error & { data: unknown; status?: number; xhr?: unknown }
|
|
317
|
+
error.data = parsedData
|
|
318
|
+
error.status = status
|
|
319
|
+
error.xhr = xhr
|
|
320
|
+
return error
|
|
129
321
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将 query 对象拼接到 URL 上
|
|
3
|
+
*
|
|
4
|
+
* 自动过滤 undefined/null 值,支持数组参数。
|
|
5
|
+
*/
|
|
6
|
+
export function appendQuery(url: string, query?: Record<string, unknown>): string {
|
|
7
|
+
if (!query) return url
|
|
8
|
+
|
|
9
|
+
const searchParams = new URLSearchParams()
|
|
10
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
11
|
+
if (value === undefined || value === null) return
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
value.forEach((item) => {
|
|
14
|
+
if (item !== undefined && item !== null) {
|
|
15
|
+
searchParams.append(key, String(item))
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
searchParams.append(key, String(value))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const queryString = searchParams.toString()
|
|
24
|
+
if (!queryString) return url
|
|
25
|
+
|
|
26
|
+
return `${url}${url.includes('?') ? '&' : '?'}${queryString}`
|
|
27
|
+
}
|
|
@@ -22,19 +22,11 @@ onMounted(() => {
|
|
|
22
22
|
if (!appStore.isNative) return
|
|
23
23
|
|
|
24
24
|
loading.value = true
|
|
25
|
-
getEncryptString(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
(reason) => {
|
|
32
|
-
loading.value = false
|
|
33
|
-
console.error(reason)
|
|
34
|
-
showToast({ message: '获取设备凭证失败,请手动登录', duration: 3000 })
|
|
35
|
-
appStore.isNative = false
|
|
36
|
-
},
|
|
37
|
-
)
|
|
25
|
+
getEncryptString((secret) => {
|
|
26
|
+
loading.value = false
|
|
27
|
+
console.log(secret)
|
|
28
|
+
// TODO: 用 secret 自动换取 token 并跳转首页
|
|
29
|
+
})
|
|
38
30
|
})
|
|
39
31
|
|
|
40
32
|
async function onSubmit() {
|
package/package.json
CHANGED
|
@@ -264,68 +264,73 @@ function createCashflowOption(): EChartsOption {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
function
|
|
268
|
-
const progress = Math.min(Math.max(card.needle, 0), card.max)
|
|
269
|
-
const rest = Math.max(card.max - progress, 0)
|
|
270
|
-
const ringGradient = new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
|
271
|
-
{ offset: 0, color: '#1278ff' },
|
|
272
|
-
{ offset: 0.5, color: '#33f0ff' },
|
|
273
|
-
{ offset: 1, color: '#58ff86' },
|
|
274
|
-
])
|
|
275
|
-
|
|
267
|
+
function createStageGaugeOption(card: GaugeCard): EChartsOption {
|
|
276
268
|
return {
|
|
277
269
|
animationDuration: 900,
|
|
278
|
-
graphic: [
|
|
279
|
-
{
|
|
280
|
-
type: 'text',
|
|
281
|
-
left: 'center',
|
|
282
|
-
top: '44%',
|
|
283
|
-
style: {
|
|
284
|
-
text: card.value,
|
|
285
|
-
fill: '#34eaff',
|
|
286
|
-
fontSize: 30,
|
|
287
|
-
fontWeight: 800,
|
|
288
|
-
textAlign: 'center',
|
|
289
|
-
textShadowBlur: 14,
|
|
290
|
-
textShadowColor: 'rgba(47, 233, 255, .55)',
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
270
|
series: [
|
|
295
271
|
{
|
|
296
|
-
type: '
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
272
|
+
type: 'gauge',
|
|
273
|
+
min: 0,
|
|
274
|
+
max: card.max,
|
|
275
|
+
radius: '93%',
|
|
276
|
+
center: ['50%', '58%'],
|
|
277
|
+
splitNumber: 5,
|
|
278
|
+
axisLine: {
|
|
279
|
+
lineStyle: {
|
|
280
|
+
width: 16,
|
|
281
|
+
color: [
|
|
282
|
+
[0.3, '#67e0e3'],
|
|
283
|
+
[0.7, '#37a2da'],
|
|
284
|
+
[1, '#fd666d'],
|
|
285
|
+
],
|
|
286
|
+
},
|
|
308
287
|
},
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
labelLine: { show: false },
|
|
321
|
-
data: [
|
|
322
|
-
{
|
|
323
|
-
value: 1,
|
|
324
|
-
itemStyle: {
|
|
325
|
-
color: 'rgba(58, 216, 255, .18)',
|
|
326
|
-
},
|
|
288
|
+
pointer: {
|
|
289
|
+
length: '58%',
|
|
290
|
+
width: 5,
|
|
291
|
+
itemStyle: { color: 'auto' },
|
|
292
|
+
},
|
|
293
|
+
axisTick: {
|
|
294
|
+
distance: -16,
|
|
295
|
+
length: 7,
|
|
296
|
+
lineStyle: {
|
|
297
|
+
color: '#ffffff',
|
|
298
|
+
width: 2,
|
|
327
299
|
},
|
|
328
|
-
|
|
300
|
+
},
|
|
301
|
+
splitLine: {
|
|
302
|
+
distance: -16,
|
|
303
|
+
length: 16,
|
|
304
|
+
lineStyle: {
|
|
305
|
+
color: '#ffffff',
|
|
306
|
+
width: 3,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
axisLabel: {
|
|
310
|
+
color: 'inherit',
|
|
311
|
+
distance: 22,
|
|
312
|
+
fontSize: 12,
|
|
313
|
+
},
|
|
314
|
+
anchor: {
|
|
315
|
+
show: true,
|
|
316
|
+
showAbove: true,
|
|
317
|
+
size: 9,
|
|
318
|
+
itemStyle: {
|
|
319
|
+
borderWidth: 2,
|
|
320
|
+
borderColor: '#7ef7ff',
|
|
321
|
+
color: '#001a34',
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
title: { show: false },
|
|
325
|
+
detail: {
|
|
326
|
+
valueAnimation: true,
|
|
327
|
+
offsetCenter: [0, '45%'],
|
|
328
|
+
color: '#30dcff',
|
|
329
|
+
fontSize: 26,
|
|
330
|
+
fontWeight: 800,
|
|
331
|
+
formatter: () => card.value,
|
|
332
|
+
},
|
|
333
|
+
data: [{ value: card.needle }],
|
|
329
334
|
},
|
|
330
335
|
],
|
|
331
336
|
}
|
|
@@ -414,9 +419,9 @@ onMounted(async () => {
|
|
|
414
419
|
updateCockpitScale()
|
|
415
420
|
await nextTick()
|
|
416
421
|
createChart(cashflowChart.value, createCashflowOption())
|
|
417
|
-
createChart(liquidityGauge.value,
|
|
418
|
-
createChart(coverageGauge.value,
|
|
419
|
-
createChart(stableGauge.value,
|
|
422
|
+
createChart(liquidityGauge.value, createStageGaugeOption(gaugeCards[0]!))
|
|
423
|
+
createChart(coverageGauge.value, createStageGaugeOption(gaugeCards[1]!))
|
|
424
|
+
createChart(stableGauge.value, createStageGaugeOption(gaugeCards[2]!))
|
|
420
425
|
createChart(maturityChart.value, createMaturityOption())
|
|
421
426
|
window.addEventListener('resize', handleViewportChange)
|
|
422
427
|
window.visualViewport?.addEventListener('resize', handleViewportChange)
|
|
@@ -1032,17 +1037,17 @@ onUnmounted(() => {
|
|
|
1032
1037
|
|
|
1033
1038
|
.gauge-chart {
|
|
1034
1039
|
position: absolute;
|
|
1035
|
-
left: -
|
|
1036
|
-
right: -
|
|
1037
|
-
top:
|
|
1038
|
-
height:
|
|
1040
|
+
left: -8px;
|
|
1041
|
+
right: -8px;
|
|
1042
|
+
top: 24px;
|
|
1043
|
+
height: 156px;
|
|
1039
1044
|
}
|
|
1040
1045
|
|
|
1041
1046
|
.gauge-card p {
|
|
1042
1047
|
position: absolute;
|
|
1043
1048
|
left: 0;
|
|
1044
1049
|
right: 0;
|
|
1045
|
-
bottom:
|
|
1050
|
+
bottom: 0;
|
|
1046
1051
|
color: #d7e6f6;
|
|
1047
1052
|
text-align: center;
|
|
1048
1053
|
font-size: 14px;
|