|
@@ -0,0 +1,384 @@
|
|
|
+<template>
|
|
|
+ <custom-layout class="main" v-loading="loading">
|
|
|
+ <div class="w_1200">
|
|
|
+ <el-container>
|
|
|
+ <el-aside width="400px" class="aside">
|
|
|
+ <el-col :span="24" class="one">
|
|
|
+ <el-col :span="24" class="one_1">
|
|
|
+ <el-col :span="4" class="left">
|
|
|
+ <a-avatar :size="60" style="background-color: #409eff">
|
|
|
+ {{ user.nick_name }}
|
|
|
+ </a-avatar>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="20" class="right">
|
|
|
+ {{ user.nick_name || '游客' }}
|
|
|
+ </el-col>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="24" class="one_2">
|
|
|
+ <a-input placeholder="查找联系人">
|
|
|
+ <template #prefix><SearchOutlined /></template>
|
|
|
+ </a-input>
|
|
|
+ </el-col>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="24" class="two">
|
|
|
+ <el-col
|
|
|
+ :span="24"
|
|
|
+ class="list"
|
|
|
+ v-for="(item, index) in chatList"
|
|
|
+ :key="index"
|
|
|
+ @click="toView(item)"
|
|
|
+ >
|
|
|
+ <div class="line" v-if="item.id == id"></div>
|
|
|
+ <el-col :span="4" class="left">
|
|
|
+ <a-badge :dot="item.is_read == '0'">
|
|
|
+ <a-avatar :size="60" style="background-color: #409eff">
|
|
|
+ {{ item.nick_name }}
|
|
|
+ </a-avatar>
|
|
|
+ </a-badge>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="20" class="right">
|
|
|
+ <el-col :span="24" class="right_1">
|
|
|
+ <div class="name">{{ item.nick_name }}</div>
|
|
|
+ <div class="time">{{ item.send_time }}</div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="24" class="right_2">{{ item.content }}</el-col>
|
|
|
+ </el-col>
|
|
|
+ </el-col>
|
|
|
+ </el-col>
|
|
|
+ </el-aside>
|
|
|
+ <el-main style="background: #eee; padding: 0">
|
|
|
+ <Chat></Chat>
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
+ </div>
|
|
|
+ </custom-layout>
|
|
|
+ <el-dialog v-model="dialog" title="智能客服">
|
|
|
+ <el-col :span="24" class="dialog">
|
|
|
+ <div class="content">
|
|
|
+ <div class="title">智能客服为您服务</div>
|
|
|
+ <div class="list">
|
|
|
+ <el-image class="image" :src="kf" fit="fill" />
|
|
|
+ <div class="message">Hi,遇到问题随时找智能客服哟~ 有什么需要我帮忙的吗?</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="foot">
|
|
|
+ <textarea class="input" placeholder="输入消息"></textarea>
|
|
|
+ <div class="button">
|
|
|
+ <div class="send">发 送</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+// 基础
|
|
|
+import moment from 'moment'
|
|
|
+import Chat from './parts/chat.vue'
|
|
|
+import { SearchOutlined } from '@ant-design/icons-vue'
|
|
|
+// import { sendWebsocket, closeWebsocket } from '@/utils/websocket'
|
|
|
+// 接口
|
|
|
+import { ChatStore } from '@/store/api/platform/chat'
|
|
|
+import { UsersStore } from '@/store/api/user/user'
|
|
|
+const store = UsersStore()
|
|
|
+const chatstore = ChatStore()
|
|
|
+import { UserStore } from '@/store/user'
|
|
|
+const userStore = UserStore()
|
|
|
+
|
|
|
+const user = computed(() => userStore.user)
|
|
|
+// 图片引入
|
|
|
+import kf from '/images/kf.png'
|
|
|
+// 路由
|
|
|
+const route = useRoute()
|
|
|
+// 加载中
|
|
|
+const loading = ref(false)
|
|
|
+// 是否弹框客服
|
|
|
+const dialog = ref(false)
|
|
|
+// 聊天者id
|
|
|
+const id = ref(route.query.id || '')
|
|
|
+// 聊天室
|
|
|
+const chatList = ref([])
|
|
|
+// 列表
|
|
|
+const list = ref([])
|
|
|
+let skip = 0
|
|
|
+let limit = inject('limit')
|
|
|
+// 搜索
|
|
|
+const searchForm = ref({})
|
|
|
+// 个人信息
|
|
|
+const info = ref({})
|
|
|
+// 消息
|
|
|
+const textarea = ref('')
|
|
|
+
|
|
|
+// 请求
|
|
|
+onMounted(async () => {
|
|
|
+ loading.value = true
|
|
|
+ await search()
|
|
|
+ // sendWebsocket('ws://localhost:9700', wsMessage, wsError, succeed)
|
|
|
+ loading.value = false
|
|
|
+})
|
|
|
+
|
|
|
+// onBeforeUnmount(() => {
|
|
|
+// closeWebsocket()
|
|
|
+// })
|
|
|
+
|
|
|
+// ws连接成功,后台返回的ws数据,组件要拿数据渲染页面等操作
|
|
|
+const wsMessage = async (data) => {
|
|
|
+ const dataJson = data
|
|
|
+ if (dataJson && dataJson.id) {
|
|
|
+ if (
|
|
|
+ (dataJson.senderid == user.value.id && dataJson.receiverid == id.value) ||
|
|
|
+ (dataJson.senderid == id.value && dataJson.receiverid == user.value.id)
|
|
|
+ ) {
|
|
|
+ await searchList()
|
|
|
+ list.value.push(dataJson)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+// // ws连接失败,组件要执行的代码
|
|
|
+// const wsError = async () => {
|
|
|
+// sendWebsocket('ws://localhost:9700', wsMessage, wsError, succeed)
|
|
|
+// }
|
|
|
+// ws连接成功,组件要执行的代码
|
|
|
+const succeed = async () => {
|
|
|
+ console.log('ws连接成功')
|
|
|
+}
|
|
|
+
|
|
|
+const search = async () => {
|
|
|
+ searchList()
|
|
|
+ if (id.value) {
|
|
|
+ let res = await store.fetch(id.value)
|
|
|
+ if (res.errcode == '0') info.value = res.data
|
|
|
+ searchChat({ skip, limit })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const searchList = async () => {
|
|
|
+ const res = await chatstore.chat()
|
|
|
+ if (res.errcode == '0') chatList.value = res.data
|
|
|
+}
|
|
|
+
|
|
|
+const toView = async (item) => {
|
|
|
+ id.value = item.id
|
|
|
+ info.value = item
|
|
|
+ await chatstore.read({ senderid: user.value.id, receiverid: id.value })
|
|
|
+ await searchList()
|
|
|
+ await searchChat({ skip, limit })
|
|
|
+}
|
|
|
+const searchChat = async (query = { skip: 0, limit }) => {
|
|
|
+ const data = {
|
|
|
+ skip: query.skip,
|
|
|
+ limit: query.limit,
|
|
|
+ user: info.value.id,
|
|
|
+ ...searchForm.value
|
|
|
+ }
|
|
|
+ const res = await chatstore.query(data)
|
|
|
+ if (res.errcode == '0' && res.data.length > 0) list.value = res.data
|
|
|
+ else chatList.value.unshift(info.value)
|
|
|
+}
|
|
|
+const toSend = async () => {
|
|
|
+ const data = {
|
|
|
+ senderid: user.value.id,
|
|
|
+ receiverid: id.value,
|
|
|
+ type: '0',
|
|
|
+ content: textarea.value,
|
|
|
+ send_time: moment().format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ }
|
|
|
+ const res = await chatstore.create(data)
|
|
|
+ if (res.errcode === 0) {
|
|
|
+ textarea.value = ''
|
|
|
+ await searchChat({ skip, limit })
|
|
|
+ }
|
|
|
+}
|
|
|
+// provide
|
|
|
+provide('id', id)
|
|
|
+provide('list', list)
|
|
|
+provide('info', info)
|
|
|
+provide('textarea', textarea)
|
|
|
+provide('toSend', toSend)
|
|
|
+</script>
|
|
|
+<style scoped lang="scss">
|
|
|
+.main {
|
|
|
+ margin: 10px 0;
|
|
|
+ .aside {
|
|
|
+ border-radius: 5px 0 0 5px;
|
|
|
+ background: #33353a;
|
|
|
+ .one {
|
|
|
+ .one_1 {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ background: #292b2e;
|
|
|
+ padding: 10px;
|
|
|
+
|
|
|
+ .left {
|
|
|
+ image {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .right {
|
|
|
+ margin: 0 0 0 10px;
|
|
|
+ white-space: nowrap;
|
|
|
+ color: #fff;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .one_2 {
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: 1px solid #404247;
|
|
|
+ background-color: hsla(0, 0%, 75.3%, 0.2);
|
|
|
+ :deep(.ant-input-affix-wrapper) {
|
|
|
+ color: #ccc !important;
|
|
|
+ background-color: transparent;
|
|
|
+ }
|
|
|
+ :deep(.ant-input) {
|
|
|
+ height: 32px;
|
|
|
+ position: relative;
|
|
|
+ caret-color: #fff;
|
|
|
+ color: #fff;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ padding: 6px 0;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ background-color: transparent;
|
|
|
+ &::placeholder {
|
|
|
+ color: #ccc !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .two {
|
|
|
+ overflow-y: auto;
|
|
|
+ height: 57vh;
|
|
|
+ .list {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: 1px solid #2c2e31;
|
|
|
+ .right {
|
|
|
+ margin: 5px 0 0 10px;
|
|
|
+ .right_1 {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 0 10px 10px 0;
|
|
|
+ .name {
|
|
|
+ color: #fff;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ }
|
|
|
+ .time {
|
|
|
+ color: #51555e;
|
|
|
+ vertical-align: top;
|
|
|
+ word-break: keep-all;
|
|
|
+ font-size: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .right_2 {
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .line {
|
|
|
+ position: absolute;
|
|
|
+ background: #ff8f2c;
|
|
|
+ width: 2px;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ height: 85px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .list:hover {
|
|
|
+ background-color: #3a3c42;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .two::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+:deep(.el-dialog__body) {
|
|
|
+ padding: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog {
|
|
|
+ .content {
|
|
|
+ padding: 20px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ padding-top: 23px;
|
|
|
+ padding-bottom: 26px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ }
|
|
|
+
|
|
|
+ .list {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .image {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 60px;
|
|
|
+ margin: 0 10px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message {
|
|
|
+ position: relative;
|
|
|
+ max-width: 330px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ line-height: $global-font-size-24;
|
|
|
+ box-sizing: border-box;
|
|
|
+ color: rgba(0, 0, 0, 0.65);
|
|
|
+ padding: 16px 11px 16px 16px;
|
|
|
+ background: #ffffff;
|
|
|
+ box-shadow: 0px 2px 10px 1px rgba(0, 0, 0, 0.12);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .foot {
|
|
|
+ height: 140px;
|
|
|
+ background: rgba(0, 0, 0, 0.04);
|
|
|
+ border: 1px solid rgba(0, 0, 0, 0.15);
|
|
|
+ padding: 13px 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+
|
|
|
+ .input {
|
|
|
+ flex: 1;
|
|
|
+ height: 0;
|
|
|
+ resize: none;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ background: transparent;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ line-height: $global-font-size-24;
|
|
|
+ }
|
|
|
+
|
|
|
+ .button {
|
|
|
+ margin-top: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: flex-end;
|
|
|
+
|
|
|
+ .send {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #fff;
|
|
|
+ background: #2f54eb;
|
|
|
+ border-radius: 4px;
|
|
|
+ width: 64px;
|
|
|
+ height: 32px;
|
|
|
+ font-size: $global-font-size-18;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|