|
|
@@ -1,716 +0,0 @@
|
|
|
-<template>
|
|
|
- <view class="chat-container" :style="{
|
|
|
- paddingTop: appStore.navbarHeight + 'px',
|
|
|
- }">
|
|
|
- <view class="mine_ybt_title flex-center bg_color_fff"
|
|
|
- :style="{ height: appStore.navbarHeight + 'px',
|
|
|
- paddingTop: appStore.statusBarHeight + 'px'}"
|
|
|
- >
|
|
|
- <!-- <text class="font_size35 bold">AI客服</text> -->
|
|
|
- <agentCheck ref="agentCheckRef"></agentCheck>
|
|
|
- </view>
|
|
|
- <!-- 聊天消息区域 -->
|
|
|
- <view class="chat-messages flex-column">
|
|
|
- <!-- 工具栏 -->
|
|
|
- <tools @addHuiHuaFn="addHuiHuaFn"></tools>
|
|
|
- <!-- 消息列表 -->
|
|
|
- <scroll-view
|
|
|
- class="scrollViewRef flex_1"
|
|
|
- scroll-y="true"
|
|
|
- :scroll-top="scrollTop"
|
|
|
- ref="scrollViewRef"
|
|
|
- :scroll-with-animation="true"
|
|
|
- @refresherrefresh="handlePullDownRefresh"
|
|
|
- :refresher-enabled="true"
|
|
|
- :refresher-triggered="triggered"
|
|
|
- >
|
|
|
- <messagesInfoDefault v-if="messages.length==0"
|
|
|
- @sendMessage="sendMessage"
|
|
|
- ref="messagesInfoDefaultRef" key="messagesInfoDefaultRef">
|
|
|
- </messagesInfoDefault>
|
|
|
- <messagesInfo
|
|
|
- :messages="messages"
|
|
|
- :isLoading="isLoading"
|
|
|
- @imageLoaded="scrollToBottom"
|
|
|
- :id="`msg-${messagesKey}`">
|
|
|
- </messagesInfo>
|
|
|
- </scroll-view>
|
|
|
- </view>
|
|
|
- <!-- 输入区域 -->
|
|
|
- <view class="input-area">
|
|
|
- <button class="image-btn" :disabled="isLoading" @click.stop.prevent="uploadImage">
|
|
|
- <image class="image-icon" src="/static/img/service/tupian.png" model="aspectFit"></image>
|
|
|
- </button>
|
|
|
- <button v-if="isvoice"
|
|
|
- class="record-btn"
|
|
|
- :class="{ recording: isRecording }"
|
|
|
- @touchstart="startRecord"
|
|
|
- @touchend="stopRecord"
|
|
|
- @touchmove="handleTouchMove"
|
|
|
- @touchcancel="cancelRecord"
|
|
|
- :disabled="isRecording && isCancel"
|
|
|
- >
|
|
|
- <view v-if="!isRecording">按住说话</view>
|
|
|
- <view v-if="isRecording && !isCancel">松手发送,上移取消</view>
|
|
|
- <view v-if="isRecording && isCancel">松手取消</view>
|
|
|
- <text v-if="recordDuration>0"></text>
|
|
|
- </button>
|
|
|
- <input v-else
|
|
|
- class="input-box"
|
|
|
- :placeholder="isLoading?'努力回答中...':'发消息或按住说话'"
|
|
|
- placeholder-style="color: #999"
|
|
|
- v-model.trim="inputText"
|
|
|
- :disabled="isLoading"
|
|
|
- @confirm="sendMessage({chatType:0,msgContent:inputText})"
|
|
|
- />
|
|
|
- <button class="isvoice-btn" @click.stop.prevent="isvoice=!isvoice;authorizeRecord()" :disabled="isLoading">
|
|
|
- <image class="mic-icon" src="/static/img/service/xiaoxi.png" v-if="isvoice" model="aspectFit"></image>
|
|
|
- <image class="mic-icon" src="/static/img/service/maikefengyuyin.png" v-else model="aspectFit"></image>
|
|
|
- </button>
|
|
|
- <button class="send-btn" @click.stop.prevent="sendMessage({chatType:0,msgContent:inputText})" :disabled="isLoading" v-if="!isvoice">
|
|
|
- <image class="send-icon" src="/static/img/service/send-icon.png"></image>
|
|
|
- </button>
|
|
|
- <view class="ai-tip">内容由AI生成,仅供参考</view>
|
|
|
- </view>
|
|
|
- <!-- 录音动画/提示 -->
|
|
|
- <view
|
|
|
- class="record-toast"
|
|
|
- v-if="isRecording"
|
|
|
- :class="{ cancel: isCancel }"
|
|
|
- >
|
|
|
- <image
|
|
|
- v-if="!isCancel"
|
|
|
- src="/static/img/voice/recording.gif"
|
|
|
- class="toast-icon"
|
|
|
- alt="录音中动画"
|
|
|
- ></image>
|
|
|
- <image
|
|
|
- v-if="isCancel"
|
|
|
- src="/static/img/voice/cancel.png"
|
|
|
- class="toast-icon"
|
|
|
- alt="取消录音图标"
|
|
|
- ></image>
|
|
|
- <text v-if="!isCancel">正在录音...({{60-recordDuration}})</text>
|
|
|
- <text v-if="isCancel">松手取消发送</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
-import { ref, nextTick, watch } from 'vue';
|
|
|
-import { onShow,onHide,onLoad } from "@dcloudio/uni-app"
|
|
|
-import { chatHistoryDetails } from '@/api/ai.js';
|
|
|
-import messagesInfo from "./components/messagesInfo.vue";
|
|
|
-import messagesInfoDefault from "./components/messagesInfoDefault.vue";
|
|
|
-import tools from "./components/tools.vue";
|
|
|
-import agentCheck from "./components/agentCheck.vue";
|
|
|
-import {
|
|
|
- HTTP_REQUEST_URL,
|
|
|
- HTTP_REQUEST_URL_WS,
|
|
|
- TOKENNAME
|
|
|
-} from '@/config/app';
|
|
|
-import { chooseImageOne,checkLoginShowModal,checkAiQuotaDailyModal,getUserInfo } from "@/utils/util.js";
|
|
|
-// 封装的websocket
|
|
|
-import WSClient from '@/utils/wsUtil.js';
|
|
|
-import { useAppStore } from '@/stores/app'
|
|
|
-import { useToast } from '@/hooks/useToast'
|
|
|
-import dayjs from "dayjs";
|
|
|
-const appStore = useAppStore();
|
|
|
-const { Toast } = useToast();
|
|
|
-const agentCheckRef = ref(null);
|
|
|
-const messagesInfoDefaultRef = ref(null);
|
|
|
-const newSession = ref(false);//是否新会话
|
|
|
-const historySession = ref('');//是否新会话
|
|
|
-
|
|
|
-// 添加滚动相关变量
|
|
|
-const scrollTimeout = ref(null);
|
|
|
-const scrollViewRef = ref(null);
|
|
|
-const scrollTop = ref(0);
|
|
|
-const messagesKey = ref(0);
|
|
|
-const triggered = ref(false);
|
|
|
-
|
|
|
-// 底部发送功能
|
|
|
-const inputText = ref('');
|
|
|
-const isLoading = ref(false);//思考中
|
|
|
-const isvoice = ref(false);//是否语音输入
|
|
|
-const wsClient = ref(null);
|
|
|
-
|
|
|
-// 模拟聊天消息数据
|
|
|
-const messages = ref([]);
|
|
|
-
|
|
|
-watch(() => appStore.agentId, (state) => {
|
|
|
- messages.value = [];
|
|
|
- getAdSearchFn()
|
|
|
-});
|
|
|
-
|
|
|
-onLoad((e)=>{
|
|
|
- console.log('ai onLoad',e);
|
|
|
-})
|
|
|
-onShow(async()=>{
|
|
|
- if(!await checkLoginShowModal())return;
|
|
|
- nextTick(async ()=>{
|
|
|
- // 初始化选择智能体
|
|
|
- await agentCheckRef.value.initAgentId();
|
|
|
- // 初始化完成后获取 agentId
|
|
|
- console.log('agentId:', agentCheckRef.value.agentId);
|
|
|
- if(appStore.msgContent){
|
|
|
- sendMessage({chatType:0,msgContent:appStore.msgContent});
|
|
|
- appStore.UPDATE_msgContent('');
|
|
|
- }else if(appStore.sessionId){
|
|
|
- handlePullDownRefresh();
|
|
|
- }else{
|
|
|
- // getAdSearchFn()
|
|
|
- }
|
|
|
-
|
|
|
- })
|
|
|
-
|
|
|
- aiStartChatFn();
|
|
|
-});
|
|
|
-onHide(()=>{
|
|
|
- isLoading.value = false;
|
|
|
- cleanupResources();
|
|
|
-});
|
|
|
-// 新增:添加回话功能
|
|
|
-function addHuiHuaFn(){
|
|
|
- newSession.value = true;
|
|
|
- messages.value = [];
|
|
|
- getAdSearchFn();
|
|
|
-}
|
|
|
-function getAdSearchFn(){
|
|
|
- nextTick(()=>{
|
|
|
- setTimeout(()=>{
|
|
|
- messagesInfoDefaultRef.value.getAdSearchFn();
|
|
|
- },50)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-function authorizeRecord() {
|
|
|
- uni.authorize({
|
|
|
- scope: 'scope.record',
|
|
|
- success() {
|
|
|
- // uni.getRecorderManager();
|
|
|
- }
|
|
|
- })
|
|
|
-}
|
|
|
-function aiStartChatFn(){
|
|
|
- cleanupResources();
|
|
|
- wsClient.value = new WSClient({
|
|
|
- url: `${HTTP_REQUEST_URL_WS}/api/websocket`,
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- "Authorization": 'Bearer '+appStore.token
|
|
|
- }
|
|
|
- });
|
|
|
- // 注册SSE事件
|
|
|
- registerEvents();
|
|
|
- wsClient.value.open({type:0,message:inputText.value})
|
|
|
-}
|
|
|
-
|
|
|
-//
|
|
|
-async function sendMessage({chatType,msgContent=""}){
|
|
|
- console.log('sendMessage',wsClient.value);
|
|
|
- // 登录检测
|
|
|
- if(!await checkLoginShowModal())return;
|
|
|
- // 今日免费提问次数检测
|
|
|
- if(!await checkAiQuotaDailyModal())return;
|
|
|
- if (!msgContent) {
|
|
|
- Toast({title:'请输入内容'});
|
|
|
- return;
|
|
|
- }
|
|
|
- isLoading.value = true
|
|
|
- messages.value.push({
|
|
|
- msgContent,
|
|
|
- chatType,// 0 (文本),1 (图片)
|
|
|
- speakerType:0//0用户消息1AI消息
|
|
|
- });
|
|
|
- messages.value.push({
|
|
|
- msgContent: "",
|
|
|
- event: "start",//sse长链接的返回状态
|
|
|
- chatType,
|
|
|
- speakerType:1,
|
|
|
- messageTimeNow:dayjs().format('HH:mm')
|
|
|
- });
|
|
|
- //type 0 (文本),1 (图片)
|
|
|
- wsClient.value.send({
|
|
|
- type:chatType,
|
|
|
- message:msgContent,
|
|
|
- agentId:agentCheckRef.value.agentId,
|
|
|
- useBalance:true,
|
|
|
- newSession:newSession.value,
|
|
|
- historySession:historySession.value
|
|
|
- });
|
|
|
- messagesKey.value++;
|
|
|
- inputText.value = '';
|
|
|
- newSession.value = false;
|
|
|
- // 每次收到新消息时滚动到底部
|
|
|
- // 如果是图片,子组件里图片加载成功后触发
|
|
|
- if(chatType!=1)scrollToBottom();
|
|
|
-}
|
|
|
-async function uploadImage(){
|
|
|
- if(!await checkLoginShowModal())return;
|
|
|
- const res = await chooseImageOne();
|
|
|
- console.log('uploadImage',res);
|
|
|
- if(res){
|
|
|
- // userInfo.value[key] = res.fileName
|
|
|
- sendMessage({chatType:1,msgContent:res.data})
|
|
|
- }
|
|
|
-}
|
|
|
-function cleanupResources(closeSSE=true) {
|
|
|
- // 关闭SSE连接
|
|
|
- if (wsClient.value) {
|
|
|
- // 移除所有事件监听
|
|
|
- if (wsClient.value.callbacks) {
|
|
|
- Object.keys(wsClient.value.callbacks).forEach(event => {
|
|
|
- wsClient.value.callbacks[event] = [];
|
|
|
- });
|
|
|
- }
|
|
|
- wsClient.value.close('clean');
|
|
|
- wsClient.value = null;
|
|
|
- }
|
|
|
-}
|
|
|
-function registerEvents(){
|
|
|
- wsClient.value.on('open', (data) => {
|
|
|
- console.log('ws连接成功',data);
|
|
|
- });
|
|
|
- wsClient.value.on('answer', (data) => {
|
|
|
- console.log('answer',data.segment,messages.value.length - 1);
|
|
|
- isLoading.value = false;
|
|
|
- const lastIndex = messages.value.length - 1;
|
|
|
- // 保存完整内容(累加片段)
|
|
|
- // const newFullAnswer = messages.value[lastIndex].msgContent + data.segment;
|
|
|
- messages.value[lastIndex].msgContent = messages.value[lastIndex].msgContent + data.segment;
|
|
|
- messagesKey.value++;
|
|
|
- console.log('answer',messages);
|
|
|
- // 每次收到新消息时滚动到底部
|
|
|
- scrollToBottom();
|
|
|
- nextTick(()=>{
|
|
|
- // 获取用户信息
|
|
|
- getUserInfo();
|
|
|
- })
|
|
|
- });
|
|
|
- wsClient.value.on('error', (err) => {
|
|
|
- isLoading.value = false;
|
|
|
- console.log('error',err);
|
|
|
- // aiStartChatFn();
|
|
|
- Toast({ title: JSON.stringify(err) || "服务器异常" });
|
|
|
- });
|
|
|
- wsClient.value.on('close', (data) => {
|
|
|
- isLoading.value = false;
|
|
|
- let lastIndex = messages.value.length - 1;
|
|
|
- console.log('close',data,lastIndex);
|
|
|
- // 判断关闭原因,取消,删除当前聊天内容,超时提示请求超时
|
|
|
- switch (data.reason) {
|
|
|
- case 'timeout': {
|
|
|
- // 提示超时
|
|
|
- // uni.showToast({
|
|
|
- // title: '请求超时,请重试',
|
|
|
- // icon: 'none'
|
|
|
- // });
|
|
|
- if(lastIndex>-1)messages.value[lastIndex].msgContent = messages.value[lastIndex].msgContent || "请求超时,请重试";
|
|
|
- aiStartChatFn();
|
|
|
- break;
|
|
|
- }
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-// 新增:滚动到底部的方法
|
|
|
-function scrollToBottom() {
|
|
|
- nextTick(() => {
|
|
|
- // if (scrollTimeout.value) return;
|
|
|
- // scrollTimeout.value = setTimeout(() => {
|
|
|
- uni.createSelectorQuery()
|
|
|
- .select(`#msg-${messagesKey.value}`)
|
|
|
- .boundingClientRect(rect => {
|
|
|
- if (rect) {
|
|
|
- scrollTop.value = rect.height;
|
|
|
- // 使用更平滑的滚动方式
|
|
|
- uni.pageScrollTo({
|
|
|
- scrollTop: rect.bottom,
|
|
|
- duration: 100
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- .exec();
|
|
|
- // scrollTimeout.value = null;
|
|
|
- // }, 80); // 延长节流间隔至80ms,减少冲突
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-/*******************************************历史数据********************************************************/
|
|
|
-
|
|
|
-const hitstoryParams = ref({
|
|
|
- pageNum: 0,
|
|
|
- pageSize: 10,
|
|
|
- sessionId:appStore.sessionId,
|
|
|
-});
|
|
|
-// 重命名方法,避免与小程序生命周期冲突
|
|
|
-function handlePullDownRefresh() {
|
|
|
- console.log('下拉刷新触发');
|
|
|
- triggered.value = true;
|
|
|
- hitstoryParams.value.pageNum++;
|
|
|
- chatHistoryDetailsFn();
|
|
|
-}
|
|
|
-function chatHistoryDetailsFn(){
|
|
|
- hitstoryParams.value.sessionId = appStore.sessionId;
|
|
|
- chatHistoryDetails(hitstoryParams.value).then(res=>{
|
|
|
- triggered.value = false;
|
|
|
- console.log('chatHistoryFn',res);
|
|
|
- const rows = res?.rows || [];
|
|
|
- if(rows.length==0 && hitstoryParams.value.pageNum>0){
|
|
|
- hitstoryParams.value.pageNum--;
|
|
|
- // Toast({title:'没有更多历史记录了'});
|
|
|
- return;
|
|
|
- }
|
|
|
- if(messages.value.length==res.total){
|
|
|
- Toast({title:'没有更多历史记录了'});
|
|
|
- return;
|
|
|
- }
|
|
|
- messages.value = [...rows, ...messages.value]
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-/*******************************************语音功能********************************************************/
|
|
|
-
|
|
|
-const isRecording = ref(false);// 是否正在录音
|
|
|
-const isCancel = ref(false); // 是否取消录音
|
|
|
-const recordDuration = ref(0); // 录音时长(秒)
|
|
|
-const tempFilePath = ref(''); // 录音临时文件路径
|
|
|
-const recordTimer = ref(null); // 录音计时定时器
|
|
|
-const recorderManager = uni.getRecorderManager(); // 录音管理
|
|
|
-// 开始录音(触摸开始)
|
|
|
-const startRecord = async (e) => {
|
|
|
- if(!await checkLoginShowModal())return;
|
|
|
- // 防止冒泡导致的异常
|
|
|
- e.stopPropagation();
|
|
|
-
|
|
|
- // 初始化录音参数
|
|
|
- const options = {
|
|
|
- format: 'mp3', // 录音格式
|
|
|
- sampleRate: 44100, // 采样率
|
|
|
- numberOfChannels: 1, // 声道数
|
|
|
- encodeBitRate: 96000 // 编码比特率
|
|
|
- };
|
|
|
-
|
|
|
- // 开始录音
|
|
|
- recorderManager.start(options);
|
|
|
- isRecording.value = true;
|
|
|
- isCancel.value = false;
|
|
|
- recordDuration.value = 0;
|
|
|
-
|
|
|
- // 计时逻辑
|
|
|
- recordTimer.value = setInterval(() => {
|
|
|
- recordDuration.value++;
|
|
|
- // 限制最大录音时长(如60秒)
|
|
|
- if (recordDuration.value >= 60) {
|
|
|
- stopRecord();
|
|
|
- }
|
|
|
- }, 1000);
|
|
|
-
|
|
|
- // 监听录音错误
|
|
|
- recorderManager.onError((err) => {
|
|
|
- console.error('录音错误:', err);
|
|
|
- cancelRecord();
|
|
|
- uni.showToast({ title: '录音失败', icon: 'none' });
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-// 停止录音(触摸结束)
|
|
|
-const stopRecord = () => {
|
|
|
- if (!isRecording.value) return;
|
|
|
-
|
|
|
- // 清除计时
|
|
|
- clearInterval(recordTimer.value);
|
|
|
-
|
|
|
- // 停止录音
|
|
|
- recorderManager.stop();
|
|
|
- recorderManager.onStop((res) => {
|
|
|
- tempFilePath.value = res.tempFilePath;
|
|
|
- console.log('recorderManager',res.tempFilePath);
|
|
|
-
|
|
|
- // 判断是否取消或录音过短
|
|
|
- if (isCancel.value) {
|
|
|
- uni.showToast({ title: '已取消发送', icon: 'none' });
|
|
|
- } else if (recordDuration.value < 1) {
|
|
|
- uni.showToast({ title: '录音时间太短', icon: 'none' });
|
|
|
- } else {
|
|
|
- // 上传录音并添加到列表
|
|
|
- uploadVoice(res.tempFilePath);
|
|
|
- }
|
|
|
- // 重置状态
|
|
|
- resetRecordState();
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
-};
|
|
|
-
|
|
|
-// 处理触摸移动(用于判断是否取消)
|
|
|
-const handleTouchMove = (e) => {
|
|
|
- if (!isRecording.value) return;
|
|
|
-
|
|
|
- // 获取按钮位置和触摸位置
|
|
|
- const buttonRect = uni.createSelectorQuery().select('.record-btn').boundingClientRect();
|
|
|
- buttonRect.exec((rects) => {
|
|
|
- const rect = rects[0];
|
|
|
- if (!rect) return;
|
|
|
-
|
|
|
- // 计算触摸点与按钮的垂直距离(向上移动超过50px视为取消)
|
|
|
- const touchY = e.touches[0].clientY;
|
|
|
- const buttonTop = rect.top;
|
|
|
- if (touchY < buttonTop - 50) {
|
|
|
- isCancel.value = true;
|
|
|
- } else {
|
|
|
- isCancel.value = false;
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-// 取消录音(触摸中断)
|
|
|
-const cancelRecord = () => {
|
|
|
- if (!isRecording.value) return;
|
|
|
-
|
|
|
- clearInterval(recordTimer.value);
|
|
|
- recorderManager.stop();
|
|
|
- resetRecordState();
|
|
|
- uni.showToast({ title: '已取消发送', icon: 'none' });
|
|
|
-};
|
|
|
-
|
|
|
-// 重置录音状态
|
|
|
-const resetRecordState = () => {
|
|
|
- isRecording.value = false;
|
|
|
- isCancel.value = false;
|
|
|
- recordDuration.value = 0;
|
|
|
- tempFilePath.value = '';
|
|
|
-};
|
|
|
-
|
|
|
-// 上传录音到服务器
|
|
|
-const uploadVoice = (filePath) => {
|
|
|
- if (!filePath) return;
|
|
|
-
|
|
|
- uni.showLoading({ title: '发送中...' });
|
|
|
-
|
|
|
- // 调用上传接口
|
|
|
- uni.uploadFile({
|
|
|
- url: `${HTTP_REQUEST_URL}/mini/chat/file/voiceUpload`, // 替换为你的后端接口
|
|
|
- filePath,
|
|
|
- name: 'file', // 后端接收文件的参数名
|
|
|
- formData: {
|
|
|
- duration: recordDuration.value // 携带录音时长
|
|
|
- },
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- "Authorization": appStore.token
|
|
|
- },
|
|
|
- success: (res) => {
|
|
|
- console.log('上传结果:', res);
|
|
|
- const result = JSON.parse(res.data);
|
|
|
- if (result.code === 200) {
|
|
|
- // 上传成功,添加到消息列表
|
|
|
- sendMessage({ chatType:0,msgContent:result.data });
|
|
|
- // messages.value.push({
|
|
|
- // id: Date.now(),
|
|
|
- // isMine: true,
|
|
|
- // avatar: '/static/avatar/user.png',
|
|
|
- // duration: recordDuration.value,
|
|
|
- // url: result.data.url, // 服务器返回的音频地址
|
|
|
- // isPlaying: false
|
|
|
- // });
|
|
|
- } else {
|
|
|
- uni.showToast({ title: '发送失败', icon: 'none' });
|
|
|
- }
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.error('语音上传失败:', err);
|
|
|
- uni.showToast({ title: '发送失败', icon: 'none' });
|
|
|
- },
|
|
|
- complete: () => {
|
|
|
- uni.hideLoading();
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="scss">
|
|
|
-.mine_ybt_title{
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- z-index: 1;
|
|
|
-}
|
|
|
-/* 录音区域 */
|
|
|
-.record-btn{
|
|
|
- flex: 1;
|
|
|
- height: 70rpx;
|
|
|
- line-height: 70rpx;
|
|
|
- font-size: 30rpx;
|
|
|
- color: #333;
|
|
|
-
|
|
|
-}
|
|
|
-.record-area {
|
|
|
- padding: 30rpx;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-
|
|
|
-.record-btn {
|
|
|
- flex: 1;
|
|
|
- height: 70rpx;
|
|
|
- line-height: 70rpx;
|
|
|
- font-size: 30rpx;
|
|
|
- color: #333;
|
|
|
- background-color: #f2f2f2;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.record-btn.recording {
|
|
|
- background-color: #ff4d4f;
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.record-btn.cancel {
|
|
|
- background-color: #999;
|
|
|
-}
|
|
|
-/* 整体容器样式 */
|
|
|
-.chat-container {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- height: 100vh;
|
|
|
- // padding-bottom: 200rpx;
|
|
|
- /* background-color: #f5f5f5; */
|
|
|
-}
|
|
|
-
|
|
|
-/* 聊天消息区域样式 */
|
|
|
-.chat-messages {
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
- .scrollViewRef{
|
|
|
- // height: 100%;
|
|
|
- padding: 16rpx;
|
|
|
- overflow-y: auto;
|
|
|
- }
|
|
|
- // height: calc(100vh - 200rpx);
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-/* 输入区域样式 */
|
|
|
-.input-area {
|
|
|
- // position: fixed;
|
|
|
- // left: 0;
|
|
|
- // bottom: 0;
|
|
|
- // width: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background-color: #fff;
|
|
|
- padding: 30rpx 40rpx;
|
|
|
- border-top: 1rpx solid #e0e0e0;
|
|
|
- position: relative;
|
|
|
-}
|
|
|
-.ai-tip{
|
|
|
- position: absolute;
|
|
|
- bottom: 5rpx;
|
|
|
- width: calc(100% - 80rpx);
|
|
|
- text-align: center;
|
|
|
- font-size: 20rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-/* 图片图标样式 */
|
|
|
-.image-btn{
|
|
|
- margin-right: 30rpx;
|
|
|
- width: 40rpx;
|
|
|
- height: 40rpx;
|
|
|
- &[disabled]{
|
|
|
- .image-icon{
|
|
|
- opacity: 0.4;
|
|
|
- }
|
|
|
- }
|
|
|
- .image-icon {
|
|
|
- width: 100%;
|
|
|
- height:100%;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* 输入框样式 */
|
|
|
-.input-box {
|
|
|
- flex: 1;
|
|
|
- height: 70rpx;
|
|
|
- font-size: 30rpx;
|
|
|
- color: #333;
|
|
|
- background-color: transparent;
|
|
|
-}
|
|
|
-
|
|
|
-/* 麦克风图标样式 */
|
|
|
-.isvoice-btn{
|
|
|
- width: 40rpx;
|
|
|
- height: 40rpx;
|
|
|
- margin: 0 40rpx;
|
|
|
- &[disabled]{
|
|
|
- .mic-icon{
|
|
|
- opacity: 0.4;
|
|
|
- }
|
|
|
- }
|
|
|
- .mic-icon {
|
|
|
- width: 100%;
|
|
|
- height:100%;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* 发送按钮样式 */
|
|
|
-.send-btn {
|
|
|
- width: 60rpx;
|
|
|
- height: 60rpx;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- padding: 0;
|
|
|
- &[disabled]{
|
|
|
- .send-icon{
|
|
|
- opacity: 0.4;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* 发送图标样式 */
|
|
|
-.send-icon {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-/* 录音提示 */
|
|
|
-.record-toast {
|
|
|
- position: fixed;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- width: 300rpx;
|
|
|
- height: 300rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
- background-color: rgba(0, 0, 0, 0.7);
|
|
|
- color: #fff;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- z-index: 999;
|
|
|
-}
|
|
|
-
|
|
|
-.record-toast.cancel {
|
|
|
- background-color: rgba(255, 77, 79, 0.8);
|
|
|
-}
|
|
|
-
|
|
|
-.toast-icon {
|
|
|
- width: 200rpx;
|
|
|
- height: 200rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-</style>
|