|
|
<template> <view class="main"> <view class="top" :style="{ height: iSMT + 'px' }"></view>
<!-- 头部导航 --> <view class="header"> <view class="back-icon"> <image @click="onBack" src="/static/customer-service-platform/cs-platform-back.png" class="header-icon-image"></image> </view> <view class="title">{{ headerTitle }}</view> <view class="notification-icon"> <image src="/static/customer-service-platform/message.png" class="header-icon-image"></image> </view> </view>
<scroll-view scroll-y class="content-container" :scroll-into-view="scrollIntoView">
<view class="content-header"> <view class="content-header-area"> <view class="logo"> <image mode="aspectFit" src="/static/customer-service-platform/ellipse-dc-img.png"></image> </view> <view class="greeting"> <text class="greet-title">嗨,我能为你做点什么?</text> <text class="greet-sub">DeepChart随时为您提供服务</text> </view> </view> </view>
<!-- 卡片部分 --> <view class="card"> <!-- 问题头部--> <view class="question-header"> <view class="question-row"> <image class="question-avatar" src="/static/customer-service-platform/robot-head.png" mode="aspectFill"></image> <view class="question-title">{{ questionTitle }}</view> </view> </view>
<!-- 卡片内容区--> <view class="card-body"> <image class="card-logo" src="/static/customer-service-platform/ellipse-dc-img.png" mode="aspectFit"></image>
<view class="card-text">
<rich-text :nodes="renderMarkdown(answerContent)"></rich-text> <view id="answer-end" style="width:1px;height:1px;"></view> <!-- <text class="card-paragraph"> {{answerContent}} </text> --> </view> </view>
</view>
<view class="login-row" v-if="showLoginRegister"> <button class="login-btn" @click="toLogin">登录</button> <button class="register-btn" @click="toRegistration">注册</button> </view> </scroll-view> </view></template>
<script> import { getAnswerIdApi, getAnswerStatusApi, getAnswerContentApi } from "../../api/customerServicePlatform/customerServicePlatform"; import marked from "marked"; // 引入 marked 库
export default { data() { return { headerTitle: '智能客服中台', iSMT: 0, questionTitle: '', answerContent: '正在思考...', showLoginRegister: false, // 轮询句柄
pollInterval: null, pollTimeout: null, scrollIntoView: '' }; }, mounted() { this.iSMT = uni.getSystemInfoSync().statusBarHeight || 0; this.getAnswerContent() }, onUnload() { clearInterval(this.pollInterval); clearTimeout(this.pollTimeout); clearInterval(this.interval); }, onLoad(options) { if (options.question) { this.questionTitle = decodeURIComponent(options.question); if (this.questionTitle.includes("如何注册")) { this.showLoginRegister = true } else { this.showLoginRegister = false } } }, methods: { scrollToBottom() { // 使用 $nextTick 保证 rich-text 的渲染/DOM 更新完成
this.$nextTick(() => { // 先设置目标 id,scroll-view 会滚动使其可见
this.scrollIntoView = 'answer-end'; // 清空值以便下次再次触发
setTimeout(() => { this.scrollIntoView = ''; }, 100); }); }, renderMarkdown(content) { const renderer = new marked.Renderer(); // renderer.heading = function (text, level) {
// return `<p>${text}</p>`;
// };
// 设置 marked 选项
marked.setOptions({ renderer: renderer, highlight: null, // 如果需要代码高亮,可以设置适当的函数
langPrefix: "language-", pedantic: false, gfm: true, breaks: false, sanitize: false, smartLists: true, smartypants: false, xhtml: false, }); if (!content) return ""; let renderedContent = marked.parse(content); renderedContent = renderedContent.replace(/\*/g, ''); return renderedContent; }, async getAnswerContent() { let conversationId = ''; let chatId = ''; //尝试获取本地缓存
try { const cache = uni.getStorageSync('conversationId'); const chatIdCache = uni.getStorageSync('chatId'); if (cache) conversationId = cache; if (chatIdCache) chatId = chatIdCache } catch (e) { conversationId = ''; } try { const res = await getAnswerIdApi({ question: this.questionTitle, conversationId: conversationId, chatId: chatId }) console.log(res)
if (res.code == 200 && res.data.chatId) { const resConversationId = res.data.conversationId; const resChatId = res.data.chatId; uni.setStorageSync('conversationId', resConversationId); uni.setStorageSync('chatId', resChatId); let pollCount = 0; const maxPoll = 10; this.pollInterval && clearInterval(this.pollInterval); this.pollTimeout && clearTimeout(this.pollTimeout); // === 轮询函数 ===
const checkStatus = async () => { pollCount++; const pollRes = await getAnswerStatusApi({ conversationId: resConversationId, chatId: resChatId });
console.log("pollRes =>", pollRes);
const status = pollRes?.data; //轮询结束,获取回答
if (status === "completed") { clearInterval(this.pollInterval); clearTimeout(this.pollTimeout); //获取最终答案
const answerRes = await getAnswerContentApi({ conversationId: resConversationId, chatId: resChatId });
console.log("answerRes =>", answerRes); const answer = answerRes?.data; //打印机效果
if (answer) { // 逐字输出
let currentIndex = 0; const answerLength = answer.length;
this.interval && clearInterval(this.interval); this.interval = setInterval(() => { this.answerContent = answer.slice(0, currentIndex); currentIndex++; this.scrollToBottom(); if (currentIndex > answerLength) { clearInterval(this.interval); this.scrollToBottom(); } }, Math.floor(Math.random() * (150 - 30 + 1)) + 30);
} else { this.answerContent = "获取回答失败,请重试"; } return; } //超过10秒就回答失败
if (pollCount >= maxPoll) { clearInterval(this.pollInterval); clearTimeout(this.pollTimeout); this.answerContent = "获取回答失败,请重试"; return; }
};
// 每 2 秒轮询
this.pollInterval = setInterval(checkStatus, 2000); // 超时兜底 10s
this.pollTimeout = setTimeout(() => { clearInterval(this.pollInterval); this.answerContent = "获取回答失败,请重试"; }, 20000); } else { this.answerContent = '获取回答失败,请重试'; } } catch { this.pollInterval && clearInterval(this.pollInterval); this.pollTimeout && clearTimeout(this.pollTimeout); this.interval && clearInterval(this.interval); this.answerContent = '获取回答失败,请重试'; } }, sleepTime() { const ms = Math.floor(Math.random() * (300 - 30 + 1)) + 30; return ms; },
toRegistration() { uni.redirectTo({ url: "/pages/start/Registration/Registration", }); }, toLogin() { uni.redirectTo({ url: "/pages/start/login/login", }); }, onBack() { if (typeof uni !== 'undefined') uni.navigateBack(); } } };</script>
<style scoped> .main { display: flex; flex-direction: column; height: 100vh; background-color: #ffffff; }
.header { display: flex; justify-content: space-between; align-items: center; padding: 20rpx 30rpx; background-color: #ffffff; }
.title { color: #000000; text-align: center; font-size: 32rpx; font-weight: 400; }
.back-icon, .notification-icon { width: 40rpx; display: flex; align-items: center; justify-content: center; }
.header-icon-image { width: 40rpx; height: 40rpx; object-fit: contain; }
.content-container { padding: 20rpx; width: 100%; box-sizing: border-box; overflow-x: hidden; }
.content-header { display: flex; align-items: center; justify-content: center; gap: 24rpx; padding: 0 60rpx; width: 100%; box-sizing: border-box; height: 188rpx; }
.content-header-area { display: flex; gap: 20rpx; }
.logo { width: 120rpx; height: 120rpx; display: flex; align-items: center; justify-content: center; flex: 0 0 112rpx; }
.greeting { display: flex; flex-direction: column; justify-content: center; flex: 1 1 auto; }
.greet-title { color: #000; font-size: 40rpx; font-style: normal; font-weight: 500; line-height: normal; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.greet-sub { color: #838383; font-size: 28rpx; font-style: normal; font-weight: 400; line-height: normal; margin-top: 12rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card { width: 90%; margin: 0 auto 20rpx; padding: 28rpx; box-sizing: border-box; border-radius: 16rpx; border: 4rpx solid #FF7C99; background: #fff; }
/* 问题头部 */ .question-header { width: 100%; margin-bottom: 48rpx; }
.question-row { display: flex; align-items: center; }
.question-avatar { width: 52rpx; height: 52rpx; border-radius: 999rpx; margin-right: 20rpx; flex-shrink: 0; }
.question-title { color: #000000; font-size: 34rpx; }
/* 卡片内部布局 */ .card-body { display: flex; gap: 20rpx; align-items: flex-start; }
.card-logo { width: 52rpx; height: 52rpx; flex: 0 0 52rpx; border-radius: 8rpx; }
.card-text { flex: 1 1 auto; }
.card-paragraph { display: block; color: #000000; font-size: 28rpx; margin-bottom: 14rpx; font-style: normal; font-weight: 500; }
.login-row { display: flex; justify-content: center; align-items: center; width: 100%; margin-top: 100rpx; }
.login-btn { width: 260rpx; height: 100rpx; border-radius: 50rpx; background: #F3F3F3; color: #000000; display: flex; justify-content: center; align-items: center; font-size: 28rpx; margin-right: 20rpx; }
.register-btn { width: 260rpx; height: 100rpx; border-radius: 60rpx; background: #000; color: #ffffff; display: flex; justify-content: center; align-items: center; font-size: 28rpx; }</style>
|