Files
smartDriveEEUniApp/pages-subpackage/ai_qa/general_qa/general_qa.vue
zhonghua.li 41ca1e5d7d 通用问答
2026-03-31 06:55:31 +08:00

445 lines
9.9 KiB
Vue
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.

<template>
<view class="qa-page">
<!-- 顶部导航参考示例左侧菜单标题新对话右侧加号 -->
<view class="qa-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="qa-header-bar">
<view class="qa-header-left" @click="goBack">
<view class="qa-header-menu-line"></view>
<view class="qa-header-menu-line qa-header-menu-line-short"></view>
</view>
<text class="qa-header-title">新对话</text>
<view class="qa-header-right">
<text class="qa-header-plus"></text>
</view>
</view>
</view>
<!-- 中间主体区域图标 + 提示文案 -->
<view class="qa-main" v-if="chatMessages.length === 0">
<view class="qa-main-icon-wrapper">
<text class="qa-main-icon">Q</text>
</view>
<text class="qa-main-text" selectable="true">今天有什么可以帮到你</text>
</view>
<!-- 对话内容 -->
<scroll-view v-else class="qa-chat" scroll-y="true">
<view class="qa-chat-inner">
<view
v-for="(m, i) in chatMessages"
:key="i"
class="qa-msg"
:class="m.role === 'user' ? 'qa-msg--user' : 'qa-msg--ai'"
>
<view class="qa-msg-actions" :class="m.role === 'ai' ? 'qa-msg-actions--left' : 'qa-msg-actions--right'">
<view class="qa-copy-btn" @click.stop="copyText(m.text)">
<text class="qa-copy-btn-icon"></text>
</view>
</view>
<text class="qa-msg-text" selectable="true">{{ m.text }}</text>
</view>
</view>
</scroll-view>
<!-- 底部输入区域 -->
<view class="qa-input-wrapper" :style="{ paddingBottom: safeBottom + 'px' }">
<view class="qa-input-row">
<view class="qa-input-box">
<textarea
class="qa-textarea"
v-model="message"
placeholder="发送消息或按住说话"
placeholder-class="qa-input-placeholder"
:auto-height="true"
:maxlength="-1"
></textarea>
</view>
<view class="qa-input-send" @click="onSend">
<uni-icons type="top" size="18" color="#fff"></uni-icons>
</view>
</view>
<view class="qa-input-actions">
<view class="qa-chip" @click="goBack">
<uni-icons type="left" size="18" color="#666"></uni-icons>
</view>
</view>
</view>
</view>
</template>
<script>
import { post } from '@/common/request.js'
import { getApiUrl } from '@/common/config.js'
function getSystemInfo() {
try {
const info = uni.getSystemInfoSync();
return {
statusBarHeight: info.statusBarHeight || 20,
safeAreaInsets: info.safeAreaInsets || { bottom: 0 }
};
} catch (e) {
return {
statusBarHeight: 20,
safeAreaInsets: { bottom: 0 }
};
}
}
export default {
data() {
const systemInfo = getSystemInfo();
return {
statusBarHeight: systemInfo.statusBarHeight,
safeBottom: systemInfo.safeAreaInsets.bottom || 0,
message: '',
sending: false,
chatMessages: [],
sessionParentId: ''
};
},
methods: {
goBack() {
uni.navigateBack();
},
copyText(text) {
const content = (text || '').toString();
if (!content.trim()) return;
uni.setClipboardData({
data: content,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' });
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' });
}
});
},
normalizeText(value) {
if (value === undefined || value === null) return '';
if (typeof value === 'string') {
// 兼容后端把换行转义成 \\n 的情况
return value.replace(/\\n/g, '\n');
}
try {
return JSON.stringify(value, null, 2);
} catch (e) {
return String(value);
}
},
onSend() {
const text = (this.message || '').trim();
if (!text) {
uni.showToast({ title: '请输入消息', icon: 'none' });
return;
}
if (this.sending) return;
this.sending = true;
// 角色消息先入栈,减少等待感
const userMsg = { role: 'user', text };
this.chatMessages.push(userMsg);
this.message = '';
uni.showLoading({ title: '生成中...', mask: true });
// 该页面对应“通用问答”
const questionType = 'general_qa';
const url = getApiUrl('/api/aiQaItem/askAI');
const payload = {
questionType,
questionContent: text
};
// parentId第一次请求不传后端返回后再带上
if (this.sessionParentId) {
payload.parentId = this.sessionParentId;
}
post(url, payload)
.then((res) => {
uni.hideLoading();
const body = res && res.data ? res.data : null;
const success = body && body.success === true;
if (!success) {
const msg = body?.message || '请求失败';
this.chatMessages.push({ role: 'ai', text: msg });
return;
}
const aiData = body.data || {};
// 若后端返回 parentId则保存并用于后续请求
const returnedParentId = aiData.parentId;
if (returnedParentId !== undefined && returnedParentId !== null && String(returnedParentId).trim()) {
this.sessionParentId = String(returnedParentId).trim();
}
// 兼容不同字段名:优先取常见答案字段,否则兜底 JSON
const rawAnswer =
aiData.answer ??
aiData.content ??
aiData.response ??
aiData.result ??
aiData.message ??
aiData;
this.chatMessages.push({ role: 'ai', text: this.normalizeText(rawAnswer) });
})
.catch((err) => {
uni.hideLoading();
console.error('[general_qa] askAI error:', err);
this.chatMessages.push({ role: 'ai', text: err?.errMsg || '调用失败,请重试' });
})
.finally(() => {
this.sending = false;
});
}
}
};
</script>
<style scoped>
.qa-page {
flex: 1;
min-height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
}
.qa-header {
background-color: #FFFFFF;
border-bottom: 1rpx solid #EDEDED;
}
.qa-header-bar {
height: 88rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.qa-header-left {
width: 60rpx;
height: 60rpx;
justify-content: center;
align-items: flex-start;
display: flex;
flex-direction: column;
}
.qa-header-menu-line {
width: 26rpx;
height: 4rpx;
border-radius: 4rpx;
background-color: #333333;
margin-bottom: 6rpx;
}
.qa-header-menu-line-short {
width: 16rpx;
}
.qa-header-title {
flex: 1;
text-align: center;
font-size: 30rpx;
font-weight: 500;
color: #333333;
}
.qa-header-right {
width: 60rpx;
height: 60rpx;
align-items: center;
justify-content: center;
display: flex;
}
.qa-header-plus {
font-size: 38rpx;
color: #333333;
}
.qa-main {
flex: 1;
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}
.qa-main-icon-wrapper {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background-color: #FFFFFF;
align-items: center;
justify-content: center;
display: flex;
margin-bottom: 32rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.qa-main-icon {
font-size: 60rpx;
color: #2A68FF;
}
.qa-main-text {
font-size: 30rpx;
color: #333333;
}
.qa-chat {
flex: 1;
width: 100%;
padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}
.qa-chat-inner {
padding: 24rpx 24rpx 12rpx;
}
.qa-msg {
margin-bottom: 20rpx;
max-width: 88%;
position: relative;
}
.qa-msg-actions {
position: absolute;
top: 8rpx;
z-index: 2;
}
.qa-msg-actions--left {
left: 8rpx;
}
.qa-msg-actions--right {
right: 8rpx;
}
.qa-copy-btn {
width: 44rpx;
height: 44rpx;
border-radius: 22rpx;
background-color: #F4F6FA;
border: 1rpx solid #E5EAF3;
display: flex;
align-items: center;
justify-content: center;
}
.qa-copy-btn-icon {
font-size: 22rpx;
color: #8a8a8a;
line-height: 1;
}
.qa-msg--user {
margin-left: auto;
background-color: #EAF2FF;
border: 1rpx solid #D7E6FF;
}
.qa-msg--ai {
margin-right: auto;
background-color: #FFFFFF;
border: 1rpx solid #F0F0F0;
}
.qa-msg--user,
.qa-msg--ai {
padding: 18rpx 20rpx;
border-radius: 16rpx;
}
.qa-msg-text {
font-size: 26rpx;
color: #333333;
line-height: 1.6;
word-break: break-word;
user-select: text;
}
.qa-input-wrapper {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
padding: 12rpx 20rpx 12rpx;
background-color: #FFFFFF;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
}
.qa-input-row {
flex-direction: row;
display: flex;
align-items: center;
}
.qa-input-box {
flex: 1;
min-height: 80rpx;
border-radius: 40rpx;
background-color: #F5F5F5;
padding: 0 32rpx;
display: flex;
align-items: center;
}
.qa-textarea {
width: 100%;
min-height: 88rpx; /* 默认两行2*44rpx */
max-height: 176rpx; /* 最大四行4*44rpx */
line-height: 44rpx;
font-size: 26rpx;
color: #333333;
padding: 18rpx 0;
box-sizing: border-box;
overflow-y: auto;
}
.qa-input-placeholder {
font-size: 26rpx;
color: #999999;
}
.qa-input-send {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-left: 16rpx;
background-color: #2A68FF;
align-items: center;
justify-content: center;
display: flex;
}
.qa-input-actions {
margin-top: 12rpx;
flex-direction: row;
display: flex;
}
.qa-chip {
padding: 8rpx 20rpx;
border-radius: 999rpx;
background-color: #F5F5F5;
margin-right: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>