af-mobile-client-vue3 1.4.59 → 1.4.60
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/__dummy__ +9 -9
- package/build/vite/optimize.ts +36 -36
- package/package.json +3 -2
- package/public/favicon.svg +4 -4
- package/scripts/verifyCommit.js +19 -19
- package/src/components/common/MateChat/components/MateChatContent.vue +274 -274
- package/src/components/common/MateChat/components/MateChatHeader.vue +337 -337
- package/src/components/common/MateChat/index.vue +444 -444
- package/src/components/common/MateChat/types.ts +247 -247
- package/src/components/data/FilePreview/index.vue +141 -0
- package/src/components/data/UserDetail/types.ts +1 -1
- package/src/components/data/XOlMap/types.ts +1 -1
- package/src/components/data/XReportGrid/XAddReport/index.ts +1 -1
- package/src/components/data/XReportGrid/XReportDrawer/index.ts +1 -1
- package/src/components/data/XTag/index.vue +10 -10
- package/src/components/layout/TabBarLayout/index.vue +40 -40
- package/src/hooks/useCommon.ts +9 -9
- package/src/plugins/AppData.ts +38 -38
- package/src/router/invoiceRoutes.ts +33 -33
- package/src/router/routes.ts +6 -0
- package/src/services/api/common.ts +109 -109
- package/src/services/api/manage.ts +8 -8
- package/src/services/api/search.ts +16 -16
- package/src/services/restTools.ts +56 -56
- package/src/utils/authority-utils.ts +84 -84
- package/src/utils/crypto.ts +39 -39
- package/src/utils/runEvalFunction.ts +13 -13
- package/src/views/component/EvaluateRecordView/index.vue +40 -40
- package/src/views/component/FilePreviewView/index.vue +28 -0
- package/src/views/component/MateChat/MateChatView.vue +10 -10
- package/src/views/component/XCellDetailView/index.vue +217 -217
- package/src/views/component/XCellListView/index.vue +2 -78
- package/src/views/component/XFormView/index.vue +13 -28
- package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
- package/src/views/component/XReportFormIframeView/index.vue +47 -47
- package/src/views/component/XReportFormView/index.vue +13 -13
- package/src/views/component/XSignatureView/index.vue +50 -50
- package/src/views/component/index.vue +5 -1
- package/src/views/component/notice.vue +46 -46
- package/src/views/component/topNav.vue +36 -36
- package/src/views/invoiceShow/index.vue +61 -61
- package/src/views/user/login/index.vue +22 -22
|
@@ -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>
|