|
|
<template>
|
|
|
<view>
|
|
|
<view class="content" @touchstart="hideDrawer">
|
|
|
<scroll-view class="msg-list" scroll-y="true" :scroll-with-animation="scrollAnimation"
|
|
|
:scroll-top="scrollTop" :scroll-into-view="scrollToView" @scrolltoupper="loadHistory"
|
|
|
upper-threshold="50">
|
|
|
<!-- 加载历史数据waitingUI -->
|
|
|
<view class="loading" v-if="loadingVisible">
|
|
|
<view class="spinner">
|
|
|
<view class="rect1"></view>
|
|
|
<view class="rect2"></view>
|
|
|
<view class="rect3"></view>
|
|
|
<view class="rect4"></view>
|
|
|
<view class="rect5"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="row" v-for="(row, index) in msgList" :key="index" :id="'msg' + row.msg.id">
|
|
|
<!-- 系统消息 -->
|
|
|
<block v-if="row.type == 'system'">
|
|
|
<view class="system">
|
|
|
<!-- 文字消息 -->
|
|
|
<view v-if="row.msg.type == 'text'" class="text">
|
|
|
{{ row.msg.content.text }}
|
|
|
</view>
|
|
|
<!-- 领取红包消息 -->
|
|
|
<view v-if="row.msg.type == 'redEnvelope'" class="red-envelope">
|
|
|
<image src="/static/img/red-envelope-chat.png"></image>
|
|
|
{{ row.msg.content.text }}
|
|
|
</view>
|
|
|
</view>
|
|
|
</block>
|
|
|
<!-- 用户消息 -->
|
|
|
<block v-if="row.type == 'user'">
|
|
|
<!-- 自己发出的消息 -->
|
|
|
<view class="my" v-if="row.msg.userinfo.uid == myuid">
|
|
|
<!-- 左-消息 -->
|
|
|
<view class="left">
|
|
|
<!-- 文字消息 -->
|
|
|
<view v-if="row.msg.type == 'text'" class="bubble">
|
|
|
<rich-text :nodes="row.msg.content.text"></rich-text>
|
|
|
</view>
|
|
|
<!-- 语言消息 -->
|
|
|
<view v-if="row.msg.type == 'voice'" class="bubble voice" @tap="playVoice(row.msg)"
|
|
|
:class="playMsgid == row.msg.id ? 'play' : ''">
|
|
|
<view class="length">{{ row.msg.content.length }}</view>
|
|
|
<view class="icon my-voice"></view>
|
|
|
</view>
|
|
|
<!-- 图片消息 -->
|
|
|
<view v-if="row.msg.type == 'img'" class="bubble img" @tap="showPic(row.msg)">
|
|
|
<image :src="row.msg.content.url"
|
|
|
:style="{ 'width': row.msg.content.w + 'px', 'height': row.msg.content.h + 'px' }">
|
|
|
</image>
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
<!-- 右-头像 -->
|
|
|
<view class="right">
|
|
|
<image :src="row.msg.userinfo.face"></image>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 别人发出的消息 -->
|
|
|
<view class="other" v-if="row.msg.userinfo.uid != myuid">
|
|
|
<!-- 左-头像 -->
|
|
|
<view class="left">
|
|
|
<image :src="row.msg.userinfo.face"></image>
|
|
|
</view>
|
|
|
<!-- 右-用户名称-时间-消息 -->
|
|
|
<view class="right">
|
|
|
<view class="username">
|
|
|
<view class="name">{{ row.msg.userinfo.username }}</view>
|
|
|
<view class="time">{{ row.msg.time }}</view>
|
|
|
</view>
|
|
|
<!-- 文字消息 -->
|
|
|
<view v-if="row.msg.type == 'text'" class="bubble">
|
|
|
<ver-response v-if="row.msg.md == '1'" theme="none"
|
|
|
:content="row.msg.content.text"></ver-response>
|
|
|
<rich-text v-if="row.msg.md != '1'" :nodes="row.msg.content.text"></rich-text>
|
|
|
<!-- <rich-text v-if="row.msg.md == '1'">
|
|
|
<ver-response theme="none" :content="row.msg.content.text"></ver-response>
|
|
|
</rich-text>
|
|
|
<rich-text v-if="row.msg.md != '1'" :nodes="row.msg.content.text"></rich-text> -->
|
|
|
|
|
|
</view>
|
|
|
<!-- 加载动画消息 -->
|
|
|
<view v-if="row.msg.type == 'loading'" class="bubble loading-bubble">
|
|
|
<view class="loading-dots">
|
|
|
<view class="dot"></view>
|
|
|
<view class="dot"></view>
|
|
|
<view class="dot"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 语音消息 -->
|
|
|
<view v-if="row.msg.type == 'voice'" class="bubble voice" @tap="playVoice(row.msg)"
|
|
|
:class="playMsgid == row.msg.id ? 'play' : ''">
|
|
|
<view class="icon other-voice"></view>
|
|
|
<view class="length">{{ row.msg.content.length }}</view>
|
|
|
</view>
|
|
|
<!-- 图片消息 -->
|
|
|
<view v-if="row.msg.type == 'img'" class="bubble img" @tap="showPic(row.msg)">
|
|
|
<image :src="row.msg.content.url"
|
|
|
:style="{ 'width': row.msg.content.w + 'px', 'height': row.msg.content.h + 'px' }">
|
|
|
</image>
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
</view>
|
|
|
</block>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
</view>
|
|
|
<!-- 抽屉栏 -->
|
|
|
<view class="popup-layer" :class="popupLayerClass" @touchmove.stop.prevent="discard">
|
|
|
<!-- 表情 -->
|
|
|
<swiper class="emoji-swiper" :class="{ hidden: hideEmoji }" indicator-dots="true" duration="150">
|
|
|
<swiper-item v-for="(page, pid) in emojiList" :key="pid">
|
|
|
<view v-for="(em, eid) in page" :key="eid" @tap="addEmoji(em)">
|
|
|
<image mode="widthFix" :src="'/static/img/emoji/' + em.url"></image>
|
|
|
</view>
|
|
|
</swiper-item>
|
|
|
</swiper>
|
|
|
<!-- 更多功能 相册-拍照-红包 -->
|
|
|
<view class="more-layer" :class="{ hidden: hideMore }">
|
|
|
<view class="list">
|
|
|
<view class="box_grid">
|
|
|
<view class="box" @tap="chooseImage">
|
|
|
<view class="icon tupian2"></view>
|
|
|
</view>
|
|
|
<view>相册</view>
|
|
|
</view>
|
|
|
<view class="box_grid">
|
|
|
<view class="box" @tap="camera">
|
|
|
<view class="icon paizhao"></view>
|
|
|
</view>
|
|
|
<view>拍摄</view>
|
|
|
</view>
|
|
|
<view class="box_grid">
|
|
|
<view class="box" @tap="handAlbumQ">
|
|
|
<view class="New-icon saoti"></view>
|
|
|
</view>
|
|
|
<view>选图做题</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="box_grid">
|
|
|
<view class="box" @tap="handCameraQ">
|
|
|
<view class="New-icon saoti"></view>
|
|
|
</view>
|
|
|
<view>拍摄做题</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 底部输入栏 -->
|
|
|
<view class="input-box" :class="popupLayerClass" @touchmove.stop.prevent="discard">
|
|
|
<!-- H5下不能录音,输入栏布局改动一下 -->
|
|
|
<!-- #ifndef H5 -->
|
|
|
<view class="voice">
|
|
|
<view class="icon" :class="isVoice ? 'jianpan' : 'yuyin'" @tap="switchVoice"></view>
|
|
|
</view>
|
|
|
<!-- #endif -->
|
|
|
<!-- #ifdef H5 -->
|
|
|
<view class="more" @tap="showMore">
|
|
|
<view class="icon add"></view>
|
|
|
</view>
|
|
|
<!-- #endif -->
|
|
|
<view class="textbox">
|
|
|
<view class="voice-mode" :class="[isVoice ? '' : 'hidden', recording ? 'recording' : '']"
|
|
|
@touchstart="voiceBegin" @touchmove.stop.prevent="voiceIng" @touchend="voiceEnd"
|
|
|
@touchcancel="voiceCancel">{{ voiceTis }}</view>
|
|
|
<view class="text-mode" :class="isVoice ? 'hidden' : ''">
|
|
|
<view class="box">
|
|
|
<textarea auto-height="true" v-model="textMsg" @focus="textareaFocus" />
|
|
|
</view>
|
|
|
<!-- <view class="em" @tap="chooseEmoji">
|
|
|
<view class="icon biaoqing"></view>
|
|
|
</view> -->
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- #ifndef H5 -->
|
|
|
<view class="more" @tap="showMore">
|
|
|
<view class="icon add"></view>
|
|
|
</view>
|
|
|
<!-- #endif -->
|
|
|
<view class="send" :class="isVoice ? 'hidden' : ''" @tap="sendText">
|
|
|
<view class="btn">发送</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 录音UI效果 -->
|
|
|
<view class="record" :class="recording ? '' : 'hidden'">
|
|
|
<view class="ing" :class="willStop ? 'hidden' : ''">
|
|
|
<view class="icon luyin2"></view>
|
|
|
</view>
|
|
|
<view class="cancel" :class="willStop ? '' : 'hidden'">
|
|
|
<view class="icon chehui"></view>
|
|
|
</view>
|
|
|
<view class="tis" :class="willStop ? 'change' : ''">{{ recordTis }}</view>
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
</template>
|
|
|
<script>
|
|
|
|
|
|
import utf8Array2Str from 'utf8array2str';
|
|
|
|
|
|
const plugin = requirePlugin("WechatSI");
|
|
|
const manager = plugin.getRecordRecognitionManager();
|
|
|
|
|
|
import permision from '../../common/permission.js';
|
|
|
import config from '@/common/config.js';
|
|
|
|
|
|
|
|
|
import {
|
|
|
getAIReply,
|
|
|
getChatLog,
|
|
|
getOSSInfo,
|
|
|
getImageReco
|
|
|
} from '@/api/services/index.js';
|
|
|
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
//文字消息
|
|
|
textMsg: '',
|
|
|
//消息列表
|
|
|
isHistoryLoading: false,
|
|
|
scrollAnimation: false,
|
|
|
loadingVisible: false,
|
|
|
scrollTop: 0,
|
|
|
scrollToView: '',
|
|
|
msgList: [],
|
|
|
msgImgList: [],
|
|
|
myuid: 0,
|
|
|
|
|
|
person_id: '',
|
|
|
voiceStartTime: 0, // 语音开始时间戳
|
|
|
voiceDuration: 0, // 语音持续时间(毫秒)
|
|
|
|
|
|
page_num: 1,
|
|
|
page_size: 10,
|
|
|
|
|
|
//录音相关参数
|
|
|
// #ifndef H5
|
|
|
//H5不能录音
|
|
|
RECORDER: uni.getRecorderManager(),
|
|
|
// #endif
|
|
|
isVoice: false,
|
|
|
voiceTis: '按住 说话',
|
|
|
recordTis: "手指上滑 取消发送",
|
|
|
recording: false,
|
|
|
willStop: false,
|
|
|
initPoint: { identifier: 0, Y: 0 },
|
|
|
recordTimer: null,
|
|
|
recordLength: 0,
|
|
|
|
|
|
//播放语音相关参数
|
|
|
AUDIO: uni.createInnerAudioContext(),
|
|
|
playMsgid: null,
|
|
|
VoiceTimer: null,
|
|
|
// 抽屉参数
|
|
|
popupLayerClass: '',
|
|
|
// more参数
|
|
|
hideMore: true,
|
|
|
//表情定义
|
|
|
hideEmoji: true,
|
|
|
emojiList: [
|
|
|
[{ "url": "100.gif", alt: "[微笑]" }, { "url": "101.gif", alt: "[伤心]" }, { "url": "102.gif", alt: "[美女]" }, { "url": "103.gif", alt: "[发呆]" }, { "url": "104.gif", alt: "[墨镜]" }, { "url": "105.gif", alt: "[哭]" }, { "url": "106.gif", alt: "[羞]" }, { "url": "107.gif", alt: "[哑]" }, { "url": "108.gif", alt: "[睡]" }, { "url": "109.gif", alt: "[哭]" }, { "url": "110.gif", alt: "[囧]" }, { "url": "111.gif", alt: "[怒]" }, { "url": "112.gif", alt: "[调皮]" }, { "url": "113.gif", alt: "[笑]" }, { "url": "114.gif", alt: "[惊讶]" }, { "url": "115.gif", alt: "[难过]" }, { "url": "116.gif", alt: "[酷]" }, { "url": "117.gif", alt: "[汗]" }, { "url": "118.gif", alt: "[抓狂]" }, { "url": "119.gif", alt: "[吐]" }, { "url": "120.gif", alt: "[笑]" }, { "url": "121.gif", alt: "[快乐]" }, { "url": "122.gif", alt: "[奇]" }, { "url": "123.gif", alt: "[傲]" }],
|
|
|
[{ "url": "124.gif", alt: "[饿]" }, { "url": "125.gif", alt: "[累]" }, { "url": "126.gif", alt: "[吓]" }, { "url": "127.gif", alt: "[汗]" }, { "url": "128.gif", alt: "[高兴]" }, { "url": "129.gif", alt: "[闲]" }, { "url": "130.gif", alt: "[努力]" }, { "url": "131.gif", alt: "[骂]" }, { "url": "132.gif", alt: "[疑问]" }, { "url": "133.gif", alt: "[秘密]" }, { "url": "134.gif", alt: "[乱]" }, { "url": "135.gif", alt: "[疯]" }, { "url": "136.gif", alt: "[哀]" }, { "url": "137.gif", alt: "[鬼]" }, { "url": "138.gif", alt: "[打击]" }, { "url": "139.gif", alt: "[bye]" }, { "url": "140.gif", alt: "[汗]" }, { "url": "141.gif", alt: "[抠]" }, { "url": "142.gif", alt: "[鼓掌]" }, { "url": "143.gif", alt: "[糟糕]" }, { "url": "144.gif", alt: "[恶搞]" }, { "url": "145.gif", alt: "[什么]" }, { "url": "146.gif", alt: "[什么]" }, { "url": "147.gif", alt: "[累]" }],
|
|
|
[{ "url": "148.gif", alt: "[看]" }, { "url": "149.gif", alt: "[难过]" }, { "url": "150.gif", alt: "[难过]" }, { "url": "151.gif", alt: "[坏]" }, { "url": "152.gif", alt: "[亲]" }, { "url": "153.gif", alt: "[吓]" }, { "url": "154.gif", alt: "[可怜]" }, { "url": "155.gif", alt: "[刀]" }, { "url": "156.gif", alt: "[水果]" }, { "url": "157.gif", alt: "[酒]" }, { "url": "158.gif", alt: "[篮球]" }, { "url": "159.gif", alt: "[乒乓]" }, { "url": "160.gif", alt: "[咖啡]" }, { "url": "161.gif", alt: "[美食]" }, { "url": "162.gif", alt: "[动物]" }, { "url": "163.gif", alt: "[鲜花]" }, { "url": "164.gif", alt: "[枯]" }, { "url": "165.gif", alt: "[唇]" }, { "url": "166.gif", alt: "[爱]" }, { "url": "167.gif", alt: "[分手]" }, { "url": "168.gif", alt: "[生日]" }, { "url": "169.gif", alt: "[电]" }, { "url": "170.gif", alt: "[炸弹]" }, { "url": "171.gif", alt: "[刀子]" }],
|
|
|
[{ "url": "172.gif", alt: "[足球]" }, { "url": "173.gif", alt: "[瓢虫]" }, { "url": "174.gif", alt: "[翔]" }, { "url": "175.gif", alt: "[月亮]" }, { "url": "176.gif", alt: "[太阳]" }, { "url": "177.gif", alt: "[礼物]" }, { "url": "178.gif", alt: "[抱抱]" }, { "url": "179.gif", alt: "[拇指]" }, { "url": "180.gif", alt: "[贬低]" }, { "url": "181.gif", alt: "[握手]" }, { "url": "182.gif", alt: "[剪刀手]" }, { "url": "183.gif", alt: "[抱拳]" }, { "url": "184.gif", alt: "[勾引]" }, { "url": "185.gif", alt: "[拳头]" }, { "url": "186.gif", alt: "[小拇指]" }, { "url": "187.gif", alt: "[拇指八]" }, { "url": "188.gif", alt: "[食指]" }, { "url": "189.gif", alt: "[ok]" }, { "url": "190.gif", alt: "[情侣]" }, { "url": "191.gif", alt: "[爱心]" }, { "url": "192.gif", alt: "[蹦哒]" }, { "url": "193.gif", alt: "[颤抖]" }, { "url": "194.gif", alt: "[怄气]" }, { "url": "195.gif", alt: "[跳舞]" }],
|
|
|
[{ "url": "196.gif", alt: "[发呆]" }, { "url": "197.gif", alt: "[背着]" }, { "url": "198.gif", alt: "[伸手]" }, { "url": "199.gif", alt: "[耍帅]" }, { "url": "200.png", alt: "[微笑]" }, { "url": "201.png", alt: "[生病]" }, { "url": "202.png", alt: "[哭泣]" }, { "url": "203.png", alt: "[吐舌]" }, { "url": "204.png", alt: "[迷糊]" }, { "url": "205.png", alt: "[瞪眼]" }, { "url": "206.png", alt: "[恐怖]" }, { "url": "207.png", alt: "[忧愁]" }, { "url": "208.png", alt: "[眨眉]" }, { "url": "209.png", alt: "[闭眼]" }, { "url": "210.png", alt: "[鄙视]" }, { "url": "211.png", alt: "[阴暗]" }, { "url": "212.png", alt: "[小鬼]" }, { "url": "213.png", alt: "[礼物]" }, { "url": "214.png", alt: "[拜佛]" }, { "url": "215.png", alt: "[力量]" }, { "url": "216.png", alt: "[金钱]" }, { "url": "217.png", alt: "[蛋糕]" }, { "url": "218.png", alt: "[彩带]" }, { "url": "219.png", alt: "[礼物]" },]
|
|
|
],
|
|
|
//表情图片图床名称 ,由于我上传的第三方图床名称会有改变,所以有此数据来做对应,您实际应用中应该不需要
|
|
|
onlineEmoji: { "100.gif": "AbNQgA.gif", "101.gif": "AbN3ut.gif", "102.gif": "AbNM3d.gif", "103.gif": "AbN8DP.gif", "104.gif": "AbNljI.gif", "105.gif": "AbNtUS.gif", "106.gif": "AbNGHf.gif", "107.gif": "AbNYE8.gif", "108.gif": "AbNaCQ.gif", "109.gif": "AbNN4g.gif", "110.gif": "AbN0vn.gif", "111.gif": "AbNd3j.gif", "112.gif": "AbNsbV.gif", "113.gif": "AbNwgs.gif", "114.gif": "AbNrD0.gif", "115.gif": "AbNDuq.gif", "116.gif": "AbNg5F.gif", "117.gif": "AbN6ET.gif", "118.gif": "AbNcUU.gif", "119.gif": "AbNRC4.gif", "120.gif": "AbNhvR.gif", "121.gif": "AbNf29.gif", "122.gif": "AbNW8J.gif", "123.gif": "AbNob6.gif", "124.gif": "AbN5K1.gif", "125.gif": "AbNHUO.gif", "126.gif": "AbNIDx.gif", "127.gif": "AbN7VK.gif", "128.gif": "AbNb5D.gif", "129.gif": "AbNX2d.gif", "130.gif": "AbNLPe.gif", "131.gif": "AbNjxA.gif", "132.gif": "AbNO8H.gif", "133.gif": "AbNxKI.gif", "134.gif": "AbNzrt.gif", "135.gif": "AbU9Vf.gif", "136.gif": "AbUSqP.gif", "137.gif": "AbUCa8.gif", "138.gif": "AbUkGQ.gif", "139.gif": "AbUFPg.gif", "140.gif": "AbUPIS.gif", "141.gif": "AbUZMn.gif", "142.gif": "AbUExs.gif", "143.gif": "AbUA2j.gif", "144.gif": "AbUMIU.gif", "145.gif": "AbUerq.gif", "146.gif": "AbUKaT.gif", "147.gif": "AbUmq0.gif", "148.gif": "AbUuZV.gif", "149.gif": "AbUliF.gif", "150.gif": "AbU1G4.gif", "151.gif": "AbU8z9.gif", "152.gif": "AbU3RJ.gif", "153.gif": "AbUYs1.gif", "154.gif": "AbUJMR.gif", "155.gif": "AbUadK.gif", "156.gif": "AbUtqx.gif", "157.gif": "AbUUZ6.gif", "158.gif": "AbUBJe.gif", "159.gif": "AbUdIO.gif", "160.gif": "AbU0iD.gif", "161.gif": "AbUrzd.gif", "162.gif": "AbUDRH.gif", "163.gif": "AbUyQA.gif", "164.gif": "AbUWo8.gif", "165.gif": "AbU6sI.gif", "166.gif": "AbU2eP.gif", "167.gif": "AbUcLt.gif", "168.gif": "AbU4Jg.gif", "169.gif": "AbURdf.gif", "170.gif": "AbUhFS.gif", "171.gif": "AbU5WQ.gif", "172.gif": "AbULwV.gif", "173.gif": "AbUIzj.gif", "174.gif": "AbUTQs.gif", "175.gif": "AbU7yn.gif", "176.gif": "AbUqe0.gif", "177.gif": "AbUHLq.gif", "178.gif": "AbUOoT.gif", "179.gif": "AbUvYF.gif", "180.gif": "AbUjFU.gif", "181.gif": "AbaSSJ.gif", "182.gif": "AbUxW4.gif", "183.gif": "AbaCO1.gif", "184.gif": "Abapl9.gif", "185.gif": "Aba9yR.gif", "186.gif": "AbaFw6.gif", "187.gif": "Abaiex.gif", "188.gif": "AbakTK.gif", "189.gif": "AbaZfe.png", "190.gif": "AbaEFO.gif", "191.gif": "AbaVYD.gif", "192.gif": "AbamSH.gif", "193.gif": "AbaKOI.gif", "194.gif": "Abanld.gif", "195.gif": "Abau6A.gif", "196.gif": "AbaQmt.gif", "197.gif": "Abal0P.gif", "198.gif": "AbatpQ.gif", "199.gif": "Aba1Tf.gif", "200.png": "Aba8k8.png", "201.png": "AbaGtS.png", "202.png": "AbaJfg.png", "203.png": "AbaNlj.png", "204.png": "Abawmq.png", "205.png": "AbaU6s.png", "206.png": "AbaaXn.png", "207.png": "Aba000.png", "208.png": "AbarkT.png", "209.png": "AbastU.png", "210.png": "AbaB7V.png", "211.png": "Abafn1.png", "212.png": "Abacp4.png", "213.png": "AbayhF.png", "214.png": "Abag1J.png", "215.png": "Aba2c9.png", "216.png": "AbaRXR.png", "217.png": "Aba476.png", "218.png": "Abah0x.png", "219.png": "Abdg58.png" },
|
|
|
//红包相关参数
|
|
|
windowsState: '',
|
|
|
redenvelopeData: {
|
|
|
rid: null, //红包ID
|
|
|
from: null,
|
|
|
face: null,
|
|
|
blessing: null,
|
|
|
money: null
|
|
|
}
|
|
|
};
|
|
|
},
|
|
|
created() {
|
|
|
this.checkPermission();
|
|
|
this.page_num = 1;
|
|
|
},
|
|
|
onLoad(option) {
|
|
|
this.person_id = uni.getStorageSync('person_id');
|
|
|
|
|
|
this.getMsgList();
|
|
|
//语音自然播放结束
|
|
|
this.AUDIO.onEnded((res) => {
|
|
|
this.playMsgid = null;
|
|
|
});
|
|
|
// #ifndef H5
|
|
|
manager.onStart = () => {
|
|
|
console.log('==onStart==')
|
|
|
this.recordBegin();
|
|
|
};
|
|
|
manager.onStop = (res) => {
|
|
|
console.log('==onStop==')
|
|
|
this.recordEnd(res);
|
|
|
};
|
|
|
manager.onError = (err) => {
|
|
|
this.recording = false;
|
|
|
this.willStop = false;
|
|
|
this.voiceTis = '按住 说话';
|
|
|
this.recordTis = '手指上滑 取消发送'
|
|
|
uni.showToast({
|
|
|
title: '错误(' + err.retcode + '):无法识别语音!',
|
|
|
icon: 'none',
|
|
|
duration: 2000, //持续时间为 2秒
|
|
|
})
|
|
|
};
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
},
|
|
|
onShow() {
|
|
|
// this.scrollTop = 9999999;
|
|
|
|
|
|
},
|
|
|
methods: {
|
|
|
// 接受消息(筛选处理)
|
|
|
screenMsg(msg) {
|
|
|
//从长连接处转发给这个方法,进行筛选处理
|
|
|
if (msg.type == 'system') {
|
|
|
// 系统消息
|
|
|
switch (msg.msg.type) {
|
|
|
case 'text':
|
|
|
this.addSystemTextMsg(msg);
|
|
|
break;
|
|
|
case 'redEnvelope':
|
|
|
this.addSystemRedEnvelopeMsg(msg);
|
|
|
break;
|
|
|
}
|
|
|
} else if (msg.type == 'user') {
|
|
|
// 用户消息
|
|
|
switch (msg.msg.type) {
|
|
|
case 'text':
|
|
|
this.addTextMsg(msg);
|
|
|
break;
|
|
|
case 'loading':
|
|
|
this.addTextMsg(msg);
|
|
|
break;
|
|
|
case 'voice':
|
|
|
this.addVoiceMsg(msg);
|
|
|
break;
|
|
|
case 'img':
|
|
|
this.addImgMsg(msg);
|
|
|
break;
|
|
|
case 'redEnvelope':
|
|
|
this.addRedEnvelopeMsg(msg);
|
|
|
break;
|
|
|
}
|
|
|
console.log('用户消息');
|
|
|
}
|
|
|
this.$nextTick(function () {
|
|
|
// 滚动到底
|
|
|
this.scrollToView = 'msg' + msg.msg.id
|
|
|
});
|
|
|
},
|
|
|
|
|
|
//触发滑动到顶部(加载历史信息记录)
|
|
|
loadHistory(e) {
|
|
|
if (this.isHistoryLoading) {
|
|
|
return;
|
|
|
}
|
|
|
this.page_num += 1;
|
|
|
this.loadingVisible = true;
|
|
|
this.isHistoryLoading = true;//参数作为进入请求标识,防止重复请求
|
|
|
this.scrollAnimation = false;//关闭滑动动画
|
|
|
let Viewid = this.msgList[0].msg.id;//记住第一个信息ID
|
|
|
//本地模拟请求历史记录效果
|
|
|
setTimeout(() => {
|
|
|
// 消息列表
|
|
|
let list = [
|
|
|
// { type: "user", msg: { id: 1, type: "text", time: "12:56", userinfo: { uid: 0, username: "大黑哥", face: "/static/img/face.jpg" }, content: { text: "为什么温度会相差那么大?" } } },
|
|
|
// { type: "user", msg: { id: 2, type: "text", time: "12:57", userinfo: { uid: 1, username: "售后客服008", face: "/static/img/im/face/face_2.jpg" }, content: { text: "这个是有偏差的,两个温度相差十几二十度是很正常的,如果相差五十度,那即是质量问题了。" } } },
|
|
|
// { type: "user", msg: { id: 3, type: "voice", time: "12:59", userinfo: { uid: 1, username: "售后客服008", face: "/static/img/im/face/face_2.jpg" }, content: { url: "/static/voice/1.mp3", length: "00:06" } } },
|
|
|
// { type: "user", msg: { id: 4, type: "voice", time: "13:05", userinfo: { uid: 0, username: "大黑哥", face: "/static/img/face.jpg" }, content: { url: "/static/voice/2.mp3", length: "00:06" } } },
|
|
|
]
|
|
|
|
|
|
|
|
|
getChatLog({
|
|
|
page: this.page_num,
|
|
|
page_size: this.page_size,
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
|
|
|
const messages = [];
|
|
|
res.data.forEach(item => {
|
|
|
// 添加AI回复消息
|
|
|
if (item.output_type == 1) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "voice",
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { url: item.audio_url, length: `${Math.ceil(item.duration)}''` }
|
|
|
}
|
|
|
});
|
|
|
} else if (item.output_type == 3) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "text",
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { text: item.model_response }
|
|
|
}
|
|
|
});
|
|
|
} else {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "text",
|
|
|
md: "1",
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { text: item.model_response }
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
// 添加用户消息
|
|
|
if (item.input_type == 1) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "text",
|
|
|
userinfo: { uid: 0, username: "", face: "/static/img/face.jpg" },
|
|
|
content: { text: item.user_input }
|
|
|
}
|
|
|
});
|
|
|
} else {
|
|
|
// 先用默认尺寸添加图片消息
|
|
|
const msgId = uni.$u.guid();
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg: {
|
|
|
id: msgId,
|
|
|
type: "img",
|
|
|
userinfo: { uid: 0, username: "", face: "/static/img/face.jpg" },
|
|
|
content: {
|
|
|
url: item.user_input,
|
|
|
w: item.image_width,
|
|
|
h: item.image_height
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
this.loadingVisible = false;
|
|
|
|
|
|
// 获取消息中的图片,并处理显示尺寸
|
|
|
for (let i = 0; i < messages.length; i++) {
|
|
|
if (messages[i].type == 'user' && messages[i].msg.type == "img") {
|
|
|
messages[i].msg.content = this.setPicSize(messages[i].msg.content);
|
|
|
this.msgImgList.push(messages[i].msg.content.url);
|
|
|
}
|
|
|
}
|
|
|
list = messages;
|
|
|
|
|
|
// 获取消息中的图片,并处理显示尺寸
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
if (list[i].type == 'user' && list[i].msg.type == "img") {
|
|
|
list[i].msg.content = this.setPicSize(list[i].msg.content);
|
|
|
this.msgImgList.unshift(list[i].msg.content.url);
|
|
|
}
|
|
|
list[i].msg.id = Math.floor(Math.random() * 1000 + 1);
|
|
|
this.msgList.unshift(list[i]);
|
|
|
}
|
|
|
|
|
|
//这段代码很重要,不然每次加载历史数据都会跳到顶部
|
|
|
this.$nextTick(function () {
|
|
|
this.scrollToView = 'msg' + Viewid;//跳转上次的第一行信息位置
|
|
|
this.$nextTick(function () {
|
|
|
this.scrollAnimation = true;//恢复滚动动画
|
|
|
});
|
|
|
|
|
|
});
|
|
|
this.isHistoryLoading = false;
|
|
|
}).catch((err) => {
|
|
|
console.log(err)
|
|
|
}).finally(() => {
|
|
|
// 滚动到底部
|
|
|
this.$nextTick(function () {
|
|
|
//进入页面滚动到底部
|
|
|
this.scrollTop = 9999;
|
|
|
this.$nextTick(function () {
|
|
|
this.scrollAnimation = true;
|
|
|
});
|
|
|
|
|
|
});
|
|
|
})
|
|
|
|
|
|
}, 1000)
|
|
|
},
|
|
|
// 加载初始页面消息
|
|
|
getMsgList() {
|
|
|
|
|
|
getChatLog({
|
|
|
page: this.page_num,
|
|
|
page_size: this.page_size,
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
const messages = [];
|
|
|
res.data.forEach(item => {
|
|
|
// 添加用户消息
|
|
|
if (item.input_type == 1) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "text",
|
|
|
userinfo: { uid: 0, username: "", face: "/static/img/face.jpg" },
|
|
|
content: { text: item.user_input }
|
|
|
}
|
|
|
});
|
|
|
} else {
|
|
|
// 先用默认尺寸添加图片消息
|
|
|
const msgId = uni.$u.guid();
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg: {
|
|
|
id: msgId,
|
|
|
type: "img",
|
|
|
userinfo: { uid: 0, username: "", face: "/static/img/face.jpg" },
|
|
|
content: {
|
|
|
url: item.user_input,
|
|
|
w: item.image_width,
|
|
|
h: item.image_height
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 异步获取图片信息并更新
|
|
|
// uni.getImageInfo({
|
|
|
// src: item.user_input,
|
|
|
// success: (image) => {
|
|
|
// console.log(image)
|
|
|
|
|
|
// const index = this.msgList.findIndex(msg => msg.msg.id === msgId);
|
|
|
|
|
|
// console.log(index)
|
|
|
|
|
|
// if (index !== -1) {
|
|
|
// this.msgList[index].msg.content.w = image.width;
|
|
|
// this.msgList[index].msg.content.h = image.height;
|
|
|
// }
|
|
|
// console.log(this.msgList[index].msg.content.w)
|
|
|
// }
|
|
|
// });
|
|
|
}
|
|
|
// 添加AI回复消息
|
|
|
if (item.output_type == 1) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "voice",
|
|
|
time: item.create_time,
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { url: item.audio_url, length: `${Math.ceil(item.duration)}''` }
|
|
|
}
|
|
|
});
|
|
|
} else if (item.output_type == 3) {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
type: "text",
|
|
|
time: item.create_time,
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { text: item.model_response }
|
|
|
}
|
|
|
});
|
|
|
} else {
|
|
|
messages.push({
|
|
|
type: "user",
|
|
|
msg:
|
|
|
{
|
|
|
id: uni.$u.guid(),
|
|
|
md: "1",
|
|
|
type: "text",
|
|
|
time: item.create_time,
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: { text: item.model_response }
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
// 获取消息中的图片,并处理显示尺寸
|
|
|
for (let i = 0; i < messages.length; i++) {
|
|
|
if (messages[i].type == 'user' && messages[i].msg.type == "img") {
|
|
|
messages[i].msg.content = this.setPicSize(messages[i].msg.content);
|
|
|
this.msgImgList.push(messages[i].msg.content.url);
|
|
|
}
|
|
|
}
|
|
|
this.msgList = messages;
|
|
|
|
|
|
});
|
|
|
}).catch((err) => {
|
|
|
console.log(err)
|
|
|
}).finally(() => {
|
|
|
// 滚动到底部
|
|
|
// this.$nextTick(function () {
|
|
|
// //进入页面滚动到底部
|
|
|
// this.scrollTop = 9999;
|
|
|
// this.$nextTick(function () {
|
|
|
// this.scrollAnimation = true;
|
|
|
// });
|
|
|
// });
|
|
|
setTimeout(() => {
|
|
|
this.$nextTick(() => {
|
|
|
this.scrollTop = 9999999;
|
|
|
this.$nextTick(() => {
|
|
|
this.scrollAnimation = true;
|
|
|
});
|
|
|
});
|
|
|
}, 200);
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
//处理图片尺寸,如果不处理宽高,新进入页面加载图片时候会闪
|
|
|
setPicSize(content) {
|
|
|
// 让图片最长边等于设置的最大长度,短边等比例缩小,图片控件真实改变,区别于aspectFit方式。
|
|
|
let maxW = uni.upx2px(350);//350是定义消息图片最大宽度
|
|
|
let maxH = uni.upx2px(350);//350是定义消息图片最大高度
|
|
|
if (content.w > maxW || content.h > maxH) {
|
|
|
let scale = content.w / content.h;
|
|
|
content.w = scale > 1 ? maxW : maxH * scale;
|
|
|
content.h = scale > 1 ? maxW / scale : maxH;
|
|
|
}
|
|
|
return content;
|
|
|
},
|
|
|
|
|
|
//更多功能(点击+弹出)
|
|
|
showMore() {
|
|
|
this.isVoice = false;
|
|
|
this.hideEmoji = true;
|
|
|
if (this.hideMore) {
|
|
|
this.hideMore = false;
|
|
|
this.openDrawer();
|
|
|
} else {
|
|
|
this.hideDrawer();
|
|
|
}
|
|
|
},
|
|
|
// 打开抽屉
|
|
|
openDrawer() {
|
|
|
this.popupLayerClass = 'showLayer';
|
|
|
},
|
|
|
// 隐藏抽屉
|
|
|
hideDrawer() {
|
|
|
this.popupLayerClass = '';
|
|
|
setTimeout(() => {
|
|
|
this.hideMore = true;
|
|
|
this.hideEmoji = true;
|
|
|
}, 150);
|
|
|
},
|
|
|
// 选择图片发送
|
|
|
chooseImage() {
|
|
|
this.getImage('album', '');
|
|
|
},
|
|
|
//拍照发送
|
|
|
camera() {
|
|
|
this.getImage('camera', '');
|
|
|
},
|
|
|
//选图做题
|
|
|
handAlbumQ() {
|
|
|
this.getImage('album', 'math');
|
|
|
},
|
|
|
//拍摄做题
|
|
|
handCameraQ() {
|
|
|
this.getImage('camera', 'math');
|
|
|
},
|
|
|
//选照片 or 拍照
|
|
|
getImage(type, flag) {
|
|
|
this.hideDrawer();
|
|
|
uni.chooseImage({
|
|
|
count: 1,
|
|
|
sourceType: [type],
|
|
|
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
|
|
success: (res) => {
|
|
|
let tempFilePath = res.tempFilePaths[0];
|
|
|
let fileExtension = tempFilePath.split('.').pop();
|
|
|
let keyName = uni.$u.guid() + '.' + fileExtension;
|
|
|
|
|
|
getOSSInfo().then((res) => {
|
|
|
const formData = {
|
|
|
key: 'Upload/' + keyName,
|
|
|
policy: res.policy,
|
|
|
'x-oss-signature-version': res.x_oss_signature_version,
|
|
|
'x-oss-credential': res.x_oss_credential,
|
|
|
'x-oss-date': res.x_oss_date,
|
|
|
'x-oss-signature': res.signature,
|
|
|
'x-oss-security-token': res.security_token,
|
|
|
success_action_status: "200"
|
|
|
};
|
|
|
|
|
|
uni.uploadFile({
|
|
|
url: config.OSS_HOST,
|
|
|
filePath: tempFilePath,
|
|
|
name: 'file',
|
|
|
formData: formData,
|
|
|
success: (uploadFileRes) => {
|
|
|
uni.getImageInfo({
|
|
|
src: tempFilePath,
|
|
|
success: (image) => {
|
|
|
let msg = { url: config.OSS_HOST + '/' + formData.key, w: image.width, h: image.height };
|
|
|
this.sendMsg(msg, 'img', flag);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
|
|
|
}).catch((err) => {
|
|
|
uni.showToast({
|
|
|
title: '获取签名失败!',
|
|
|
icon: 'none',
|
|
|
duration: 2000, //持续时间为 2秒
|
|
|
})
|
|
|
});
|
|
|
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
// 选择表情
|
|
|
chooseEmoji() {
|
|
|
this.hideMore = true;
|
|
|
if (this.hideEmoji) {
|
|
|
this.hideEmoji = false;
|
|
|
this.openDrawer();
|
|
|
} else {
|
|
|
this.hideDrawer();
|
|
|
}
|
|
|
},
|
|
|
//添加表情
|
|
|
addEmoji(em) {
|
|
|
this.textMsg += em.alt;
|
|
|
},
|
|
|
|
|
|
//获取焦点,如果不是选表情ing,则关闭抽屉
|
|
|
textareaFocus() {
|
|
|
if (this.popupLayerClass == 'showLayer' && this.hideMore == false) {
|
|
|
this.hideDrawer();
|
|
|
}
|
|
|
},
|
|
|
// 发送文字消息
|
|
|
sendText() {
|
|
|
this.hideDrawer();//隐藏抽屉
|
|
|
if (!this.textMsg) {
|
|
|
return;
|
|
|
}
|
|
|
let content = this.replaceEmoji(this.textMsg);
|
|
|
let msg = { text: this.textMsg }
|
|
|
this.sendMsg(msg, 'text', '');
|
|
|
this.textMsg = '';//清空输入框
|
|
|
},
|
|
|
//替换表情符号为图片
|
|
|
replaceEmoji(str) {
|
|
|
let replacedStr = str.replace(/\[([^(\]|\[)]*)\]/g, (item, index) => {
|
|
|
for (let i = 0; i < this.emojiList.length; i++) {
|
|
|
let row = this.emojiList[i];
|
|
|
for (let j = 0; j < row.length; j++) {
|
|
|
let EM = row[j];
|
|
|
if (EM.alt == item) {
|
|
|
let onlinePath = 'https://s2.ax1x.com/2019/04/12/'
|
|
|
let imgstr = '<img src="' + onlinePath + this.onlineEmoji[EM.url] + '">';
|
|
|
return imgstr;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
return '<div style="display: flex;align-items: center;word-wrap:break-word;">' + replacedStr + '</div>';
|
|
|
},
|
|
|
|
|
|
sendMsg(content, type, flag) {
|
|
|
let lastid = uni.$u.guid();
|
|
|
let msg = { type: 'user', msg: { id: lastid, type: type, userinfo: { uid: 0, username: "", face: "/static/img/face.jpg" }, content: content } }
|
|
|
// 发送消息
|
|
|
this.screenMsg(msg);
|
|
|
|
|
|
// 先发送一个加载状态的消息(使用新类型 'loading')
|
|
|
let loadingMsgId = uni.$u.guid();
|
|
|
let loadingMsg = {
|
|
|
type: 'user',
|
|
|
msg: {
|
|
|
id: loadingMsgId,
|
|
|
type: 'loading', // 新的消息类型
|
|
|
userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" },
|
|
|
content: {} // 内容可以为空,因为我们将在模板中直接渲染加载动画
|
|
|
}
|
|
|
}
|
|
|
this.screenMsg(loadingMsg);
|
|
|
|
|
|
if (type == 'text') {
|
|
|
getAIReply({
|
|
|
prompt: content.text,
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
// 找到加载消息的索引
|
|
|
const loadingIndex = this.msgList.findIndex(item => item.msg.id === loadingMsgId);
|
|
|
if (loadingIndex !== -1) {
|
|
|
// 替换加载消息为实际回复
|
|
|
this.msgList.splice(loadingIndex, 1);
|
|
|
let _id = uni.$u.guid();
|
|
|
msg = { type: 'user', msg: { id: _id, type: 'voice', userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" }, content: { url: res.url, length: `${Math.ceil(res.duration)}''` } } }
|
|
|
// 发送实际回复消息
|
|
|
this.screenMsg(msg);
|
|
|
}
|
|
|
|
|
|
}).catch((err) => {
|
|
|
const loadingIndex = this.msgList.findIndex(item => item.msg.id === loadingMsgId);
|
|
|
if (loadingIndex !== -1) {
|
|
|
// 替换加载消息为实际回复
|
|
|
this.msgList.splice(loadingIndex, 1);
|
|
|
let _id = uni.$u.guid();
|
|
|
msg = { type: 'user', msg: { id: _id, type: 'text', userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" }, content: { text: '抱歉,获取回复失败,请重试' } } }
|
|
|
// 发送实际回复消息
|
|
|
this.screenMsg(msg);
|
|
|
}
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
let that = this;
|
|
|
|
|
|
const index = this.msgList.findIndex(msg => msg.msg.id === loadingMsgId);
|
|
|
|
|
|
let _path = '/aichat/recognize_content?image_url='
|
|
|
|
|
|
if (flag == 'math') {
|
|
|
_path = '/aichat/recognize_math?image_url='
|
|
|
}
|
|
|
|
|
|
console.log(config.BASE_URL + _path + content.url)
|
|
|
|
|
|
const requestTask = uni.request({
|
|
|
|
|
|
url: config.BASE_URL + _path + content.url,
|
|
|
method: 'GET',
|
|
|
timeout: 90000,
|
|
|
enableChunked: true, // 开启流式传输
|
|
|
responseType: 'text', // 指定响应的数据类型
|
|
|
header: {
|
|
|
Authorization: 'Bearer ' + uni.getStorageSync('jwt'),
|
|
|
},
|
|
|
success: (res) => {
|
|
|
console.log(this.msgList);
|
|
|
// 处理完整的响应数据
|
|
|
this.$nextTick(function () {
|
|
|
//进入页面滚动到底部
|
|
|
this.scrollTop = 9999;
|
|
|
this.$nextTick(function () {
|
|
|
this.scrollAnimation = true;
|
|
|
});
|
|
|
|
|
|
});
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
// 处理错误情况
|
|
|
}
|
|
|
});
|
|
|
|
|
|
let accumulatedText = ""; // 存储未处理的流数据
|
|
|
|
|
|
|
|
|
requestTask.onHeadersReceived(function (res) {
|
|
|
console.log(res);
|
|
|
// 出现错误时返回
|
|
|
const loadingIndex = that.msgList.findIndex(item => item.msg.id === loadingMsgId);
|
|
|
if (loadingIndex !== -1) {
|
|
|
// 替换加载消息为实际回复
|
|
|
that.msgList.splice(loadingIndex, 1);
|
|
|
let _id = uni.$u.guid();
|
|
|
msg = { type: 'user', msg: { id: _id, type: 'text', userinfo: { uid: 1, username: "芽芽星语", face: "/static/img/im/face/face_2.jpg" }, content: { text: '抱歉,获取回复失败,请重试' } } }
|
|
|
// 发送实际回复消息
|
|
|
that.screenMsg(msg);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
requestTask.onChunkReceived(function (res) {
|
|
|
// 处理接收到的数据块
|
|
|
// let decoder = new TextDecoder('utf-8');
|
|
|
// let chunkText = decoder.decode(new Uint8Array(res.data));
|
|
|
let chunkText = that.arrayBufferToStringManual(res.data);
|
|
|
accumulatedText += chunkText;
|
|
|
|
|
|
that.msgList[index].msg.type = 'text';
|
|
|
|
|
|
if (flag == 'math') {
|
|
|
that.msgList[index].msg.md = '1';
|
|
|
}
|
|
|
|
|
|
// that.msgList[index].msg.content.text = accumulatedText;
|
|
|
that.$set(that.msgList[index].msg.content, 'text', accumulatedText);
|
|
|
|
|
|
// 使用双重延迟确保渲染完成后滚动
|
|
|
setTimeout(() => {
|
|
|
that.$nextTick(() => {
|
|
|
// 强制重新计算布局后滚动
|
|
|
that.scrollTop = that.scrollTop + 100;
|
|
|
that.$nextTick(() => {
|
|
|
this.scrollAnimation = true;
|
|
|
that.scrollTop = 999999;
|
|
|
});
|
|
|
});
|
|
|
}, 10);
|
|
|
|
|
|
});
|
|
|
}
|
|
|
|
|
|
},
|
|
|
arrayBufferToStringManual(buffer) {
|
|
|
const uint8Array = new Uint8Array(buffer);
|
|
|
let str = '';
|
|
|
let i = 0;
|
|
|
while (i < uint8Array.length) {
|
|
|
const byte1 = uint8Array[i++];
|
|
|
if (byte1 < 0x80) {
|
|
|
// 单字节字符(ASCII)
|
|
|
str += String.fromCharCode(byte1);
|
|
|
} else if (byte1 >= 0xC0 && byte1 < 0xE0) {
|
|
|
// 双字节字符
|
|
|
const byte2 = uint8Array[i++];
|
|
|
str += String.fromCharCode(((byte1 & 0x1F) << 6) | (byte2 & 0x3F));
|
|
|
} else if (byte1 >= 0xE0 && byte1 < 0xF0) {
|
|
|
// 三字节字符(中文字符通常在这里)
|
|
|
const byte2 = uint8Array[i++];
|
|
|
const byte3 = uint8Array[i++];
|
|
|
str += String.fromCharCode(((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F));
|
|
|
} else if (byte1 >= 0xF0 && byte1 < 0xF8) {
|
|
|
// 四字节字符
|
|
|
const byte2 = uint8Array[i++];
|
|
|
const byte3 = uint8Array[i++];
|
|
|
const byte4 = uint8Array[i++];
|
|
|
str += String.fromCharCode(((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F));
|
|
|
}
|
|
|
}
|
|
|
return str;
|
|
|
},
|
|
|
|
|
|
// 添加文字消息到列表
|
|
|
addTextMsg(msg) {
|
|
|
this.msgList.push(msg);
|
|
|
},
|
|
|
// 添加语音消息到列表
|
|
|
addVoiceMsg(msg) {
|
|
|
this.msgList.push(msg);
|
|
|
},
|
|
|
// 添加图片消息到列表
|
|
|
addImgMsg(msg) {
|
|
|
msg.msg.content = this.setPicSize(msg.msg.content);
|
|
|
this.msgImgList.push(msg.msg.content.url);
|
|
|
this.msgList.push(msg);
|
|
|
},
|
|
|
addRedEnvelopeMsg(msg) {
|
|
|
this.msgList.push(msg);
|
|
|
},
|
|
|
// 添加系统文字消息到列表
|
|
|
addSystemTextMsg(msg) {
|
|
|
this.msgList.push(msg);
|
|
|
},
|
|
|
sendSystemMsg(content, type) {
|
|
|
let lastid = this.msgList[this.msgList.length - 1].msg.id;
|
|
|
lastid++;
|
|
|
let row = { type: "system", msg: { id: lastid, type: type, content: content } };
|
|
|
this.screenMsg(row)
|
|
|
},
|
|
|
// 预览图片
|
|
|
showPic(msg) {
|
|
|
uni.previewImage({
|
|
|
indicator: "none",
|
|
|
current: msg.content.url,
|
|
|
urls: this.msgImgList
|
|
|
});
|
|
|
},
|
|
|
// 播放语音
|
|
|
playVoice(msg) {
|
|
|
this.playMsgid = msg.id;
|
|
|
this.AUDIO.src = msg.content.url;
|
|
|
this.$nextTick(function () {
|
|
|
this.AUDIO.play();
|
|
|
});
|
|
|
},
|
|
|
// 录音开始
|
|
|
voiceBegin(e) {
|
|
|
// 记录开始时间戳
|
|
|
this.voiceStartTime = Date.now();
|
|
|
if (e.touches.length > 1) {
|
|
|
return;
|
|
|
}
|
|
|
this.initPoint.Y = e.touches[0].clientY;
|
|
|
this.initPoint.identifier = e.touches[0].identifier;
|
|
|
if (!this.permisionState) {
|
|
|
this.checkPermission();
|
|
|
return;
|
|
|
}
|
|
|
manager.start({
|
|
|
duration: 60000,
|
|
|
lang: "zh_CN"
|
|
|
});
|
|
|
},
|
|
|
//录音开始UI效果
|
|
|
recordBegin() {
|
|
|
this.recording = true;
|
|
|
this.voiceTis = '松开 结束';
|
|
|
this.recordLength = 0;
|
|
|
this.recordTimer = setInterval(() => {
|
|
|
this.recordLength++;
|
|
|
}, 1000)
|
|
|
},
|
|
|
// 录音被打断
|
|
|
voiceCancel() {
|
|
|
this.recording = false;
|
|
|
this.voiceTis = '按住 说话';
|
|
|
this.recordTis = '手指上滑 取消发送'
|
|
|
this.willStop = true;//不发送录音
|
|
|
manager.stop();
|
|
|
},
|
|
|
// 录音中(判断是否触发上滑取消发送)
|
|
|
voiceIng(e) {
|
|
|
if (!this.recording) {
|
|
|
return;
|
|
|
}
|
|
|
let touche = e.touches[0];
|
|
|
//上滑一个导航栏的高度触发上滑取消发送
|
|
|
if (this.initPoint.Y - touche.clientY >= uni.upx2px(100)) {
|
|
|
this.willStop = true;
|
|
|
this.recordTis = '松开手指 取消发送'
|
|
|
} else {
|
|
|
this.willStop = false;
|
|
|
this.recordTis = '手指上滑 取消发送'
|
|
|
}
|
|
|
},
|
|
|
// 结束录音
|
|
|
voiceEnd(e) {
|
|
|
this.voiceDuration = Date.now() - this.voiceStartTime;
|
|
|
console.log('语音时长:', this.voiceDuration, '毫秒');
|
|
|
// if(this.voiceDuration<1000){
|
|
|
// uni.showToast({
|
|
|
// title: '录音时间太短',
|
|
|
// icon: 'none'
|
|
|
// });
|
|
|
// this.recording = false;
|
|
|
// this.willStop = false;
|
|
|
// this.voiceTis = '按住 说话';
|
|
|
// this.recordTis = '手指上滑 取消发送'
|
|
|
// return;
|
|
|
// }
|
|
|
if (!this.recording) {
|
|
|
return;
|
|
|
}
|
|
|
this.recording = false;
|
|
|
this.voiceTis = '按住 说话';
|
|
|
this.recordTis = '手指上滑 取消发送'
|
|
|
manager.stop();
|
|
|
},
|
|
|
//录音结束(回调文件)
|
|
|
recordEnd(res) {
|
|
|
clearInterval(this.recordTimer);
|
|
|
if (!this.willStop) {
|
|
|
// let content = this.replaceEmoji(res.result);
|
|
|
let msg = { text: res.result }
|
|
|
this.sendMsg(msg, 'text', '');
|
|
|
} else {
|
|
|
console.log('取消发送录音');
|
|
|
}
|
|
|
this.willStop = false;
|
|
|
},
|
|
|
// 切换语音/文字输入
|
|
|
switchVoice() {
|
|
|
this.hideDrawer();
|
|
|
this.isVoice = this.isVoice ? false : true;
|
|
|
},
|
|
|
discard() {
|
|
|
return;
|
|
|
},
|
|
|
async checkPermission() {
|
|
|
var that = this;
|
|
|
// #ifdef APP-PLUS
|
|
|
// 先判断os
|
|
|
let os = uni.getSystemInfoSync().osName;
|
|
|
if (os == 'ios') {
|
|
|
this.permisionState = await permision.judgeIosPermission('record');
|
|
|
} else {
|
|
|
this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
|
|
|
}
|
|
|
if (this.permisionState !== true && this.permisionState !== 1) {
|
|
|
uni.showToast({
|
|
|
title: '请先授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
uni.authorize({
|
|
|
scope: 'scope.record',
|
|
|
success(e) {
|
|
|
that.permisionState = true;
|
|
|
},
|
|
|
fail() {
|
|
|
uni.showToast({
|
|
|
title: '请授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
// #endif
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
<style lang="scss">
|
|
|
@import "./style.scss";
|
|
|
</style> |