af-mobile-client-vue3 1.4.53 → 1.4.55
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/build/vite/optimize.ts +36 -36
- package/package.json +1 -1
- package/public/favicon.svg +4 -4
- package/scripts/verifyCommit.js +19 -19
- package/src/App.vue +14 -2
- package/src/components/common/MateChat/apiService.ts +285 -0
- package/src/components/common/MateChat/assets/035-avatar-13.svg +1 -0
- package/src/components/common/MateChat/components/MateChatContent.vue +281 -0
- package/src/components/common/MateChat/components/MateChatHeader.vue +298 -0
- package/src/components/common/MateChat/components/PasswordDialog.vue +97 -0
- package/src/components/common/MateChat/components/PromptList/PromptList.vue +189 -0
- package/src/components/common/MateChat/components/PromptList/index.ts +1 -0
- package/src/components/common/MateChat/composables/useChatHistoryCache.ts +117 -0
- package/src/components/common/MateChat/composables/useChatMessagesCache.ts +72 -0
- package/src/components/common/MateChat/composables/useMateChat.ts +372 -0
- package/src/components/common/MateChat/composables/usePasswordManager.ts +38 -0
- package/src/components/common/MateChat/index.vue +429 -0
- package/src/components/common/MateChat/types.ts +236 -0
- package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +137 -137
- package/src/components/common/otherCharge/CodePayment.vue +357 -357
- package/src/components/common/otherCharge/FileUploader.vue +602 -602
- package/src/components/common/otherCharge/GridFileUploader.vue +846 -846
- package/src/components/common/otherCharge/PaymentMethodSelector.vue +202 -202
- package/src/components/common/otherCharge/PaymentMethodSelectorCard.vue +45 -45
- package/src/components/common/otherCharge/ReceiptModal.vue +273 -273
- package/src/components/common/otherCharge/index.ts +43 -43
- package/src/components/data/OtherCharge/OtherChargeItemModal.vue +547 -547
- package/src/components/data/UserDetail/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/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/queryFormDefaultRangePicker.ts +57 -57
- package/src/utils/runEvalFunction.ts +13 -13
- package/src/views/component/EvaluateRecordView/index.vue +40 -40
- package/src/views/component/MateChat/MateChatView.vue +10 -254
- package/src/views/component/XCellDetailView/index.vue +217 -217
- package/src/views/component/XCellListView/index.vue +107 -138
- package/src/views/component/XFormGroupView/index.vue +78 -82
- package/src/views/component/XFormView/index.vue +41 -46
- 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/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
- package/vite.config.ts +2 -1
- package/src/views/component/MateChat/apiService.ts +0 -104
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChatBizResult,
|
|
3
|
+
ChatStreamCallbacks,
|
|
4
|
+
MateChatConfig,
|
|
5
|
+
MateChatMessage,
|
|
6
|
+
} from '@af-mobile-client-vue3/components/common/MateChat/types'
|
|
7
|
+
import { useChatHistoryCache } from '@af-mobile-client-vue3/components/common/MateChat/composables/useChatHistoryCache'
|
|
8
|
+
import { useChatMessagesCache } from '@af-mobile-client-vue3/components/common/MateChat/composables/useChatMessagesCache'
|
|
9
|
+
import { showToast } from 'vant'
|
|
10
|
+
import { ref } from 'vue'
|
|
11
|
+
import { chatBiz, chatCompletionsStream, getPaginationRecords } from '../apiService'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 生成随机 chatId
|
|
15
|
+
*/
|
|
16
|
+
function generateChatId(): string {
|
|
17
|
+
return `chat_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 封装 MateChat 核心对话逻辑的组合式函数
|
|
22
|
+
* - 负责 startPage / inputValue / messages 等状态
|
|
23
|
+
* - 根据配置中的 useStream 决定使用非流式(chatBiz)还是流式(chatCompletionsStream)
|
|
24
|
+
* @param config MateChat 配置对象
|
|
25
|
+
*/
|
|
26
|
+
export function useMateChat(config: MateChatConfig) {
|
|
27
|
+
const startPage = ref(true)
|
|
28
|
+
const inputValue = ref('')
|
|
29
|
+
const messages = ref<MateChatMessage[]>([])
|
|
30
|
+
const chatId = ref(generateChatId())
|
|
31
|
+
const useStream = config.useStream
|
|
32
|
+
|
|
33
|
+
// 使用历史会话缓存
|
|
34
|
+
const { clearCache } = useChatHistoryCache()
|
|
35
|
+
// 使用会话消息缓存
|
|
36
|
+
const { getCachedMessages, setCachedMessages, appendMessages } = useChatMessagesCache()
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 新建会话:回到起始页并清空历史消息,生成新的 chatId
|
|
40
|
+
* 同时清除历史会话列表缓存,以便下次打开时重新获取
|
|
41
|
+
*/
|
|
42
|
+
function newConversation() {
|
|
43
|
+
startPage.value = true
|
|
44
|
+
messages.value = []
|
|
45
|
+
chatId.value = generateChatId()
|
|
46
|
+
// 清除历史会话列表缓存,下次打开历史会话时会重新获取
|
|
47
|
+
clearCache(config.appId, config.appKey)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 发送一条消息
|
|
52
|
+
* - 推入用户消息
|
|
53
|
+
* - 添加一条 loading 的模型消息
|
|
54
|
+
* - 根据 useStream 调用对应的接口
|
|
55
|
+
*/
|
|
56
|
+
async function onSubmit(evt: string) {
|
|
57
|
+
if (!evt.trim()) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 如果是从空状态发起的新会话,清除历史会话列表缓存
|
|
62
|
+
const isNewConversation = startPage.value
|
|
63
|
+
if (isNewConversation) {
|
|
64
|
+
clearCache(config.appId, config.appKey)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
inputValue.value = ''
|
|
68
|
+
startPage.value = false
|
|
69
|
+
|
|
70
|
+
// 用户发送消息
|
|
71
|
+
messages.value.push({
|
|
72
|
+
from: 'user',
|
|
73
|
+
content: evt,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// 添加 loading 状态的 model 消息
|
|
77
|
+
const loadingMessageIndex = messages.value.length
|
|
78
|
+
messages.value.push({
|
|
79
|
+
from: 'model',
|
|
80
|
+
content: '',
|
|
81
|
+
loading: true,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (!useStream) {
|
|
85
|
+
// 非流式:一次性拿到完整结果
|
|
86
|
+
try {
|
|
87
|
+
const result: ChatBizResult = await chatBiz(
|
|
88
|
+
evt,
|
|
89
|
+
config.appId,
|
|
90
|
+
config.appKey,
|
|
91
|
+
chatId.value,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if (result.type === 'transfer') {
|
|
95
|
+
// 移除 loading 消息
|
|
96
|
+
messages.value.splice(loadingMessageIndex, 1)
|
|
97
|
+
// 添加人工客服消息
|
|
98
|
+
messages.value.push({
|
|
99
|
+
from: 'service',
|
|
100
|
+
content: '您好,客服xxx工号xxx为你服务。功能开发中,敬请期待。',
|
|
101
|
+
})
|
|
102
|
+
// 更新缓存:追加用户消息和客服消息
|
|
103
|
+
appendMessages(chatId.value, [
|
|
104
|
+
{ from: 'user', content: evt },
|
|
105
|
+
{ from: 'service', content: '您好,客服xxx工号xxx为你服务。功能开发中,敬请期待。' },
|
|
106
|
+
])
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// 正常消息:替换 loading 为模型回复
|
|
110
|
+
messages.value[loadingMessageIndex] = {
|
|
111
|
+
from: 'model',
|
|
112
|
+
content: result.content,
|
|
113
|
+
loading: false,
|
|
114
|
+
}
|
|
115
|
+
// 更新缓存:追加用户消息和模型回复
|
|
116
|
+
appendMessages(chatId.value, [
|
|
117
|
+
{ from: 'user', content: evt },
|
|
118
|
+
{ from: 'model', content: result.content, loading: false },
|
|
119
|
+
])
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error: any) {
|
|
123
|
+
// 处理错误
|
|
124
|
+
console.error('聊天请求失败:', error)
|
|
125
|
+
messages.value[loadingMessageIndex] = {
|
|
126
|
+
from: 'model',
|
|
127
|
+
content: '抱歉,服务暂时不可用,请稍后再试。',
|
|
128
|
+
loading: false,
|
|
129
|
+
}
|
|
130
|
+
// 更新缓存:追加用户消息和错误消息
|
|
131
|
+
appendMessages(chatId.value, [
|
|
132
|
+
{ from: 'user', content: evt },
|
|
133
|
+
{ from: 'model', content: '抱歉,服务暂时不可用,请稍后再试。', loading: false },
|
|
134
|
+
])
|
|
135
|
+
showToast(error?.message || '请求失败,请稍后再试')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 流式:使用 FastGPT SSE,增量更新最后一条模型消息内容,并在前缀为 {"msgType":"transfer"} 时转人工
|
|
142
|
+
const transferPrefix = '{"msgType":"transfer"'
|
|
143
|
+
let checkedTransfer = false
|
|
144
|
+
let transferHandled = false
|
|
145
|
+
let prefixBuffer = ''
|
|
146
|
+
|
|
147
|
+
const callbacks: ChatStreamCallbacks = {
|
|
148
|
+
onMessage(chunk) {
|
|
149
|
+
if (transferHandled) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 尚未判断是否为转人工前缀
|
|
154
|
+
if (!checkedTransfer) {
|
|
155
|
+
const trimmed = chunk.trimStart()
|
|
156
|
+
if (!trimmed) {
|
|
157
|
+
// 纯空白,等待下一帧
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 第一个有效字符不是 { ,本次不会是转人工 JSON,后续直接按普通文本处理
|
|
162
|
+
if (!prefixBuffer && trimmed[0] !== '{') {
|
|
163
|
+
checkedTransfer = true
|
|
164
|
+
const msg = messages.value[loadingMessageIndex]
|
|
165
|
+
if (!msg) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
msg.content += chunk
|
|
169
|
+
msg.loading = true
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 有可能是 JSON,累积前缀做精准匹配
|
|
174
|
+
prefixBuffer += trimmed
|
|
175
|
+
|
|
176
|
+
// 如果当前前缀还是 transferPrefix 的前缀,继续等后续 chunk
|
|
177
|
+
if (transferPrefix.startsWith(prefixBuffer)) {
|
|
178
|
+
// 还没完整匹配上整个标识,继续等待
|
|
179
|
+
if (prefixBuffer.length < transferPrefix.length) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (prefixBuffer.startsWith(transferPrefix)) {
|
|
185
|
+
// 确认是转人工:移除 loading 模型气泡,插入客服气泡
|
|
186
|
+
messages.value.splice(loadingMessageIndex, 1)
|
|
187
|
+
messages.value.push({
|
|
188
|
+
from: 'service',
|
|
189
|
+
content: '您好,客服xxx工号xxx为你服务。功能开发中,敬请期待。',
|
|
190
|
+
})
|
|
191
|
+
transferHandled = true
|
|
192
|
+
checkedTransfer = true
|
|
193
|
+
// 更新缓存:追加用户消息和客服消息(在 onComplete 中统一处理)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 前缀与约定不匹配,当作普通内容处理,并不再尝试转人工识别
|
|
198
|
+
checkedTransfer = true
|
|
199
|
+
const msg = messages.value[loadingMessageIndex]
|
|
200
|
+
if (!msg) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
msg.content += prefixBuffer
|
|
204
|
+
msg.loading = true
|
|
205
|
+
prefixBuffer = ''
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 已经判断过不会转人工,正常流式追加内容
|
|
210
|
+
const msg = messages.value[loadingMessageIndex]
|
|
211
|
+
if (!msg) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
msg.content += chunk
|
|
215
|
+
msg.loading = true
|
|
216
|
+
},
|
|
217
|
+
onComplete() {
|
|
218
|
+
if (transferHandled) {
|
|
219
|
+
// 转人工情况:更新缓存
|
|
220
|
+
appendMessages(chatId.value, [
|
|
221
|
+
{ from: 'user', content: evt },
|
|
222
|
+
{ from: 'service', content: '您好,客服xxx工号xxx为你服务。功能开发中,敬请期待。' },
|
|
223
|
+
])
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
const msg = messages.value[loadingMessageIndex]
|
|
227
|
+
if (!msg) {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
msg.loading = false
|
|
231
|
+
// 流式完成后更新缓存:追加用户消息和完整的模型回复
|
|
232
|
+
appendMessages(chatId.value, [
|
|
233
|
+
{ from: 'user', content: evt },
|
|
234
|
+
{ from: 'model', content: msg.content, loading: false },
|
|
235
|
+
])
|
|
236
|
+
},
|
|
237
|
+
onError(error) {
|
|
238
|
+
console.error('聊天请求失败:', error)
|
|
239
|
+
const msg = messages.value[loadingMessageIndex]
|
|
240
|
+
if (!msg) {
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
msg.content = '抱歉,服务暂时不可用,请稍后再试。'
|
|
244
|
+
msg.loading = false
|
|
245
|
+
// 更新缓存:追加用户消息和错误消息
|
|
246
|
+
appendMessages(chatId.value, [
|
|
247
|
+
{ from: 'user', content: evt },
|
|
248
|
+
{ from: 'model', content: '抱歉,服务暂时不可用,请稍后再试。', loading: false },
|
|
249
|
+
])
|
|
250
|
+
showToast((error as any)?.message || '请求失败,请稍后再试')
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
await chatCompletionsStream(
|
|
256
|
+
evt,
|
|
257
|
+
config.appId,
|
|
258
|
+
config.appKey,
|
|
259
|
+
chatId.value,
|
|
260
|
+
callbacks,
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
catch (error: any) {
|
|
264
|
+
// 兜底错误处理(理论上 callbacks.onError 已经处理)
|
|
265
|
+
console.error('聊天流式请求异常:', error)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 加载历史会话消息
|
|
271
|
+
* @param targetChatId 会话 ID
|
|
272
|
+
*/
|
|
273
|
+
async function loadHistoryMessages(targetChatId: string) {
|
|
274
|
+
// 先检查缓存
|
|
275
|
+
const cachedMessages = getCachedMessages(targetChatId)
|
|
276
|
+
if (cachedMessages) {
|
|
277
|
+
// 使用缓存数据
|
|
278
|
+
messages.value = cachedMessages
|
|
279
|
+
chatId.value = targetChatId
|
|
280
|
+
startPage.value = false
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 缓存不存在,从服务器获取
|
|
285
|
+
try {
|
|
286
|
+
const response = await getPaginationRecords(
|
|
287
|
+
config.appId,
|
|
288
|
+
config.appKey,
|
|
289
|
+
targetChatId,
|
|
290
|
+
0,
|
|
291
|
+
10,
|
|
292
|
+
true,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
console.log('getPaginationRecords 响应:', response)
|
|
296
|
+
|
|
297
|
+
// 检查响应格式
|
|
298
|
+
if (!response) {
|
|
299
|
+
throw new Error('响应数据为空')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (response.code !== 200) {
|
|
303
|
+
throw new Error(response.message || '获取历史消息失败')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!response.data) {
|
|
307
|
+
throw new Error('响应数据格式错误:缺少 data 字段')
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!Array.isArray(response.data.list)) {
|
|
311
|
+
throw new TypeError('响应数据格式错误:list 不是数组')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 清空当前消息
|
|
315
|
+
messages.value = []
|
|
316
|
+
// 设置 chatId
|
|
317
|
+
chatId.value = targetChatId
|
|
318
|
+
|
|
319
|
+
// 转换历史消息格式
|
|
320
|
+
const historyMessages: MateChatMessage[] = []
|
|
321
|
+
for (const record of response.data.list) {
|
|
322
|
+
if (record.hideInUI) {
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 提取消息内容
|
|
327
|
+
const content = record.value?.[0]?.text?.content || ''
|
|
328
|
+
|
|
329
|
+
if (!content) {
|
|
330
|
+
// 跳过空内容的消息
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (record.obj === 'Human') {
|
|
335
|
+
historyMessages.push({
|
|
336
|
+
from: 'user',
|
|
337
|
+
content,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
else if (record.obj === 'AI') {
|
|
341
|
+
historyMessages.push({
|
|
342
|
+
from: 'model',
|
|
343
|
+
content,
|
|
344
|
+
loading: false,
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
messages.value = historyMessages
|
|
350
|
+
startPage.value = false
|
|
351
|
+
|
|
352
|
+
// 缓存历史消息
|
|
353
|
+
setCachedMessages(targetChatId, historyMessages)
|
|
354
|
+
|
|
355
|
+
console.log('历史消息加载成功,共', historyMessages.length, '条消息')
|
|
356
|
+
}
|
|
357
|
+
catch (error: any) {
|
|
358
|
+
console.error('加载历史消息失败:', error)
|
|
359
|
+
showToast(error?.message || '加载历史消息失败,请稍后再试')
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
startPage,
|
|
365
|
+
inputValue,
|
|
366
|
+
messages,
|
|
367
|
+
chatId,
|
|
368
|
+
newConversation,
|
|
369
|
+
onSubmit,
|
|
370
|
+
loadHistoryMessages,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createStorage } from '@af-mobile-client-vue3/utils/Storage'
|
|
2
|
+
|
|
3
|
+
// 创建 localStorage 实例用于存储密码
|
|
4
|
+
const passwordStorage = createStorage({ storage: localStorage })
|
|
5
|
+
const PASSWORD_STORAGE_KEY = 'mateChat_password'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 密码管理组合式函数
|
|
9
|
+
* 提供密码的存储、读取和清除功能
|
|
10
|
+
*/
|
|
11
|
+
export function usePasswordManager() {
|
|
12
|
+
/**
|
|
13
|
+
* 从 localStorage 读取保存的密码
|
|
14
|
+
*/
|
|
15
|
+
function getStoredPassword(): string | null {
|
|
16
|
+
return passwordStorage.get(PASSWORD_STORAGE_KEY, null)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 将密码保存到 localStorage
|
|
21
|
+
*/
|
|
22
|
+
function savePassword(password: string) {
|
|
23
|
+
passwordStorage.set(PASSWORD_STORAGE_KEY, password)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 清除保存的密码
|
|
28
|
+
*/
|
|
29
|
+
function clearPassword() {
|
|
30
|
+
passwordStorage.remove(PASSWORD_STORAGE_KEY)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
getStoredPassword,
|
|
35
|
+
savePassword,
|
|
36
|
+
clearPassword,
|
|
37
|
+
}
|
|
38
|
+
}
|