Files
Neo-ZQYY/apps/miniprogram - 副本/miniprogram/pages/chat/chat.ts

169 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// pages/chat/chat.ts — AI 对话页
import { mockChatMessages } from '../../utils/mock-data'
import type { ChatMessage } from '../../utils/mock-data'
import { simulateStreamOutput } from '../../utils/chat'
/** 将 referenceCard.data (Record<string,string>) 转为数组供 WXML wx:for 渲染 */
function toDataList(data?: Record<string, string>): Array<{ key: string; value: string }> {
if (!data) return []
return Object.keys(data).map((k) => ({ key: k, value: data[k] }))
}
/** 为消息列表中的 referenceCard 补充 dataList 字段 */
function enrichMessages(msgs: ChatMessage[]) {
return msgs.map((m) => ({
...m,
referenceCard: m.referenceCard
? { ...m.referenceCard, dataList: toDataList(m.referenceCard.data) }
: undefined,
}))
}
/** Mock AI 回复模板 */
const mockAIReplies = [
'根据数据分析,这位客户近期消费频次有所下降,建议安排一次主动回访,了解具体原因。',
'好的,我已经记录了你的需求。下次回访时我会提醒你。',
'这位客户偏好中式台球,最近对斯诺克也产生了兴趣,可以推荐相关课程。',
]
Page({
data: {
/** 页面状态 */
pageState: 'loading' as 'loading' | 'empty' | 'normal',
/** 消息列表 */
messages: [] as Array<ChatMessage & { referenceCard?: ChatMessage['referenceCard'] & { dataList?: Array<{ key: string; value: string }> } }>,
/** 输入框内容 */
inputText: '',
/** AI 正在流式回复 */
isStreaming: false,
/** 流式输出中的内容 */
streamingContent: '',
/** 滚动锚点 */
scrollToId: '',
/** 页面顶部引用卡片(从其他页面跳转时) */
referenceCard: null as { title: string; summary: string } | null,
/** 客户 ID */
customerId: '',
},
/** 消息计数器,用于生成唯一 ID */
_msgCounter: 0,
onLoad(options) {
const customerId = options?.customerId || ''
this.setData({ customerId })
this.loadMessages(customerId)
},
/** 加载消息Mock */
loadMessages(customerId: string) {
this.setData({ pageState: 'loading' })
setTimeout(() => {
// TODO: 替换为真实 API 调用
const messages = enrichMessages(mockChatMessages)
this._msgCounter = messages.length
// 如果携带 customerId显示引用卡片
const referenceCard = customerId
? { title: '客户详情', summary: `正在查看客户 ${customerId} 的相关信息` }
: null
const isEmpty = messages.length === 0 && !referenceCard
this.setData({
pageState: isEmpty ? 'empty' : 'normal',
messages,
referenceCard,
})
// 滚动到底部
this.scrollToBottom()
}, 500)
},
/** 输入框内容变化 */
onInputChange(e: WechatMiniprogram.Input) {
this.setData({ inputText: e.detail.value })
},
/** 发送消息 */
onSendMessage() {
const text = this.data.inputText.trim()
if (!text || this.data.isStreaming) return
this._msgCounter++
const userMsg = {
id: `msg-user-${this._msgCounter}`,
role: 'user' as const,
content: text,
timestamp: new Date().toISOString(),
}
const messages = [...this.data.messages, userMsg]
this.setData({
messages,
inputText: '',
pageState: 'normal',
})
this.scrollToBottom()
// 模拟 AI 回复
setTimeout(() => {
this.triggerAIReply()
}, 300)
},
/** 触发 AI 流式回复 */
triggerAIReply() {
this._msgCounter++
const aiMsgId = `msg-ai-${this._msgCounter}`
const replyText = mockAIReplies[this._msgCounter % mockAIReplies.length]
// 先添加空的 AI 消息占位
const aiMsg = {
id: aiMsgId,
role: 'assistant' as const,
content: '',
timestamp: new Date().toISOString(),
}
const messages = [...this.data.messages, aiMsg]
this.setData({
messages,
isStreaming: true,
streamingContent: '',
})
this.scrollToBottom()
// 流式输出
const aiIndex = messages.length - 1
simulateStreamOutput(replyText, (partial: string) => {
const key = `messages[${aiIndex}].content`
this.setData({
[key]: partial,
streamingContent: partial,
})
this.scrollToBottom()
}).then(() => {
this.setData({
isStreaming: false,
streamingContent: '',
})
})
},
/** 滚动到底部 */
scrollToBottom() {
// 使用 nextTick 确保 DOM 更新后再滚动
setTimeout(() => {
this.setData({ scrollToId: '' })
setTimeout(() => {
this.setData({ scrollToId: 'scroll-bottom' })
}, 50)
}, 50)
},
})