af-mobile-client-vue3 1.6.12 → 1.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.6.12",
4
+ "version": "1.6.14",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -1,96 +1,96 @@
1
- import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
2
- import { APP_WEB_CONFIG_KEY } from '@af-mobile-client-vue3/stores/mutation-type'
3
- import { secureStorageBatchWrite } from '@af-mobile-client-vue3/utils/secureStorage'
4
- import { createStorage } from '@af-mobile-client-vue3/utils/Storage'
5
- import { defineStore } from 'pinia'
6
- import { ref } from 'vue'
7
- import { useHomeAppStore } from './homeApp'
8
-
9
- export interface WebConfig {
10
- isAttendance: boolean | false
11
- systemName: string
12
- routerName: string
13
- systemDesc: string
14
- homePage: any
15
- defaultAvatarUrl: string
16
- wechatLogin: boolean
17
- wxLoginAge: boolean
18
- wxAutoLogin: string
19
- tenantName: string
20
- systemLogo: string
21
- homeAppList: Array<any>
22
- slideshowList: Array<any>
23
- registerRequire: boolean
24
- requestEncrypt: boolean
25
- phoneLoginVerification: boolean
26
- hiddenTraditionalLogin: boolean
27
- isFaceRecognition: boolean // 登录是否需要人脸识别
28
- intervalFaceRecognition: boolean // 登录是否需要开启定时人脸识别
29
- faceIntervalMinutes: number // 定时时间 单位分钟
30
- faceRetryCount: number // 定时验证 重试次数
31
- getPlatformUser: boolean // 获取外部用户信息
32
- navigationStyle: any // app顶部导航区域样式控制
33
- captchaEnable: boolean // 是否启用登录验证码(前台本地处理)
34
- }
35
-
36
- // 存放 webConfig 中的 setting 配置
37
- export const useSettingStore = defineStore('setting', () => {
38
- const setting = ref<WebConfig>(undefined)
39
- const isInitialized = ref(false)
40
-
41
- const setSetting = (newSetting: WebConfig) => {
42
- setting.value = newSetting
43
- }
44
-
45
- const getSetting = () => {
46
- return setting.value
47
- }
48
-
49
- // 将 init 作为 store 的方法导出
50
- const init = async () => {
51
- if (isInitialized.value)
52
- return
53
-
54
- const useStore = createStorage()
55
- const catchWebConfig = useStore.get(APP_WEB_CONFIG_KEY)
56
- const homeAppStore = useHomeAppStore()
57
-
58
- if (catchWebConfig?.setting) {
59
- setSetting(catchWebConfig?.setting)
60
- // homeAppList 仅在用户缓存没有时才回填,避免覆盖用户自定义
61
- if (!homeAppStore.homeAppList?.length && catchWebConfig.setting.homeAppList) {
62
- homeAppStore.setHomeAppList(catchWebConfig.setting.homeAppList)
63
- }
64
- }
65
- else {
66
- const res = await getConfigByNameAsync('webMobileConfig', 'af-system')
67
- if (res.setting) {
68
- useStore.set(APP_WEB_CONFIG_KEY, res)
69
- setSetting(res.setting)
70
- // homeAppList 仅在用户缓存没有时才回填
71
- if (!homeAppStore.homeAppList?.length && res.setting.homeAppList) {
72
- homeAppStore.setHomeAppList(res.setting.homeAppList)
73
- }
74
- // 如果有pos相关的配置存储到app安全存储中
75
- if (res.setting.posConfig) {
76
- secureStorageBatchWrite([
77
- {
78
- key: 'posConfig',
79
- value: res.setting.posConfig,
80
- },
81
- ])
82
- }
83
- }
84
- }
85
- isInitialized.value = true
86
- }
87
-
88
- return {
89
- setSetting,
90
- getSetting,
91
- init, // 导出 init 方法
92
- isInitialized,
93
- }
94
- })
95
-
96
- export default useSettingStore
1
+ import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
2
+ import { APP_WEB_CONFIG_KEY } from '@af-mobile-client-vue3/stores/mutation-type'
3
+ import { secureStorageBatchWrite } from '@af-mobile-client-vue3/utils/secureStorage'
4
+ import { createStorage } from '@af-mobile-client-vue3/utils/Storage'
5
+ import { defineStore } from 'pinia'
6
+ import { ref } from 'vue'
7
+ import { useHomeAppStore } from './homeApp'
8
+
9
+ export interface WebConfig {
10
+ isAttendance: boolean | false
11
+ systemName: string
12
+ routerName: string
13
+ systemDesc: string
14
+ homePage: any
15
+ defaultAvatarUrl: string
16
+ wechatLogin: boolean
17
+ wxLoginAge: boolean
18
+ wxAutoLogin: string
19
+ tenantName: string
20
+ systemLogo: string
21
+ homeAppList: Array<any>
22
+ slideshowList: Array<any>
23
+ registerRequire: boolean
24
+ requestEncrypt: boolean
25
+ phoneLoginVerification: boolean
26
+ hiddenTraditionalLogin: boolean
27
+ isFaceRecognition: boolean // 登录是否需要人脸识别
28
+ intervalFaceRecognition: boolean // 登录是否需要开启定时人脸识别
29
+ faceIntervalMinutes: number // 定时时间 单位分钟
30
+ faceRetryCount: number // 定时验证 重试次数
31
+ getPlatformUser: boolean // 获取外部用户信息
32
+ navigationStyle: any // app顶部导航区域样式控制
33
+ captchaEnable: boolean // 是否启用登录验证码(前台本地处理)
34
+ }
35
+
36
+ // 存放 webConfig 中的 setting 配置
37
+ export const useSettingStore = defineStore('setting', () => {
38
+ const setting = ref<WebConfig>(undefined)
39
+ const isInitialized = ref(false)
40
+
41
+ const setSetting = (newSetting: WebConfig) => {
42
+ setting.value = newSetting
43
+ }
44
+
45
+ const getSetting = () => {
46
+ return setting.value
47
+ }
48
+
49
+ // 将 init 作为 store 的方法导出
50
+ const init = async () => {
51
+ if (isInitialized.value)
52
+ return
53
+
54
+ const useStore = createStorage()
55
+ const catchWebConfig = useStore.get(APP_WEB_CONFIG_KEY)
56
+ const homeAppStore = useHomeAppStore()
57
+
58
+ if (catchWebConfig?.setting) {
59
+ setSetting(catchWebConfig?.setting)
60
+ // homeAppList 仅在用户缓存没有时才回填,避免覆盖用户自定义
61
+ if (!homeAppStore.homeAppList?.length && catchWebConfig.setting.homeAppList) {
62
+ homeAppStore.setHomeAppList(catchWebConfig.setting.homeAppList)
63
+ }
64
+ }
65
+ else {
66
+ const res = await getConfigByNameAsync('webMobileConfig')
67
+ if (res.setting) {
68
+ useStore.set(APP_WEB_CONFIG_KEY, res)
69
+ setSetting(res.setting)
70
+ // homeAppList 仅在用户缓存没有时才回填
71
+ if (!homeAppStore.homeAppList?.length && res.setting.homeAppList) {
72
+ homeAppStore.setHomeAppList(res.setting.homeAppList)
73
+ }
74
+ // 如果有pos相关的配置存储到app安全存储中
75
+ if (res.setting.posConfig) {
76
+ secureStorageBatchWrite([
77
+ {
78
+ key: 'posConfig',
79
+ value: res.setting.posConfig,
80
+ },
81
+ ])
82
+ }
83
+ }
84
+ }
85
+ isInitialized.value = true
86
+ }
87
+
88
+ return {
89
+ setSetting,
90
+ getSetting,
91
+ init, // 导出 init 方法
92
+ isInitialized,
93
+ }
94
+ })
95
+
96
+ export default useSettingStore
@@ -126,6 +126,9 @@ export const useUserStore = defineStore('app-user', () => {
126
126
  const getToken = () => {
127
127
  return userState.value.token || Storage.get(ACCESS_TOKEN, '') as string
128
128
  }
129
+ const getSessionKey = () => {
130
+ return Storage.get('v4-session-key', '') as string || localStorage.getItem('v4-session-key')
131
+ }
129
132
  const getLastUpdateTime = () => {
130
133
  return userState.value.lastUpdateTime
131
134
  }
@@ -162,7 +165,7 @@ export const useUserStore = defineStore('app-user', () => {
162
165
  const expiresInSeconds = expiresInMinutes * 60
163
166
  const BUFFER_SECONDS = 30
164
167
  const safeExpire = Math.max(expiresInSeconds - BUFFER_SECONDS, 0)
165
- Storage.set('v4-session-key', key, safeExpire)
168
+ Storage.set('v4-session-key', key, safeExpire)
166
169
  }
167
170
  else {
168
171
  localStorage.setItem('v4-session-key', key)
@@ -377,6 +380,7 @@ export const useUserStore = defineStore('app-user', () => {
377
380
  Storage.remove(PLATFORM_TYPE)
378
381
  Storage.remove(EXTERNAL_USER_INFO)
379
382
  Storage.remove('LoginTicket')
383
+ Storage.remove('v4-session-key')
380
384
  // 清除本地加密秘钥
381
385
  localStorage.removeItem('v4-session-key')
382
386
  secureStorageWrite('v4-session-key', '')
@@ -423,6 +427,7 @@ export const useUserStore = defineStore('app-user', () => {
423
427
  loginExternal,
424
428
  loginExternalMini,
425
429
  getToken,
430
+ getSessionKey,
426
431
  getLastUpdateTime,
427
432
  logout,
428
433
  registerClean,
@@ -1,124 +1,124 @@
1
- // 默认缓存期限为7天
2
- const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
3
-
4
- /**
5
- * 创建本地缓存对象
6
- */
7
- export function createStorage({ prefixKey = '', storage = localStorage } = {}) {
8
- /**
9
- * 本地缓存类
10
- * @class Storage
11
- */
12
- const Storage = class {
13
- private storage = storage
14
- private prefixKey?: string = prefixKey
15
-
16
- private getKey(key: string) {
17
- return `${this.prefixKey}${key}`.toUpperCase()
18
- }
19
-
20
- /**
21
- * @description 设置缓存
22
- * @param {string} key 缓存键
23
- * @param {*} value 缓存值
24
- * @param expire
25
- */
26
- set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
27
- const stringData = JSON.stringify({
28
- value,
29
- expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
30
- })
31
- this.storage.setItem(this.getKey(key), stringData)
32
- }
33
-
34
- /**
35
- * 读取缓存
36
- * @param {string} key 缓存键
37
- * @param {*=} def 默认值
38
- */
39
- get(key: string, def: any = null) {
40
- const item = this.storage.getItem(this.getKey(key))
41
- if (item) {
42
- try {
43
- const data = JSON.parse(item)
44
- const { value, expire } = data
45
- // 在有效期内直接返回
46
- if (expire === null || expire >= Date.now())
47
- return value
48
-
49
- this.remove(key)
50
- }
51
- catch {
52
- return def
53
- }
54
- }
55
- return def
56
- }
57
-
58
- /**
59
- * 从缓存删除某项
60
- * @param {string} key
61
- */
62
- remove(key: string) {
63
- this.storage.removeItem(this.getKey(key))
64
- }
65
-
66
- /**
67
- * 清空所有缓存
68
- * @memberOf Cache
69
- */
70
- clear(): void {
71
- this.storage.clear()
72
- }
73
-
74
- /**
75
- * 设置cookie
76
- * @param {string} name cookie 名称
77
- * @param {*} value cookie 值
78
- * @param {number=} expire 过期时间
79
- * 如果过期时间为设置,默认关闭浏览器自动删除
80
- * @example
81
- */
82
- setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
83
- document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`
84
- }
85
-
86
- /**
87
- * 根据名字获取cookie值
88
- * @param name
89
- */
90
- getCookie(name: string): string {
91
- const cookieArr = document.cookie.split('; ')
92
- for (let i = 0, length = cookieArr.length; i < length; i++) {
93
- const kv = cookieArr[i].split('=')
94
- if (kv[0] === this.getKey(name))
95
- return kv[1]
96
- }
97
- return ''
98
- }
99
-
100
- /**
101
- * 根据名字删除指定的cookie
102
- * @param {string} key
103
- */
104
- removeCookie(key: string) {
105
- this.setCookie(key, 1, -1)
106
- }
107
-
108
- /**
109
- * 清空cookie,使所有cookie失效
110
- */
111
- clearCookie(): void {
112
- const keys = document.cookie.match(/[^ =;]+(?==)/g)
113
- if (keys) {
114
- for (let i = keys.length; i--;)
115
- document.cookie = `${keys[i]}=0;expire=${new Date(0).toUTCString()}`
116
- }
117
- }
118
- }
119
- return new Storage()
120
- }
121
-
122
- export const storage = createStorage()
123
-
124
- export default Storage
1
+ // 默认缓存期限为7天
2
+ const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
3
+
4
+ /**
5
+ * 创建本地缓存对象
6
+ */
7
+ export function createStorage({ prefixKey = '', storage = localStorage } = {}) {
8
+ /**
9
+ * 本地缓存类
10
+ * @class Storage
11
+ */
12
+ const Storage = class {
13
+ private storage = storage
14
+ private prefixKey?: string = prefixKey
15
+
16
+ private getKey(key: string) {
17
+ return `${this.prefixKey}${key}`.toUpperCase()
18
+ }
19
+
20
+ /**
21
+ * @description 设置缓存
22
+ * @param {string} key 缓存键
23
+ * @param {*} value 缓存值
24
+ * @param expire
25
+ */
26
+ set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
27
+ const stringData = JSON.stringify({
28
+ value,
29
+ expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
30
+ })
31
+ this.storage.setItem(this.getKey(key), stringData)
32
+ }
33
+
34
+ /**
35
+ * 读取缓存
36
+ * @param {string} key 缓存键
37
+ * @param {*=} def 默认值
38
+ */
39
+ get(key: string, def: any = null) {
40
+ const item = this.storage.getItem(this.getKey(key))
41
+ if (item) {
42
+ try {
43
+ const data = JSON.parse(item)
44
+ const { value, expire } = data
45
+ // 在有效期内直接返回
46
+ if (expire === null || expire >= Date.now())
47
+ return value
48
+
49
+ this.remove(key)
50
+ }
51
+ catch {
52
+ return def
53
+ }
54
+ }
55
+ return def
56
+ }
57
+
58
+ /**
59
+ * 从缓存删除某项
60
+ * @param {string} key
61
+ */
62
+ remove(key: string) {
63
+ this.storage.removeItem(this.getKey(key))
64
+ }
65
+
66
+ /**
67
+ * 清空所有缓存
68
+ * @memberOf Cache
69
+ */
70
+ clear(): void {
71
+ this.storage.clear()
72
+ }
73
+
74
+ /**
75
+ * 设置cookie
76
+ * @param {string} name cookie 名称
77
+ * @param {*} value cookie 值
78
+ * @param {number=} expire 过期时间
79
+ * 如果过期时间为设置,默认关闭浏览器自动删除
80
+ * @example
81
+ */
82
+ setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
83
+ document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`
84
+ }
85
+
86
+ /**
87
+ * 根据名字获取cookie值
88
+ * @param name
89
+ */
90
+ getCookie(name: string): string {
91
+ const cookieArr = document.cookie.split('; ')
92
+ for (let i = 0, length = cookieArr.length; i < length; i++) {
93
+ const kv = cookieArr[i].split('=')
94
+ if (kv[0] === this.getKey(name))
95
+ return kv[1]
96
+ }
97
+ return ''
98
+ }
99
+
100
+ /**
101
+ * 根据名字删除指定的cookie
102
+ * @param {string} key
103
+ */
104
+ removeCookie(key: string) {
105
+ this.setCookie(key, 1, -1)
106
+ }
107
+
108
+ /**
109
+ * 清空cookie,使所有cookie失效
110
+ */
111
+ clearCookie(): void {
112
+ const keys = document.cookie.match(/[^ =;]+(?==)/g)
113
+ if (keys) {
114
+ for (let i = keys.length; i--;)
115
+ document.cookie = `${keys[i]}=0;expire=${new Date(0).toUTCString()}`
116
+ }
117
+ }
118
+ }
119
+ return new Storage()
120
+ }
121
+
122
+ export const storage = createStorage()
123
+
124
+ export default Storage
@@ -1,225 +1,225 @@
1
- import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
2
- import { ContentTypeEnum, ResultEnum } from '@af-mobile-client-vue3/enums/requestEnum'
3
- import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
4
- import { ACCESS_TOKEN } from '@af-mobile-client-vue3/stores/mutation-type'
5
- import { encryptUtil } from '@af-mobile-client-vue3/utils/EncryptUtil'
6
- import axios from 'axios'
7
- import { showToast } from 'vant'
8
-
9
- // 默认 axios 实例请求配置
10
- const configDefault = {
11
- headers: {
12
- 'Content-Type': ContentTypeEnum.JSON,
13
- },
14
- // 请求超时时间
15
- timeout: 20000,
16
- // API 请求的默认前缀
17
- baseURL: import.meta.env.VITE_APP_API_BASE_URL,
18
- data: {},
19
- }
20
-
21
- class Http {
22
- // 当前实例
23
- private static axiosInstance: AxiosInstance
24
- // 请求配置
25
- private static axiosConfigDefault: AxiosRequestConfig
26
-
27
- // 请求拦截
28
- private httpInterceptorsRequest(): void {
29
- Http.axiosInstance.interceptors.request.use(
30
- (config) => {
31
- // 发送请求前,可在此让每个请求携带自定义 token, 请根据实际情况修改
32
- const savedToken = useUserStore().getToken()
33
- // 如果 token 存在
34
- if (savedToken)
35
- config.headers[ACCESS_TOKEN] = savedToken
36
- const v4SessionKey = localStorage.getItem('v4-session-key')
37
- if (['post'].includes(config.method.toLowerCase()) && v4SessionKey) {
38
- if (config.data && !(config.data instanceof FormData)) {
39
- config.data = {
40
- encrypted: encryptUtil.AESEncryptCBC(config.data, v4SessionKey),
41
- }
42
- config.headers['X-Sec'] = '1'
43
- config.headers['X-Rand'] = Math.random().toString(36).substr(2, 5)
44
- config.headers['X-Ts'] = Date.now()
45
- }
46
- }
47
- return config
48
- },
49
- (error: AxiosError) => {
50
- showToast({
51
- message: error.message,
52
- position: 'bottom',
53
- })
54
- return Promise.reject(error)
55
- },
56
- )
57
- }
58
-
59
- // 响应拦截
60
- private httpInterceptorsResponse(): void {
61
- Http.axiosInstance.interceptors.response.use(
62
- async (response: AxiosResponse) => {
63
- // 判断是否需要解密
64
- if (response.headers && response.headers['x-encrypted'] === '1') {
65
- const v4SessionKey = localStorage.getItem('v4-session-key')
66
- if (v4SessionKey && response.data) {
67
- const decryptedData = encryptUtil.decryptResponse(response?.data, v4SessionKey)
68
- // 如果解密成功且不等于原数据,说明解密有效
69
- if (decryptedData !== response.data) {
70
- // 响应解密成功
71
- response.data = decryptedData
72
- }
73
- }
74
- }
75
- const compatible = import.meta.env.VITE_APP_COMPATIBLE
76
- if (compatible !== 'V4') {
77
- return response.data
78
- }
79
- // 与后端协定的返回字段
80
- const { code, msg, data } = response.data
81
- // 临时向v3请求上传服务,因为没有code,未来会改
82
- if (code === undefined) {
83
- return response.data
84
- }
85
- // 判断请求是否成功
86
- const isSuccess = code === ResultEnum.SUCCESS
87
- if (isSuccess) {
88
- // 兼容 V3 请求没有data的情况
89
- if (data) {
90
- return data
91
- }
92
- else {
93
- return response.data
94
- }
95
- }
96
- else {
97
- if (code === 401 && response.config.url?.indexOf('af-auth/logout') === -1) {
98
- showToast({
99
- message: '登录态已失效,请重新登录',
100
- position: 'bottom',
101
- })
102
- await useUserStore().logout()
103
- }
104
- else {
105
- showToast({
106
- message: msg,
107
- position: 'bottom',
108
- })
109
- console.error(`请求失败,返回结果:${msg}`)
110
- }
111
- return Promise.reject(response.data)
112
- }
113
- },
114
- (error: AxiosError) => {
115
- // 处理 HTTP 网络错误
116
- let message: string = error.response?.data as string
117
- if (!message) {
118
- // HTTP 状态码
119
- const status = error.response?.status
120
- switch (status) {
121
- case 400:
122
- message = '请求错误'
123
- break
124
- case 401:
125
- message = '登录态已失效,请重新登录'
126
- // 401状态码处理:自动登出并跳转到登录页
127
- useUserStore().logout()
128
- break
129
- case 403:
130
- message = '拒绝访问'
131
- break
132
- case 404:
133
- message = `请求地址出错: ${error.response?.config?.url}`
134
- break
135
- case 408:
136
- message = '请求超时'
137
- break
138
- case 500:
139
- message = '服务器内部错误'
140
- break
141
- case 501:
142
- message = '服务未实现'
143
- break
144
- case 502:
145
- message = '网关错误'
146
- break
147
- case 503:
148
- message = '服务不可用'
149
- break
150
- case 504:
151
- message = '网关超时'
152
- break
153
- case 505:
154
- message = 'HTTP版本不受支持'
155
- break
156
- default:
157
- message = '网络连接故障'
158
- }
159
- }
160
- showToast({
161
- message,
162
- position: 'bottom',
163
- })
164
- return Promise.reject(error)
165
- },
166
- )
167
- }
168
-
169
- constructor(config: AxiosRequestConfig) {
170
- Http.axiosConfigDefault = config
171
- Http.axiosInstance = axios.create(config)
172
- this.httpInterceptorsRequest()
173
- this.httpInterceptorsResponse()
174
- }
175
-
176
- // 通用请求函数
177
- public request<T>(paramConfig: AxiosRequestConfig): Promise<T> {
178
- const config = { ...Http.axiosConfigDefault, ...paramConfig }
179
- return new Promise((resolve, reject) => {
180
- Http.axiosInstance
181
- .request(config)
182
- .then((response: any) => {
183
- resolve(response)
184
- })
185
- .catch((error) => {
186
- reject(error)
187
- })
188
- })
189
- }
190
- }
191
-
192
- const METHOD = {
193
- GET: 'get',
194
- POST: 'post',
195
- PUT: 'put',
196
- DELETE: 'delete',
197
- }
198
- /**
199
- * axios请求
200
- * @param url 请求地址
201
- * @param method {METHOD} http method
202
- * @param params 请求参数
203
- * @param config
204
- * @returns {Promise<AxiosResponse<T>>}
205
- */
206
- async function request(url, method, params, config) {
207
- switch (method) {
208
- case METHOD.GET:
209
- return axios.get(url, { params, ...config })
210
- case METHOD.POST:
211
- return axios.post(url, params, config)
212
- case METHOD.PUT:
213
- return axios.put(url, params, config)
214
- case METHOD.DELETE:
215
- return axios.delete(url, { params, ...config })
216
- default:
217
- return axios.get(url, { params, ...config })
218
- }
219
- }
220
-
221
- export const http = new Http(configDefault)
222
- export {
223
- METHOD,
224
- request,
225
- }
1
+ import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
2
+ import { ContentTypeEnum, ResultEnum } from '@af-mobile-client-vue3/enums/requestEnum'
3
+ import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
4
+ import { ACCESS_TOKEN } from '@af-mobile-client-vue3/stores/mutation-type'
5
+ import { encryptUtil } from '@af-mobile-client-vue3/utils/EncryptUtil'
6
+ import axios from 'axios'
7
+ import { showToast } from 'vant'
8
+
9
+ // 默认 axios 实例请求配置
10
+ const configDefault = {
11
+ headers: {
12
+ 'Content-Type': ContentTypeEnum.JSON,
13
+ },
14
+ // 请求超时时间
15
+ timeout: 20000,
16
+ // API 请求的默认前缀
17
+ baseURL: import.meta.env.VITE_APP_API_BASE_URL,
18
+ data: {},
19
+ }
20
+
21
+ class Http {
22
+ // 当前实例
23
+ private static axiosInstance: AxiosInstance
24
+ // 请求配置
25
+ private static axiosConfigDefault: AxiosRequestConfig
26
+
27
+ // 请求拦截
28
+ private httpInterceptorsRequest(): void {
29
+ Http.axiosInstance.interceptors.request.use(
30
+ (config) => {
31
+ // 发送请求前,可在此让每个请求携带自定义 token, 请根据实际情况修改
32
+ const savedToken = useUserStore().getToken()
33
+ // 如果 token 存在
34
+ if (savedToken)
35
+ config.headers[ACCESS_TOKEN] = savedToken
36
+ const v4SessionKey = useUserStore().getSessionKey()
37
+ if (['post'].includes(config.method.toLowerCase()) && v4SessionKey) {
38
+ if (config.data && !(config.data instanceof FormData)) {
39
+ config.data = {
40
+ encrypted: encryptUtil.AESEncryptCBC(config.data, v4SessionKey),
41
+ }
42
+ config.headers['X-Sec'] = '1'
43
+ config.headers['X-Rand'] = Math.random().toString(36).substr(2, 5)
44
+ config.headers['X-Ts'] = Date.now()
45
+ }
46
+ }
47
+ return config
48
+ },
49
+ (error: AxiosError) => {
50
+ showToast({
51
+ message: error.message,
52
+ position: 'bottom',
53
+ })
54
+ return Promise.reject(error)
55
+ },
56
+ )
57
+ }
58
+
59
+ // 响应拦截
60
+ private httpInterceptorsResponse(): void {
61
+ Http.axiosInstance.interceptors.response.use(
62
+ async (response: AxiosResponse) => {
63
+ // 判断是否需要解密
64
+ if (response.headers && response.headers['x-encrypted'] === '1') {
65
+ const v4SessionKey = useUserStore().getSessionKey()
66
+ if (v4SessionKey && response.data) {
67
+ const decryptedData = encryptUtil.decryptResponse(response?.data, v4SessionKey)
68
+ // 如果解密成功且不等于原数据,说明解密有效
69
+ if (decryptedData !== response.data) {
70
+ // 响应解密成功
71
+ response.data = decryptedData
72
+ }
73
+ }
74
+ }
75
+ const compatible = import.meta.env.VITE_APP_COMPATIBLE
76
+ if (compatible !== 'V4') {
77
+ return response.data
78
+ }
79
+ // 与后端协定的返回字段
80
+ const { code, msg, data } = response.data
81
+ // 临时向v3请求上传服务,因为没有code,未来会改
82
+ if (code === undefined) {
83
+ return response.data
84
+ }
85
+ // 判断请求是否成功
86
+ const isSuccess = code === ResultEnum.SUCCESS
87
+ if (isSuccess) {
88
+ // 兼容 V3 请求没有data的情况
89
+ if (data) {
90
+ return data
91
+ }
92
+ else {
93
+ return response.data
94
+ }
95
+ }
96
+ else {
97
+ if (code === 401 && response.config.url?.indexOf('af-auth/logout') === -1) {
98
+ showToast({
99
+ message: '登录态已失效,请重新登录',
100
+ position: 'bottom',
101
+ })
102
+ await useUserStore().logout()
103
+ }
104
+ else {
105
+ showToast({
106
+ message: msg,
107
+ position: 'bottom',
108
+ })
109
+ console.error(`请求失败,返回结果:${msg}`)
110
+ }
111
+ return Promise.reject(response.data)
112
+ }
113
+ },
114
+ (error: AxiosError) => {
115
+ // 处理 HTTP 网络错误
116
+ let message: string = error.response?.data as string
117
+ if (!message) {
118
+ // HTTP 状态码
119
+ const status = error.response?.status
120
+ switch (status) {
121
+ case 400:
122
+ message = '请求错误'
123
+ break
124
+ case 401:
125
+ message = '登录态已失效,请重新登录'
126
+ // 401状态码处理:自动登出并跳转到登录页
127
+ useUserStore().logout()
128
+ break
129
+ case 403:
130
+ message = '拒绝访问'
131
+ break
132
+ case 404:
133
+ message = `请求地址出错: ${error.response?.config?.url}`
134
+ break
135
+ case 408:
136
+ message = '请求超时'
137
+ break
138
+ case 500:
139
+ message = '服务器内部错误'
140
+ break
141
+ case 501:
142
+ message = '服务未实现'
143
+ break
144
+ case 502:
145
+ message = '网关错误'
146
+ break
147
+ case 503:
148
+ message = '服务不可用'
149
+ break
150
+ case 504:
151
+ message = '网关超时'
152
+ break
153
+ case 505:
154
+ message = 'HTTP版本不受支持'
155
+ break
156
+ default:
157
+ message = '网络连接故障'
158
+ }
159
+ }
160
+ showToast({
161
+ message,
162
+ position: 'bottom',
163
+ })
164
+ return Promise.reject(error)
165
+ },
166
+ )
167
+ }
168
+
169
+ constructor(config: AxiosRequestConfig) {
170
+ Http.axiosConfigDefault = config
171
+ Http.axiosInstance = axios.create(config)
172
+ this.httpInterceptorsRequest()
173
+ this.httpInterceptorsResponse()
174
+ }
175
+
176
+ // 通用请求函数
177
+ public request<T>(paramConfig: AxiosRequestConfig): Promise<T> {
178
+ const config = { ...Http.axiosConfigDefault, ...paramConfig }
179
+ return new Promise((resolve, reject) => {
180
+ Http.axiosInstance
181
+ .request(config)
182
+ .then((response: any) => {
183
+ resolve(response)
184
+ })
185
+ .catch((error) => {
186
+ reject(error)
187
+ })
188
+ })
189
+ }
190
+ }
191
+
192
+ const METHOD = {
193
+ GET: 'get',
194
+ POST: 'post',
195
+ PUT: 'put',
196
+ DELETE: 'delete',
197
+ }
198
+ /**
199
+ * axios请求
200
+ * @param url 请求地址
201
+ * @param method {METHOD} http method
202
+ * @param params 请求参数
203
+ * @param config
204
+ * @returns {Promise<AxiosResponse<T>>}
205
+ */
206
+ async function request(url, method, params, config) {
207
+ switch (method) {
208
+ case METHOD.GET:
209
+ return axios.get(url, { params, ...config })
210
+ case METHOD.POST:
211
+ return axios.post(url, params, config)
212
+ case METHOD.PUT:
213
+ return axios.put(url, params, config)
214
+ case METHOD.DELETE:
215
+ return axios.delete(url, { params, ...config })
216
+ default:
217
+ return axios.get(url, { params, ...config })
218
+ }
219
+ }
220
+
221
+ export const http = new Http(configDefault)
222
+ export {
223
+ METHOD,
224
+ request,
225
+ }