使用wx.request的enableChunked: true,实现流式传输,通过定时器实现聊天交互效果
UI框架使用vant-weapp
重点问题:
1.在流式请求时因为延迟显示打字效果,导致流式数据返回后处理顺序错乱,我们需要一个缓冲队列来保存这些数据
2.输入每个字的时候需要调用页面滚动来展示数据,这时候,需要做节流处理,否则会出现scrollTop已经赋值了,但是dom还没加载完成,导致滚动不到位
1.主聊天界面
流式请求
// pages/chatGptDemo/chatGptDemo.js
import chatRequest from './chatRequest'
import { CDN_PREFIX } from '../../utils/key'
import Dialog from '../../miniprogram/miniprogram_npm/@vant/weapp/dialog/dialog';
import { formatTime} from'../../utils/util';
import local from '../../utils/local';
import {apiGetChatList,apiDeleteDreamChat,apiGetChatUserInfo} from '../../http/api'
const { navigation } = require("../../utils/navigation");
const app = getApp()
const {
events,
userInfo,
} = app.globalData
//自己维护消息队列 重点******
let isProcessing = false;
let queue = [];
Page({
/**
* 页面的初始数据
*/
data: {
navigationBarHeight: 0,
scrollTop: 0,
inputValue: "",
valueTemp: "",
isFirstSend: false,
valueList: [],
inputDisabled:false,
focus:false,
enableAnimation:true,
throttleScroll:true,
// 分页
triggered: false, //向上刷新load
page: 0,
page_size: 10,
lastPageId:0,//锚链接
showViewIdName:'',
//解梦模板
dreamModuleVisable: false,
dreamModuleAnimate:false,
//进度条
progress:{
total_num:20,
remain_num:0,
user_type:'free',
perce:0,
}
},
//显示欢迎语
showWelcomeMsg(){
let list = []
let nickName = userInfo.nickname ? userInfo.nickname+',':''
let msg = `你好,${nickName}我是布咕,可以帮你解梦,也可以陪你聊天,你想聊些什么吗?`
list.push({
type: 'left',
value: msg
})
setTimeout(()=>{
this.setData({
valueList:list
})
},500)
},
//滚到到指定ID
autoScrollId(){
this.setData({
showViewIdName:'item'+this.data.lastPageId
},()=>{
this.setData({
enableAnimation:true
})
})
},
//滚动条至最底部
autoScroll() {
if (!this.data.throttleScroll) return;
this.data.throttleScroll = false;
setTimeout(() => {
this.data.throttleScroll = true;
}, 500);
var that = this
let query = wx.createSelectorQuery()
query.select('.chat-value').boundingClientRect(res => {
that.setData({
scrollTop: res.height*10,
})
})
query.exec(res => {})
},
//清除历史记录
onclear() {
Dialog.confirm({
title: '删除聊天记录',
message: '确定要删除聊天记录吗?\n删除后不可查看历史聊天记录',
confirmButtonText:'删除'
})
.then(async() => {
const res = await apiDeleteDreamChat()
if(res.code==200){
local.setSync('welcomeTime',JSON.stringify(0))
this.setData({
valueList: [],
valueTemp: '',
page:0
});
}
})
.catch(() => {
// on cancel
});
},
//没有机会弹窗
onBalance(){
Dialog.confirm({
title: '提示',
message: '免费使用次数已用完\n点击前往获取更多次数',
confirmButtonText:'获取更多'
})
.then(() => {
this.routeToCenter()
})
.catch(() => {
// on cancel
});
},
//点击重新加载
resendOutputMsg() {
this.resendInputMsg()
},
//点击重新发送
resendInputMsg() {
let list = this.data.valueList
let lastItem = list.reduce((prev, curr) => {
if (curr.type === 'right') {
return curr
}
return prev
}, {})
list.splice(list.length-2,1)
if (lastItem.value) {
this.setData({
inputValue: lastItem.value,
valueList: list
}, () => {
this.onSearchBaidu()
})
}
},
clickInputView(){
this.setData({
focus:true
})
},
//创建发送消息
createSendMsg() {
let list = this.data.valueList
list.push({
type: 'right',
value: this.data.inputValue,
time:formatTime(new Date(),'yyyy-MM-dd HH:mm:ss')
})
list =this.addShowTime(list)
this.setData({
valueList: list,
inputValue: '',
},()=>{
this.autoScroll()
setTimeout(()=>{
this.createLoadingMsg()
},500)
})
},
//创建回复空消息
createLoadingMsg(){
let list = this.data.valueList
list.push({
type: 'left',
value: ''
})
this.setData({
valueList: list,
},()=>{
this.autoScroll()
})
},
//创建回复消息
async sendBotMsg(data) {
await new Promise((resolve, reject) => {
let str = data.content
let is_end = data.is_end
const contentCharArr = Array.from(str);
this.showText(0, contentCharArr,is_end,resolve);
});
},
//打字效果
showText(key = 0, value,is_end,resolve) {
if (key >= value.length) {
if(is_end){
this.setData({
inputDisabled:false
})
}
resolve();
return;
}
this.setData({
valueTemp: this.data.valueTemp + value[key]
}, () => {
let itemlist = this.data.valueList
let index = this.data.valueList.length - 1
let tempStr = itemlist[index]
tempStr.value = this.data.valueTemp
console.log( this.data.valueTemp)
this.setData({
[`valueList[${index}]`]: tempStr,
})
setTimeout(() => {
/* 递归渲染内容 */
this.showText(key + 1, value,is_end,resolve);
this.autoScroll()
}, 50);
})
},
//清空所有的异常消息
onClearErrMsg() {
let array = this.data.valueList
let errorCodes = [1, 2]
let list = array.filter(item => {
return !errorCodes.includes(item.errType)
})
this.setData({
valueList: list
})
},
//模板解梦
templateDream(e){
if(e.detail){
this.setData({
inputValue:e.detail
},()=>{
wx.showLoading({
title: '正在整合信息',
mask:true
})
setTimeout(()=>{
this.onSearchBaidu()
wx.hideLoading()
},500)
})
}
},
//发送消息
onSearchBaidu() {
if(this.data.progress.remain_num<1){
//次数用完
this.onBalance()
return
}
this.getDecCount()
let inputValue = this.data.inputValue.trim()
if(!inputValue){
wx.showToast({
title: '请输入内容',
icon:'none'
})
return
}
let welcomeTime = new Date().getTime()
local.setSync('welcomeTime',JSON.stringify(welcomeTime))
this.onClearErrMsg()
this.setData({
isFirstSend: true,
inputDisabled:true,
valueTemp: ''
})
let params = {
content: inputValue
}
this.createSendMsg()
setTimeout(()=>{
this.streamingRequests(params)
},1000)
},
//处理消息队列 难点******
processQueue() {
if(queue.length === 0) {
return;
}
const res = queue.shift();
return this.doProcess(res).then(() => {
return this.processQueue();
});
},
async doProcess(res) {
return new Promise(async resolve => {
let str = this.Uint8ArrayToStringBaidu(res.data)
let arr = str.split(':dream-answer')
for(let item of arr) {
if(item) {
try {
console.debug(JSON.parse(item))
let {code,data,msg} = JSON.parse(item)
if (code == 200) {
await this.sendBotMsg(data)
} else {
this.handleError(code,msg)
}
} catch (error) {
console.error(error)
this.setData({
inputDisabled:false,
})
console.log('报错了')
this.handleError(500,'数据错误,请联系客服')
}
}
}
resolve();
})
},
//流式请求
streamingRequests(params){
//重点 流处理的时候可能会丢失数据。我们需要一个缓冲队列来保存这些数据
const requestTask = chatRequest(params, '/api/dreamChat/crateStreamChat')
requestTask.onChunkReceived( (res) => {
queue.push(res);
if(!isProcessing) {
isProcessing = true;
this.processQueue().then(() => {
isProcessing = false;
});
}
})
},
//流式解析数据
Uint8ArrayToStringBaidu(fileData) {
const str = decodeURIComponent(escape(String.fromCharCode.apply(null, new Uint8Array(fileData))))
return str
},
//错误信息处理
handleError(code, msg) {
if (!this.data.isFirstSend) {
this.getChatUserInfo()
return
}
if (code == 422) {
//报错重新发送
let itemlist = this.data.valueList
//errType 0 正常 1重新发送 2重新加载
itemlist[this.data.valueList.length - 1].errType =2
this.setData({
valueList: itemlist,
isFirstSend: false,
}, () => {
this.autoScroll()
})
}else if(code==423){
//敏感消息报错
let itemlist = this.data.valueList
itemlist = itemlist.slice(0, -2);
this.setData({
valueList: itemlist,
isFirstSend: false
}, () => {
this.autoScroll()
})
} else {
//报错重新发送
let itemlist = this.data.valueList
itemlist = this.removeLastLeft(itemlist)
//errType 0 正常 1重新发送 2重新加载
itemlist[this.data.valueList.length - 1].errType = 1
this.setData({
valueList: itemlist,
isFirstSend: false
}, () => {
this.autoScroll()
})
}
wx.showToast({
title: msg,
icon: 'none'
})
this.setData({
inputDisabled:false,
})
},
//报错删除最后一个回复
removeLastLeft(arr) {
let lastIndex = -1;
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i].type === 'left') {
lastIndex = i;
break;
}
}
if (lastIndex !== -1) {
arr.splice(lastIndex, 1);
}
return arr;
},
//手动减次数
getDecCount(){
let progress = this.data.progress
if(progress.user_type!=='mouth'){
let user_type = progress.user_type
let total_num = progress.total_num ||0
let remain_num = progress.remain_num - 1 || 0
let perce = Math.round(remain_num * 100 / total_num)
let obj = {
total_num,
remain_num,
user_type,
perce,
}
this.setData({
progress:obj
})
}
},
//下拉加载
async bindrefresherrefresh() {
this.data.page++
await this.getChatHistoryList()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let navigationBarHeight = getApp().globalNavbarInfo.navigationBarHeight
this.setData({
navigationBarHeight,
})
// 监听微信登录成功
events.on('on-launch-executed', this, async res => {
this.initialData()
this.getChatUserInfo()
})
this.initialData()
},
initialData(){
let welcome = local.getSync('welcomeTime')
if(!welcome){
this.showWelcomeMsg()
}else{
let welcomeTime = Number(JSON.parse(local.getSync('welcomeTime')))
let dayTime = new Date().getTime();
if(dayTime - welcomeTime < 24*60*60*1000){
this.data.page=1
this.getChatHistoryList()
}else{
this.showWelcomeMsg()
}
}
},
//用户信息
async getChatUserInfo(){
const {data,code} = await apiGetChatUserInfo()
if(code!==200)return
let user_type = 'free'//免费用户
let total_num = 20//总次数
let remain_num =0 //剩余次数
let card_status=data.card_status//会员卡状态
if(card_status==1){
//会员用户
if(data.once_card && data.once_card.remain_num>0){
//次卡用户
user_type ='once'
total_num = data.once_card.total_num
remain_num = data.once_card.remain_num
}
if(data.month_card && data.month_card.remain_num>0){
//月卡用户
user_type ='mouth'
total_num = data.month_card.total_num
remain_num = data.month_card.remain_num
}
}else{
remain_num =data.dream_free
}
let perce = Math.round(remain_num * 100 / total_num)
let progress = {
total_num,
remain_num,
user_type,
perce,
}
this.setData({
progress
})
},
//聊天记录
async getChatHistoryList() {
let page = this.data.page
let params = {
page,
size: this.data.page_size,
}
const res = await apiGetChatList(params)
if (res.code == 200) {
let result =[ ]
if(res.data.data&&res.data.data.length>0){
result = res.data.data.map(message => {
return {
id:message.id,
value: message.content,
time: message.created_at,
type: message.user_type === 1 ? 'right' : 'left'
};
});
}
let resData = [];
if (page == 1) {
resData = this.data.valueList;
} else {
if(this.data.valueList[0] &&this.data.valueList[0].id){
this.data.lastPageId = this.data.valueList[0].id
}
resData = this.data.valueList
}
if (result.length == 0) {
page -= 1
if (page == 0){
page = 1
}
//首次不提示
wx.showToast({
title: '没有更多了~',
icon: "none"
})
this.setData({
triggered: false,
page
})
return
}
resData = result.reverse().concat(resData);
const listWithShowTime = this.addShowTime(resData)
this.setData({
page,
valueList: listWithShowTime,
triggered: false,
},()=>{
if (this.data.lastPageId!==0){
// 向上分页锚链接
this.setData({
enableAnimation:false
},()=>{
this.autoScrollId()
})
}else{
this.autoScroll()
}
})
}
},
//时间转换
addShowTime(list) {
let prevTime = null
list.forEach(item => {
const currTime = new Date(item.time).getTime()
if (prevTime) {
const diff = (currTime - prevTime) / (1000 * 60 * 60)
if (diff > 1) {
item.showTime = true
} else {
item.showTime = false
}
} else {
item.showTime = true
}
prevTime = currTime
})
return list
},
//打开解梦模板
openDreamModule() {
this.setData({
dreamModuleVisable: true,
dreamModuleAnimate:true,
})
},
//关闭解梦模板
closeDreamModule() {
this.setData({
dreamModuleVisable: false
},()=>{
setTimeout(()=>{
this.setData({
dreamModuleAnimate:false,
})
},300)
})
},
routeToCenter(){
navigation.push({
url: '/pages/member/member',
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.getChatUserInfo()
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
events.remove('on-launch-executed', this)
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
let imageUrl = `${CDN_PREFIX}dream_share_img.png`
return {
title: '聊天解梦样样精通,快来试试吧',
path: `/pages/chatDream/chatDream`,
imageUrl:imageUrl
}
}
})
JS
// pages/chatGptDemo/chatGptDemo.js
import chatRequest from './chatRequest'
import { CDN_PREFIX } from '../../utils/key'
import Dialog from '../../miniprogram/miniprogram_npm/@vant/weapp/dialog/dialog';
import { formatTime} from'../../utils/util';
import local from '../../utils/local';
import {apiGetChatList,apiDeleteDreamChat,apiGetChatUserInfo} from '../../http/api'
const { navigation } = require("../../utils/navigation");
const app = getApp()
const {
events,
userInfo,
} = app.globalData
Page({
/**
* 页面的初始数据
*/
data: {
navigationBarHeight: 0,
scrollTop: 0,
inputValue: "",
valueTemp: "",
isFirstSend: false,
valueList: [],
inputDisabled:false,
focus:false,
enableAnimation:true,
// 分页
triggered: false, //向上刷新load
page: 1,
page_size: 10,
lastPageId:0,//锚链接
showViewIdName:'',
//解梦模板
dreamModuleVisable: false,
dreamModuleAnimate:false,
//进度条
progress:{
total_num:20,
remain_num:0,
user_type:'free',
perce:0,
}
},
//显示欢迎语
showWelcomeMsg(){
let list = []
let nickName = userInfo.nickname ? userInfo.nickname+',':''
let msg = `你好,${nickName}我是小羊,可以帮你解梦,也可以陪你聊天,你想聊些什么吗?`
list.push({
type: 'left',
value: msg
})
setTimeout(()=>{
this.setData({
valueList:list
})
},500)
},
//滚到到指定ID
autoScrollId(){
this.setData({
showViewIdName:'item'+this.data.lastPageId
},()=>{
this.setData({
enableAnimation:true
})
})
},
//滚动条至最底部
autoScroll() {
var that = this
let query = wx.createSelectorQuery()
query.select('.value-scroll').boundingClientRect(res => {
//延迟滚动,否则不生效
that.setData({
scrollTop: res.height*100
})
})
query.exec(res => {})
},
//清除历史记录
onclear() {
Dialog.confirm({
title: '删除聊天记录',
message: '确定要删除聊天记录吗?\n删除后不可查看历史聊天记录',
confirmButtonText:'删除'
})
.then(async() => {
const res = await apiDeleteDreamChat()
if(res.code==200){
local.setSync('welcomeTime',JSON.stringify(0))
this.setData({
valueList: [],
valueTemp: '',
page:0
});
}
})
.catch(() => {
// on cancel
});
},
//没有机会弹窗
onBalance(){
Dialog.confirm({
title: '提示',
message: '免费使用次数已用完\n点击前往获取更多次数',
confirmButtonText:'获取更多'
})
.then(() => {
this.routeToCenter()
})
.catch(() => {
// on cancel
});
},
//点击重新加载
resendOutputMsg() {
this.resendInputMsg()
},
//点击重新发送
resendInputMsg() {
let list = this.data.valueList
let lastItem = list.reduce((prev, curr) => {
if (curr.type === 'right') {
return curr
}
return prev
}, {})
list.splice(list.length-2,1)
if (lastItem.value) {
this.setData({
inputValue: lastItem.value,
valueList: list
}, () => {
this.onSearchBaidu()
})
}
},
clickInputView(){
this.setData({
focus:true
})
},
//创建发送消息
createSendMsg() {
let list = this.data.valueList
list.push({
type: 'right',
value: this.data.inputValue,
time:formatTime(new Date(),'yyyy-MM-dd HH:mm:ss')
})
list =this.addShowTime(list)
console.log(list)
this.setData({
valueList: list,
inputValue: '',
},()=>{
this.autoScroll()
setTimeout(()=>{
this.createLoadingMsg()
},500)
})
},
//创建回复空消息
createLoadingMsg(){
let list = this.data.valueList
list.push({
type: 'left',
value: ''
})
this.setData({
valueList: list,
},()=>{
this.autoScroll()
})
},
//创建回复消息
sendBotMsg(data) {
let str = data.content
this.setData({
valueTemp: this.data.valueTemp + str
}, () => {
let itemlist = this.data.valueList
itemlist[this.data.valueList.length - 1].value = this.data.valueTemp
this.setData({
valueList: itemlist,
}, () => {
this.autoScroll()
if(data.is_end){
this.setData({
inputDisabled:false
})
}
})
})
},
//清空所有的异常消息
onClearErrMsg() {
let array = this.data.valueList
let errorCodes = [1, 2]
let list = array.filter(item => {
return !errorCodes.includes(item.errType)
})
this.setData({
valueList: list
})
},
//模板解梦
templateDream(e){
if(e.detail){
this.setData({
inputValue:e.detail
},()=>{
wx.showLoading({
title: '正在整合信息',
mask:true
})
setTimeout(()=>{
this.onSearchBaidu()
wx.hideLoading()
},500)
})
}
},
//发送消息
onSearchBaidu() {
if(this.data.progress.remain_num<1){
//次数用完
this.onBalance()
return
}
this.getDecCount()
let inputValue = this.data.inputValue.trim()
if(!inputValue){
wx.showToast({
title: '请输入内容',
icon:'none'
})
return
}
let welcomeTime = new Date().getTime()
local.setSync('welcomeTime',JSON.stringify(welcomeTime))
this.onClearErrMsg()
this.setData({
isFirstSend: true,
inputDisabled:true,
valueTemp: ''
})
let params = {
content: inputValue
}
this.createSendMsg()
setTimeout(()=>{
this.streamingRequests(params)
},1000)
},
//流式请求
streamingRequests(params){
const requestTask = chatRequest(params, '/api/dreamChat/crateStreamChat')
requestTask.onChunkReceived((res) => {
let str = this.Uint8ArrayToStringBaidu(res.data)
let arr = str.split(':dream-answer')
arr.map(item => {
if (item) {
console.log(item)
try {
console.log(JSON.parse(item))
let {code,data,msg} = JSON.parse(item)
if (code == 200) {
this.sendBotMsg(data)
} else {
this.handleError(code,msg)
}
} catch (error) {
console.error(error)
this.setData({
inputDisabled:false,
})
console.log('报错了')
this.handleError(500,'数据错误,请联系客服')
}
}
})
})
},
//流式解析数据
Uint8ArrayToStringBaidu(fileData) {
const str = decodeURIComponent(escape(String.fromCharCode.apply(null, new Uint8Array(fileData))))
return str
},
//错误信息处理
handleError(code, msg) {
if (!this.data.isFirstSend) {
this.getChatUserInfo()
return
}
if (code == 422) {
//报错重新发送
let itemlist = this.data.valueList
//errType 0 正常 1重新发送 2重新加载
itemlist[this.data.valueList.length - 1].errType =2
this.setData({
valueList: itemlist,
isFirstSend: false,
}, () => {
this.autoScroll()
})
}else if(code==423){
//敏感消息报错
let itemlist = this.data.valueList
itemlist = itemlist.slice(0, -2);
this.setData({
valueList: itemlist,
isFirstSend: false
}, () => {
this.autoScroll()
})
} else {
//报错重新发送
let itemlist = this.data.valueList
itemlist = this.removeLastLeft(itemlist)
//errType 0 正常 1重新发送 2重新加载
itemlist[this.data.valueList.length - 1].errType = 1
this.setData({
valueList: itemlist,
isFirstSend: false
}, () => {
this.autoScroll()
})
}
wx.showToast({
title: msg,
icon: 'none'
})
this.setData({
inputDisabled:false,
})
},
//报错删除最后一个回复
removeLastLeft(arr) {
let lastIndex = -1;
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i].type === 'left') {
lastIndex = i;
break;
}
}
if (lastIndex !== -1) {
arr.splice(lastIndex, 1);
}
return arr;
},
//手动减次数
getDecCount(){
let progress = this.data.progress
if(progress.user_type!=='mouth'){
let user_type = progress.user_type
let total_num = progress.total_num ||0
let remain_num = progress.remain_num - 1 || 0
let perce = Math.round(remain_num * 100 / total_num)
let obj = {
total_num,
remain_num,
user_type,
perce,
}
this.setData({
progress:obj
})
}
},
//下拉加载
async bindrefresherrefresh() {
this.data.page++
await this.getChatHistoryList()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let navigationBarHeight = getApp().globalNavbarInfo.navigationBarHeight
this.setData({
navigationBarHeight,
})
// 监听微信登录成功
events.on('on-launch-executed', this, async res => {
this.initialData()
this.getChatUserInfo()
})
this.initialData()
},
initialData(){
let welcome = local.getSync('welcomeTime')
if(!welcome){
this.showWelcomeMsg()
}else{
let welcomeTime = Number(JSON.parse(local.getSync('welcomeTime')))
let dayTime = new Date().getTime();
if(dayTime - welcomeTime < 24*60*60*1000){
this.getChatHistoryList()
}else{
this.showWelcomeMsg()
}
}
},
//用户信息
async getChatUserInfo(){
const {data,code} = await apiGetChatUserInfo()
if(code!==200)return
let user_type = 'free'//免费用户
let total_num = 20//总次数
let remain_num =0 //剩余次数
let card_status=data.card_status//会员卡状态
if(card_status==1){
//会员用户
if(data.once_card && data.once_card.remain_num>0){
//次卡用户
user_type ='once'
total_num = data.once_card.total_num
remain_num = data.once_card.remain_num
}
if(data.month_card && data.month_card.remain_num>0){
//月卡用户
user_type ='mouth'
total_num = data.month_card.total_num
remain_num = data.month_card.remain_num
}
}else{
remain_num =data.dream_free
}
let perce = Math.round(remain_num * 100 / total_num)
let progress = {
total_num,
remain_num,
user_type,
perce,
}
this.setData({
progress
})
},
//聊天记录
async getChatHistoryList() {
let params = {
page: this.data.page,
size: this.data.page_size,
}
const res = await apiGetChatList(params)
if (res.code == 200) {
let result =[ ]
if(res.data.data&&res.data.data.length>0){
result = res.data.data.map(message => {
return {
id:message.id,
value: message.content,
time: message.created_at,
type: message.user_type === 1 ? 'right' : 'left'
};
});
}
let resData = [];
if (this.data.page == 1) {
resData = [];
} else {
if(this.data.valueList[0] &&this.data.valueList[0].id){
this.data.lastPageId = this.data.valueList[0].id
}
resData = this.data.valueList
}
let page = this.data.page;
if (result.length == 0) {
page = this.data.page - 1;
if (page == 0){
page = 1
} else{
//首次不提示
wx.showToast({
title: '没有更多了~',
icon: "none"
})
this.setData({
triggered: false,
})
return
}
}
resData = result.reverse().concat(resData);
const listWithShowTime = this.addShowTime(resData)
this.setData({
page,
valueList: listWithShowTime,
triggered: false,
},()=>{
if (this.data.lastPageId!==0){
// 向上分页锚链接
this.setData({
enableAnimation:false
},()=>{
this.autoScrollId()
})
}else{
this.autoScroll()
}
})
}
},
//时间转换
addShowTime(list) {
let prevTime = null
list.forEach(item => {
const currTime = new Date(item.time).getTime()
if (prevTime) {
const diff = (currTime - prevTime) / (1000 * 60 * 60)
if (diff > 1) {
item.showTime = true
} else {
item.showTime = false
}
} else {
item.showTime = true
}
prevTime = currTime
})
return list
},
//打开解梦模板
openDreamModule() {
this.setData({
dreamModuleVisable: true,
dreamModuleAnimate:true,
})
},
//关闭解梦模板
closeDreamModule() {
this.setData({
dreamModuleVisable: false
},()=>{
setTimeout(()=>{
this.setData({
dreamModuleAnimate:false,
})
},300)
})
},
routeToCenter(){
navigation.push({
url: '/pages/member/member',
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.getChatUserInfo()
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
events.remove('on-launch-executed', this)
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
let imageUrl = `${CDN_PREFIX}dream_share_img.png`
return {
title: '聊天解梦样样精通,快来试试吧',
path: `/pages/chatDream/chatDream`,
imageUrl:imageUrl
}
}
})
WXML
<!--pages/chatGptDemo/chatGptDemo.wxml-->
<wxs src="/wxs/cdn.wxs" module="cdn" />
<nav-bar id="nav-bar" customBackground="#fff" textStyle='black' pageTitle='小羊'></nav-bar>
<view class="gpt-contgainer" style="padding-top:{{navigationBarHeight}}px;">
<scroll-view class="value-scroll" scroll-y="true"
scroll-into-view-within-extent="{{true}}"
scroll-into-view="{{showViewIdName}}"
scroll-top="{{scrollTop}}" scroll-with-animation="{{enableAnimation}}" enhanced show-scrollbar="{{false}}" bounces="{{false}}" enable-passive="{{true}}"
refresher-enabled bindrefresherrefresh="bindrefresherrefresh" enable-flex refresher-triggered="{{triggered}}">
<view class="chat-value">
<view wx:key="index" class="valueItem" id="item{{item.id}}" wx:for="{{valueList}}">
<!-- <van-transition custom-class="valueItem" show="{{ item }}" name="slide-up"> -->
<view class="itemTime" wx:if="{{item.showTime&&item.time}}">{{item.time}}</view>
<view wx:if="{{item.type=='right'}}" class="itemRight">
<text user-select="{{true}}" selectable="{{true}}">{{item.value}}</text>
</view>
<view wx:if="{{item.type=='left'}}" class="itemLeft">
<text user-select="{{true}}" selectable="{{true}}">{{item.value||'正在输入中...'}}</text>
</view>
<view wx:if="{{item.errType && item.errType==1}}" bind:tap="resendInputMsg" class="resend-input-msg">
<view class="err-icon-box">
<van-icon custom-class="err-icon" name="fail" />
</view>
发送失败 点击 <text style="color: #9B5617;">重新发送</text>
</view>
<view class="resend-output-msg" bind:tap="resendOutputMsg" wx:if="{{item.errType && item.errType==2}}">发送失败 点击<text style="color: #9B5617;">重新加载</text>
</view>
<!-- </van-transition> -->
</view>
</view>
</scroll-view>
<!-- 进度条 -->
<view class="header-box" style="top: {{navigationBarHeight}}px;">
<view class="progress-bar-box">
<view class="top-box">
<view wx:if="{{progress.user_type=='free'}}" class="top-title">
<text>剩余免费次数</text>
<text style="color: #9B5617;font-weight: 600;"> {{progress.remain_num}} </text>
<text>次</text>
</view>
<view wx:if="{{progress.user_type=='mouth'}}" class="top-title">
<text>剩余天数</text>
<text style="color: #9B5617;font-weight: 600;"> {{progress.remain_num}} </text>
<text>天</text>
</view>
<view wx:if="{{progress.user_type=='once'}}" class="top-title">
<text>剩余次数</text>
<text style="color: #9B5617;font-weight: 600;"> {{progress.remain_num}} </text>
<text>次</text>
</view>
<auth-btn>
<van-button bind:click="routeToCenter" custom-class="header-btn-style" round>{{progress.user_type=='free'?'获取更多':'会员中心'}}</van-button>
</auth-btn>
</view>
<view class="bottom-box">
<view class="progress-bg-box">
<van-progress show-pivot="{{false}}" percentage="{{progress.perce}}" color="linear-gradient(90deg, #FFB079 0%, #FFB0FC 46.25%, #938AFF 100%);" custom-class="progress-bar-style" />
</view>
<view class="progress-text">{{progress.remain_num}}/{{progress.total_num}}</view>
</view>
</view>
</view>
<!-- 输入框 -->
<view class="footer-box">
<auth-btn>
<view class="module-style">
<van-button bind:click="openDreamModule" custom-class="module-btn-style" icon="{{cdn.image('dream_module_icon.png')}}" round>
解梦大师
</van-button>
</view>
<view class="group-style" >
<view class="clear-icon-box">
<van-icon custom-class="clear-icon" bind:click="onclear" name="{{cdn.image('chat_delete_icon.png')}}" />
</view>
<view bind:tap="clickInputView">
<van-field show-confirm-bar="{{false}}" bind:focus="autoScroll" focus="{{focus}}" bind:click-input="clickInputView" custom-class="custom-input-style" input-class="input-style" model:value="{{ inputValue }}" label="" clearable type="textarea" placeholder="请输入内容" autosize border="{{ false }}" />
</view>
<van-button bind:click="onSearchBaidu" disabled="{{inputDisabled}}" custom-class="send-btn-style" round>发送</van-button>
</view>
</auth-btn>
</view>
</view>
<!-- 解梦模板 -->
<dream-module wx:if="{{dreamModuleAnimate}}" bind:templateDream="templateDream" navigationBarHeight="{{navigationBarHeight}}" bind:closeDreamModule="closeDreamModule" dreamModuleVisable="{{dreamModuleVisable}}"></dream-module>
<!-- dialog弹窗 -->
<van-dialog id="van-dialog" confirm-button-class="alert-confirm-button" cancle-button-class="alert-cancle-button" custom-class="alert-class" />
WXSS
.gpt-contgainer {
background: #F7F7F7;
height: 100vh;
overflow: hidden;
padding-bottom: calc(env(safe-area-inset-bottom) + 125rpx);
box-sizing: border-box;
}
/*---------顶部进度条---------*/
.header-box {
position: fixed;
height: 126rpx;
width: 100%;
background: #fff;
filter: drop-shadow(0rpx 2rpx 20rpx rgba(0, 0, 0, 0.05));
display: flex;
justify-content: center;
}
.progress-bar-box {
width: 100%;
box-sizing: border-box;
padding: 24rpx 40rpx 0;
}
.progress-bar-box .top-box {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.progress-bar-box .bottom-box {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 4rpx;
}
.progress-bar-style {
height: 6rpx !important;
width: 430rpx !important;
border-radius: 10rpx !important;
overflow: hidden !important;
box-sizing: border-box !important;
background: #F2F2F2 !important;
}
.progress-bar-box .top-box .top-title {
color: #333;
font-size: 28rpx;
line-height: 39rpx;
}
.progress-bg-box {
padding: 4rpx;
width: 434rpx;
border-radius: 10rpx;
background: #F2F2F2;
}
.progress-text {
font-size: 22rpx;
margin-left: 4rpx;
line-height: 31rpx;
color: #333
}
.progress-icon-box {
background: #C9DCFD;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
margin-left: -8rpx;
}
.header-btn-style {
padding: 0 !important;
width: 144rpx !important;
height: 48rpx !important;
font-size: 24rpx !important;
color: #fff !important;
border-radius: 100rpx;
font-weight: 600 !important;
background: linear-gradient(180deg, #FFDF8E 0%, #FF7B45 100%), linear-gradient(180deg, #FFE4A0 0%, #FFA985 100%) !important;
border: none !important;
box-shadow: 1rpx -1rpx 3rpx 0rpx rgba(255, 255, 255, 0.80) inset !important;
}
/*-------------底部输入框-------------------*/
.footer-box {
position: fixed;
bottom: 0;
width: 100%;
padding-bottom: env(safe-area-inset-bottom);
left: 50%;
transform: translateX(-50%);
z-index: 1;
box-sizing: border-box;
background: #fff;
box-shadow: 0rpx -4rpx 20rpx 0rpx rgba(0, 0, 0, 0.05);
}
.group-style {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 24rpx;
}
.input-style {
font-size: 30rpx !important;
color: #333 !important;
box-sizing: border-box;
max-height: 234rpx;
}
.group-style .van-field__body--textarea {
padding: 0 !important;
}
.custom-input-style {
width: 486rpx !important;
border-radius: 64rpx !important;
padding: 16rpx 32rpx !important;
background: #f7f8fa !important;
}
.clear-icon-box{
background: #f7f7f7;
border-radius: 50%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 48rpx;
height: 48rpx;
}
.clear-icon {
font-size: 33rpx !important;
}
.footer-box-module .van-field__body--textarea{
padding: 0 !important;
overflow:visible !important;
}
.footer-box-module .van-cell{
--cell-font-size:30rpx;
--cell-line-height:30rpx;
--cell-text-color:#333;
}
.footer-box-module .van-field__control--textarea{
--field-text-area-min-height:45rpx;
}
.send-btn-style {
padding: 0 !important;
width: 120rpx !important;
height: 64rpx !important;
border: none !important;
border-radius: 100rpx !important;
background: linear-gradient(180deg, #FFDF8E 0%, #FF7B45 100%), linear-gradient(180deg, #FFE4A0 0%, #FFA985 100%) !important;
box-shadow: 1rpx -1rpx 3rpx 0rpx rgba(255, 255, 255, 0.80) inset !important;
color: #fff !important;
font-size: 28rpx !important;
font-weight: 600 !important;
line-height: 44rpx !important;
}
.module-style {
position: absolute;
top: -96rpx;
left: 0;
display: flex;
flex-direction: row;
align-items: center;
padding: 0rpx 24rpx;
}
.van-button__icon{
padding: 0 !important;
font-size: 46rpx !important;
}
.module-btn-style {
display: flex;
flex-direction: row !important;
align-items: center !important;
padding: 0 !important;
width: 213rpx !important;
height: 64rpx !important;
font-size: 28rpx !important;
color: #697AF6 !important;
border-radius: 32rpx !important;
border: 2rpx solid #697AF6 !important;
font-weight: 600 !important;
background: #F7F7F7
}
/*--------------聊天内容-----------------*/
.value-scroll {
width: 100%;
height: 100%;
flex-grow: 1;
margin-bottom: 0;
/* margin-top: 24rpx; */
border-radius: 10rpx;
overflow: hidden;
box-sizing: border-box;
padding-top: 126rpx;
/* padding-top: calc(env(safe-area-inset-bottom) + 100rpx); */
}
.chat-value {
padding: 0 32rpx;
padding-bottom:235rpx ;
}
.valueItem {
display: grid;
justify-items: center;
width: 100%;
}
.itemTime{
width: 100%;
color: #999;
text-align: center;
font-size: 24rpx;
line-height: 34rpx;
margin-top: 32rpx;
}
.itemRight {
justify-self: end;
margin-top: 32rpx;
max-width: 654rpx;
padding: 32rpx;
font-size: 30rpx;
line-height: 45rpx;
color: #333;
background-color: #FFF7E9;
border-radius: 64rpx 16rpx 64rpx 64rpx;
box-sizing: border-box;
word-break:break-all;
}
.itemLeft {
justify-self: start;
margin-top: 32rpx;
max-width: 654rpx;
padding: 32rpx;
font-size: 30rpx;
line-height: 45rpx;
background-color: #fff;
color: #333;
border-radius: 16rpx 64rpx 64rpx 64rpx;
box-sizing: border-box;
word-break:break-all
}
.resend-input-msg{
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
margin-top: 16rpx;
font-size: 24rpx;
line-height: 34rpx;
color: #333;
width: 100%;
padding: 0 32rpx;
box-sizing: border-box;
}
.resend-output-msg{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-top: 16rpx;
font-size: 24rpx;
line-height: 34rpx;
color: #333;
width: 100%;
padding: 0 32rpx;
box-sizing: border-box;
}
.resend-input-msg .err-icon-box{
display: flex;
align-items: center;
justify-content: center;
width: 33rpx;
height: 33rpx;
border-radius: 50%;
background: #F84F60;
margin-right: 8rpx;
}
.resend-input-msg .err-icon{
font-size: 28rpx !important;
color: #fff !important;
}
/* 弹窗 */
.alert-class{
border-radius: 16rpx !important;
width: auto !important;
padding: 0 !important;
box-sizing: border-box !important;
--dialog-header-padding-top:48rpx;
}
.alert-class .van-dialog__header{
font-weight: 600!important;
line-height: 56rpx !important;
font-size: 36rpx !important;
color: rgba(0, 0, 0, 0.85) !important;
}
.alert-class .van-dialog__message{
padding: 24rpx 48rpx 48rpx !important;
text-align: center !important;
color: rgba(0, 0, 0, 0.65) !important;
font-size: 28rpx !important;
line-height: 44rpx !important;
}
.alert-confirm-button{
color: #AF8603 !important;
font-size: 36rpx !important;
padding: 22rpx 0 !important;
}
.alert-cancle-button{
color: rgba(0, 0, 0, 0.65)!important;
font-size: 36rpx !important;
padding: 22rpx 0 !important;
}
JSON配置文件
{
"usingComponents": {
"van-search": "../../miniprogram/miniprogram_npm/@vant/weapp/search/index",
"van-field": "../../miniprogram/miniprogram_npm/@vant/weapp/field/index",
"van-icon": "../../miniprogram/miniprogram_npm/@vant/weapp/icon/index",
"van-button": "../../miniprogram/miniprogram_npm/@vant/weapp/button/index",
"van-progress": "../../miniprogram/miniprogram_npm/@vant/weapp/progress/index",
"nav-bar":"../../components/navBar/navBar",
"dream-module":"./components/dreamModule/dreamModule",
"van-dialog": "../../miniprogram/miniprogram_npm/@vant/weapp/dialog/index",
"van-transition": "../../../../miniprogram/miniprogram_npm/@vant/weapp/transition/index",
"auth-btn": "../../components/authBtn/authBtn"
},
"navigationStyle": "custom"
}
2.弹窗模板聊天组件
JS
// pages/chatGptDemo/components/dreamModule/dreamModule.js
Component({
options: { styleIsolation: 'apply-shared' },
/**
* 组件的属性列表
*/
properties: {
dreamModuleVisable: {
type: Boolean,
value: false
},
navigationBarHeight: {
type: Number,
value: 0
}
},
/**
* 组件的初始数据
*/
data: {
dreamList: [],
questionList: [],
inputValue: '',
round: 0, //轮数
scrollTop: 0,
inputDisabled:true
},
lifetimes: {
created() {
},
attached() {
this.getQuestionList()
},
ready() {
this.getStartModule()
},
moved() {
},
detached() {
}
},
/**
* 组件的方法列表
*/
methods: {
getQuestionList() {
let list = [{
value: '你好!我是解梦大师,接下来请你回答我4个问题,来帮助我更好的了解你的梦境!解梦过程中,请你尽量不要离开我和你的聊天室,避免我们的聊天内容丢失。如果过程中,你感觉有任何不适,可以提前结束我们的对话~下面,让我们开始吧!',
type: 'left',
answerType: 0 //回答问题 0 已回答 1 未回答 2已跳过
},
{
value: '能否简单描述一下你做了什么梦?',
type: 'left',
example: '例如:梦到了吃火锅、梦到了老同学、梦到下了很大的雨等',
answerType: 0 //回答问题 0 已回答 1 未回答 2已跳过
},
{
value: '在这个梦里,你有什么特殊的情感体验吗?',
type: 'left',
example: '例如:感到开心、感到伤心、感到恐惧等',
answerType: 1 //回答问题 0 已回答 1 未回答 2已跳过
},
{
value: '有哪些和这个梦有关的事或人导致你做了这个梦吗?',
type: 'left',
example: '例如:白天看了很多关于飞机飞行的视频',
answerType: 1 //回答问题 0 已回答 1 未回答 2已跳过
},
{
value: '你解梦的目的是什么?',
type: 'left',
example: '例如:想要知道这个梦代表什么意思,是吉是凶,对我的生活会不会有什么影响',
answerType: 0 //回答问题 0 已回答 1 未回答 2已跳过
}
]
this.setData({
questionList: list
})
},
//加载模板
getStartModule() {
let questionList = this.data.questionList
const typeQuestions = questionList.slice(0, 2)
// 计数器
let count = 0
// 用setInterval模拟插入
const timer = setInterval(() => {
let dreamList = this.data.dreamList
// 获取一条新数据
const newItem = typeQuestions[count]
// 拼接到列表头部
const list = [...dreamList, newItem]
// 更新列表
this.setData({
round: this.data.round + 1,
dreamList: list
})
count++
// 如果数据插入完了,停止
if (count >= typeQuestions.length) {
this.setData({
inputDisabled:false
})
clearInterval(timer)
}
}, 500) // 每隔0.5秒执行一次
},
//发送信息
onSendMsg() {
let inputValue = this.data.inputValue.trim()
if(!inputValue){
wx.showToast({
title: '请输入内容',
icon:'none'
})
return
}
this.setData({
inputDisabled:true
})
let list = this.data.dreamList
list[list.length - 1].answerType = 0 //已回答
let cueType //1.梦境描述 2.情感体验 3.背景信息 4.解梦目的
switch(this.data.round){
case 2:
cueType = 1;
break;
case 3:
cueType = 2;
break;
case 4:
cueType = 3;
break;
case 5:
cueType = 4;
break;
default:
cueType = 0;
break;
}
list.push({
type: 'right',
value: inputValue,
cueType,
})
this.setData({
dreamList: list,
inputValue: '',
}, () => {
if(this.data.round<5){
//下一轮
this.onReply()
}else{
//解梦
this.composeMsg()
}
})
},
//开启下一轮问题
onReply() {
let round = this.data.round + 1
let list = this.data.dreamList
list.push(this.data.questionList[round - 1])
setTimeout(() => {
this.setData({
dreamList: list,
round,
}, () => {
this.setData({
inputDisabled:false
})
this.autoScroll()
})
}, 500)
},
//跳过问题
skipAnswer(){
this.setData({
inputDisabled:true
})
const list = this.data.dreamList
list[list.length-1].answerType = 2//跳过
this.setData({
dreamList:list
},()=>{
this.onReply()
})
},
onClearInput(){
this.setData({
inputValue:''
})
},
//组合提示词
composeMsg(){
let list = this.data.dreamList
let allSendMsg = list.filter(item => item.type === 'right')
let tempMsg = '我梦见了'
allSendMsg.map(item=>{
if(item.cueType==1){
tempMsg = tempMsg+`${item.value}。`
}else if(item.cueType==2){
tempMsg = tempMsg + `在这个梦里,我感受到了${item.value}。`
}else if(item.cueType==3){
tempMsg = tempMsg + `为了更好地理解我的梦境,我想提供一些背景信息。最近我经历了${item.value},这可能与我的梦境有关。`
}else if(item.cueType==4){
tempMsg = tempMsg + `通过解梦,我希望得到${item.value}。我希望解梦师可以帮助我解释梦境本身,或者提供对个人生活、情感或未来的指引。`
}
})
this.triggerEvent('templateDream',tempMsg)
this.closeDreamModule()
},
//关闭弹窗
closeDreamModule() {
this.triggerEvent('closeDreamModule')
},
//蒙层静止滚动
preventTouchMove: function (e) {
},
//滚动条至最底部
autoScroll() {
var that = this
let query = this.createSelectorQuery()
query.select('.scroll-view-container').boundingClientRect(res => {
//延迟滚动,否则不生效
setTimeout(()=>{
that.setData({
scrollTop: res.height*100
})
},201)
})
query.exec(res => {})
},
}
})
WXML
<view style="--navigationBarHeight:{{navigationBarHeight}}px">
<van-action-sheet z-index="99999" catchtouchmove="preventTouchMove" custom-class="sheet-custom-class" bind:close="closeDreamModule" show="{{ dreamModuleVisable }}">
<view class="sheet-container">
<view class="sheet-header" style="padding: 32rpx;">
<text style="flex: 1;"></text>
<text style="flex: 1;" class="sheet-title">解梦大师</text>
<view style="flex: 1;" class="sheet-title-icon">
<van-icon bind:click="closeDreamModule" custom-class="sheet-close-icon" name="cross" />
</view>
</view>
<!-- 内容 -->
<scroll-view class="scroll-box" scroll-y scroll-top="{{scrollTop}}" scroll-with-animation enhanced bounces="{{false}}" enable-passive="{{true}}" enable-flex show-scrollbar="{{false}}" >
<view class="scroll-view-container">
<view wx:key="index" wx:for="{{dreamList}}">
<van-transition custom-class="valueItem" show="{{ item }}" name="slide-up">
<view wx:if="{{item.type=='right'}}" class="itemRight">
<text user-select="{{true}}" selectable="{{true}}">{{item.value}}</text>
</view>
<view wx:if="{{item.type=='left'}}" class="itemLeft">
<text user-select="{{true}}" selectable="{{true}}">{{item.value}}</text>
<text class="example-text">{{item.example}}</text>
</view>
<view class="skip-answer-box">
<van-button wx:if="{{item.answerType==1}}" bind:click="skipAnswer" custom-class="skip-answer" round>跳过这个问题</van-button>
</view>
<view wx:if="{{item.answerType==2}}" class="skipped-answer">
<view class="dot"></view>
<text>已跳过该问题</text>
</view>
</van-transition>
</view>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="footer-box-module">
<view class="group-style-module">
<van-field custom-class="custom-input-style-module"
bind:focus="autoScroll"
input-class="input-style-module" model:value="{{ inputValue }}" label="" clearable
bind:clear = "onClearInput"
type="textarea" show-confirm-bar="{{false}}" disable-default-padding placeholder="请输入内容" autosize border="{{ false }}" />
<van-button disabled="{{inputDisabled}}" bind:click="onSendMsg" custom-class="send-btn-style" round>{{round<5?'发送':'解梦'}}</van-button>
</view>
</view>
</view>
</van-action-sheet>
</view>
WXSS
/* pages/chatGptDemo/components/dreamModule/dreamModule.wxss */
/* 动作面板 */
.sheet-custom-class {
border-radius: 16rpx !important;
max-height:calc(100% - var(--navigationBarHeight) - 126rpx) !important;
height: 100% !important;
box-sizing: border-box;
background: #fff !important;
padding-bottom: 0rpx !important;
}
.sheet-container{
position: relative;
flex-grow: 1;
width: 100%;
height: 100%;
}
.sheet-header{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: absolute;
width: 100%;
box-sizing: border-box;
}
.sheet-title {
color: #333;
text-align: center;
font-family: PingFang SC;
font-size: 36rpx;
font-style: normal;
font-weight: 600;
line-height: 56rpx
}
.sheet-title-icon {
text-align: right;
}
.sheet-close-icon{
padding: 14rpx 0 14rpx 14rpx;
font-size: 30rpx !important;
color: #3333334D !important;
font-weight: 600 !important;
}
.scroll-box {
padding-top: 100rpx;
width: 100%;
height: 100%;
flex-grow: 1;
margin-bottom: 0;
border-radius: 10rpx;
overflow: hidden;
box-sizing: border-box;
}
.scroll-view-container{
box-sizing: border-box;
padding-left: 32rpx;
padding-right: 32rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 300rpx);
}
/* 底部输入框 */
.footer-box-module {
position: fixed;
bottom: 0;
width: 100%;
padding-bottom: env(safe-area-inset-bottom);
left: 50%;
transform: translateX(-50%);
z-index: 1;
box-sizing: border-box;
background: #fff;
box-shadow: 0rpx -4rpx 20rpx 0rpx rgba(0, 0, 0, 0.05);
}
.group-style-module {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 24rpx;
}
.custom-input-style-module {
display: flex;
flex-direction: row;
align-items: center;
width: 530rpx !important;
border-radius: 64rpx !important;
padding: 16rpx 32rpx !important;
background: #f7f8fa !important;
box-sizing: border-box !important;
overflow:visible !important;
}
.input-style-module {
max-height: 234rpx;
}
.send-btn-style {
padding: 0 !important;
width: 120rpx !important;
height: 64rpx !important;
border: none !important;
border-radius: 100rpx !important;
background: linear-gradient(180deg, #C3CAFF 0%, #475DFF 100%) !important;
box-shadow: 1rpx -1rpx 3rpx 0rpx rgba(255, 255, 255, 0.80) inset !important;
color: #fff !important;
font-size: 28rpx !important;
font-weight: 600 !important;
line-height: 44rpx !important;
}
/* 中间内容 */
.valueItem {
display: grid;
justify-items: center;
width: 100%;
}
.itemRight {
justify-self: end;
margin-top: 32rpx;
max-width: 654rpx;
padding: 32rpx;
font-size: 30rpx;
line-height: 45rpx;
color: #fff;
background-color: #697AF6;
border-radius: 64rpx 16rpx 64rpx 64rpx;
box-sizing: border-box;
word-wrap: break-word;
}
.itemLeft {
justify-self: start;
margin-top: 32rpx;
max-width: 654rpx;
padding: 32rpx;
font-size: 30rpx;
line-height: 45rpx;
background-color:rgba(105, 122, 246, 0.10);
color: #333;
border-radius: 16rpx 64rpx 64rpx 64rpx;
box-sizing: border-box;
word-wrap: break-word;
}
.example-text{
color: #999;
}
.skip-answer-box{
display: flex;
flex-direction: row;
justify-content: flex-end;
width: 100%;
}
.skip-answer{
width: 192rpx!important;
height: 48rpx!important;
margin-top: 16rpx!important;
margin-right: 32rpx!important;
color: #697AF6 !important;
padding: 2rpx 0 !important;
font-size: 24rpx !important;
font-weight: 600 !important;
line-height: 44rpx !important;
border-radius: 32rpx !important;
border: 2rpx solid rgba(105, 122, 246, 0.40) !important;
background: rgba(255, 255, 255, 0.00)!important;
}
.skipped-answer{
margin-top: 16rpx;
margin-right: 32rpx;
justify-self: end;
display: flex;
flex-direction: row;
align-items: center;
color: #999;
font-size: 24rpx;
font-weight: 400;
line-height: 34rpx;
}
.skipped-answer .dot{
width: 16rpx;
height: 16rpx;
background-color: rgba(105, 122, 246, 0.40);
border-radius: 50%;
margin-right: 8rpx;
}
JSON配置文件
{
"component": true,
"usingComponents": {
"van-action-sheet": "../../../../miniprogram/miniprogram_npm/@vant/weapp/action-sheet/index",
"van-field": "../../../../miniprogram/miniprogram_npm/@vant/weapp/field/index",
"van-icon": "../../../../miniprogram/miniprogram_npm/@vant/weapp/icon/index",
"van-button": "../../../../miniprogram/miniprogram_npm/@vant/weapp/button/index",
"van-transition": "../../../../miniprogram/miniprogram_npm/@vant/weapp/transition/index"
}
}