<template>
|
<div>
|
<ul ref="chatList" class="chat-list" :style="`height:${height};`">
|
|
<li v-for="(item, index) in list" :key="index">
|
<!-- 时间 -->
|
<div v-if="item.time_tx" class="flex"><div class="chat-time">{{ item.time_tx }}</div></div>
|
|
<!-- 聊天主体 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
|
<!-- 通常主体 有用户信息 -->
|
<div class="chat-item" :class="{'me-item': getDirection(item.fromUser)=='right'}">
|
<img :src="item.avatar" alt="" class="avatar" fit="cover">
|
<div class="info">
|
<!-- ({{ item.msgtype }}) -->
|
<div class="name">{{ item.name }}</div>
|
|
<!-- 消息内容 ↓↓↓↓↓↓↓↓↓↓ -->
|
<!-- 文本 -->
|
<textPop v-if="item.msgtype === 'text'" :data="item.content" :direction="getDirection(item.fromUser)" />
|
<!-- 图片 -->
|
<imagePop v-if="item.msgtype === 'image'" :data="item" :direction="getDirection(item.fromUser)" />
|
<!-- 消息内容 ↑↑↑↑↑↑↑↑↑↑ -->
|
</div>
|
</div>
|
<!-- 聊天主体 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
|
</li>
|
|
<div v-if="loading" class="text-center">
|
<div class="ball-rotate"><div /></div>
|
</div>
|
</ul>
|
|
<div class="handle-wrap flex flex-ver">
|
<input v-model="myTalk" type="text" class="input flex-1" maxlength="100" @keyup.enter="handleSend">
|
<div class="send-btn" @click="handleSend">发送</div>
|
</div>
|
|
<!-- audioFileUrl -->
|
<audio ref="refAudio" src="">
|
您的浏览器不支持 audio 标签。
|
</audio>
|
</div>
|
</template>
|
|
<script>
|
// const listMockData = require('./chat_list_mockData')
|
const chatFn = require('@/utils/chat/chat.js')
|
import imagePop from './msg/imagePop.vue'
|
import textPop from './msg/textPop.vue'
|
import config from '../../config/index'
|
export default {
|
name: 'ChatList',
|
components: { textPop, imagePop },
|
mixins: [],
|
props: {
|
// 高度
|
height: {
|
type: String,
|
default: '100vh'
|
}
|
},
|
data() {
|
return {
|
userId: '',
|
myTalk: '',
|
|
// 列表数据
|
loading: false,
|
list: []
|
}
|
},
|
mounted() {
|
this.init()
|
},
|
methods: {
|
init() {
|
this.userId = this.getQueryString('userId')
|
console.log('this.userId', this.userId)
|
if (!this.userId) window.appToast('缺少userID,请重新进入')
|
},
|
|
// 判断位置 return 'left' 'right'
|
getDirection(itemUserId) {
|
return itemUserId === 'mySelf' ? 'right' : 'left'
|
},
|
|
// 点击发送
|
handleSend() {
|
if (this.loading) return window.appToast('操作过快,请稍后')
|
if (!this.userId) return window.appToast('缺少userID,请重新进入')
|
|
const myTalk = JSON.parse(JSON.stringify(this.myTalk))
|
const list = JSON.parse(JSON.stringify(this.list))
|
|
if (!myTalk) return window.appToast('请输入内容')
|
this.myTalk = ''
|
|
list.push({
|
content: { 'content': myTalk },
|
name: '我',
|
msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'),
|
action: 'send',
|
id: this.$moment().format('yyyyMMDDHHmmss'),
|
avatar: require('@/assets/imgs/mySelf.png'),
|
msgtype: 'text',
|
fromUser: 'mySelf'
|
})
|
|
// 处理聊天显示的日期
|
list && list.forEach((item, index) => {
|
if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime)
|
})
|
this.list = list
|
|
this.$nextTick(() => { this.scrollBottom() })
|
|
this.getData(myTalk)
|
},
|
|
// 获取ai返回内容
|
getData(keyWord) {
|
console.log('getChatList')
|
var { loading, userId } = this
|
const list = JSON.parse(JSON.stringify(this.list))
|
if (loading) return
|
|
this.loading = true
|
|
const url = config.is_use_test_server ? 'crm-user/chatGpt/chat' : 'chat/msg'
|
|
this.Req.http.post({
|
url: url,
|
data: {
|
msg: keyWord,
|
userId: userId
|
},
|
udData: { noLoading: true, nokey: true },
|
mockData: {
|
code: 100,
|
msg: '',
|
data: {
|
text: '机器人回复'
|
}
|
}
|
}).then(res => {
|
this.loading = false
|
|
if (res.data) {
|
const content = res.data.replace(/\n/g, '</br>')
|
list.push({
|
content: { 'content': content },
|
name: 'AI',
|
msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'),
|
action: 'send',
|
id: this.$moment().format('yyyyMMDDHHmmss'),
|
avatar: require('@/assets/imgs/AI.png'),
|
msgtype: 'text',
|
fromUser: 'robot'
|
})
|
}
|
|
// 处理聊天显示的日期
|
list && list.forEach((item, index) => {
|
if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime)
|
})
|
|
this.list = list
|
|
this.$nextTick(() => { this.scrollBottom() })
|
}).catch(res => {
|
this.loading = false
|
const warnText = res.msg || ('请稍后再试...')
|
window.appToast(warnText)
|
})
|
},
|
|
scrollBottom() {
|
this.$nextTick(() => {
|
this.$refs.chatList.scrollTop = this.$refs.chatList.scrollHeight
|
})
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.loading-tx{
|
font-size: 22px;
|
color: #999;
|
}
|
|
.chat-list{
|
overflow-y: auto;
|
background-color: #fff;
|
padding-top: 40px;
|
box-sizing: border-box;
|
padding-bottom: 140px;
|
}
|
|
.chat-time{
|
background-color: #DADADA;
|
color: #fff;
|
font-size: 24px;
|
line-height: 1.2;
|
padding: 4px 0;
|
border-radius: 6px;
|
text-align: center;
|
margin: 6px auto 10px;
|
padding: 4px 10px;
|
}
|
|
.chat-item{
|
margin-bottom: 20px;
|
padding: 0 20px ;
|
}
|
.chat-item .avatar{
|
height: 60px;
|
width: 60px;
|
display: block;
|
margin-right: 10px;
|
border-radius: 4px;
|
float: left;
|
}
|
.chat-item .info{
|
/* padding-top: 4px; */
|
display: inline-block;
|
text-align: left;
|
}
|
.chat-item .info .name{
|
line-height: 1.2;
|
height: 1.2em;
|
margin-bottom: 4px;
|
font-size: 22px;
|
}
|
.chat-item.me-item{
|
text-align: right;
|
}
|
.chat-item.me-item .avatar{
|
float: right;
|
margin-right: 0;
|
margin-left: 10px;
|
}
|
.chat-item.me-item .name{
|
text-align: right;
|
}
|
|
.handle-wrap{
|
background-color: #F7F7F7;
|
position: fixed;
|
bottom: 0;
|
left: 0;
|
width: 100%;
|
padding: 20px 30px 60px 30px;
|
box-sizing: border-box;
|
}
|
.handle-wrap .input{
|
height: 60px;
|
line-height: 30px;
|
font-size: 26px;
|
border: none;
|
background-color: #fff;
|
border-radius: 6px;
|
outline: none;
|
}
|
.handle-wrap .input:focus{
|
border: none;
|
}
|
.handle-wrap .send-btn{
|
background-color: #45B1D7;
|
height: 60px;
|
line-height: 60px;
|
color: #fff;
|
padding: 0 20px;
|
border-radius: 6px;
|
letter-spacing: 4px;
|
margin-left: 20px;
|
font-size: 28px;
|
}
|
|
@keyframes ball-rotate {
|
0% {transform: rotate(0deg) scale(1); }
|
|
50% {transform: rotate(180deg) scale(0.6);}
|
|
100% {transform: rotate(360deg) scale(1);}
|
}
|
.ball-rotate{position: relative;width: 100px;height: 70px;margin: 0 auto 20px;}
|
.ball-rotate > div {
|
background-color: #000;
|
width: 30px;
|
height: 30px;
|
border-radius: 100%;
|
margin: 4px;
|
animation-fill-mode: both;
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
margin: -16px 0 0 -16px;
|
}
|
.ball-rotate > div:first-child {
|
animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite;
|
}
|
.ball-rotate > div:before, .ball-rotate > div:after {
|
background-color: #000;
|
width: 30px;
|
height: 30px;
|
border-radius: 100%;
|
margin: 4px;
|
content: ""!important;
|
position: absolute;
|
opacity: .8;
|
}
|
.ball-rotate > div:before {top: 0px;left: -56px}
|
.ball-rotate > div:after {top: 0px;left: 50px}
|
</style>
|