flat: 优化语音
This commit is contained in:
@@ -63,6 +63,7 @@
|
||||
<ai-paging ref="paging"></ai-paging>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义tabbar -->
|
||||
<!-- <tabbar-custom :currentpage="2"></tabbar-custom> -->
|
||||
</view>
|
||||
@@ -143,12 +144,13 @@ function updateSetting() {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
header-height = 88rpx
|
||||
|
||||
/* 页面容器 */
|
||||
.container {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
z-index: 1000;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: calc(100% - var(--window-bottom));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -169,7 +171,7 @@ header-height = 88rpx
|
||||
top: 0;
|
||||
left: 0; /* 如果要右侧弹出改为 right: 0; */
|
||||
width: 523rpx;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
background: #e7e7e6;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
@@ -271,7 +273,7 @@ header-height = 88rpx
|
||||
/* 主要内容区域 */
|
||||
.main-content
|
||||
width: 100%;
|
||||
height: calc(100% - var(--window-bottom));
|
||||
height: 100%;
|
||||
transition: margin-left 0.3s ease-in-out;
|
||||
position: relative
|
||||
background: #FFFFFF
|
||||
@@ -286,9 +288,8 @@ header-height = 88rpx
|
||||
right: var(--window-right);
|
||||
height:header-height;
|
||||
padding-top: calc(14rpx);
|
||||
border: 2rpx solid #F4F4F4;
|
||||
background: #FFFFFF
|
||||
z-index: 998;
|
||||
z-index: 1;
|
||||
transition-property: all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -308,6 +309,7 @@ header-height = 88rpx
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
border-top: 2rpx solid #F4F4F4;
|
||||
|
||||
/* 页面被挤压时向右移动 */
|
||||
.main-content.shift {
|
||||
|
@@ -102,43 +102,36 @@ const initWaveBars = () => {
|
||||
// 更新波形显示
|
||||
const updateWaveform = () => {
|
||||
if (!props.isActive) return;
|
||||
// 如果没有传入音频数据,则使用模拟数据
|
||||
|
||||
const AMPLIFY = 1.6; // 振幅放大
|
||||
const center = centerIndex.value;
|
||||
|
||||
// 如果没有传入音频数据,则使用模拟数据(加强振幅)
|
||||
const audioData =
|
||||
props.audioData.length > 0
|
||||
? props.audioData
|
||||
: Array(centerIndex.value + 1)
|
||||
? props.audioData.map((v) => Math.min(v * AMPLIFY, 1))
|
||||
: Array(center + 1)
|
||||
.fill(0)
|
||||
.map(() => Math.random() * 0.5 + 0.2);
|
||||
// 从中间向两侧处理
|
||||
for (let i = 0; i <= centerIndex.value; i++) {
|
||||
// 左侧条索引
|
||||
const leftIndex = centerIndex.value - i;
|
||||
// 右侧条索引
|
||||
const rightIndex = centerIndex.value + i;
|
||||
.map(() => Math.random() * 0.7 + 0.3); // 模拟值更明显
|
||||
|
||||
// 获取音频数据值 (归一化到0-1)
|
||||
for (let i = 0; i <= center; i++) {
|
||||
const leftIndex = center - i;
|
||||
const rightIndex = center + i;
|
||||
const value = audioData[i] || 0;
|
||||
|
||||
// 更新左侧条
|
||||
if (leftIndex >= 0) {
|
||||
updateWaveBar(leftIndex, value);
|
||||
}
|
||||
|
||||
// 更新右侧条(避免重复更新中心条)
|
||||
if (leftIndex >= 0) updateWaveBar(leftIndex, value);
|
||||
if (rightIndex < waveBars.value.length && rightIndex !== leftIndex) {
|
||||
updateWaveBar(rightIndex, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 继续动画
|
||||
animationId = requestAnimationFrame(updateWaveform);
|
||||
};
|
||||
|
||||
// 更新单个波形条
|
||||
const updateWaveBar = (index, value) => {
|
||||
// 动态高度 (4rpx到200rpx之间)
|
||||
const height = 2 + value * 98;
|
||||
|
||||
// 动态高度 (4rpx到42rpx之间)
|
||||
const height = 2 + value * 38;
|
||||
// // 动态颜色
|
||||
// let color;
|
||||
// if (props.isCanceling) {
|
||||
|
@@ -1,116 +1,114 @@
|
||||
<template>
|
||||
<view class="chat-container">
|
||||
<FadeView :show="!messages.length" :duration="600">
|
||||
<view class="chat-background">
|
||||
<image class="backlogo" src="/static/icon/backAI.png"></image>
|
||||
<view class="back-rowTitle">嗨!欢迎使用青岛AI智能求职</view>
|
||||
<view class="back-rowText">
|
||||
我可以根据您的简历和求职需求,帮你精准匹配青岛市互联网招聘信息,对比招聘信息的优缺点,提供面试指导等,请把你的任务交给我吧~
|
||||
</view>
|
||||
<view class="back-rowh3">猜你所想</view>
|
||||
<view class="back-rowmsg" v-for="item in queries" @click="sendMessageGuess(item)">
|
||||
{{ item }}
|
||||
<view class="chat-background" v-fade:600="!messages.length">
|
||||
<image class="backlogo" src="/static/icon/backAI.png"></image>
|
||||
<view class="back-rowTitle">嗨!欢迎使用青岛AI智能求职</view>
|
||||
<view class="back-rowText">
|
||||
我可以根据您的简历和求职需求,帮你精准匹配青岛市互联网招聘信息,对比招聘信息的优缺点,提供面试指导等,请把你的任务交给我吧~
|
||||
</view>
|
||||
<view class="back-rowh3">猜你所想</view>
|
||||
<view class="back-rowmsg" v-for="item in queries" @click="sendMessageGuess(item)">
|
||||
{{ item }}
|
||||
</view>
|
||||
<view class="chat-item self" v-if="isRecording">
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view class="chat-list scrollView" :scroll-top="scrollTop" :scroll-y="true" scroll-with-animation>
|
||||
<view class="chat-list list-content" v-fade:600="messages.length >= 1">
|
||||
<view
|
||||
v-for="(msg, index) in messages"
|
||||
:key="index"
|
||||
:id="'msg-' + index"
|
||||
class="chat-item"
|
||||
:class="{ self: msg.self }"
|
||||
>
|
||||
<view class="message" v-if="msg.self">
|
||||
<view class="msg-filecontent" v-if="msg.files.length">
|
||||
<view
|
||||
class="msg-files btn-light"
|
||||
v-for="(file, vInex) in msg.files"
|
||||
:key="vInex"
|
||||
@click="jumpUrl(file)"
|
||||
>
|
||||
<image class="msg-file-icon" src="/static/icon/Vector2.png"></image>
|
||||
<text class="msg-file-text">{{ file.name || '附件' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
{{ msg.displayText }}
|
||||
</view>
|
||||
<view class="message" :class="{ messageNull: !msg.displayText }" v-else>
|
||||
<!-- {{ msg.displayText }} -->
|
||||
<view class="message-markdown">
|
||||
<md-render
|
||||
:content="msg.displayText"
|
||||
:typing="isTyping && messages.length - 1 === index"
|
||||
></md-render>
|
||||
<view class="message-controll" v-show="showControll(index)">
|
||||
<view class="controll-left">
|
||||
<image
|
||||
class="controll-icon btn-light"
|
||||
src="/static/icon/copy.png"
|
||||
@click="copyMarkdown(msg.displayText)"
|
||||
></image>
|
||||
<image
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/feedback.png"
|
||||
v-if="!msg.userBadFeedback"
|
||||
@click="userGoodFeedback(msg)"
|
||||
></image>
|
||||
<image
|
||||
v-if="isSpeaking && !isPaused && speechIndex === index"
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/stop.png"
|
||||
@click="stopMarkdown(msg.displayText, index)"
|
||||
></image>
|
||||
<image
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/broadcast.png"
|
||||
@click="readMarkdown(msg.displayText, index)"
|
||||
v-else
|
||||
></image>
|
||||
</view>
|
||||
<view class="controll-right">
|
||||
<image
|
||||
class="controll-icon mar_ri10 btn-light"
|
||||
src="/static/icon/refresh.png"
|
||||
@click="refreshMarkdown(index)"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- guess -->
|
||||
<view
|
||||
class="guess"
|
||||
v-if="showGuess && !msg.self && messages.length - 1 === index && msg.displayText"
|
||||
>
|
||||
<view class="gulist">
|
||||
<view
|
||||
class="guess-list"
|
||||
@click="sendMessageGuess(item)"
|
||||
v-for="(item, index) in guessList"
|
||||
:key="index"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-item self" v-if="isRecording">
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</FadeView>
|
||||
<scroll-view class="chat-list scrollView" :scroll-top="scrollTop" :scroll-y="true" scroll-with-animation>
|
||||
<FadeView :show="messages.length >= 1" :duration="600">
|
||||
<view class="chat-list list-content">
|
||||
<view
|
||||
v-for="(msg, index) in messages"
|
||||
:key="index"
|
||||
:id="'msg-' + index"
|
||||
class="chat-item"
|
||||
:class="{ self: msg.self }"
|
||||
>
|
||||
<view class="message" v-if="msg.self">
|
||||
<view class="msg-filecontent" v-if="msg.files.length">
|
||||
<view
|
||||
class="msg-files btn-light"
|
||||
v-for="(file, vInex) in msg.files"
|
||||
:key="vInex"
|
||||
@click="jumpUrl(file)"
|
||||
>
|
||||
<image class="msg-file-icon" src="/static/icon/Vector2.png"></image>
|
||||
<text class="msg-file-text">{{ file.name || '附件' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
{{ msg.displayText }}
|
||||
</view>
|
||||
<view class="message" :class="{ messageNull: !msg.displayText }" v-else>
|
||||
<!-- {{ msg.displayText }} -->
|
||||
<view class="message-markdown">
|
||||
<md-render
|
||||
:content="msg.displayText"
|
||||
:typing="isTyping && messages.length - 1 === index"
|
||||
></md-render>
|
||||
<view class="message-controll" v-show="showControll(index)">
|
||||
<view class="controll-left">
|
||||
<image
|
||||
class="controll-icon btn-light"
|
||||
src="/static/icon/copy.png"
|
||||
@click="copyMarkdown(msg.displayText)"
|
||||
></image>
|
||||
<image
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/feedback.png"
|
||||
@click="userGoodFeedback(msg)"
|
||||
></image>
|
||||
<image
|
||||
v-if="isSpeaking && !isPaused && speechIndex === index"
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/stop.png"
|
||||
@click="stopMarkdown(msg.displayText, index)"
|
||||
></image>
|
||||
<image
|
||||
class="controll-icon mar_le10 btn-light"
|
||||
src="/static/icon/broadcast.png"
|
||||
@click="readMarkdown(msg.displayText, index)"
|
||||
v-else
|
||||
></image>
|
||||
</view>
|
||||
<view class="controll-right">
|
||||
<image
|
||||
class="controll-icon mar_ri10 btn-light"
|
||||
src="/static/icon/refresh.png"
|
||||
@click="refreshMarkdown(index)"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- guess -->
|
||||
<view
|
||||
class="guess"
|
||||
v-if="showGuess && !msg.self && messages.length - 1 === index && msg.displayText"
|
||||
>
|
||||
<view class="gulist">
|
||||
<view
|
||||
class="guess-list"
|
||||
@click="sendMessageGuess(item)"
|
||||
v-for="(item, index) in guessList"
|
||||
:key="index"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-item self" v-if="isRecording">
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
<view v-if="isTyping" class="self">
|
||||
<text class="message msg-loading">
|
||||
<span class="ai-loading"></span>
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="isTyping" class="self">
|
||||
<text class="message msg-loading">
|
||||
<span class="ai-loading"></span>
|
||||
</text>
|
||||
</view>
|
||||
</FadeView>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="vio_container" @click="handleTouchEnd" :class="status" v-if="status !== 'idle'">
|
||||
<!-- 使用 v-show 保证不销毁事件 -->
|
||||
<view class="vio_container" :class="status" v-show="status !== 'idle'">
|
||||
<view class="record-tip">{{ statusText }}</view>
|
||||
<WaveDisplay
|
||||
:background="audiowaveStyle"
|
||||
@@ -119,7 +117,7 @@
|
||||
:showInfo="isRecording"
|
||||
/>
|
||||
</view>
|
||||
<view class="input-area" v-else>
|
||||
<view class="input-area" v-show="status === 'idle'">
|
||||
<view class="areatext">
|
||||
<input
|
||||
v-model="textInput"
|
||||
@@ -133,7 +131,7 @@
|
||||
/>
|
||||
<view
|
||||
class="input_vio"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchstart.prevent="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
@touchcancel="handleTouchCancel"
|
||||
@@ -222,28 +220,42 @@
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
<PopupFeeBack ref="feeback" @onSend="confirmFeeBack"></PopupFeeBack>
|
||||
<MsgTips ref="feeBackTips" content="已收到反馈,感谢您的关注" title="反馈成功" :icon="successIcon"></MsgTips>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, nextTick, defineProps, defineEmits, onMounted, toRaw, reactive, computed } from 'vue';
|
||||
import {
|
||||
ref,
|
||||
inject,
|
||||
nextTick,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
toRaw,
|
||||
reactive,
|
||||
computed,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import config from '@/config.js';
|
||||
import useChatGroupDBStore from '@/stores/userChatGroupStore';
|
||||
import MdRender from '@/components/md-render/md-render.vue';
|
||||
import CollapseTransition from '@/components/CollapseTransition/CollapseTransition.vue';
|
||||
import FadeView from '@/components/FadeView/FadeView.vue';
|
||||
import PopupFeeBack from './popupbadFeeback.vue';
|
||||
import AudioWave from './AudioWave.vue';
|
||||
import WaveDisplay from './WaveDisplay.vue';
|
||||
import FileIcon from './fileIcon.vue';
|
||||
import FileText from './fileText.vue';
|
||||
import { useSpeechReader } from '@/hook/useSpeechReader';
|
||||
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
||||
import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
|
||||
// 全局
|
||||
const { $api, navTo, throttle } = inject('globalFunction');
|
||||
const emit = defineEmits(['onConfirm']);
|
||||
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
|
||||
|
||||
import successIcon from '@/static/icon/success.png';
|
||||
// hook
|
||||
const {
|
||||
isRecording,
|
||||
@@ -256,7 +268,7 @@ const {
|
||||
lastFinalText,
|
||||
} = useAudioRecorder(config.vioceBaseURl);
|
||||
|
||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useSpeechReader();
|
||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio, audioUrl } = useTTSPlayer(config.speechSynthesis);
|
||||
|
||||
// state
|
||||
const queries = ref([]);
|
||||
@@ -272,7 +284,11 @@ const startY = ref(0);
|
||||
const cancelThreshold = 100;
|
||||
const speechIndex = ref(0);
|
||||
const isAudioPermission = ref(false);
|
||||
|
||||
const feebackData = ref(null);
|
||||
// ref for DOM element
|
||||
const voiceBtn = ref(null);
|
||||
const feeback = ref(null);
|
||||
const feeBackTips = ref(null);
|
||||
const state = reactive({
|
||||
uploadFileTips: '请根据以上附件,帮我推荐岗位。',
|
||||
});
|
||||
@@ -510,7 +526,6 @@ const handleTouchStart = async (e) => {
|
||||
return tipsPermisson();
|
||||
}
|
||||
cancelAudio();
|
||||
console.log('handleTouchStart');
|
||||
startY.value = e.touches[0].clientY;
|
||||
status.value = 'recording';
|
||||
showfile.value = false;
|
||||
@@ -521,7 +536,6 @@ const handleTouchStart = async (e) => {
|
||||
};
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
console.log('handleTouchMove');
|
||||
const moveY = e.touches[0].clientY;
|
||||
if (startY.value - moveY > cancelThreshold) {
|
||||
status.value = 'cancel';
|
||||
@@ -531,28 +545,23 @@ const handleTouchMove = (e) => {
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
console.log('handleTouchEnd');
|
||||
if (status.value === 'cancel') {
|
||||
console.log('取消发送');
|
||||
cancelRecording();
|
||||
} else {
|
||||
console.log('stopRecording');
|
||||
stopRecording();
|
||||
$api.sleep(1000).then(() => {
|
||||
if (isAudioPermission.value) {
|
||||
if (recognizedText.value) {
|
||||
sendMessage(recognizedText.value);
|
||||
} else {
|
||||
$api.msg('说话时长太短');
|
||||
}
|
||||
if (isAudioPermission.value) {
|
||||
if (recognizedText.value) {
|
||||
sendMessage(recognizedText.value);
|
||||
} else {
|
||||
$api.msg('说话时长太短');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
status.value = 'idle';
|
||||
};
|
||||
|
||||
const handleTouchCancel = () => {
|
||||
console.log('handleTouchCancel');
|
||||
stopRecording();
|
||||
status.value = 'idle';
|
||||
};
|
||||
@@ -578,9 +587,18 @@ function copyMarkdown(value) {
|
||||
}
|
||||
|
||||
function userGoodFeedback(msg) {
|
||||
$api.msg('该功能正在开发中,敬请期待后续更新!');
|
||||
console.log(msg.dataId);
|
||||
// useChatGroupDBStore().badFeedback(msg.dataId, msg.parentGroupId);
|
||||
// $api.msg('该功能正在开发中,敬请期待后续更新!');
|
||||
feeback.value?.open();
|
||||
feebackData.value = msg;
|
||||
}
|
||||
|
||||
function confirmFeeBack(value) {
|
||||
useChatGroupDBStore()
|
||||
.badFeedback(feebackData.value, value)
|
||||
.then(() => {
|
||||
feeback.value?.close();
|
||||
feeBackTips.value?.open();
|
||||
});
|
||||
}
|
||||
|
||||
function readMarkdown(value, index) {
|
||||
@@ -788,14 +806,14 @@ image-margin-top = 40rpx
|
||||
-webkit-user-select: text;
|
||||
.message-markdown
|
||||
border-radius: 0 20rpx 20rpx 20rpx;
|
||||
padding: 20rpx 20rpx 0 20rpx;
|
||||
padding: 20rpx 20rpx 20rpx 20rpx;
|
||||
background: #F6F6F6;
|
||||
.message-controll
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
border-top: 2rpx solid #EAEAEA
|
||||
padding: 24rpx 0
|
||||
padding: 24rpx 0 4rpx 0
|
||||
margin-top: 10rpx
|
||||
.controll-left
|
||||
.controll-right
|
||||
@@ -870,12 +888,19 @@ image-margin-top = 40rpx
|
||||
-khtml-user-select:none;
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
touch-action: none; /* 禁用默认滚动 */
|
||||
.input_vio:active
|
||||
background: #e8e8e8
|
||||
.vio_container
|
||||
background: transparent
|
||||
padding: 28rpx
|
||||
text-align: center
|
||||
-webkit-touch-callout:none;
|
||||
-webkit-user-select:none;
|
||||
-khtml-user-select:none;
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
touch-action: none; /* 禁用默认滚动 */
|
||||
.record-tip
|
||||
font-weight: 400;
|
||||
color: #909090;
|
||||
|
@@ -1,8 +1,197 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" borderRadius="12px 12px 0 0" background-color="#F6F6F6">
|
||||
<view class="feeback">
|
||||
<view class="titile">反馈</view>
|
||||
<view class="pop-h3">针对问题</view>
|
||||
<view class="pop-content">
|
||||
<view
|
||||
class="item"
|
||||
:class="{ active: item.check }"
|
||||
@click="toggleCheck(item.id)"
|
||||
v-for="item in wt"
|
||||
:key="item.id"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="pop-h3">针对回答</view>
|
||||
<view class="pop-content">
|
||||
<view
|
||||
class="item"
|
||||
:class="{ active: item.check }"
|
||||
@click="toggleCheck(item.id)"
|
||||
v-for="item in hd"
|
||||
:key="item.id"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="pop-h3">我要补充</view>
|
||||
<view class="supplement">
|
||||
<textarea v-model="inputText" cols="30" rows="10"></textarea>
|
||||
</view>
|
||||
<view class="btn button-click" @click="send">提交</view>
|
||||
<view class="close-btn" @click="close"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, inject, defineEmits } from 'vue';
|
||||
const emit = defineEmits(['onSend']);
|
||||
const { $api } = inject('globalFunction');
|
||||
const popup = ref(null);
|
||||
const inputText = ref('');
|
||||
const wt = ref([
|
||||
{ label: '上下文错误', id: 1, check: false },
|
||||
{ label: '理解错误', id: 2, check: false },
|
||||
{ label: '未识别问题中的错误', id: 3, check: false },
|
||||
]);
|
||||
const hd = ref([
|
||||
{ label: '违法有害', id: 11, check: false },
|
||||
{ label: '内容不专业', id: 12, check: false },
|
||||
{ label: '推理错误', id: 13, check: false },
|
||||
{ label: '计算错误', id: 14, check: false },
|
||||
{ label: '内容不完整', id: 15, check: false },
|
||||
{ label: '事实错误', id: 16, check: false },
|
||||
]);
|
||||
|
||||
function open() {
|
||||
resetCheck();
|
||||
inputText.value = '';
|
||||
popup.value.open();
|
||||
}
|
||||
|
||||
function close() {
|
||||
popup.value.close();
|
||||
}
|
||||
|
||||
function send() {
|
||||
const text = getLabel();
|
||||
if (text) {
|
||||
emit('onSend', text);
|
||||
// close();
|
||||
} else {
|
||||
$api.msg('清输入反馈内容');
|
||||
}
|
||||
}
|
||||
|
||||
function getLabel() {
|
||||
const wtArr = wt.value.filter((item) => item.check).map((item) => item.label);
|
||||
const hdArr = hd.value.filter((item) => item.check).map((item) => item.label);
|
||||
let str = '';
|
||||
wtArr.length ? (str += `问题:${wtArr.join(',')}. `) : '';
|
||||
hdArr.length ? (str += `回答:${hdArr.join(',')}. `) : '';
|
||||
inputText.value ? (str += `描述:${inputText.value}. `) : '';
|
||||
return str;
|
||||
}
|
||||
|
||||
function resetCheck() {
|
||||
wt.value = wt.value.map((item) => {
|
||||
return { ...item, check: false }; // 创建新对象,确保更新响应式
|
||||
});
|
||||
hd.value = hd.value.map((item) => {
|
||||
return { ...item, check: false }; // 创建新对象,确保更新响应式
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCheck(id) {
|
||||
if (id < 10) {
|
||||
const newWt = wt.value.map((item) => {
|
||||
if (item.id === id) {
|
||||
return { ...item, check: !item.check };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
wt.value = newWt;
|
||||
} else {
|
||||
const newHd = hd.value.map((item) => {
|
||||
if (item.id === id) {
|
||||
return { ...item, check: !item.check };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
hd.value = newHd;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style lang="stylus" scoped>
|
||||
.feeback
|
||||
padding: 38rpx 32rpx;
|
||||
.titile
|
||||
font-weight: 500;
|
||||
font-size: 36rpx;
|
||||
color: #333333;
|
||||
line-height: 42rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
.pop-h3
|
||||
font-weight: 600;
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
line-height: 38rpx;
|
||||
text-align: left;
|
||||
padding: 8rpx 0
|
||||
margin-top: 32rpx
|
||||
.pop-content
|
||||
.item
|
||||
width: fit-content;
|
||||
height: 80rpx;
|
||||
background: #E8EAEE;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
padding: 0 36rpx
|
||||
border: 2rpx solid transparent;
|
||||
display: inline-block
|
||||
margin-right: 28rpx
|
||||
margin-top: 28rpx
|
||||
.active
|
||||
border: 2rpx solid #256BFA;
|
||||
color: #256BFA;
|
||||
.supplement
|
||||
height: 200rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||
margin-top: 28rpx
|
||||
padding: 20rpx 24rpx
|
||||
.btn
|
||||
height: 90rpx;
|
||||
background: #256BFA;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
line-height: 90rpx;
|
||||
text-align: center;
|
||||
margin-top: 62rpx
|
||||
.close-btn
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
top: 32rpx;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
.close-btn::before
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 2rpx
|
||||
background: #5A5A68;
|
||||
transform: translate(50%, -50%) rotate(-45deg) ;
|
||||
.close-btn::after
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 2rpx
|
||||
background: #5A5A68;
|
||||
transform: translate(50%, -50%) rotate(45deg)
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user