af-mobile-client-vue3 1.4.10 → 1.4.11-cs

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/.env CHANGED
@@ -8,3 +8,4 @@ VITE_APP_WEB_CONFIG_KEY=admin.webconfig
8
8
  VITE_APP_SYSTEM_NAME=af-system
9
9
  # 最低兼容性 V4(最新产品)V3(V3产品) OA(公司OA)
10
10
  VITE_APP_COMPATIBLE=V4
11
+ VITE_RSA_PRIVATE_KEY=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIRrmftLDHCQqREEJ132Onu+W3vmFbdF7QD751SrcDDGDTfzuz1zBuElvkHhuDBb7KZkXrCIe+MhvX2IvxcLObl3faX+evYlnfj2HRbF0hIpQLuIq22tL06ZcV5w7wqLxUZRpFElIFm8gZTkUvfKXVuHw89e4daDVhU5hK3GHNGTAgMBAAECgYABiINrFaE1E8pkBYx1JJA5yuhL73aUktfd2TeCU00vFg6kyrWCI85Sa2RKu/6CJNZWeOFgdubEUv7a22tRrNIZb3yUMaqtTwSso78mspIOJqjWXTkTH9WPElfTcdpdIse/lgZtPz6egxkuhadSvwrM9Y6NgusiW/5+x95Ct08iOQJBAN5aK+7uISURvGQj2EaRtgGEd8+d4oHl+BYvvTeG3qSgUikHQW3j0sp4gXPw2kxw6sjVgLFOc4FB6LGqwzOTzokCQQCYdYG8ty3Uo/ebUlNzeJFxHXjy/KvBSytAUzAXkRu3nZrkEaPQsi3dgOkZgk+F1fMDzfQ4EbDIU6xvqOoZXHg7AkATCW9XfoXR8anKfRMoP5Nwn9HOMbtR2cmaxK2TknV/bMZ8AsYETYwfj5+tuIJIJybC2RyykX/sIiN1CqS5xr7ZAkArj19rMRdaKyMi8MnBM1Cy9g3Jt2HHj5ejAGG8SgyWUOShh1y70z0BjcSMMkxQXAncK2s83ekZw7aADM4eQupjAkARRgTwwMOnn3IoKmQusKhZk0uxilZ4Zc2LH6Z4GiWnvteM0W8Zw4Z1lJUcjgQq3dGqL2RdmzeQZ+HgPIOXrZVK
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.4.10",
4
+ "version": "1.4.11-cs",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -44,7 +44,9 @@
44
44
  "vue-i18n": "^11.1.10",
45
45
  "vue-router": "^4.5.1",
46
46
  "vue3-hash-calendar": "^1.1.3",
47
- "weixin-js-sdk": "^1.6.5"
47
+ "weixin-js-sdk": "^1.6.5",
48
+ "jsencrypt": "^3.3.2",
49
+ "bcryptjs": "^2.4.3"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@antfu/eslint-config": "4.17.0",
@@ -37,7 +37,7 @@ import {
37
37
  Switch as VanSwitch,
38
38
  TimePicker as VanTimePicker,
39
39
  } from 'vant'
40
- import { computed, defineEmits, defineModel, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
40
+ import { computed, defineEmits, defineModel, defineProps, getCurrentInstance, onBeforeMount, ref, watch, nextTick } from 'vue'
41
41
 
42
42
  const props = defineProps({
43
43
  attr: {
@@ -620,7 +620,9 @@ function onCalendarConfirm(values) {
620
620
  // js 函数作为数据源
621
621
  async function updateOptions() {
622
622
  if (attr.keyName && (attr.keyName.toString().includes('async ') || attr.keyName.toString().includes('function '))) {
623
- option.value = await executeStrFunctionByContext(currInst, attr.keyName, [props.form, runLogic, props.mode, getConfigByNameAsync, post])
623
+ await nextTick(async () => {
624
+ option.value = await executeStrFunctionByContext(currInst, attr.keyName, [props.form, runLogic, props.mode, getConfigByNameAsync, post])
625
+ })
624
626
  }
625
627
  }
626
628
 
@@ -21,6 +21,7 @@ export interface WebConfig {
21
21
  homeAppList: Array<any>
22
22
  slideshowList: Array<any>
23
23
  registerRequire: boolean
24
+ requestEncrypt: boolean
24
25
  }
25
26
 
26
27
  // 存放 webConfig 中的 setting 配置
@@ -56,7 +57,6 @@ export const useSettingStore = defineStore('setting', () => {
56
57
  const res = await getConfigByNameAsync('webMobileConfig')
57
58
  if (res.setting) {
58
59
  useStore.set(APP_WEB_CONFIG_KEY, res)
59
- console.log('res.setting', res.setting)
60
60
  setSetting(res.setting)
61
61
  // homeAppList 仅在用户缓存没有时才回填
62
62
  if (!homeAppStore.homeAppList?.length && res.setting.homeAppList) {
@@ -17,6 +17,7 @@ import {
17
17
  } from '@af-mobile-client-vue3/stores/mutation-type'
18
18
  import { getPlatformRoutePrefix } from '@af-mobile-client-vue3/types/platform'
19
19
  import crypto from '@af-mobile-client-vue3/utils/crypto'
20
+ import { encryptUtil } from '@af-mobile-client-vue3/utils/EncryptUtil'
20
21
  import { indexedDB } from '@af-mobile-client-vue3/utils/indexedDB'
21
22
  import { createStorage } from '@af-mobile-client-vue3/utils/Storage'
22
23
  import { defineStore } from 'pinia'
@@ -212,6 +213,9 @@ export const useUserStore = defineStore('app-user', () => {
212
213
  setToken(data.access_token)
213
214
  // 第三方教培系统鉴权兼容
214
215
  const LoginTicket = crypto.AESEncrypt(JSON.stringify(params), '3KMKqvgwR8ULbR8Z')
216
+ if (data.session && useSettingStore().getSetting()?.requestEncrypt) {
217
+ localStorage.setItem('v4-session-key', encryptUtil.RSADecrypt(data.session as string))
218
+ }
215
219
  Storage.set('LoginTicket', LoginTicket)
216
220
  }
217
221
  return Promise.resolve(data)
@@ -0,0 +1,246 @@
1
+ import CryptoJS from 'crypto-js'
2
+ import JSEncrypt from 'jsencrypt'
3
+
4
+ /**
5
+ * 加密工具类
6
+ * 提供AES和RSA加密解密功能
7
+ */
8
+ class EncryptUtil {
9
+ /**
10
+ * RSA公钥
11
+ */
12
+ private readonly RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqPvovSfXcwBbW8cKMCgwqNpsYuzF8RPAPFb7LGsnVo44JhM/xxzDyzoYtdfNmtbIuKVi9PzIsyp6rg+09gbuI6UGwBZ5DWBDBMqv5MPdOF5dCQkB2Bbr5yPfURPENypUz+pBFBg41d+BC+rwRiXELwKy7Y9caD/MtJyHydj8OUwIDAQAB'
13
+
14
+ /**
15
+ * RSA私钥
16
+ */
17
+ private readonly RSA_PRIVATE_KEY = 'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKo++i9J9dzAFtbxwowKDCo2mxi7MXxE8A8VvssaydWjjgmEz/HHMPLOhi1182a1si4pWL0/MizKnquD7T2Bu4jpQbAFnkNYEMEyq/kw904Xl0JCQHYFuvnI99RE8Q3KlTP6kEUGDjV34EL6vBGJcQvArLtj1xoP8y0nIfJ2Pw5TAgMBAAECgYAGGB8IllMwxceLhjf6n1l0IWRH7FuHIUieoZ6k0p6rASHSgWiYNRMxfecbtX8zDAoG0QAWNi7rn40ygpR5gS1fWDAKhmnhKgQIT6wW0VmD4hraaeyP78iy8BLhlvblri2nCPIhDH5+l96v7D47ZZi3ZSOzcj89s1eS/k7/N4peEQJBAPEtGGJY+lBoCxQMhGyzuzDmgcS1Un1ZE2pt+XNCVl2b+T8fxWJH3tRRR8wOY5uvtPiK1HM/IjT0T5qwQeH8Yk0CQQC0tcv3d/bDb7bOe9QzUFDQkUSpTdPWAgMX2OVPxjdq3Sls9oA5+fGNYEy0OgyqTjde0b4iRzlD1O0OhLqPSUMfAkEAh5FIvqezdRU2/PsYSR4yoAdCdLdT+h/jGRVefhqQ/6eYUJJkWp15tTFHQX3pIe9/s6IeT/XyHYAjaxmevxAmlQJBAKSdhvQjf9KAjZKDEsa7vyJ/coCXuQUWSCMNHbcR5aGfXgE4e45UtUoIE1eKGcd6AM6LWhx3rR6xdFDpb9je8BkCQB0SpevGfOQkMk5i8xkEt9eeYP0fi8nv6eOUcK96EXbzs4jV2SAoQJ9oJegPtPROHbhIvVUmNQTbuP10Yjg59+8='
18
+
19
+ /**
20
+ * AES ECB模式加密
21
+ * @param word 待加密的字符串
22
+ * @param encryKey 加密密钥
23
+ * @returns 加密后的字符串
24
+ */
25
+ AESEncrypt(word: string, encryKey: string): string {
26
+ try {
27
+ const key = CryptoJS.enc.Utf8.parse(encryKey)
28
+ const srcs = CryptoJS.enc.Utf8.parse(word)
29
+ const encrypted = CryptoJS.AES.encrypt(srcs, key, {
30
+ mode: CryptoJS.mode.ECB,
31
+ padding: CryptoJS.pad.Pkcs7,
32
+ })
33
+ return encrypted.toString()
34
+ }
35
+ catch (error) {
36
+ throw new Error(`AES ECB加密失败: ${error}`)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * AES ECB模式解密
42
+ * @param word 待解密的字符串
43
+ * @param encryKey 解密密钥
44
+ * @returns 解密后的数据(可能是字符串或对象)
45
+ */
46
+ AESDecrypt(word: string, encryKey: string): string | object {
47
+ try {
48
+ const key = CryptoJS.enc.Utf8.parse(encryKey)
49
+ const decrypt = CryptoJS.AES.decrypt(word, key, {
50
+ mode: CryptoJS.mode.ECB,
51
+ padding: CryptoJS.pad.Pkcs7,
52
+ })
53
+ const ret = CryptoJS.enc.Utf8.stringify(decrypt).toString()
54
+
55
+ try {
56
+ return JSON.parse(ret)
57
+ }
58
+ catch {
59
+ return ret
60
+ }
61
+ }
62
+ catch (error) {
63
+ throw new Error(`AES ECB解密失败: ${error}`)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * RSA公钥加密
69
+ *
70
+ * @param data 需要加密的数据
71
+ * @param publicKey PEM格式的公钥
72
+ * @returns Base64编码的加密结果
73
+ */
74
+ RSAEncrypt(data: string, publicKey: string): string {
75
+ try {
76
+ const encryptor = new JSEncrypt()
77
+ encryptor.setPublicKey(publicKey)
78
+ const encrypted = encryptor.encrypt(data)
79
+
80
+ if (!encrypted) {
81
+ throw new Error('加密失败:空结果')
82
+ }
83
+ return encrypted
84
+ }
85
+ catch (error) {
86
+ console.error('RSA加密错误:', error)
87
+ throw new Error('RSA加密失败,请检查公钥格式')
88
+ }
89
+ }
90
+
91
+ /**
92
+ * RSA私钥解密
93
+ *
94
+ * @param encryptedData Base64编码的加密数据
95
+ * @returns 解密后的原始字符串
96
+ */
97
+ RSADecrypt(encryptedData: string): string {
98
+ try {
99
+ const decryptor = new JSEncrypt()
100
+ decryptor.setPrivateKey(import.meta.env.VITE_RSA_PRIVATE_KEY)
101
+ const decrypted = decryptor.decrypt(encryptedData)
102
+
103
+ if (!decrypted) {
104
+ throw new Error('解密失败:空结果')
105
+ }
106
+ return decrypted
107
+ }
108
+ catch (error) {
109
+ console.error('RSA解密错误:', error)
110
+ throw new Error('RSA解密失败,请检查私钥格式')
111
+ }
112
+ }
113
+
114
+ /**
115
+ * AES CBC模式加密
116
+ * @param data 待加密的数据
117
+ * @param cryptoKey 十六进制格式的加密密钥
118
+ * @returns 组合格式的加密结果
119
+ */
120
+ AESEncryptCBC(data: any, cryptoKey: string): string {
121
+ try {
122
+ // 生成随机IV(每次加密不同)
123
+ const iv = CryptoJS.lib.WordArray.random(16)
124
+
125
+ // 使用 AES CBC 模式进行加密
126
+ const encrypted = CryptoJS.AES.encrypt(
127
+ JSON.stringify(data),
128
+ CryptoJS.enc.Hex.parse(cryptoKey),
129
+ {
130
+ iv, // IV
131
+ mode: CryptoJS.mode.CBC,
132
+ padding: CryptoJS.pad.Pkcs7,
133
+ },
134
+ )
135
+
136
+ // 组合IV和密文,分别Base64编码:IV(base64) + : + 密文(base64)
137
+ const ivBase64 = iv.toString(CryptoJS.enc.Base64)
138
+ const encryptedBase64 = encrypted.toString()
139
+
140
+ return `${ivBase64}:${encryptedBase64}`
141
+ }
142
+ catch (error) {
143
+ throw new Error(`AES CBC加密失败: ${error}`)
144
+ }
145
+ }
146
+
147
+ /**
148
+ * AES CBC模式解密
149
+ * @param combinedText 格式:Base64(iv):Base64(ciphertext)
150
+ * @param hexKey 十六进制格式的加密密钥
151
+ * @returns 解密后的原始字符串
152
+ */
153
+ AESDecryptCBC(combinedText: string, hexKey: string): string {
154
+ try {
155
+ // 分离 IV 和 密文
156
+ const parts = combinedText.split(':')
157
+ if (parts.length !== 2) {
158
+ throw new Error('Invalid encrypted data format')
159
+ }
160
+
161
+ const ivBase64 = parts[0]
162
+ const encryptedBase64 = parts[1]
163
+
164
+ // 解析IV和密文
165
+ const iv = CryptoJS.enc.Base64.parse(ivBase64)
166
+ const key = CryptoJS.enc.Hex.parse(hexKey)
167
+
168
+ // 解密
169
+ const decrypted = CryptoJS.AES.decrypt(encryptedBase64, key, {
170
+ iv,
171
+ mode: CryptoJS.mode.CBC,
172
+ padding: CryptoJS.pad.Pkcs7,
173
+ })
174
+
175
+ // 将解密后的数据转换为字符串
176
+ return decrypted.toString(CryptoJS.enc.Utf8)
177
+ }
178
+ catch (error) {
179
+ throw new Error(`AES-CBC解密失败: ${error}`)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 安全的AES-CBC解密(失败时返回null)
185
+ * 用于响应自动解密,解密失败时不抛出异常
186
+ * @param combinedText 格式:Base64(iv):Base64(ciphertext)
187
+ * @param hexKey 十六进制格式的密钥
188
+ * @returns 解密后的字符串,失败时返回null
189
+ */
190
+ safeAESDecryptCBC(combinedText: string, hexKey: string): string | null {
191
+ try {
192
+ return this.AESDecryptCBC(combinedText, hexKey)
193
+ }
194
+ catch {
195
+ // AES-CBC解密失败,静默处理
196
+ return null
197
+ }
198
+ }
199
+
200
+ /**
201
+ * 智能响应解密
202
+ * 自动检测响应数据格式并进行解密
203
+ * @param responseData 响应数据
204
+ * @param sessionKey 会话密钥
205
+ * @returns 解密后的数据,失败时返回原数据
206
+ */
207
+ decryptResponse(responseData: any, sessionKey: string): any {
208
+ try {
209
+ let dataStr = ''
210
+
211
+ // 处理不同类型的响应数据
212
+ if (typeof responseData === 'string') {
213
+ dataStr = responseData
214
+ }
215
+ else if (typeof responseData === 'object') {
216
+ dataStr = JSON.stringify(responseData)
217
+ }
218
+ else {
219
+ dataStr = String(responseData)
220
+ }
221
+
222
+ // 尝试解密
223
+ const decryptedStr = this.safeAESDecryptCBC(dataStr, sessionKey)
224
+ if (decryptedStr) {
225
+ try {
226
+ return JSON.parse(decryptedStr)
227
+ }
228
+ catch {
229
+ return decryptedStr
230
+ }
231
+ }
232
+ }
233
+ catch (error) {
234
+ console.warn('响应解密处理失败:', error)
235
+ // 响应解密处理失败,静默处理
236
+ }
237
+
238
+ // 解密失败或数据格式不正确时,返回原数据
239
+ return responseData
240
+ }
241
+ }
242
+
243
+ // 创建单例实例
244
+ const encryptUtil = new EncryptUtil()
245
+
246
+ export { EncryptUtil, encryptUtil }
@@ -2,6 +2,7 @@ import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } fro
2
2
  import { ContentTypeEnum, ResultEnum } from '@af-mobile-client-vue3/enums/requestEnum'
3
3
  import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
4
4
  import { ACCESS_TOKEN } from '@af-mobile-client-vue3/stores/mutation-type'
5
+ import { encryptUtil } from '@af-mobile-client-vue3/utils/EncryptUtil'
5
6
  import axios from 'axios'
6
7
  import { showToast } from 'vant'
7
8
 
@@ -32,6 +33,17 @@ class Http {
32
33
  // 如果 token 存在
33
34
  if (savedToken)
34
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
+ }
35
47
  return config
36
48
  },
37
49
  (error: AxiosError) => {
@@ -48,6 +60,18 @@ class Http {
48
60
  private httpInterceptorsResponse(): void {
49
61
  Http.axiosInstance.interceptors.response.use(
50
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
+ }
51
75
  const compatible = import.meta.env.VITE_APP_COMPATIBLE
52
76
  if (compatible !== 'V4') {
53
77
  return response.data
@@ -10,6 +10,7 @@ import { UserType } from '@af-mobile-client-vue3/types/auth'
10
10
  import { isWechat } from '@af-mobile-client-vue3/utils/platform-auth'
11
11
  import { funcToRouter, loadRoutes } from '@af-mobile-client-vue3/utils/routerUtil'
12
12
  import { secureStorageBatchWrite, secureStorageRead } from '@af-mobile-client-vue3/utils/secureStorage'
13
+ import bcrypt from 'bcryptjs'
13
14
  import {
14
15
  closeToast,
15
16
  showDialog,
@@ -123,7 +124,7 @@ function handleSubmit() {
123
124
  console.warn(setting)
124
125
  const data: any = await userState.Login({
125
126
  username,
126
- password,
127
+ password: bcrypt.hashSync(password, 10),
127
128
  resourceName: setting.getSetting()?.routerName || '智慧手机',
128
129
  })
129
130
  login.f = data
package/vite.config.ts CHANGED
@@ -11,8 +11,8 @@ export default ({ mode }: ConfigEnv): UserConfig => {
11
11
 
12
12
  const appProxys = {}
13
13
 
14
- const v4Server = 'http://192.168.50.67:31567'
15
- const v3Server = 'http://192.168.50.67:31567'
14
+ const v4Server = 'http://117.35.109.162:31577'
15
+ const v3Server = 'http://117.35.109.162:31577'
16
16
  const OSSServerDev = 'http://192.168.50.67:30351'
17
17
  const geoserver = 'http://39.104.49.8:30372'
18
18
  const mockServer = 'http://127.0.0.1:8086'