af-mobile-client-vue3 1.4.68 → 1.4.69

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.
Files changed (49) hide show
  1. package/__dummy__ +9 -9
  2. package/build/vite/optimize.ts +36 -36
  3. package/package.json +120 -121
  4. package/public/favicon.svg +4 -4
  5. package/scripts/verifyCommit.js +19 -19
  6. package/src/components/common/MateChat/components/MateChatContent.vue +274 -274
  7. package/src/components/common/MateChat/components/MateChatHeader.vue +337 -337
  8. package/src/components/common/MateChat/index.vue +444 -444
  9. package/src/components/common/MateChat/types.ts +247 -247
  10. package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +137 -137
  11. package/src/components/common/otherCharge/CodePayment.vue +357 -357
  12. package/src/components/common/otherCharge/FileUploader.vue +602 -602
  13. package/src/components/common/otherCharge/GridFileUploader.vue +846 -846
  14. package/src/components/common/otherCharge/PaymentMethodSelector.vue +202 -202
  15. package/src/components/common/otherCharge/PaymentMethodSelectorCard.vue +45 -45
  16. package/src/components/common/otherCharge/ReceiptModal.vue +273 -273
  17. package/src/components/common/otherCharge/index.ts +43 -43
  18. package/src/components/core/ImageUploader/index.vue +9 -2
  19. package/src/components/data/OtherCharge/OtherChargeItemModal.vue +547 -547
  20. package/src/components/data/UserDetail/types.ts +1 -1
  21. package/src/components/data/XReportGrid/XAddReport/index.ts +1 -1
  22. package/src/components/data/XReportGrid/XReportDrawer/index.ts +1 -1
  23. package/src/components/data/XTag/index.vue +10 -10
  24. package/src/components/layout/TabBarLayout/index.vue +40 -40
  25. package/src/hooks/useCommon.ts +9 -9
  26. package/src/plugins/AppData.ts +38 -38
  27. package/src/router/invoiceRoutes.ts +33 -33
  28. package/src/services/api/common.ts +109 -109
  29. package/src/services/api/manage.ts +8 -8
  30. package/src/services/api/search.ts +16 -16
  31. package/src/services/restTools.ts +56 -56
  32. package/src/utils/authority-utils.ts +84 -84
  33. package/src/utils/crypto.ts +39 -39
  34. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  35. package/src/utils/runEvalFunction.ts +13 -13
  36. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  37. package/src/views/component/MateChat/MateChatView.vue +10 -10
  38. package/src/views/component/XCellDetailView/index.vue +217 -217
  39. package/src/views/component/XCellListView/index.vue +138 -107
  40. package/src/views/component/XFormGroupView/index.vue +82 -78
  41. package/src/views/component/XFormView/index.vue +46 -41
  42. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  43. package/src/views/component/XReportFormView/index.vue +13 -13
  44. package/src/views/component/XSignatureView/index.vue +50 -50
  45. package/src/views/component/notice.vue +46 -46
  46. package/src/views/component/topNav.vue +36 -36
  47. package/src/views/invoiceShow/index.vue +61 -61
  48. package/src/views/user/login/index.vue +22 -22
  49. package/pnpm-lock.yaml +0 -11070
@@ -1,444 +1,444 @@
1
- <script setup lang="ts">
2
- import type { MateChatConfig, MateChatConfigs } from '@af-mobile-client-vue3/components/common/MateChat/types'
3
- import MateChatContent from '@af-mobile-client-vue3/components/common/MateChat/components/MateChatContent.vue'
4
- import PasswordDialog from '@af-mobile-client-vue3/components/common/MateChat/components/PasswordDialog.vue'
5
- import { usePasswordManager } from '@af-mobile-client-vue3/components/common/MateChat/composables/usePasswordManager'
6
- import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
7
- // MateChat ai 对话相关
8
- import { showFailToast, Button as VanButton, Icon as VanIcon, Loading as VanLoading } from 'vant'
9
- import { computed, defineProps, onMounted, ref } from 'vue'
10
-
11
- interface MateChatProps {
12
- /**
13
- * 配置名称,用于从配置中心获取配置
14
- */
15
- configName: string
16
- /**
17
- * 服务名
18
- */
19
- serviceName?: string
20
- }
21
-
22
- const props = defineProps<MateChatProps>()
23
-
24
- // 使用密码管理 composable
25
- const { getStoredPassword, savePassword, clearPassword } = usePasswordManager()
26
-
27
- // 配置加载状态
28
- const isLoading = ref(true)
29
- const configError = ref<string | null>(null)
30
- const showPasswordDialog = ref(false)
31
- const passwordDialogRef = ref<InstanceType<typeof PasswordDialog> | null>(null)
32
-
33
- // 临时变量:强制保持 loading 状态(用于调整 loading 样式)
34
- // 设置为 true 时,组件将始终保持 loading 状态
35
- const forceLoading = ref(false)
36
-
37
- // 计算最终的 loading 状态
38
- // 当有错误时也应该保持 loading 状态(显示错误信息),而不是显示对话页面
39
- const finalLoading = computed(() => forceLoading.value || isLoading.value || !!configError.value)
40
-
41
- // 当前使用的配置
42
- const currentConfig = ref<MateChatConfig | null>(null)
43
-
44
- const defaultBackgroundGradient = 'background: linear-gradient(to bottom, #d0c9ff 0%, #e6d6f0 8%, #f1dbea 12%, #c8dcfb 40%, #abc6f6 60%, #87aefe 90%)'
45
- // 琉璃配色
46
- // background: linear-gradient(to bottom, #a8f0ed 0%, #a8e6cf 8%, #c5b3ff 12%, #e0b3ff 40%, #fff4b8 60%, #ffb8d1 90%);
47
- // 紫罗兰配色
48
- // background: linear-gradient(to bottom, #d0c9ff 0%, #e6d6f0 8%, #f1dbea 12%, #c8dcfb 40%, #abc6f6 60%, #87aefe 90%);
49
- // 蓝白配色
50
- // 'linear-gradient(to bottom, #1c57e0 0%, #3b82f6 20%, #60a5fa 40%, #93c5fd 60%, #dbeafe 80%, #ffffff 100%)'
51
- // 计算背景渐变样式
52
- const backgroundStyle = computed(() => {
53
- const gradient = currentConfig.value?.backgroundGradient || defaultBackgroundGradient
54
- return {
55
- background: gradient,
56
- }
57
- })
58
-
59
- // 存储原始配置数据
60
- const rawConfigs = ref<MateChatConfigs | MateChatConfig | null>(null)
61
-
62
- /**
63
- * 检查配置对象中是否有密码保护的配置
64
- */
65
- function hasPasswordProtectedConfig(configs: MateChatConfigs | MateChatConfig | null): boolean {
66
- if (!configs) {
67
- return false
68
- }
69
- if (Array.isArray(configs)) {
70
- return configs.some(config => !!config.password)
71
- }
72
- if (typeof configs === 'object' && configs !== null) {
73
- // 如果是配置对象集合
74
- return Object.values(configs).some((config) => {
75
- if (typeof config === 'object' && config !== null && 'password' in config) {
76
- return !!config.password
77
- }
78
- return false
79
- })
80
- }
81
- // 单个配置对象
82
- return !!(configs as MateChatConfig).password
83
- }
84
-
85
- /**
86
- * 根据密码匹配配置
87
- */
88
- function findConfigByPassword(
89
- configs: MateChatConfigs | MateChatConfig | null,
90
- password: string,
91
- ): MateChatConfig | null {
92
- if (!configs) {
93
- return null
94
- }
95
- if (typeof configs === 'object' && configs !== null) {
96
- // 如果是配置对象集合(MateChatConfigs)
97
- if (props.configName in configs) {
98
- const targetConfig = (configs as MateChatConfigs)[props.configName]
99
- if (targetConfig && targetConfig.password === password) {
100
- return targetConfig
101
- }
102
- }
103
- // 遍历所有配置查找匹配的密码
104
- for (const config of Object.values(configs)) {
105
- if (typeof config === 'object' && config !== null && 'password' in config) {
106
- if (config.password === password) {
107
- return config as MateChatConfig
108
- }
109
- }
110
- }
111
- }
112
- // 单个配置对象
113
- const singleConfig = configs as MateChatConfig
114
- if (singleConfig && singleConfig.password === password) {
115
- return singleConfig
116
- }
117
- return null
118
- }
119
-
120
- /**
121
- * 获取配置(无密码保护时)
122
- */
123
- function getConfigWithoutPassword(
124
- configs: MateChatConfigs | MateChatConfig | null,
125
- ): MateChatConfig | null {
126
- if (!configs) {
127
- return null
128
- }
129
- if (typeof configs === 'object' && configs !== null) {
130
- // 如果是配置对象集合,根据 configName 获取
131
- if (props.configName in configs) {
132
- return (configs as MateChatConfigs)[props.configName]
133
- }
134
- // 如果没有找到,返回第一个配置
135
- const firstConfig = Object.values(configs)[0]
136
- if (firstConfig && typeof firstConfig === 'object' && 'appId' in firstConfig) {
137
- return firstConfig as MateChatConfig
138
- }
139
- }
140
- // 单个配置对象,直接返回
141
- return configs as MateChatConfig
142
- }
143
-
144
- /**
145
- * 处理密码确认
146
- */
147
- function handlePasswordConfirm(password: string) {
148
- if (!rawConfigs.value) {
149
- showFailToast('配置数据异常')
150
- return
151
- }
152
-
153
- const matchedConfig = findConfigByPassword(rawConfigs.value, password)
154
- if (matchedConfig) {
155
- // 验证配置的有效性
156
- if (!validateConfig(matchedConfig)) {
157
- configError.value = '配置不完整,请联系管理员检查配置'
158
- showPasswordDialog.value = false
159
- isLoading.value = false
160
- return
161
- }
162
- // 验证成功,保存密码
163
- savePassword(password)
164
- currentConfig.value = matchedConfig
165
- showPasswordDialog.value = false
166
- isLoading.value = false
167
- }
168
- else {
169
- // 验证失败,清除保存的密码
170
- clearPassword()
171
- // 清空输入框并显示错误,保持对话框打开
172
- passwordDialogRef.value?.clearPassword()
173
- passwordDialogRef.value?.setError('密码错误,请重新输入')
174
- }
175
- }
176
-
177
- /**
178
- * 处理密码取消
179
- */
180
- function handlePasswordCancel() {
181
- // 取消时保持 loading 状态,不加载配置
182
- showPasswordDialog.value = false
183
- }
184
-
185
- /**
186
- * 验证配置的有效性
187
- * @param config 配置对象
188
- * @returns 如果配置有效返回 true,否则返回 false
189
- */
190
- function validateConfig(config: MateChatConfig | null): boolean {
191
- if (!config) {
192
- return false
193
- }
194
-
195
- // 检查必需的字段
196
- if (!config.appId || typeof config.appId !== 'string' || config.appId.trim() === '') {
197
- return false
198
- }
199
-
200
- if (!config.appKey || typeof config.appKey !== 'string' || config.appKey.trim() === '') {
201
- return false
202
- }
203
-
204
- return true
205
- }
206
-
207
- /**
208
- * 获取友好的错误提示信息
209
- */
210
- function getFriendlyErrorMessage(error: string | null): string {
211
- if (!error) {
212
- return '加载配置失败,请稍后再试'
213
- }
214
-
215
- // 根据错误信息返回更友好的提示
216
- if (error.includes('未找到') || error.includes('资源')) {
217
- return '未找到相关配置,请联系管理员'
218
- }
219
-
220
- if (error.includes('网络') || error.includes('请求')) {
221
- return '网络连接异常,请检查网络后重试'
222
- }
223
-
224
- if (error.includes('超时')) {
225
- return '请求超时,请稍后重试'
226
- }
227
-
228
- // 默认返回原始错误信息,但可以稍作优化
229
- return error.length > 50 ? `${error.substring(0, 50)}...` : error
230
- }
231
-
232
- /**
233
- * 处理重试
234
- */
235
- function handleRetry() {
236
- loadConfig()
237
- }
238
-
239
- /**
240
- * 加载配置
241
- */
242
- async function loadConfig() {
243
- try {
244
- isLoading.value = true
245
- configError.value = null
246
-
247
- const configs = await getConfigByNameAsync(props.configName, props.serviceName)
248
-
249
- // 检查配置是否为空或无效
250
- if (!configs) {
251
- configError.value = '未找到有效配置'
252
- isLoading.value = false
253
- return
254
- }
255
-
256
- rawConfigs.value = configs
257
-
258
- // 检查是否有密码保护的配置
259
- if (hasPasswordProtectedConfig(configs)) {
260
- // 尝试从 localStorage 读取保存的密码
261
- const storedPassword = getStoredPassword()
262
- if (storedPassword) {
263
- // 自动验证保存的密码
264
- const matchedConfig = findConfigByPassword(configs, storedPassword)
265
- if (matchedConfig) {
266
- // 验证配置的有效性
267
- if (validateConfig(matchedConfig)) {
268
- // 验证成功,直接加载配置
269
- currentConfig.value = matchedConfig
270
- isLoading.value = false
271
- return
272
- }
273
- else {
274
- // 配置无效,清除密码并显示对话框
275
- clearPassword()
276
- showPasswordDialog.value = true
277
- return
278
- }
279
- }
280
- else {
281
- // 密码不匹配,清除保存的密码并显示对话框
282
- clearPassword()
283
- showPasswordDialog.value = true
284
- return
285
- }
286
- }
287
- // 没有保存的密码,显示密码输入对话框
288
- showPasswordDialog.value = true
289
- // 保持 loading 状态,等待密码验证
290
- return
291
- }
292
-
293
- // 没有密码保护,直接加载配置
294
- const config = getConfigWithoutPassword(configs)
295
- if (config) {
296
- // 验证配置的有效性
297
- if (!validateConfig(config)) {
298
- configError.value = '配置不完整,请联系管理员检查配置'
299
- isLoading.value = false
300
- return
301
- }
302
- currentConfig.value = config
303
- isLoading.value = false
304
- }
305
- else {
306
- configError.value = '未找到有效配置'
307
- isLoading.value = false
308
- }
309
- }
310
- catch (error: any) {
311
- console.error('加载配置失败:', error)
312
- configError.value = error?.message || '加载配置失败,请稍后再试'
313
- isLoading.value = false
314
- }
315
- }
316
-
317
- onMounted(() => {
318
- loadConfig()
319
- })
320
- </script>
321
-
322
- <template>
323
- <div id="mate-chat-view" :style="backgroundStyle">
324
- <!-- 密码输入对话框 -->
325
- <PasswordDialog
326
- ref="passwordDialogRef"
327
- :visible="showPasswordDialog"
328
- @confirm="handlePasswordConfirm"
329
- @cancel="handlePasswordCancel"
330
- />
331
- <!-- Loading 状态 -->
332
- <div v-if="finalLoading" class="loading-container">
333
- <div v-if="configError" class="error-container">
334
- <VanIcon name="warning-o" size="48" class="error-icon" />
335
- <div class="error-title">
336
- 抱歉,加载失败
337
- </div>
338
- <div class="error-message">
339
- {{ getFriendlyErrorMessage(configError) }}
340
- </div>
341
- <VanButton
342
- type="primary"
343
- size="small"
344
- class="retry-button"
345
- @click="handleRetry"
346
- >
347
- 重试
348
- </VanButton>
349
- </div>
350
- <VanLoading v-else vertical>
351
- <template #icon>
352
- <VanIcon name="star-o" size="30" />
353
- </template>
354
- 加载中...
355
- </VanLoading>
356
- </div>
357
- <!-- 对话内容 -->
358
- <MateChatContent
359
- v-else-if="currentConfig"
360
- :config="currentConfig"
361
- />
362
- </div>
363
- </template>
364
-
365
- <style scoped lang="less">
366
- #mate-chat-view {
367
- /* 外层渐变背景容器 */
368
- width: 100%;
369
- height: 100%;
370
- min-height: 100%;
371
- /* 背景色通过 :style 绑定从配置中获取 */
372
- padding: 20px;
373
- display: flex;
374
- justify-content: center;
375
- align-items: flex-start;
376
- box-sizing: border-box;
377
-
378
- /* 移动端适配 */
379
- @media (max-width: 768px) {
380
- padding: 8px;
381
- }
382
-
383
- .loading-container {
384
- width: 100%;
385
- max-width: 1200px;
386
- height: calc(100vh - 40px);
387
- background: #ffffff;
388
- border-radius: 24px;
389
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
390
- display: flex;
391
- align-items: center;
392
- justify-content: center;
393
- min-height: 400px;
394
- padding: 20px;
395
- box-sizing: border-box;
396
-
397
- /* 移动端适配 */
398
- @media (max-width: 768px) {
399
- height: calc(100% - 18px);
400
- border-radius: 16px;
401
- padding: 8px;
402
- }
403
-
404
- .loading-message {
405
- font-size: 14px;
406
- color: #71757f;
407
- text-align: center;
408
- }
409
-
410
- .error-container {
411
- display: flex;
412
- flex-direction: column;
413
- align-items: center;
414
- justify-content: center;
415
- padding: 24px;
416
- text-align: center;
417
-
418
- .error-icon {
419
- color: #ff9800;
420
- margin-bottom: 16px;
421
- }
422
-
423
- .error-title {
424
- font-size: 16px;
425
- font-weight: 500;
426
- color: #323233;
427
- margin-bottom: 8px;
428
- }
429
-
430
- .error-message {
431
- font-size: 14px;
432
- color: #969799;
433
- line-height: 1.5;
434
- margin-bottom: 24px;
435
- max-width: 280px;
436
- }
437
-
438
- .retry-button {
439
- min-width: 100px;
440
- }
441
- }
442
- }
443
- }
444
- </style>
1
+ <script setup lang="ts">
2
+ import type { MateChatConfig, MateChatConfigs } from '@af-mobile-client-vue3/components/common/MateChat/types'
3
+ import MateChatContent from '@af-mobile-client-vue3/components/common/MateChat/components/MateChatContent.vue'
4
+ import PasswordDialog from '@af-mobile-client-vue3/components/common/MateChat/components/PasswordDialog.vue'
5
+ import { usePasswordManager } from '@af-mobile-client-vue3/components/common/MateChat/composables/usePasswordManager'
6
+ import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
7
+ // MateChat ai 对话相关
8
+ import { showFailToast, Button as VanButton, Icon as VanIcon, Loading as VanLoading } from 'vant'
9
+ import { computed, defineProps, onMounted, ref } from 'vue'
10
+
11
+ interface MateChatProps {
12
+ /**
13
+ * 配置名称,用于从配置中心获取配置
14
+ */
15
+ configName: string
16
+ /**
17
+ * 服务名
18
+ */
19
+ serviceName?: string
20
+ }
21
+
22
+ const props = defineProps<MateChatProps>()
23
+
24
+ // 使用密码管理 composable
25
+ const { getStoredPassword, savePassword, clearPassword } = usePasswordManager()
26
+
27
+ // 配置加载状态
28
+ const isLoading = ref(true)
29
+ const configError = ref<string | null>(null)
30
+ const showPasswordDialog = ref(false)
31
+ const passwordDialogRef = ref<InstanceType<typeof PasswordDialog> | null>(null)
32
+
33
+ // 临时变量:强制保持 loading 状态(用于调整 loading 样式)
34
+ // 设置为 true 时,组件将始终保持 loading 状态
35
+ const forceLoading = ref(false)
36
+
37
+ // 计算最终的 loading 状态
38
+ // 当有错误时也应该保持 loading 状态(显示错误信息),而不是显示对话页面
39
+ const finalLoading = computed(() => forceLoading.value || isLoading.value || !!configError.value)
40
+
41
+ // 当前使用的配置
42
+ const currentConfig = ref<MateChatConfig | null>(null)
43
+
44
+ const defaultBackgroundGradient = 'background: linear-gradient(to bottom, #d0c9ff 0%, #e6d6f0 8%, #f1dbea 12%, #c8dcfb 40%, #abc6f6 60%, #87aefe 90%)'
45
+ // 琉璃配色
46
+ // background: linear-gradient(to bottom, #a8f0ed 0%, #a8e6cf 8%, #c5b3ff 12%, #e0b3ff 40%, #fff4b8 60%, #ffb8d1 90%);
47
+ // 紫罗兰配色
48
+ // background: linear-gradient(to bottom, #d0c9ff 0%, #e6d6f0 8%, #f1dbea 12%, #c8dcfb 40%, #abc6f6 60%, #87aefe 90%);
49
+ // 蓝白配色
50
+ // 'linear-gradient(to bottom, #1c57e0 0%, #3b82f6 20%, #60a5fa 40%, #93c5fd 60%, #dbeafe 80%, #ffffff 100%)'
51
+ // 计算背景渐变样式
52
+ const backgroundStyle = computed(() => {
53
+ const gradient = currentConfig.value?.backgroundGradient || defaultBackgroundGradient
54
+ return {
55
+ background: gradient,
56
+ }
57
+ })
58
+
59
+ // 存储原始配置数据
60
+ const rawConfigs = ref<MateChatConfigs | MateChatConfig | null>(null)
61
+
62
+ /**
63
+ * 检查配置对象中是否有密码保护的配置
64
+ */
65
+ function hasPasswordProtectedConfig(configs: MateChatConfigs | MateChatConfig | null): boolean {
66
+ if (!configs) {
67
+ return false
68
+ }
69
+ if (Array.isArray(configs)) {
70
+ return configs.some(config => !!config.password)
71
+ }
72
+ if (typeof configs === 'object' && configs !== null) {
73
+ // 如果是配置对象集合
74
+ return Object.values(configs).some((config) => {
75
+ if (typeof config === 'object' && config !== null && 'password' in config) {
76
+ return !!config.password
77
+ }
78
+ return false
79
+ })
80
+ }
81
+ // 单个配置对象
82
+ return !!(configs as MateChatConfig).password
83
+ }
84
+
85
+ /**
86
+ * 根据密码匹配配置
87
+ */
88
+ function findConfigByPassword(
89
+ configs: MateChatConfigs | MateChatConfig | null,
90
+ password: string,
91
+ ): MateChatConfig | null {
92
+ if (!configs) {
93
+ return null
94
+ }
95
+ if (typeof configs === 'object' && configs !== null) {
96
+ // 如果是配置对象集合(MateChatConfigs)
97
+ if (props.configName in configs) {
98
+ const targetConfig = (configs as MateChatConfigs)[props.configName]
99
+ if (targetConfig && targetConfig.password === password) {
100
+ return targetConfig
101
+ }
102
+ }
103
+ // 遍历所有配置查找匹配的密码
104
+ for (const config of Object.values(configs)) {
105
+ if (typeof config === 'object' && config !== null && 'password' in config) {
106
+ if (config.password === password) {
107
+ return config as MateChatConfig
108
+ }
109
+ }
110
+ }
111
+ }
112
+ // 单个配置对象
113
+ const singleConfig = configs as MateChatConfig
114
+ if (singleConfig && singleConfig.password === password) {
115
+ return singleConfig
116
+ }
117
+ return null
118
+ }
119
+
120
+ /**
121
+ * 获取配置(无密码保护时)
122
+ */
123
+ function getConfigWithoutPassword(
124
+ configs: MateChatConfigs | MateChatConfig | null,
125
+ ): MateChatConfig | null {
126
+ if (!configs) {
127
+ return null
128
+ }
129
+ if (typeof configs === 'object' && configs !== null) {
130
+ // 如果是配置对象集合,根据 configName 获取
131
+ if (props.configName in configs) {
132
+ return (configs as MateChatConfigs)[props.configName]
133
+ }
134
+ // 如果没有找到,返回第一个配置
135
+ const firstConfig = Object.values(configs)[0]
136
+ if (firstConfig && typeof firstConfig === 'object' && 'appId' in firstConfig) {
137
+ return firstConfig as MateChatConfig
138
+ }
139
+ }
140
+ // 单个配置对象,直接返回
141
+ return configs as MateChatConfig
142
+ }
143
+
144
+ /**
145
+ * 处理密码确认
146
+ */
147
+ function handlePasswordConfirm(password: string) {
148
+ if (!rawConfigs.value) {
149
+ showFailToast('配置数据异常')
150
+ return
151
+ }
152
+
153
+ const matchedConfig = findConfigByPassword(rawConfigs.value, password)
154
+ if (matchedConfig) {
155
+ // 验证配置的有效性
156
+ if (!validateConfig(matchedConfig)) {
157
+ configError.value = '配置不完整,请联系管理员检查配置'
158
+ showPasswordDialog.value = false
159
+ isLoading.value = false
160
+ return
161
+ }
162
+ // 验证成功,保存密码
163
+ savePassword(password)
164
+ currentConfig.value = matchedConfig
165
+ showPasswordDialog.value = false
166
+ isLoading.value = false
167
+ }
168
+ else {
169
+ // 验证失败,清除保存的密码
170
+ clearPassword()
171
+ // 清空输入框并显示错误,保持对话框打开
172
+ passwordDialogRef.value?.clearPassword()
173
+ passwordDialogRef.value?.setError('密码错误,请重新输入')
174
+ }
175
+ }
176
+
177
+ /**
178
+ * 处理密码取消
179
+ */
180
+ function handlePasswordCancel() {
181
+ // 取消时保持 loading 状态,不加载配置
182
+ showPasswordDialog.value = false
183
+ }
184
+
185
+ /**
186
+ * 验证配置的有效性
187
+ * @param config 配置对象
188
+ * @returns 如果配置有效返回 true,否则返回 false
189
+ */
190
+ function validateConfig(config: MateChatConfig | null): boolean {
191
+ if (!config) {
192
+ return false
193
+ }
194
+
195
+ // 检查必需的字段
196
+ if (!config.appId || typeof config.appId !== 'string' || config.appId.trim() === '') {
197
+ return false
198
+ }
199
+
200
+ if (!config.appKey || typeof config.appKey !== 'string' || config.appKey.trim() === '') {
201
+ return false
202
+ }
203
+
204
+ return true
205
+ }
206
+
207
+ /**
208
+ * 获取友好的错误提示信息
209
+ */
210
+ function getFriendlyErrorMessage(error: string | null): string {
211
+ if (!error) {
212
+ return '加载配置失败,请稍后再试'
213
+ }
214
+
215
+ // 根据错误信息返回更友好的提示
216
+ if (error.includes('未找到') || error.includes('资源')) {
217
+ return '未找到相关配置,请联系管理员'
218
+ }
219
+
220
+ if (error.includes('网络') || error.includes('请求')) {
221
+ return '网络连接异常,请检查网络后重试'
222
+ }
223
+
224
+ if (error.includes('超时')) {
225
+ return '请求超时,请稍后重试'
226
+ }
227
+
228
+ // 默认返回原始错误信息,但可以稍作优化
229
+ return error.length > 50 ? `${error.substring(0, 50)}...` : error
230
+ }
231
+
232
+ /**
233
+ * 处理重试
234
+ */
235
+ function handleRetry() {
236
+ loadConfig()
237
+ }
238
+
239
+ /**
240
+ * 加载配置
241
+ */
242
+ async function loadConfig() {
243
+ try {
244
+ isLoading.value = true
245
+ configError.value = null
246
+
247
+ const configs = await getConfigByNameAsync(props.configName, props.serviceName)
248
+
249
+ // 检查配置是否为空或无效
250
+ if (!configs) {
251
+ configError.value = '未找到有效配置'
252
+ isLoading.value = false
253
+ return
254
+ }
255
+
256
+ rawConfigs.value = configs
257
+
258
+ // 检查是否有密码保护的配置
259
+ if (hasPasswordProtectedConfig(configs)) {
260
+ // 尝试从 localStorage 读取保存的密码
261
+ const storedPassword = getStoredPassword()
262
+ if (storedPassword) {
263
+ // 自动验证保存的密码
264
+ const matchedConfig = findConfigByPassword(configs, storedPassword)
265
+ if (matchedConfig) {
266
+ // 验证配置的有效性
267
+ if (validateConfig(matchedConfig)) {
268
+ // 验证成功,直接加载配置
269
+ currentConfig.value = matchedConfig
270
+ isLoading.value = false
271
+ return
272
+ }
273
+ else {
274
+ // 配置无效,清除密码并显示对话框
275
+ clearPassword()
276
+ showPasswordDialog.value = true
277
+ return
278
+ }
279
+ }
280
+ else {
281
+ // 密码不匹配,清除保存的密码并显示对话框
282
+ clearPassword()
283
+ showPasswordDialog.value = true
284
+ return
285
+ }
286
+ }
287
+ // 没有保存的密码,显示密码输入对话框
288
+ showPasswordDialog.value = true
289
+ // 保持 loading 状态,等待密码验证
290
+ return
291
+ }
292
+
293
+ // 没有密码保护,直接加载配置
294
+ const config = getConfigWithoutPassword(configs)
295
+ if (config) {
296
+ // 验证配置的有效性
297
+ if (!validateConfig(config)) {
298
+ configError.value = '配置不完整,请联系管理员检查配置'
299
+ isLoading.value = false
300
+ return
301
+ }
302
+ currentConfig.value = config
303
+ isLoading.value = false
304
+ }
305
+ else {
306
+ configError.value = '未找到有效配置'
307
+ isLoading.value = false
308
+ }
309
+ }
310
+ catch (error: any) {
311
+ console.error('加载配置失败:', error)
312
+ configError.value = error?.message || '加载配置失败,请稍后再试'
313
+ isLoading.value = false
314
+ }
315
+ }
316
+
317
+ onMounted(() => {
318
+ loadConfig()
319
+ })
320
+ </script>
321
+
322
+ <template>
323
+ <div id="mate-chat-view" :style="backgroundStyle">
324
+ <!-- 密码输入对话框 -->
325
+ <PasswordDialog
326
+ ref="passwordDialogRef"
327
+ :visible="showPasswordDialog"
328
+ @confirm="handlePasswordConfirm"
329
+ @cancel="handlePasswordCancel"
330
+ />
331
+ <!-- Loading 状态 -->
332
+ <div v-if="finalLoading" class="loading-container">
333
+ <div v-if="configError" class="error-container">
334
+ <VanIcon name="warning-o" size="48" class="error-icon" />
335
+ <div class="error-title">
336
+ 抱歉,加载失败
337
+ </div>
338
+ <div class="error-message">
339
+ {{ getFriendlyErrorMessage(configError) }}
340
+ </div>
341
+ <VanButton
342
+ type="primary"
343
+ size="small"
344
+ class="retry-button"
345
+ @click="handleRetry"
346
+ >
347
+ 重试
348
+ </VanButton>
349
+ </div>
350
+ <VanLoading v-else vertical>
351
+ <template #icon>
352
+ <VanIcon name="star-o" size="30" />
353
+ </template>
354
+ 加载中...
355
+ </VanLoading>
356
+ </div>
357
+ <!-- 对话内容 -->
358
+ <MateChatContent
359
+ v-else-if="currentConfig"
360
+ :config="currentConfig"
361
+ />
362
+ </div>
363
+ </template>
364
+
365
+ <style scoped lang="less">
366
+ #mate-chat-view {
367
+ /* 外层渐变背景容器 */
368
+ width: 100%;
369
+ height: 100%;
370
+ min-height: 100%;
371
+ /* 背景色通过 :style 绑定从配置中获取 */
372
+ padding: 20px;
373
+ display: flex;
374
+ justify-content: center;
375
+ align-items: flex-start;
376
+ box-sizing: border-box;
377
+
378
+ /* 移动端适配 */
379
+ @media (max-width: 768px) {
380
+ padding: 8px;
381
+ }
382
+
383
+ .loading-container {
384
+ width: 100%;
385
+ max-width: 1200px;
386
+ height: calc(100vh - 40px);
387
+ background: #ffffff;
388
+ border-radius: 24px;
389
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
390
+ display: flex;
391
+ align-items: center;
392
+ justify-content: center;
393
+ min-height: 400px;
394
+ padding: 20px;
395
+ box-sizing: border-box;
396
+
397
+ /* 移动端适配 */
398
+ @media (max-width: 768px) {
399
+ height: calc(100% - 18px);
400
+ border-radius: 16px;
401
+ padding: 8px;
402
+ }
403
+
404
+ .loading-message {
405
+ font-size: 14px;
406
+ color: #71757f;
407
+ text-align: center;
408
+ }
409
+
410
+ .error-container {
411
+ display: flex;
412
+ flex-direction: column;
413
+ align-items: center;
414
+ justify-content: center;
415
+ padding: 24px;
416
+ text-align: center;
417
+
418
+ .error-icon {
419
+ color: #ff9800;
420
+ margin-bottom: 16px;
421
+ }
422
+
423
+ .error-title {
424
+ font-size: 16px;
425
+ font-weight: 500;
426
+ color: #323233;
427
+ margin-bottom: 8px;
428
+ }
429
+
430
+ .error-message {
431
+ font-size: 14px;
432
+ color: #969799;
433
+ line-height: 1.5;
434
+ margin-bottom: 24px;
435
+ max-width: 280px;
436
+ }
437
+
438
+ .retry-button {
439
+ min-width: 100px;
440
+ }
441
+ }
442
+ }
443
+ }
444
+ </style>