2025-12-08 19:01:09 +08:00
|
|
|
|
<template>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
<view class="chat-container">
|
|
|
|
|
|
<!-- 顶部标题栏 -->
|
|
|
|
|
|
<view class="header" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
|
|
|
|
|
|
<text class="title">泰豪小电</text>
|
2025-12-22 19:16:53 +08:00
|
|
|
|
<view class="header-right">
|
|
|
|
|
|
<button class="workcase-btn" @tap="switchMockUser" v-if="isMockMode">
|
|
|
|
|
|
<text class="btn-text">切换</text>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="workcase-btn" @tap="goToChatRoomList">
|
|
|
|
|
|
<text class="btn-text">聊天室</text>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="workcase-btn" @tap="goToWorkList">
|
|
|
|
|
|
<image class="btn-icon" src="/static/imgs/case.svg" />
|
|
|
|
|
|
<text class="btn-text">工单</text>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
</view>
|
2025-12-22 19:16:53 +08:00
|
|
|
|
<!-- 欢迎区域(机器人+浮动标签) -->
|
|
|
|
|
|
<view class="hero" v-if="messages.length === 0">
|
|
|
|
|
|
<view class="rings">
|
|
|
|
|
|
<view class="ring r1"></view>
|
|
|
|
|
|
<view class="ring r2"></view>
|
|
|
|
|
|
<view class="ring r3"></view>
|
|
|
|
|
|
<view class="ring r4"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="robot">
|
|
|
|
|
|
<view class="robot-face">
|
|
|
|
|
|
<view class="eye left"></view>
|
|
|
|
|
|
<view class="eye right"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="float-tag t1">查询质保状态</view>
|
|
|
|
|
|
<view class="float-tag t2">发动机无法启动</view>
|
|
|
|
|
|
<view class="float-tag t3">申请上门维修</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="greeting" v-if="messages.length === 0">
|
|
|
|
|
|
<text class="greeting-title">Hi~ 有什么可以帮您!</text>
|
|
|
|
|
|
<text class="greeting-sub">泰豪小电为您服务:)</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
<!-- 聊天消息区域 -->
|
2025-12-22 19:16:53 +08:00
|
|
|
|
<scroll-view class="chat-messages" scroll-y="true" :scroll-top="scrollTop" scroll-with-animation="true" :class="{ started: messages.length > 0 }">
|
|
|
|
|
|
<!-- AI初始消息 -->
|
|
|
|
|
|
<view class="ai-initial-msg" v-if="messages.length === 0">
|
|
|
|
|
|
<text class="ai-msg-text">您好,我是泰豪小电智能客服。请描述您的问题,我会尽力协助。</text>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 聊天消息列表 -->
|
|
|
|
|
|
<view class="messages-list" v-else>
|
|
|
|
|
|
<view class="message-item" v-for="(item, index) in messages" :key="index"
|
|
|
|
|
|
:class="item.type === 'user' ? 'user-message' : 'bot-message'">
|
|
|
|
|
|
<!-- 用户消息(右侧) -->
|
|
|
|
|
|
<view class="user-message-content" v-if="item.type === 'user'">
|
|
|
|
|
|
<view class="message-bubble user-bubble">
|
|
|
|
|
|
<text class="message-text">{{item.content}}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="avatar user-avatar">
|
|
|
|
|
|
<text class="avatar-text">我</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- Bot/员工消息(左侧) -->
|
|
|
|
|
|
<view class="bot-message-content" v-else>
|
|
|
|
|
|
<view class="avatar bot-avatar">
|
|
|
|
|
|
<text class="avatar-text">AI</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="message-bubble bot-bubble">
|
|
|
|
|
|
<text class="message-text">{{item.content}}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="message-time">{{item.time}}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
<!-- 底部操作区域 -->
|
|
|
|
|
|
<view class="bottom-area">
|
2025-12-22 19:16:53 +08:00
|
|
|
|
<!-- 快捷按钮横向滚动 -->
|
|
|
|
|
|
<scroll-view class="quick-scroll" scroll-x="true">
|
|
|
|
|
|
<view class="quick-list">
|
|
|
|
|
|
<view class="quick-btn has-icon" @tap="contactHuman">
|
|
|
|
|
|
<text class="quick-icon">☎</text>
|
|
|
|
|
|
<text class="quick-text">联系人工</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="quick-btn has-icon" @tap="showCreator">
|
|
|
|
|
|
<text class="quick-icon">⊕</text>
|
|
|
|
|
|
<text class="quick-text">创建工单</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="quick-divider"></view>
|
|
|
|
|
|
<view class="quick-btn" @tap="handleQuickQuestion('查询质保状态')">
|
2025-12-10 17:00:54 +08:00
|
|
|
|
<text class="quick-text">查询质保状态</text>
|
2025-12-22 19:16:53 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
<view class="quick-btn" @tap="handleQuickQuestion('发动机无法启动')">
|
|
|
|
|
|
<text class="quick-text">发动机无法启动</text>
|
|
|
|
|
|
</view>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
</view>
|
2025-12-22 19:16:53 +08:00
|
|
|
|
</scroll-view>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 输入区域 -->
|
2025-12-22 19:16:53 +08:00
|
|
|
|
<view class="chat-input-wrap">
|
|
|
|
|
|
<input class="chat-input" v-model="inputText" placeholder="输入问题 来问问我~" @confirm="sendMessage" />
|
|
|
|
|
|
<view class="send-btn" @tap="sendMessage">
|
|
|
|
|
|
<text class="send-icon">➤</text>
|
|
|
|
|
|
</view>
|
2025-12-10 17:00:54 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 工单创建弹窗 -->
|
|
|
|
|
|
<WorkcaseCreator v-if="showWorkcaseCreator" :show="showWorkcaseCreator" @close="hideCreator"
|
|
|
|
|
|
@success="onWorkcaseCreated" />
|
2025-12-08 19:01:09 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, nextTick, onMounted } from 'vue'
|
|
|
|
|
|
import WorkcaseCreator from '@/components/WorkcaseCreator/WorkcaseCreator.uvue'
|
2025-12-23 13:27:36 +08:00
|
|
|
|
import { guestAPI, workcaseChatAPI } from '@/api'
|
|
|
|
|
|
import type { TbWorkcaseDTO } from '@/types'
|
|
|
|
|
|
|
|
|
|
|
|
// 前端消息展示类型
|
|
|
|
|
|
interface ChatMessageItem {
|
|
|
|
|
|
type: 'user' | 'bot'
|
|
|
|
|
|
content: string
|
|
|
|
|
|
time: string
|
|
|
|
|
|
actions?: string[] | null
|
2025-12-10 17:00:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
2025-12-23 13:27:36 +08:00
|
|
|
|
const messages = ref<ChatMessageItem[]>([])
|
2025-12-10 17:00:54 +08:00
|
|
|
|
const inputText = ref<string>('')
|
|
|
|
|
|
const isTyping = ref<boolean>(false)
|
|
|
|
|
|
const scrollTop = ref<number>(0)
|
|
|
|
|
|
const showWorkcaseCreator = ref<boolean>(false)
|
|
|
|
|
|
const statusBarHeight = ref<number>(0)
|
|
|
|
|
|
const headerPaddingTop = ref<number>(44) // header顶部padding,默认44px
|
|
|
|
|
|
const headerTotalHeight = ref<number>(76) // header总高度,默认76px
|
|
|
|
|
|
|
2025-12-22 19:16:53 +08:00
|
|
|
|
// 用户信息
|
|
|
|
|
|
const userInfo = ref({
|
|
|
|
|
|
wechatId: '',
|
|
|
|
|
|
username: '',
|
2025-12-23 13:27:36 +08:00
|
|
|
|
phone: '',
|
|
|
|
|
|
userId: ''
|
2025-12-22 19:16:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
const isMockMode = ref(true) // 开发环境mock模式
|
|
|
|
|
|
|
2025-12-23 13:27:36 +08:00
|
|
|
|
// AI 对话相关
|
|
|
|
|
|
const chatId = ref<string>('') // 当前会话ID
|
|
|
|
|
|
const currentTaskId = ref<string>('') // 当前任务ID(用于停止)
|
|
|
|
|
|
|
2025-12-22 19:16:53 +08:00
|
|
|
|
// 初始化用户信息
|
2025-12-23 13:27:36 +08:00
|
|
|
|
async function initUserInfo() {
|
2025-12-22 19:16:53 +08:00
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
|
|
// 正式环境:从微信获取用户信息
|
|
|
|
|
|
// wx.login({
|
|
|
|
|
|
// success: (loginRes) => {
|
|
|
|
|
|
// // 使用code换取openid等信息
|
|
|
|
|
|
// console.log('微信登录code:', loginRes.code)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// })
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// 开发环境:使用mock数据
|
|
|
|
|
|
if (isMockMode.value) {
|
|
|
|
|
|
userInfo.value = {
|
|
|
|
|
|
wechatId: '17857100375',
|
|
|
|
|
|
username: '测试用户',
|
2025-12-23 13:27:36 +08:00
|
|
|
|
phone: '17857100375',
|
|
|
|
|
|
userId: ''
|
2025-12-22 19:16:53 +08:00
|
|
|
|
}
|
2025-12-23 13:27:36 +08:00
|
|
|
|
await doIdentify()
|
2025-12-22 19:16:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换mock用户(开发调试用)
|
|
|
|
|
|
function switchMockUser() {
|
|
|
|
|
|
uni.showActionSheet({
|
|
|
|
|
|
itemList: ['员工 (17857100375)', '访客 (17857100376)'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.tapIndex === 0) {
|
2025-12-23 13:27:36 +08:00
|
|
|
|
userInfo.value = { wechatId: '17857100375', username: '员工用户', phone: '17857100375', userId: '' }
|
2025-12-22 19:16:53 +08:00
|
|
|
|
} else {
|
2025-12-23 13:27:36 +08:00
|
|
|
|
userInfo.value = { wechatId: '17857100376', username: '访客用户', phone: '17857100376', userId: '' }
|
2025-12-22 19:16:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
doIdentify()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用identify接口
|
2025-12-23 13:27:36 +08:00
|
|
|
|
async function doIdentify() {
|
2025-12-22 19:16:53 +08:00
|
|
|
|
uni.showLoading({ title: '登录中...' })
|
2025-12-23 13:27:36 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const res = await guestAPI.identify({
|
|
|
|
|
|
wechatId: userInfo.value.wechatId,
|
|
|
|
|
|
phone: userInfo.value.phone
|
|
|
|
|
|
})
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
if (res.success && res.data) {
|
|
|
|
|
|
const loginDomain = res.data
|
|
|
|
|
|
uni.setStorageSync('token', loginDomain.token || '')
|
|
|
|
|
|
uni.setStorageSync('userInfo', JSON.stringify(loginDomain.user))
|
|
|
|
|
|
uni.setStorageSync('wechatId', userInfo.value.wechatId)
|
|
|
|
|
|
userInfo.value.userId = loginDomain.user?.userId || ''
|
|
|
|
|
|
console.log('identify成功:', loginDomain)
|
|
|
|
|
|
uni.showToast({ title: '登录成功', icon: 'success' })
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('identify失败:', res.message)
|
2025-12-22 19:16:53 +08:00
|
|
|
|
}
|
2025-12-23 13:27:36 +08:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
console.error('identify请求失败:', err)
|
|
|
|
|
|
}
|
2025-12-22 19:16:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
// 生命周期
|
|
|
|
|
|
onMounted(() => {
|
2025-12-22 19:16:53 +08:00
|
|
|
|
// 初始化用户信息
|
|
|
|
|
|
initUserInfo()
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
// 设置页面标题
|
|
|
|
|
|
uni.setNavigationBarTitle({
|
|
|
|
|
|
title: '智能助手'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 获取系统信息和安全区域
|
|
|
|
|
|
uni.getSystemInfo({
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
console.log('系统信息:', res)
|
|
|
|
|
|
console.log('状态栏高度:', res.statusBarHeight)
|
|
|
|
|
|
statusBarHeight.value = res.statusBarHeight || 0
|
|
|
|
|
|
console.log('安全区域:', res.safeArea)
|
|
|
|
|
|
console.log('安全区域insets:', res.safeAreaInsets)
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
|
|
// 获取胶囊按钮信息(仅小程序),计算header位置
|
|
|
|
|
|
try {
|
|
|
|
|
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
|
|
|
|
|
console.log('胶囊按钮信息:', menuButtonInfo)
|
|
|
|
|
|
|
|
|
|
|
|
// 计算header的paddingTop和总高度
|
|
|
|
|
|
// paddingTop = 胶囊按钮的top值
|
|
|
|
|
|
// 总高度 = 胶囊按钮bottom值
|
|
|
|
|
|
headerPaddingTop.value = menuButtonInfo.top
|
|
|
|
|
|
headerTotalHeight.value = menuButtonInfo.bottom
|
|
|
|
|
|
|
|
|
|
|
|
console.log('header paddingTop:', headerPaddingTop.value)
|
|
|
|
|
|
console.log('header totalHeight:', headerTotalHeight.value)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log('获取胶囊按钮信息失败:', e)
|
|
|
|
|
|
// 使用默认值
|
|
|
|
|
|
headerPaddingTop.value = 44
|
|
|
|
|
|
headerTotalHeight.value = 76
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
2025-12-08 19:01:09 +08:00
|
|
|
|
}
|
2025-12-10 17:00:54 +08:00
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息
|
2025-12-23 13:27:36 +08:00
|
|
|
|
async function sendMessage() {
|
2025-12-10 17:00:54 +08:00
|
|
|
|
const text = inputText.value.trim()
|
|
|
|
|
|
if (!text || isTyping.value) return
|
2025-12-08 19:01:09 +08:00
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
// 添加用户消息
|
|
|
|
|
|
addMessage('user', text)
|
|
|
|
|
|
inputText.value = ''
|
2025-12-08 19:01:09 +08:00
|
|
|
|
|
2025-12-23 13:27:36 +08:00
|
|
|
|
// 调用AI聊天接口
|
|
|
|
|
|
await callAIChat(text)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用AI聊天接口
|
|
|
|
|
|
async function callAIChat(query : string) {
|
|
|
|
|
|
isTyping.value = true
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 如果没有会话ID,先创建会话
|
|
|
|
|
|
if (!chatId.value) {
|
|
|
|
|
|
const createRes = await workcaseChatAPI.createChat({
|
|
|
|
|
|
title: '智能助手对话',
|
|
|
|
|
|
userId: userInfo.value.userId || userInfo.value.wechatId
|
|
|
|
|
|
})
|
|
|
|
|
|
if (createRes.success && createRes.data) {
|
|
|
|
|
|
chatId.value = createRes.data.chatId || ''
|
|
|
|
|
|
console.log('创建会话成功:', chatId.value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(createRes.message || '创建会话失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 准备流式对话
|
|
|
|
|
|
const prepareRes = await workcaseChatAPI.prepareChatMessageSession({
|
|
|
|
|
|
chatId: chatId.value,
|
|
|
|
|
|
message: query
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!prepareRes.success || !prepareRes.data) {
|
|
|
|
|
|
throw new Error(prepareRes.message || '准备对话失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
const sessionId = prepareRes.data
|
|
|
|
|
|
console.log('准备流式对话成功:', sessionId)
|
|
|
|
|
|
|
|
|
|
|
|
// 添加空的AI消息占位
|
|
|
|
|
|
const messageIndex = messages.value.length
|
|
|
|
|
|
addMessage('bot', '')
|
|
|
|
|
|
|
|
|
|
|
|
// 建立SSE连接
|
|
|
|
|
|
streamChat(sessionId, messageIndex)
|
|
|
|
|
|
} catch (error : any) {
|
|
|
|
|
|
console.error('AI聊天失败:', error)
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
addMessage('bot', '抱歉,AI服务暂时不可用,请稍后重试。')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSE 流式对话
|
|
|
|
|
|
function streamChat(sessionId : string, messageIndex : number) {
|
|
|
|
|
|
const url = `http://localhost:8180${workcaseChatAPI.getStreamUrl(sessionId)}`
|
|
|
|
|
|
console.log('建立SSE连接:', url)
|
|
|
|
|
|
|
|
|
|
|
|
const requestTask = uni.request({
|
|
|
|
|
|
url: url,
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
header: { 'Accept': 'text/event-stream' },
|
|
|
|
|
|
enableChunked: true,
|
|
|
|
|
|
success: (res : any) => {
|
|
|
|
|
|
console.log('SSE请求完成:', res)
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.error('SSE请求失败:', err)
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
messages.value[messageIndex].content = '抱歉,网络连接失败,请稍后重试。'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 监听分块数据
|
|
|
|
|
|
requestTask.onChunkReceived((res : any) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const decoder = new TextDecoder('utf-8')
|
|
|
|
|
|
const text = decoder.decode(new Uint8Array(res.data))
|
|
|
|
|
|
console.log('收到分块数据:', text)
|
|
|
|
|
|
|
|
|
|
|
|
const lines = text.split('\n')
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
|
if (line.startsWith('data:')) {
|
|
|
|
|
|
const dataStr = line.substring(5).trim()
|
|
|
|
|
|
if (dataStr && dataStr !== '[DONE]') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = JSON.parse(dataStr)
|
|
|
|
|
|
const event = data.event
|
|
|
|
|
|
|
|
|
|
|
|
if (event === 'message' || event === 'agent_message') {
|
|
|
|
|
|
if (data.answer) {
|
|
|
|
|
|
messages.value[messageIndex].content += data.answer
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (event === 'message_end') {
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
if (data.task_id) {
|
|
|
|
|
|
currentTaskId.value = data.task_id
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (event === 'error') {
|
|
|
|
|
|
console.error('SSE错误:', data.message)
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
messages.value[messageIndex].content = data.message || '抱歉,发生错误,请稍后重试。'
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log('解析SSE数据失败:', dataStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('处理分块数据失败:', e)
|
|
|
|
|
|
}
|
|
|
|
|
|
nextTick(() => scrollToBottom())
|
|
|
|
|
|
})
|
2025-12-10 17:00:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加消息
|
|
|
|
|
|
function addMessage(type : 'user' | 'bot', content : string, actions : string[] | null = null) {
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
|
|
|
|
|
|
|
|
|
|
|
|
messages.value.push({
|
|
|
|
|
|
type,
|
|
|
|
|
|
content,
|
|
|
|
|
|
time,
|
|
|
|
|
|
actions
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到底部
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟AI回复
|
|
|
|
|
|
function simulateAIResponse(userMessage : string) {
|
|
|
|
|
|
isTyping.value = true
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
isTyping.value = false
|
|
|
|
|
|
|
|
|
|
|
|
let response = ''
|
|
|
|
|
|
let actions : string[] | null = null
|
|
|
|
|
|
|
|
|
|
|
|
// 根据用户输入生成回复
|
|
|
|
|
|
if (userMessage.includes('工单') || userMessage.includes('报修') || userMessage.includes('问题')) {
|
|
|
|
|
|
response = '我理解您需要处理工单相关的事务。我可以帮您:'
|
|
|
|
|
|
actions = ['创建新工单', '查看工单状态', '联系客服']
|
|
|
|
|
|
} else if (userMessage.includes('你好') || userMessage.includes('您好')) {
|
|
|
|
|
|
response = '您好!很高兴为您服务。请问有什么可以帮助您的吗?'
|
|
|
|
|
|
actions = ['创建工单', '查看工单', '常见问题']
|
|
|
|
|
|
} else if (userMessage.includes('帮助') || userMessage.includes('功能')) {
|
|
|
|
|
|
response = '我可以为您提供以下服务:\n1. 创建工单 - 报告问题或提交服务请求\n2. 查看工单 - 跟踪您的工单处理进度\n3. 智能问答 - 解答常见问题'
|
|
|
|
|
|
actions = ['创建工单', '查看工单']
|
|
|
|
|
|
} else {
|
|
|
|
|
|
response = '感谢您的咨询。如果您遇到具体问题,建议创建工单,我们的专业团队会尽快为您处理。'
|
|
|
|
|
|
actions = ['创建工单', '联系人工客服']
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addMessage('bot', response, actions)
|
|
|
|
|
|
}, 1000 + Math.random() * 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 快捷操作
|
|
|
|
|
|
function quickAction(action : string) {
|
|
|
|
|
|
if (action === '创建工单') {
|
|
|
|
|
|
showCreator()
|
|
|
|
|
|
} else if (action === '查看工单') {
|
|
|
|
|
|
goToWorkList()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
addMessage('user', action)
|
|
|
|
|
|
simulateAIResponse(action)
|
2025-12-08 19:01:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
// 处理建议操作
|
|
|
|
|
|
function handleSuggestedAction(action : string) {
|
|
|
|
|
|
if (action === '创建工单' || action === '创建新工单') {
|
|
|
|
|
|
showCreator()
|
|
|
|
|
|
} else if (action === '查看工单' || action === '查看工单状态') {
|
|
|
|
|
|
goToWorkList()
|
|
|
|
|
|
} else if (action === '联系客服' || action === '联系人工客服') {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '联系客服',
|
|
|
|
|
|
content: '客服电话:400-123-4567\n工作时间:9:00-18:00',
|
|
|
|
|
|
showCancel: false
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
addMessage('user', action)
|
|
|
|
|
|
simulateAIResponse(action)
|
|
|
|
|
|
}
|
2025-12-08 19:01:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:00:54 +08:00
|
|
|
|
// 显示工单创建器
|
|
|
|
|
|
function showCreator() {
|
|
|
|
|
|
showWorkcaseCreator.value = true
|
2025-12-08 19:01:09 +08:00
|
|
|
|
}
|
2025-12-10 17:00:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 隐藏工单创建器
|
|
|
|
|
|
function hideCreator() {
|
|
|
|
|
|
showWorkcaseCreator.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 工单创建成功
|
2025-12-23 13:27:36 +08:00
|
|
|
|
function onWorkcaseCreated(workcaseData : TbWorkcaseDTO) {
|
2025-12-10 17:00:54 +08:00
|
|
|
|
hideCreator()
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '工单创建成功',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 添加成功消息
|
2025-12-23 13:27:36 +08:00
|
|
|
|
addMessage('bot', `工单创建成功!\n类型:${workcaseData.type || ''}\n设备:${workcaseData.device || ''}\n我们会尽快处理您的问题。`, ['查看工单', '创建新工单'])
|
2025-12-10 17:00:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到工单列表
|
|
|
|
|
|
function goToWorkList() {
|
|
|
|
|
|
uni.navigateTo({
|
2025-12-22 19:16:53 +08:00
|
|
|
|
url: '/pages/workcase/workcaseList/workcaseList'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到聊天室列表
|
|
|
|
|
|
function goToChatRoomList() {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: '/pages/chatRoom/chatRoomList/chatRoomList'
|
2025-12-10 17:00:54 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到底部
|
|
|
|
|
|
function scrollToBottom() {
|
|
|
|
|
|
scrollTop.value = 999999
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 联系人工客服
|
|
|
|
|
|
function contactHuman() {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '联系人工客服',
|
|
|
|
|
|
content: '客服电话:400-123-4567\n工作时间:9:00-18:00\n\n是否拨打电话?',
|
|
|
|
|
|
confirmText: '拨打',
|
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
uni.makePhoneCall({
|
|
|
|
|
|
phoneNumber: '400-123-4567'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理快速问题
|
2025-12-23 13:27:36 +08:00
|
|
|
|
async function handleQuickQuestion(question : string) {
|
2025-12-22 19:16:53 +08:00
|
|
|
|
addMessage('user', question)
|
2025-12-23 13:27:36 +08:00
|
|
|
|
await callAIChat(question)
|
2025-12-10 17:00:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示上传选项
|
|
|
|
|
|
function showUploadOptions() {
|
|
|
|
|
|
uni.showActionSheet({
|
|
|
|
|
|
itemList: ['拍照', '从相册选择', '选择文件'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
switch (res.tapIndex) {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
// 拍照
|
|
|
|
|
|
chooseImageFromCamera()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
// 从相册选择
|
|
|
|
|
|
chooseImageFromAlbum()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
// 选择文件
|
|
|
|
|
|
chooseFile()
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拍照
|
|
|
|
|
|
function chooseImageFromCamera() {
|
|
|
|
|
|
uni.chooseImage({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
sourceType: ['camera'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
// 处理图片上传逻辑
|
|
|
|
|
|
console.log('选择的图片:', res.tempFilePaths)
|
|
|
|
|
|
addMessage('user', '[图片]')
|
|
|
|
|
|
simulateAIResponse('收到您发送的图片')
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从相册选择
|
|
|
|
|
|
function chooseImageFromAlbum() {
|
|
|
|
|
|
uni.chooseImage({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
sourceType: ['album'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
// 处理图片上传逻辑
|
|
|
|
|
|
console.log('选择的图片:', res.tempFilePaths)
|
|
|
|
|
|
addMessage('user', '[图片]')
|
|
|
|
|
|
simulateAIResponse('收到您发送的图片')
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选择文件
|
|
|
|
|
|
function chooseFile() {
|
|
|
|
|
|
// 这里可以扩展文件选择功能
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '文件选择功能开发中',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
@import './index.scss';
|
|
|
|
|
|
</style>
|