修改客服记录

This commit is contained in:
龙运模 2024-11-16 13:38:45 +08:00
parent 8ea101637e
commit ecb2bd247f
10 changed files with 538 additions and 21 deletions

View File

@ -4,10 +4,19 @@ NODE_ENV = production
# 标题
VUE_APP_TITLE = 象纬云科
# 接口地址
# 测试环境
VUE_APP_API_BASEURL = https://dev.api.linkwing.com/api/v1
VUE_APP_API_DEV = https://dev.api.linkwing.com/api/v1
# WS地址
VUE_APP_WS_URL = wss://dev.api.linkwing.com/wss
VUE_APP_WSS_URL = wss://dev.api.linkwing.com/wss
# 线上环境
# 接口地址
# VUE_APP_API_BASEURL = https://prod.api.linkwing.com/api/v1
# VUE_APP_API_DEV = https://prod.api.linkwing.com/api/v1
# WS地址
# VUE_APP_WS_URL = wss://prod.api.linkwing.com/wss
# VUE_APP_WSS_URL = wss://prod.api.linkwing.com/wss

View File

@ -65,4 +65,5 @@ export default {
return await http.post(this.url, data);
}
},
}

View File

@ -21,7 +21,11 @@
</el-avatar>
</div>
<div class="msgText">
<div class="msgTitle">{{item.from_user && item.from_user.name?item.from_user.name:'匿名'}}</div>
<div class="msgTitle">
<span class="leftTime" v-if="user_id != item.to_user_id">{{item.created_at}}</span>
{{item.from_user && item.from_user.name?item.from_user.name:'匿名'}}
<span class="time" v-if="user_id === item.to_user_id">{{item.created_at}}</span>
</div>
<div class="textCom">
<div v-html="item.to_message"></div>
</div>
@ -37,7 +41,7 @@
</el-scrollbar>
</div>
<div class="footer">
<el-input class="customTextarea" v-model="params.to_message" resize="none" type="textarea" :row="2" placeholder="请简短描述您的问题"></el-input>
<el-input class="customTextarea" v-model="params.to_message" @keydown="handleKeydown" resize="none" type="textarea" :row="2" placeholder="Shift+Enter换行 请简短描述您的问题"></el-input>
<div class="saveBtn" :class="params.to_message!==''?'saveActive':''" @click="sendCustomer"> </div>
</div>
</div>
@ -136,6 +140,15 @@ export default {
typeActive(item){
this.params.type = item.value;
},
handleKeydown(event){
if(event.key === 'Enter' && !event.shiftKey){
event.preventDefault(); //
this.sendCustomer();
}else if(event.key === 'Enter' && event.shiftKey){
this.params.to_message += '';
}
},
async sendCustomer() {
if(this.params.to_message =="") return
const res = await this.$API.customer.send.post(this.params);
@ -180,7 +193,7 @@ export default {
.customerBox{
position: absolute;
right: 40px;
bottom: 40px;
bottom: 80px;
z-index: 1000;
.img{
width: 56px;
@ -240,7 +253,7 @@ export default {
padding: 10px;
.msgItem{
display: flex;
margin-bottom: 8px;
margin-bottom: 12px;
.avatar{
padding-right: 10px;
}
@ -248,11 +261,23 @@ export default {
.msgTitle{
padding: 0 0 8px 0;
color: #555;
.time{
margin-left: 8px;
font-size: 12px;
color: #888;
}
.leftTime{
margin-right: 8px;
font-size: 12px;
color: #888;
}
}
.textCom{
background: #f5f5f5;
border-radius: 4px;
padding: 10px;
display: inline-block;
text-align: left;
}
}
}
@ -263,12 +288,14 @@ export default {
padding-right: 0;
}
.msgText{
text-align: right;
.msgTitle{
text-align: right;
}
.textCom{
background: var(--el-color-primary);
color: var(--el-color-white);
text-align: left;
}
}
}

View File

@ -287,6 +287,7 @@
this.$store.commit("set_repair_count", res.data.list_repair_info.repair_count);
}
let apiMenu = tool.objCopy(tool.data.get("MENU"));
if(apiMenu.length<1) return
apiMenu.forEach(item=>{
if(item.name == 'order'){
item.meta.tag = 0;

View File

@ -3,6 +3,7 @@ import { ElNotification, ElMessageBox, ElMessage } from 'element-plus';
import sysConfig from "@/config";
import tool from '@/utils/tool';
import router from '@/router';
import {closeSock} from "@/utils/websocket"
axios.defaults.baseURL = ''
@ -52,7 +53,8 @@ axios.interceptors.response.use(
done();
},
}).then(() => {
// closeSock(true);
closeSock(true);
tool.data.clear();
tool.cookie.remove("TOKEN");
router.replace({ path: "/login" });
})

View File

@ -65,11 +65,15 @@ export default {
fileType:{
type:String,
default:'txt'
},
size:{
type:String,
default:'large'
}
},
data(){
return{
size:'large'
}
},
mounted() {

View File

@ -0,0 +1,320 @@
<template>
<slot name="header"></slot>
<el-dialog v-bind="$attrs" :title="title" v-model="visible" :width="560" destroy-on-close draggable @closed="visible=false">
<div class="importBody" v-loading="saveLoading" element-loading-text="处理中...">
<el-upload
class="upload"
:action="oss.host"
:data="upload_data"
:file-list="fileList"
:show-file-list="false"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:before-upload="beforeUpload"
:http-request="uploadFile"
drag
multiple
:auto-upload="true"
:limit="5"
>
<div>
<el-icon class="el-icon--upload"><sc-icon-Upload /></el-icon>
<div class="el-upload__text">
<span class="laText">请将附件拖到此处或点击上传</span>
</div>
</div>
<template #tip>
<div class="el-upload__tip">
<span class="tip">最多上传5个文件,单个文件不要超过10M,请上传 图片/xlsx/docx/zip 格式文件</span>
<span class="btn">
<slot name="download"></slot>
</span>
</div>
<div class="importBox" v-if="progressShow && (fileList.length>0 || listProgress.length>0)">
<div v-if="uploadShow">
<div class="item" v-for="(item,index) in fileList" :key="item">
<div class="name">{{item.name}}</div>
<el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="0" />
<div class="deleteFile" @click="deleteFileNum(index)"><el-icon class="icon"><el-icon-Close/></el-icon></div>
</div>
</div>
<div v-else>
<div class="item" v-for="(item) in listProgress" :key="item">
<div class="name">{{item.client_file_name}}</div>
<el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" />
</div>
</div>
</div>
</template>
</el-upload>
</div>
<template #footer>
<el-button @click="close"> </el-button>
<el-button type="primary" @click="save" :disabled="saveLoading"> </el-button>
</template>
</el-dialog>
</template>
<script>
import {eventBus} from "@/utils/eventBus"
export default {
name: "attachmentUpload",
emits: ['closed', "uploadFileSuccess"],
props:{
size:{type:String, default:'small'},
title:{type:String, default: ""},
},
data(){
return{
visible: false,
saveLoading:false,
fileList:[], //
listProgress:[], //
oss:{
host:''
},
upload_data:{},
params:{},
parentParams:{
to_user_id:"",
unique:""
},
progressShow:false,
uploadShow:false,
}
},
setup(){
},
watch:{
},
mounted() {
eventBus.$on('sockBack', this.getWsResult);
},
unmounted() {
eventBus.$off('sockBack', this.getWsResult);
},
methods:{
getWsResult(res){
if(res.data && (res.data.type == 12)){
this.progressShow = true;
if(this.listProgress.length==0){
this.listProgress.push(res.data);
}
let ids = this.listProgress.map(em=>em.file_unique_id);
if(!ids.includes(res.data.file_unique_id)){
this.listProgress.push(res.data);
}
this.listProgress.forEach(em=>{
if(em.file_unique_id == res.data.file_unique_id){
em.rate = res.data.rate
}
})
let list = this.listProgress.filter(em=> em.rate != 100);
if(list.length==0 && res.data.status == 1){
this.progressShow = false;
}
if(res.data.status == 1){
this.fileList.splice(this.fileList[0],1);
this.params = {};
this.$emit('uploadFileSuccess');
}
}
},
//
importFile(params){
this.visible = true;
this.parentParams = params;
},
//
beforeUpload(file){
this.progressShow = true;
this.uploadShow = true;
this.fileList.push(file);
return true
},
async uploadFile() {
try {
this.setParamsData();
} catch (error) {
console.log(error)
}
},
//
setParamsData(){
const formData = new FormData();
for(let i in this.fileList){
formData.append('files[]', this.fileList[i]);
}
for (const key in this.upload_data) {
formData.append(key, this.upload_data[key]);
}
formData.append('to_user_id', this.parentParams['to_user_id']);
this.params = formData;
},
deleteFileNum(num){
this.handleRemove(this.fileList[num], this.fileList);
this.setParamsData();
},
close(){
this.visible = false;
this.progressShow = false;
this.listProgress = [];
this.handleRemove('',this.fileList,true);
},
async save() {
this.saveLoading = true;
const res = await this.$API.customer.attachmentMessage.post(this.params);
this.saveLoading = false;
if (res.code == 200) {
this.$message.success('提交成功,正在上传');
this.listProgress = [];
this.visible = false;
// this.uploadShow = false;
// this.$emit('uploadFileSuccess');
}
},
handleExceed(){
this.$message.warning('文件超出最大限制');
},
handleRemove(file,fileList,isArr=false){
if(isArr){
fileList = [];
// this.listProgress = [];
}else{
const index = fileList.findIndex(f => f.uid === file.uid);
if (index !== -1) {
fileList.splice(index, 1);
}
}
},
//
handleSuccess(response, file, fileList){
const index = fileList.findIndex(f => f.uid === file.uid);
if (index !== -1) {
fileList.splice(index, 1);
}
},
//
handleError(){
this.$message.warning('上传失败请重新上传');
}
}
}
</script>
<style scoped lang="scss">
.importBody{
padding: 0 20px;
}
.upload .el-icon--upload{
width: 54px;height: 54px;
}
.upload ::v-deep .el-upload__text{
.trueIcon{
position: relative;
border: 1px solid var(--el-border-color);
border-radius: 4px;
}
.laText{
color: var(--el-color-dark);
}
.tipText{
color: var(--el-text-color-placeholder);
}
.deleteFile{
position: absolute;
right: -6px;
top: -10px;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--el-color-danger);
display: flex;
align-items: center;
justify-content: center;
.icon{
width: 12px;height: 12px;
color: var(--el-color-white);
}
}
}
.upload ::v-deep .el-upload__tip{
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 15px;
color: var(--el-text-color-placeholder);
.btn{
color: var(--el-color-primary);
cursor: pointer;
}
}
.importBox{
border: 1px solid var(--el-border-color);
padding: 10px 5px;
margin: 15px 0;
border-radius: 4px;
.item{
margin-bottom: 10px;position: relative;
.deleteFile{
position: absolute;
right: 0;
top: 1px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #AAAAAA;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.icon{
width: 12px;height: 12px;
color: var(--el-color-white);
}
}
}
.item:last-child{margin-bottom: 0;}
.name{margin-bottom: 5px;font-size: 13px;}
.exportPopover ::v-deep .el-progress-bar__innerText{
height: 100%;
display: flex;align-items: center;justify-content: flex-end;
font-size: 10px;
}
}
.errText{
margin: 15px 0 0 0;
padding: 10px;
border-radius: 6px;
background: var(--el-color-danger-light-9);
position: relative;
.name{
color: var(--el-color-danger);
font-size: 12px;
line-height: 20px;
}
.deleteErr{
position: absolute;
right: 4px;
top: 4px;
z-index: 20;
cursor: pointer;
.icon{
font-size: 16px;
}
}
}
</style>

View File

@ -1,10 +1,56 @@
<template>
<div></div>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="600" draggable destroy-on-close @closed="$emit('closed')">
<el-input placeholder="搜索"></el-input>
<el-tabs>
<el-tab-pane label="全部">
<records-list></records-list>
</el-tab-pane>
<el-tab-pane label="文档">
647658
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script>
import recordsList from "@/views/service/components/recordsList";
export default {
name: "customerRecords"
name:"customerRecords",
components:{
recordsList
},
emits: ['success', 'closed'],
data() {
return {
mode: "show",
titleMap: {
show: '聊天记录',
},
visible: false,
isSave: false,
}
},
mounted() {
},
methods: {
//
open(mode='show'){
this.mode = mode;
this.visible = true;
return this
},
//
async setData(data) {
let params = {
page:1,
pageSize:100,
to_user_id:data
}
await this.$API.customer.recordList.post(params);
}
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<div class="recordView">
<!-- <div class="itemCol" v-for="(item,index) in list">-->
<!-- <div class=""></div>-->
<!-- </div>-->
</div>
</template>
<script>
export default {
name: "recordsList",
props:{
// list:{
// type:Array,
// default:[]
// }
},
data(){
return{
}
},
mounted() {
},
methods:{
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -26,7 +26,7 @@
</el-aside>
<el-main class="userMain userMainPadding_0" style="border-top-right-radius: 0;border-bottom-right-radius: 0;padding: 0;">
<div class="mainBody">
<div class="mainTitle">{{contactsInfo.to_user && contactsInfo.to_user.name?contactsInfo.to_user.name:"匿名"}}</div>
<div class="mainTitle">{{contactsInfo.from_user && contactsInfo.from_user.name?contactsInfo.from_user.name:"匿名"}}</div>
<div class="mainView">
<el-scrollbar ref="scrollbar">
<div class="msgList">
@ -37,18 +37,22 @@
</el-avatar>
</div>
<div class="msgText">
<div class="msgTitle">{{item.from_user && item.from_user.name?item.from_user.name:'匿名'}}</div>
<div class="msgTitle">
<span class="leftTime" v-if="user_id != item.to_user_id">{{item.created_at}}</span>
{{item.from_user && item.from_user.name?item.from_user.name:'匿名'}}
<span class="time" v-if="user_id === item.to_user_id">{{item.created_at}}</span>
</div>
<div class="textCom" v-if="item.send_message_type === 1">
<div v-html="item.to_message"></div>
</div>
<div class="textCom" v-if="item.send_message_type === 2">
<div class="textCom textComNoneBack" v-if="item.send_message_type === 2">
<div v-for="(em,ind) in item.to_message_list" :key="ind">
<span v-if="em.extension === 'xlsx'">
文件 ====
</span>
<span v-else>
<span v-if="em.extension === 'png' || em.extension === 'jpg' || em.extension === 'gif' || em.extension === 'jpeg'">
<el-image style="max-width: 280px;max-height: 240px;" fit="contain" :src="em.file?em.file:em" preview-teleported :preview-src-list="[em.file?em.file:em]"></el-image>
</span>
<span class="fileView" v-else>
<fileType size="60px" :fileType="em.extension" />
</span>
</div>
</div>
</div>
@ -63,12 +67,12 @@
</div>
<div class="mainFooter">
<div class="tagList">
<div class="tagItem"><el-icon size="18"><sc-icon-Attachment/></el-icon></div>
<div class="tagItem"><el-icon size="18"><sc-icon-Record/></el-icon></div>
<div class="tagItem" @click="uploadFile"><el-icon size="18"><sc-icon-Attachment/></el-icon></div>
<div class="tagItem" @click="recordsShow"><el-icon size="18"><sc-icon-Record/></el-icon></div>
<div class="tagItem"><el-icon size="18"><sc-icon-Expression/></el-icon></div>
</div>
<div class="sendView">
<el-input v-model="params.to_message" class="customTextarea" type="textarea" placeholder="请输入.." :rows="3" resize="none"></el-input>
<el-input @keydown="handleKeydown" v-model="params.to_message" class="customTextarea" type="textarea" placeholder="Shift+Enter换行 请输入.." :rows="3" resize="none"></el-input>
<div class="saveBtn" @click="sendCustomer" :class="params.to_message!=''?'saveActive':''">
<el-icon size="22"><sc-icon-SendCustom /></el-icon>
</div>
@ -104,12 +108,23 @@
</el-container>
</el-aside>
</el-container>
<customer-records-dialog v-if="dialog.show" ref="recordsMsgDialog" @closed="dialog.show=false"></customer-records-dialog>
<uploadFile ref="uploadFile" @uploadFileSuccess="uploadFileSuccess" title="上传附件"></uploadFile>
</template>
<script>
import {eventBus} from "@/utils/eventBus";
import customerRecordsDialog from "@/views/service/components/customerRecords"
import uploadFile from "@/views/service/components/attachmentUpload";
import fileType from "@/views/docsManager/fileType"
export default {
name: "", // faq
components:{
customerRecordsDialog,
uploadFile,
fileType
},
data(){
return{
contactsList:[],
@ -121,6 +136,10 @@ export default {
params:{
to_user_id:"",
to_message:"",
},
dialog:{
show:false
}
}
},
@ -178,6 +197,14 @@ export default {
}
await this.$API.customer.list.post(params);
},
handleKeydown(event){
if(event.key === 'Enter' && !event.shiftKey){
event.preventDefault(); //
this.sendCustomer();
}else if(event.key === 'Enter' && event.shiftKey){
this.params.to_message += '';
}
},
async sendCustomer() {
if(this.params.to_message =="") return
const res = await this.$API.customer.send.post(this.params);
@ -197,6 +224,23 @@ export default {
}
},
//
uploadFile(){
this.$nextTick(()=>{
this.$refs.uploadFile.importFile(this.params);
})
},
uploadFileSuccess(){
this.getCustomerMsgList();
},
recordsShow(){
this.dialog.show = true;
this.$nextTick(() => {
this.$refs.recordsMsgDialog.open('show').setData(this.to_user_id);
})
},
scrollDown() {
this.$nextTick(() => {
const wrap = this.$refs.scrollbar;
@ -305,7 +349,8 @@ export default {
padding: 10px;
.msgItem{
display: flex;
margin-bottom: 8px;
margin-bottom: 12px;
padding-right: 40px;
.avatar{
padding-right: 10px;
}
@ -313,27 +358,54 @@ export default {
.msgTitle{
padding:0 0 8px 0;
color: #555;
.time{
margin-left: 8px;
font-size: 12px;
color: #888;
}
.leftTime{
margin-right: 8px;
font-size: 12px;
color: #888;
}
}
.textCom{
background: #f5f5f5;
border-radius: 4px;
padding: 10px;
display: inline-block;
text-align: left;
.fileView{
cursor: pointer;
}
}
.textComNoneBack{
background: none;
}
}
}
.msgRightItem{
justify-content: flex-end;
padding:0 0 0 40px;
.avatar{
padding-left: 10px;
padding-right: 0;
}
.msgText{
text-align: right;
.msgTitle{
text-align: right;
}
.textCom{
background: var(--el-color-primary);
color: var(--el-color-white);
text-align: left;
}
.textComNoneBack{
background: none;
.el-image{
border-radius: 4px;
}
}
}
}
@ -355,6 +427,7 @@ export default {
align-items: center;
justify-content: center;
margin-left: 15px;
cursor: pointer;
}
}
.customTextarea{