This commit is contained in:
feng0207
2026-08-11 22:22:10 +08:00
commit 5e312c1266
6985 changed files with 1061169 additions and 0 deletions
@@ -0,0 +1,129 @@
<template>
<div class="container">
<div class="header_banner">
<div class="header_banner_content">
<div class="header_banner_content_title"><span>提建议或举报不良内容</span></div>
<div class="header_banner_content_message">
<span>若您收到不良信息请及时反馈我们非常重视您给我们提出宝贵的建议帮助我们不断完善产品谢谢</span></div>
</div>
<div class="header_banner_image">
<div class="header_banner_image_imageBox">
<image src="~static/image/feedback/bg.png" mode="widthFix"></image>
</div>
</div>
</div>
<!-- <div class="organization_work">
<div class="organization_work_item">
<div class="organization_work_item_label">
<span>09:00~22:00</span>
</div>
<div class="organization_work_item_value">
<span>我们的工作时间</span>
</div>
</div>
<div class="tip"></div>
<div class="organization_work_item">
<div class="organization_work_item_label">
<span>400-8888-794</span>
</div>
<div class="organization_work_item_value">
<span>售前客服电话</span>
</div>
</div>
</div> -->
<div class="feedback">
<div class="feedback_title"><span>我要投诉</span></div>
<div class="feedback_list">
<div class="feedback_list_item" v-for="(item, index) in feedbackList" :key="index"
@click="selectItem(item)">
<div>
<div :class="{ feedback_list_item_selected: item.selected }"></div>
</div>
<div>{{ item.name }}</div>
</div>
</div>
<!-- <div class="feedback_input">
<input v-model="feedBackData.rela_name" type="text" value="" placeholder="请输入您的姓名"/>
</div>
<div class="feedback_input">
<input v-model="feedBackData.phone" type="text" value="" placeholder="请输入您的联系电话" />
</div> -->
<div class="feedback_textarea"><textarea v-model="feedBackData.content" value="" placeholder="请填写留言内容" />
</div>
<div class="handle_button" @click="handleSubmit"><span>提交</span></div>
</div>
<uni-popup ref="feedbackReslove" animation>
<submit-response @handleSubmitRes="handleSubmitRes"></submit-response>
</uni-popup>
</div>
</template>
<script>
import {
navigateBack
} from 'pages/utils/uniApi.js';
import submitResponse from '../feedback/component/submitResponse.vue';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
components: {
submitResponse
},
data() {
return {
feedBackData: {
content: '',
user_id: '',
cate_id: []
},
feedbackList: [],
userId: 0,
};
},
onLoad(option) {
this.userId = option.id || 0;
},
onShow() {
this.getUserComplain();
},
methods: {
// 获取投诉内容列表
getUserComplain() {
http(api.userComplain).then(res => {
this.feedbackList = res;
this.feedbackList.forEach(item => {
this.$set(item, 'selected', false);
});
});
},
// 选择投诉项目
selectItem(item) {
item.selected = !item.selected;
},
handleSubmit() {
this.feedBackData.cate_id = this.feedbackList.filter(item => item.selected).map(val => val.id);
this.feedBackData.user_id = this.userId;
http(api.postUserComplain, this.feedBackData).then(res => {
this.$refs.feedbackReslove.open('center');
});
},
handleSubmitRes() {
navigateBack(1);
this.$refs.feedbackReslove.close();
}
}
};
</script>
<style lang="less" scoped>
@import url('../feedback/less/feedback.less');
</style>
@@ -0,0 +1,536 @@
<!-- 话数库 -->
<template>
<div class="scriptLibary_container" :style="{ 'max-height': maxHeight, height: height,'min-height': minWidth, width: width }">
<div class="script_group">
<scroll-view scroll-y="true" class="script_group_scroll">
<div class="tag_list flex-center" @click="addGroup" v-if="speechArtType == 1">
<span class="iconfont">&#xe6c1;</span>
<span class="title_value">分组</span>
</div>
<div class="tag_list tag_list_group" v-for="(item, index) in tagList" :key="index" :class="{ selectEdTag: cateId == item.id }" @click="selectTags(item)">
<div class="tag_list_tag" v-if="cateId == item.id"></div>
<div class="tag_list_label">{{ item.name }}</div>
</div>
</scroll-view>
</div>
<div class="script_list">
<scroll-view scroll-y="true" class="script_list_scroll">
<div class="add_script" v-if="speechArtType == 1" @click="addSpeech">
<div><span class="iconfont">&#xe6b9;</span></div>
<div><span>添加话术</span></div>
</div>
<div class="script_list_item" v-for="(item, index) in scriptList" :key="index" @click="selectScript(item)">
<div class="script_list_item_label">{{ item.title }}</div>
<div class="script_list_item_value line2">{{ item.message }}</div>
<div class="edit_box" v-if="speechArtType == 1">
<div @click.stop="editSpeech(item)"><span class="iconfont">&#xe6b2;</span></div>
<div @click.stop="handleDeleteServiceSpeechcraft(item)"><span class="iconfont">&#xe6b3;</span></div>
</div>
</div>
</scroll-view>
</div>
<uni-popup ref="addEdGroup" animation type="center">
<div class="addGroup_container">
<div class="addGroup_container_title">
<div></div>
<div>添加话术</div>
<div @click="closeAddGroup"><span class="iconfont">&#xe6b5;</span></div>
</div>
<div class="addGroup_form">
<div class="input_box"><input class="inp_title" maxlength="20" v-model="speechData.title" type="text" placeholder="请输入标题名称" /></div>
<div class="input_box"><textarea v-model="speechData.message" type="text" placeholder="请输入您的话术"></textarea></div>
<div class="button_box" :class="{ canInput: speechData.title && speechData.message }" @click="handleSubmiteditAdd"><div>确定</div></div>
</div>
</div>
</uni-popup>
<uni-popup ref="addEdUserGroups" animation type="center">
<div class="addGroup_container">
<div class="addGroup_container_title">
<div></div>
<div>{{handleGroupTitle}}</div>
<div @click="closeUserGroups"><span class="iconfont">&#xe6b5;</span></div>
</div>
<div class="addGroup_form">
<div class="input_box"><input v-model.trim="userGroupData.name" maxlength="4" type="text" placeholder="请输入分组名称" /></div>
<div class="input_box"><input v-model="userGroupData.sort" type="text" placeholder="请输入分组排序"></input></div>
<div class="button_box" :class="{ canInput: userGroupData.name }" @click="handleSubmitUserGroup"><div>确定</div></div>
</div>
</div>
</uni-popup>
</div>
</template>
<script>
import { Toast, throttle, serialize, Modal, ActionSheet } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
props: {
// 话术库分类
speechArtType: {
type: Number | String,
default: '1'
},
// 话术检索
title: {
type: String,
default: ''
},
// 是否展示添加话术
icCanEdit: {
type: Boolean,
default: false
},
maxHeight: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
minWidth: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
},
data() {
return {
// 话术分类
tagList: {
type: Array,
default() {
return [];
}
},
scriptList: [],
pageData: {
page: 1,
limit: '200'
},
cateId: '', // 选中的侧边栏ID
speechData: {
title: '',
message: ''
},
userGroupData: {
name: '',
sort: ''
},
handleAdEdType: 1, // 1新增 2修改
handleGroupTitle: '新增分组',
groupType: 1, // 1新增 2修改
};
},
watch: {
speechArtType: {
handler(val) {
this.selectScriptLibary();
// this.getSpeechArtList('init');
console.log(this.tagList);
}
},
title: {
handler(val) {
setTimeout(() => {
this.getSpeechArtList();
}, 500);
}
},
tagList: {
handler(val) {
this.getSpeechArtList('init');
}
}
},
created() {
this.selectScriptLibary();
},
methods: {
// 查询话术类
selectScriptLibary() {
http(api.kefuServiceCate, {
type: this.speechArtType
}).then(res => {
this.tagList = res.data;
this.getSpeechArtList('init');
});
},
// 获取话术列表
getSpeechArtList(type = 'select') {
if (this.tagList.length) {
if (type == 'init') {
this.cateId = this.tagList[0].id;
}
http(api.serviceSpeechcraft, {
cate_id: this.cateId,
type: this.speechArtType,
...this.pageData,
title: this.title
}).then(res => {
this.scriptList = res;
this.speechData = {
title: '',
message: ''
}
console.log(this.scriptList);
});
} else {
// setTimeout(() => {
// this.getSpeechArtList(type);
// }, 500);
}
},
// 选择标签
selectTags(item) {
if(this.cateId == item.id) {
console.log(item);
this.userGroupData = serialize(item);
ActionSheet(['编辑', '删除']).then(res => {
console.log(res)
switch(res) {
case 0:
this.groupType = 2;
this.handleGroupTitle = '编辑分组';
this.$refs.addEdUserGroups.open();
break;
case 1:
Modal('温馨提示', `分组 "${item.name}"将被删除, 请问是否继续?`).then(() => {
http(api.deleteServiceCate, item).then(res => {
Toast('删除成功');
this.selectScriptLibary('init');
})
})
break;
default:
break;
}
})
} else {
this.cateId = item.id;
this.getSpeechArtList();
}
},
// 选择话术
selectScript(item) {
this.$emit('sendSpeechMessage', item);
},
// 添加话术
addSpeech() {
this.speechData.title = '';
this.speechData.message = '';
this.handleAdEdType = 1;
this.$refs.addEdGroup.open();
},
// 修改话术
editSpeech(item) {
this.handleAdEdType = 2;
this.speechData = serialize(item);
this.$refs.addEdGroup.open();
},
// 确认新增,修改话术
handleSubmiteditAdd() {
if (this.handleAdEdType == 1) {
this.addSpeechClass();
return;
}
if ((this.handleAdEdType = 2)) {
this.editSpeechClass();
return;
}
},
// 修改话术事件
editSpeechClass() {
let canInput = true;
console.log(this.speechData);
Object.keys(this.speechData).forEach(item => {
if (!this.speechData.title || !this.speechData.message) {
canInput = false;
}
});
if (!canInput) {
Toast('请将内容填写完整');
return;
}
http(api.putServiceSpeechcraft, this.speechData).then(res => {
Toast('修改成功');
this.getSpeechArtList();
this.$refs.addEdGroup.close();
});
},
// 新增话术事件
addSpeechClass() {
let canInput = true;
Object.keys(this.speechData).forEach(item => {
if (!this.speechData[item]) {
canInput = false;
}
});
if (!canInput) {
Toast('请将内容填写完整');
return;
}
http(api.postServiceSpeechcraft, {
...this.speechData,
cate_id: this.cateId
}).then(res => {
Toast('新增成功');
this.getSpeechArtList();
this.$refs.addEdGroup.close();
});
},
// 删除话术
handleDeleteServiceSpeechcraft(item) {
Modal('温馨提示', `话术"${item.title}"将被删除,请问是否继续?`).then(res => {
http(api.deleteServiceSpeechcraft, item).then(res => {
Toast('删除成功');
this.getSpeechArtList();
});
});
},
closeAddGroup() {
this.$refs.addEdGroup.close();
},
// 新增分组
addGroup() {
this.groupType = 1;
this.$refs.addEdUserGroups.open();
},
// 确定新增 或 修改 分组 1增 2改
async handleSubmitUserGroup() {
if(this.groupType == 1) {
await http(api.serviceCate, {
...this.userGroupData
}).then(res => {
Toast('添加成功');
})
}
if(this.groupType == 2) {
console.log(this.userGroupData);
await http(api.putServiceCate, this.userGroupData).then(res => {
Toast('修改成功');
this.selectScriptLibary();
this.$refs.addEdUserGroups.close();
})
}
this.selectScriptLibary();
this.$refs.addEdUserGroups.close();
this.userGroupData = {
name: '',
sort: ''
}
},
// 关闭分组弹框
closeUserGroups() {
this.$refs.addEdUserGroups.close();
}
}
};
</script>
<style lang="less" scoped>
.scriptLibary_container {
display: flex;
overflow: hidden;
.script_group {
// width: 176rpx;
width: 20%;
position: fixed;
background: #f5f6f9;
.script_group_scroll {
width: 100%;
height: 100%;
.tag_list {
width: 100%;
height: 109rpx;
.iconfont {
color: #9f9f9f;
font-size: 11px;
margin-right: 4rpx;
}
.title_value {
color: #9f9f9f;
font-size: 13px;
position: relative;
top: 2rpx;
}
.tag_list_tag {
width: 6rpx;
height: 46rpx;
background: @primaryColor;
opacity: 1;
border-radius: 0px 4px 4px 0px;
}
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.tag_list_group {
display: flex;
align-items: center;
justify-content: flex-start;
position: relative;
.tag_list_label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
color: #282828;
font-size: 13px;
font-weight: bold;
}
}
}
}
.script_list {
// flex: 1;
width: 80%;
position: absolute;
right: 0;
.add_script {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 30rpx;
height: 109rpx;
}
.script_list_scroll {
height: 100%;
.script_list_item {
padding: 25rpx 30rpx;
&_label {
color: #282828;
font-size: 14px;
font-weight: bold;
}
&_value {
font-size: 14px;
color: #9f9f9f;
}
}
.edit_box {
display: flex;
justify-content: flex-end;
> div {
margin-left: 48rpx;
}
.iconfont {
font-size: 16px;
}
}
}
}
}
.selectEdTag {
background: #fff !important;
.tag_list_label {
color: @primaryColor !important;
}
}
.addGroup_container {
width: 690rpx;
background: #fff;
border-radius: 12px;
&_title {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
padding: 30rpx 30rpx 50rpx 0;
.iconfont {
font-size: 22px;
color: #c8cad0;
}
> div {
flex: 1;
text-align: center;
}
> div:nth-child(3) {
text-align: right;
}
}
.addGroup_form {
padding: 0 30rpx;
padding-bottom: 69rpx;
.input_box {
margin-bottom: 32rpx;
background: #f5f5f5;
border-radius: 14rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
input {
font-size: 14px;
height: 68rpx;
}
textarea {
padding: 8px 0;
font-size: 14px;
height: 192rpx;
}
}
.button_box {
margin-top: 60rpx;
> div {
height: 86rpx;
background: #c8cad0;
opacity: 1;
border-radius: 43px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
}
}
.canInput {
> div {
height: 86rpx;
background: @primaryColor;
opacity: 1;
border-radius: 43px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
}
}
}
}
.line2 {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.inp_title {
width: 600rpx;
}
</style>
@@ -0,0 +1,176 @@
<template>
<div class="transfer_container">
<div class="transfer_container_title">
<div class="transfer_container_title_value"><span>转接客服</span></div>
<div class="transfer_container_title_icon" @click="close"><span class="iconfont">&#xe6b5;</span></div>
</div>
<div class="customerServer_list">
<scroll-view scroll-y="true" class="max_height">
<div class="customerServer_list_item" v-for="(item, index) in serverList" :key="index" @click="selectCustomerServer(item)">
<div class="customerServer_list_item_radio">
<div><div v-if="item.select"></div></div>
</div>
<div class="customerServer_list_item_avar"><image :src="item.avatar" mode=""></image></div>
<div class="customerServer_list_item_nickName">
<span>{{ item.nickname }}</span>
</div>
</div>
</scroll-view>
</div>
<div class="handle_box" @click="handleSubmit"><div>确定</div></div>
</div>
</template>
<script>
import { Toast } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
props:{
userInfo:{
type:Object,
default:{}
}
},
data() {
return {
serverList: []
};
},
created() {
this.initData();
},
methods: {
// 查询其他客服
initData() {
http(api.serviceTransferList).then(res => {
this.serverList = res.list;
this.serverList.forEach(item => {
this.$set(item, 'select', false);
});
console.log(this.serverList);
});
},
// 选择客服
selectCustomerServer(item) {
if (item.select) {
item.select = false;
} else {
this.serverList.forEach(item => {
item.select = false;
});
item.select = true;
}
},
// 确认转接
handleSubmit() {
http(api.serviceTransfer, {
kefuToUserId: this.serverList.find(item => item.select).user_id, // 转接人id
user_id: this.userInfo.id
}).then(res => {
Toast('转接成功');
this.$emit('handleSubmit');
});
},
close() {
this.$emit('close');
}
}
};
</script>
<style lang="less" scoped>
.transfer_container {
padding: 40rpx 0 68rpx 0;
background: #fff;
border-radius: 12rpx 12rpx 0px 0px;
&_title {
position: relative;
display: flex;
justify-content: flex-end;
padding: 0 30rpx;
&_value {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #282828;
font-weight: 600;
font-size: 16px;
}
&_icon {
.iconfont {
font-size: 22px;
color: #c8cad0;
}
}
}
.customerServer_list {
&_item {
display: flex;
align-items: center;
padding: 0 30px;
height: 122rpx;
border-bottom: 1px solid #f0f2f7;
&_radio {
margin-right: 38rpx;
> div {
width: 34rpx;
height: 34rpx;
border: 1px solid @primaryColor;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
> div {
width: 18rpx;
height: 18rpx;
background: @primaryColor;
border-radius: 50%;
}
}
}
&_avar {
width: 90rpx;
height: 90rpx;
border-radius: 6px;
overflow: hidden;
margin-right: 28rpx;
image {
width: 100%;
height: 100%;
}
}
&_nickName {
color: #282828;
font-size: 15px;
font-weight: bold;
}
}
}
.handle_box {
display: flex;
justify-content: center;
margin-top: 80rpx;
> div {
width: 690rpx;
height: 86rpx;
background: #3875ea;
opacity: 1;
border-radius: 43px;
color: #ffffff;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.max_height {
max-height: 772rpx;
}
</style>
@@ -0,0 +1,336 @@
<template>
<div class="container">
<lay-out titleName="客户信息" headerColor="#fff">
<!-- <div slot="hander_handle">
<div @click="changeInputRadio"><span class="iconfont" :class="{ selected: inputRadio }">&#xe6b2;</span></div>
</div> -->
<div slot="content" class="content">
<div class="content_header">
<div class="content_header_avar"><image :src="userDetilsData.avatar" mode="widthFix"></image></div>
<div class="content_header_userName">
<span>{{ userDetilsData.nickname }}</span>
</div>
<div class="content_header_tag">
<span>{{ formType[userDetilsData.type] }}</span>
</div>
</div>
<div class="content_message">
<div class="content_message_item">
<div class="content_message_item_label"><span>备注昵称</span></div>
<div class="content_message_item_value">
<input type="text" v-model="userDetilsData.remark_nickname" placeholder="请输入备注昵称" v-if="inputRadio" />
<span v-else>{{ userDetilsData.remark_nickname ? userDetilsData.remark_nickname : '暂无数据' }}</span>
</div>
</div>
<div class="content_message_item">
<div class="content_message_item_label"><span>手机号</span></div>
<div class="content_message_item_value">
<input type="text" v-model="userDetilsData.phone" placeholder="请输入客户联系方式" v-if="inputRadio" />
<span v-else>{{ userDetilsData.phone ? userDetilsData.phone : '暂无数据' }}</span>
</div>
</div>
<div class="content_message_item">
<div class="content_message_item_label"><span>性别</span></div>
<div class="content_message_item_value" v-if="inputRadio">
<picker mode="selector" :range="sexArr" range-key="label" @change="changeSex">
<view>{{ sexType[userDetilsData.sex] }}</view>
</picker>
</div>
<div class="content_message_item_value" v-else>
<span>{{ sexType[userDetilsData.sex] }}</span>
</div>
</div>
<div class="content_message_item">
<div class="content_message_item_label"><span>备注</span></div>
<div class="content_message_item_value">
<input type="text" v-model="userDetilsData.remarks" placeholder="请输入备注" v-if="inputRadio" />
<span v-else>{{ userDetilsData.remarks ? userDetilsData.remarks : '暂无数据' }}</span>
</div>
</div>
<div class="content_message_item" @click="openUserTag">
<div class="content_message_item_label"><span>用户标签</span></div>
<div class="content_message_item_value flex">
<div class="tag_list">
<span v-for="(item, index) in userDetilsData.label" :key="index">{{ item.label }}</span>
</div>
<div class="to_link" v-if="inputRadio"><span class="iconfont font">&#xe6c3;</span></div>
</div>
</div>
<div class="content_message_item" @click="editGroup">
<div class="content_message_item_label"><span>分组</span></div>
<div class="content_message_item_value">
<span>{{ userGroupConcat[userDetilsData.group_id] }}</span>
</div>
<div class="to_link" v-if="inputRadio"><span class="iconfont">&#xe6c3;</span></div>
</div>
<div class="content_message_item">
<div class="content_message_item_label"><span>用户类型</span></div>
<div class="content_message_item_value">
<span>{{ formType[userDetilsData.type] }}</span>
</div>
</div>
<!-- <div class="content_message_item">
<div class="content_message_item_label"><span>生日</span></div>
<div class="content_message_item_value"><span>9月5号</span></div>
</div> -->
<div class="content_message_item marginTop16" @click="handelComplaint">
<div class="content_message_item_label"><span>投诉</span></div>
<div class="content_message_item_value">
<span>{{ formType[userDetilsData.type] }}</span>
</div>
</div>
<div class="content_message_item marginTop16" @click="handelblock">
<div class="content_message_item_label"><span>拉黑</span></div>
<div class="content_message_item_value">
<span>{{ formType[userDetilsData.type] }}</span>
</div>
</div>
</div>
<!-- <div class="userTag_container_handle" v-if="inputRadio" @click="handleEditUserMessage">
<div class="userTag_container_handle_button"><span>确认修改</span></div>
</div> -->
<!-- <div class="userTag_container_handle" @click="toMessage">
<div class="userTag_container_handle_button"><span>发消息</span></div>
</div> -->
</div>
</lay-out>
<uni-popup ref="userTagModel" type="center" animation>
<div class="userTag_container">
<div class="userTag_container_title">
<div class="userTag_container_title_message"><span>用户标签</span></div>
<div class="closeModel" @click="closeUserTagModel"><span class="iconfont">&#xe6c6;</span></div>
</div>
<div class="userTag_container_list">
<div class="userTag_container_list_item" :class="{ mg40: index == 0 }" v-for="(item, index) in userTagList" :key="index">
<div class="userTag_container_list_item_label">
<span>{{ item.name }}</span>
</div>
<div class="userTag_container_list_item_value">
<span :class="{ select: val.disabled }" v-for="(val, i) in item.label" :key="i" @click="selectTags(val)">{{ val.label }}</span>
</div>
</div>
</div>
<div class="userTag_container_handle" @click="handleSetTags">
<div class="userTag_container_handle_button" ><span>确定</span></div>
</div>
</div>
</uni-popup>
<uni-popup ref="userGroupModel" type="center" animation>
<div class="userTag_container">
<div class="userTag_container_title">
<div class="userTag_container_title_message"><span>用户分组</span></div>
<div class="closeModel" @click="closeUserGroupModel"><span class="iconfont">&#xe6c6;</span></div>
</div>
<div class="userTag_container_list">
<div class="userTag_container_list_item">
<div class="userTag_container_list_item_value">
<span :class="{ select: val.id == selectGroupId }" v-for="(val, i) in userGroup" :key="i" @click="selectGroup(val)">{{ val.group_name }}</span>
</div>
</div>
</div>
<div class="userTag_container_handle" @click="handleSetGroup">
<div class="userTag_container_handle_button"><span>确定</span></div>
</div>
</div>
</uni-popup>
</div>
</template>
<script>
import { Toast, navigateTo, Modal } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
data() {
return {
inputRadio: true,
selectGroupId: '',
userTagList: [],
formType: {
0: 'pc',
1: '微信',
2: '小程序',
3: 'H5'
},
sexType: {
0: '未知',
1: '男',
2: '女'
},
sexArr: [
{
value: 0,
label: '未知'
},
{
value: 1,
label: '男'
},
{
value: 2,
label: '女'
}
],
userGroup: [], // 用户分组
userGroupConcat: {},
queryUserData: {}, // url上挂载的参数
userDetilsData: {} // 用户详细信息
};
},
onLoad(opt) {
this.queryUserData = opt;
this.initUserGroup();
},
onNavigationBarButtonTap() {
this.handleEditUserMessage();
},
methods: {
// 拉黑
handelblock() {
Modal('拉黑用户', `"${this.userDetilsData.nickname}"将被拉黑,请问是否继续?`).then(() => {
http(api.putUserStatus, { userId: this.userDetilsData.user_id }).then(res => {
Toast('拉黑成功');
this.$store.commit('setRefresh', true);
navigateTo(4, '/pages/view/messageList/index');
});
});
},
// 点击投诉按钮,跳转投诉界面
handelComplaint() {
navigateTo(1, '/pages/view/customerServer/complaint?id='+this.userDetilsData.id);
},
// 获取用户详细信息
initData() {
console.log(this.queryUserData);
http(api.userInfo, this.queryUserData).then(res => {
this.userDetilsData = res;
this.selectGroupId = this.userDetilsData.group_id;
});
},
// inputRadio 为 true 时,可以修改客户信息
changeInputRadio() {
this.inputRadio = !this.inputRadio;
},
// 获取所有分组 ---> 获取详细信息
initUserGroup() {
http(api.UserGroup).then(res => {
this.userGroup = res;
this.userGroup.forEach(item => {
this.userGroupConcat[item.id] = item.group_name;
});
this.initData();
});
},
// 打开用户标签选择选项
openUserTag() {
if (!this.inputRadio) {
Toast('请打开修改开关');
return;
}
http(api.userLabelSelect, { id: this.queryUserData.user_id }).then(res => {
this.userTagList = res;
this.$refs.userTagModel.open('center');
});
},
// 选择标签
selectTags(item) {
this.$set(item, 'disabled', !item.disabled);
},
// 选择性别
changeSex(e) {
this.userDetilsData.sex = e.detail.value;
},
// 选择分组弹框
editGroup() {
if (!this.inputRadio) {
Toast('请打开修改开关');
return;
}
this.$refs.userGroupModel.open();
},
// 关闭选择分组
closeUserGroupModel() {
this.$refs.userGroupModel.close();
},
// 选择分组
selectGroup(item) {
if (this.selectGroupId == item.id) {
this.selectGroupId = '';
return;
}
this.selectGroupId = item.id;
},
// 确认选择分组
handleSetGroup() {
this.userDetilsData.group_id = this.selectGroupId;
http(api.setUserGroup, {
id: this.userDetilsData.group_id,
userId: this.userDetilsData.id
}).then(res => {
Toast('用户分组设置完成');
this.$refs.userGroupModel.close();
});
},
// 设置标签
handleSetTags() {
let postData = {
label_ids: [],
un_label_ids: [],
userId: this.queryUserData.user_id
},Label = [];
this.userTagList.forEach(item => {
item.label.forEach(val => {
if (val.disabled) {
postData.label_ids.push(val.id);
Label.push(val)
} else {
postData.un_label_ids.push(val.id);
}
});
});
http(api.putUserLabel, postData).then(res => {
this.$refs.userTagModel.close();
// this.initData();
this.selectGroupId = postData.label_ids
this.userDetilsData.label = Label
});
},
// 关闭用户标签选项
closeUserTagModel() {
this.$refs.userTagModel.close();
},
// 确认修改用户信息
handleEditUserMessage() {
http(api.userUpdateUser, { ...this.userDetilsData, userId: this.userDetilsData.id }).then(res => {
// this.inputRadio = !this.inputRadio;
this.initUserGroup();
Toast('修改成功');
});
},
toMessage() {
navigateTo('2', '/pages/view/customerServer/index', this.queryUserData);
}
}
};
</script>
<style scoped lang="less">
@import url('./less/customerMessage.less');
</style>
+651
View File
@@ -0,0 +1,651 @@
<template>
<view class="container">
<lay-out id='layOut' ref="layOut" :scrollTop="scrollTop" :scrollWithAnimation="false" @goTop="goTop"
@layoutScroll="scroll">
<!-- #ifdef H5 -->
<view slot="header" class="header">
<view class="header_left" @click="goBackToMessageList"><span class="iconfont icon-fanhui"></span></view>
<view class="header_center">
{{ userData.remark_nickname ? userData.remark_nickname : userData.nickname }}
</view>
<view class="header_right" @click="selectCustomerServerOfTopRight(userData)"><span
class="iconfont">&#xe6d3;</span></view>
</view>
<!-- #endif -->
<view slot="content" class="content" id="contentMessage">
<view class="content_list" v-for="(item, index) in messageList" :key="index"
:class="{ right_box: customerServerData.user_ids && customerServerData.user_ids.indexOf(item.user_id) !== -1 }">
<view class="content_list_avar" @click="selectCustomerServer(item)">
<image :src="item.avatar" mode="widthFix"></image>
</view>
<view class="content_list_message">
<view class="content_list_message_value" v-if="item.msn_type <= 2"><text selectable="true"
v-html="replace_em(item.msn)"></text></view>
<view class="content_list_message_product" v-if="item.msn_type == 5">
<view class="content_list_message_orderId">
<span class="content_list_message_orderId_label">商品名称:</span>
<span class="content_list_message_orderId_value">{{ item.other.store_name }}</span>
</view>
<view class="content_list_message_detils">
<view class="content_list_message_detils_image">
<image :src="item.other.image" mode="widthFix"></image>
</view>
<view class="content_list_message_detils_value">
<view class="content_list_message_detils_value_shopMessag">
<view class="content_list_message_detils_value_shopMessag_item">
<view class="content_list_message_detils_value_shopMessag_item_label">
<span>库存:</span>
</view>
<view class="content_list_message_detils_value_shopMessag_item_value">
<span>{{ item.other.stock }}</span>
</view>
</view>
<view class="content_list_message_detils_value_shopMessag_item">
<view class="content_list_message_detils_value_shopMessag_item_label">
<span>销量:</span>
</view>
<view class="content_list_message_detils_value_shopMessag_item_value">
<span>{{ parseInt(item.other.sales) + parseInt(item.other.ficti ? item.other.ficti : 0) }}</span>
</view>
</view>
</view>
<view class="content_list_message_detils_value_shopPrice">
<span>{{ item.other.price }}</span>
</view>
</view>
</view>
</view>
<view class="content_list_message_image" v-if="item.msn_type == 3">
<image @load="imageLoad" :src="item.msn" mode="widthFix" @click="showImage(item.msn)">
</image>
</view>
</view>
<view class="chat_result" v-if="item.user_id == customerServerData.user_id">
<view class="chat_result_ing" v-if="item.is_send == 0">
<!-- 正在发送 -->
<view></view>
</view>
<view class="chat_result_faile" v-if="item.is_send == -1">
<!-- 发送失败 -->
<view>!</view>
</view>
</view>
</view>
</view>
<view slot="bottom" class="footer" :class="{ translateY0: selectModel }">
<view class="footer_input">
<view class="footer_input_icon mr18" @click="selectScriptLibary"><span
class="iconfont">&#xe6b8;</span></view>
<view class="footer_input_content mr18">
<textarea v-model="sendMessage" @input="textareaChange($event)" @focus="textareaFocus"
class="font" value="" @confirm="sendText" auto-height confirm-type="send"
:focus="sendMessageFocus" placeholder="" />
<!-- <p v-html="pCont" class="font"></p> -->
<!-- <span class="iconfont" :class="{ canSend: sendMessage }" @click="sendText">&#xe6bb;</span> -->
</view>
<view class="footer_input_icon">
<span class="iconfont mr18" @click="selectOption(1)">&#xe6cb;</span>
<span class="sendMessage" v-if="sendMessage" @click.stop="sendText">发送</span>
<span class="iconfont" @click="selectOption(2)" v-if="!sendMessage">&#xe6c2;</span>
</view>
</view>
<view class="footer_option" :style="{ height: selectModel ? '440rpx' : '0' }">
<view class="option" v-if="selectModel == 2">
<view @click="uploadImage">
<image src="~static/image/messageList/picture.png" mode="widthFix"></image>
<view>图片</view>
</view>
<view @click="transfer">
<image src="~static/image/messageList/connection.png" mode="widthFix"></image>
<view>转接</view>
</view>
<view @click="authReply">
<image src="~static/images/auth-reply.png" mode="widthFix"></image>
<view>自动回复</view>
</view>
</view>
<scroll-view class="emoji" v-if="selectModel == 1" scroll-y>
<view class="emoji-item" v-for="(item, index) in emoji" :key="index" @click="sendEmoji(item)"><i
class="em" :class="item"></i></view>
</scroll-view>
</view>
</view>
</lay-out>
<uni-popup ref="transferModel" animation type="bottom">
<transfer @close="transferModelClose" :userInfo="userData" @handleSubmit="handleSubmitTransfer"></transfer>
</uni-popup>
<uni-popup ref="scriptLibaryModel" animation type="bottom">
<view class="scriptLibary">
<view class="scriptLibary_header">
<view class="scriptLibary_header_title">
<view class="tit" v-for="(item, index) in speechArtTypeList" :key="index" @click="selectSpeechArtType(item)"
:class="{ speechArtSelectEd: speechArtType == item.id }">
<span>{{ item.label }}</span>
</view>
</view>
<view class="scriptLibary_header_icon">
<view @click="editScript"><span class="iconfont icon_edit">&#xe6b4;</span></view>
<view @click="closeScript"><span class="iconfont">&#xe6b5;</span></view>
</view>
</view>
<view class="scriptLibary_search">
<view class="search">
<span @click="handleSetTitle" class="iconfont icon_search">&#xe6d8;</span>
<input v-model="scriptTitle" type="text" placeholder="搜索快捷回复" />
</view>
</view>
<view class="scriptLibary_content">
<scripts-libary :tagList="speechTagList" :speechArtType="speechArtType" maxHeight="1000rpx"
height="900rpx" :title="scriptTitle" @sendSpeechMessage="sendSpeechMessage"></scripts-libary>
</view>
</view>
</uni-popup>
<uni-popup ref="showImage" animation type="center">
<view class="image_content">
<span class="iconfont" @click="closeImage">&#xe6b5;</span>
<image :src="selectMessageImage" mode="widthFix"></image>
</view>
</uni-popup>
</view>
</template>
<script>
import emoji from 'pages/utils/emoji.js';
import {
navigateBack,
navigateTo,
serialize,
chooseImage,
Toast,
Modal
} from 'pages/utils/uniApi.js';
import transfer from './component/transfer.vue';
import scriptsLibary from './component/scriptLibary.vue';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import {
mapGetters
} from 'vuex';
const app = getApp();
export default {
components: {
transfer, // 转接客服
scriptsLibary // 话术库
},
computed: mapGetters(['chatList', 'token']),
data() {
return {
selectMessageImage: '', // 选中的图片
sendMessageFocus: false,
pCont: '',
sendMessage: '', // 发送的消息
selectModel: 0, // 1:选择表情 2: 功能选择(转接,上传图片)
emoji: [],
messageList: [],
userData: {},
pageData: {
limit: 20,
upperId: 0
},
speechArtType: 1, // 0 全局 1 私有
speechTagList: [], // 话术分类
scriptTitle: '', // 搜索输入的内容
propstitle: '',
speechArtTypeList: [{
id: 1,
label: '个人库'
},
{
id: 0,
label: '公共库'
}
],
customerServerData: {
user_ids: []
},
scrollTop: 0, // 滚动位置
refresherTriggered: true, // 下拉刷新装状态
audioFun: '', // 音频对象
old: {
scrollTop: 0
},
kefuInfo: {}, //当前客服信息
userId: 0, //当前用户user_id
toChatSend: false //是否发送过to_chat
};
},
watch: {
messageList: {
deep: true,
handler() {
this.messageList.map((item, index) => {
if (index) {
if (item.add_time - this.messageList[index - 1].add_time >= 300) {
item.show = true;
} else {
item.show = false;
}
} else {
item.show = true;
}
return item;
});
}
}
},
onLoad(opt) {
this.emoji = emoji;
this.userId = opt.to_user_id;
if (!this.userId) {
return navigateBack(1);
}
this.customerServerData = this.$store.getters.kefuInfo;
console.log(this.customerServerData)
this.kefuInfo = this.$store.getters.kefuInfo;
this.messageList = this.$store.getters.chatLog(this.userId);
this.userData = this.$store.getters.userInfo(this.userId);
this.getUserInfo();
this.getKefuInfo();
this.initData();
this.sockEventListenr();
this.$store.dispatch('updataUserCount').then(res => {
this.scoket.setBadgeNumber(res.count);
});
},
onUnload() {
console.log('聊天界面卸载');
uni.$off('chat', this.chatEvent);
uni.$off('chat_auth', this.chatAuthEvent);
uni.$off('reply', this.replyEvent);
uni.$off('success', this.successTochat);
uni.$off('close', this.closeEvent);
uni.$off('rm_transfer', this.rmTransferEvent);
this.toChatSend = false;
this.scoket.send({
data: {
id: 0
},
type: 'to_chat'
});
},
// 监听,点击界面右上角的按钮
onNavigationBarButtonTap(e) {
this.selectCustomerServerOfTopRight();
},
methods: {
authReply() {
navigateTo(1, '/pages/view/authReply/index');
},
getKefuInfo() {
this.$store.dispatch('getKeufuInfo').then(res => {
this.kefuInfo = res;
});
},
//获取用户信息
getUserInfo() {
this.$store
.dispatch('getUserInfo', this.userId)
.then(res => {
this.userData = res; // 获取用户信息
uni.setNavigationBarTitle({
title: this.userData.remark_nickname ? this.userData.remark_nickname : this
.userData.nickname
});
})
.catch(err => {
uni.setNavigationBarTitle({
title: this.userData.remark_nickname ? this.userData.remark_nickname : this
.userData.nickname
});
});
},
scroll(value) {
this.old.scrollTop = value;
},
successTochat() {
console.log(this.toChatSend);
//没发过在发送to_chat
if (!this.toChatSend) {
this.scoket.send({
data: {
id: this.userId
},
type: 'to_chat'
});
this.toChatSend = true;
}
},
replyEvent(data) {
this.pushMessageToList(data);
},
chatAuthEvent(data) {
if (data.length) {
data.map(item => {
item.msn = this.replace_em(item.msn);
this.pushMessageToList(item);
})
}
},
chatEvent(data) {
this.messageList.map(item => {
if (item.guid === data.guid) {
item.is_send = 1;
}
});
this.$store.commit('delChatList', {
id: this.userId,
guid: data.guid
});
},
closeEvent() {
this.toChatSend = false;
},
rmTransferEvent(data) {
if (data.recored.to_user_id == this.userId) {
navigateBack(1);
}
},
// socket监听
sockEventListenr() {
if (!this.toChatSend && this.scoket.connectStatus) {
this.scoket.send({
data: {
id: this.userId
},
type: 'to_chat'
});
this.toChatSend = true;
}
uni.$on('success', this.successTochat);
uni.$on('chat', this.chatEvent);
uni.$on('chat_auth', this.chatAuthEvent);
uni.$on('reply', this.replyEvent);
uni.$on('close', this.closeEvent);
uni.$on('rm_transfer', this.rmTransferEvent);
},
// 获取聊天列表
initData() {
this.$store
.dispatch('getChatLogList', {
user_id: this.userId,
pageData: this.pageData
})
.then(res => {
let upperId = this.pageData.upperId;
this.messageList = upperId === 0 ? res : res.concat(this.messageList);
this.pageData.upperId = this.messageList.length ? this.messageList[0].id : 0;
if (this.chatList[this.userId]) {
this.chatList[this.userId].map(item => {
this.messageList.push(item);
});
}
if (upperId === 0) {
this.$nextTick(() => {
this.goBottom();
});
}
})
.catch(err => {
if (this.chatList[this.userId]) {
this.chatList[this.userId].map(item => {
this.messageList.push(item);
});
}
if (upperId === 0) {
this.$nextTick(() => {
this.goBottom();
});
}
});
},
// 滑动到顶部
goTop() {
this.refresherTriggered = true;
this.initData();
},
// 发送文本消息
sendText(e) {
if (this.sendMessage) {
// this.sendMessageFocus = true;
this.sendMsg(this.sendMessage, 1);
this.sendMessage = '';
} else {
return;
// Toast('请输入要发送的消息');
}
},
// 选择表情
sendEmoji(item) {
this.sendMessage += `[${item}]`;
},
// 发送图片
uploadImage() {
let that = this;
chooseImage(1).then(res => {
//#ifndef H5
uni.compressImage({
src: res.tempFilePaths[0],
width: 'auto',
height: 'auto',
success(res) {
that.upload(res.tempFilePath);
},
fail(e) {
console.log(e);
that.upload(res.tempFilePaths[0]);
}
});
//#endif
//#ifdef H5
that.upload(res.tempFilePaths[0]);
//#endif
});
},
upload(tempFilePath) {
uni.uploadFile({
url: app.globalData.http + api.kefuUploadAvatar.url,
filePath: tempFilePath,
name: 'file',
formData: {
// file: res.tempFilePaths[0],
filename: 'file'
},
header: {
// #ifdef MP || APP-PLUS
'Content-Type': 'multipart/form-data',
// #endif
'Authori-zation': 'Bearer ' + this.$store.state.token
},
success: uploadFileRes => {
let unloadResponse = JSON.parse(uploadFileRes.data);
console.log(unloadResponse);
if (unloadResponse.status == 200) {
this.sendMsg(unloadResponse.data.url, 3);
}
},
fail: res => {
Toast('发送图片失败');
},
complete: res => {
console.log(res);
}
});
},
pushMessageToList(data) {
this.messageList.push(data);
this.$nextTick(() => {
this.goBottom();
});
},
imageLoad() {
},
goBottom() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.select('#contentMessage')
.boundingClientRect(data => {
this.scrollTop = this.old.scrollTop;
this.scrollTop = data.height + 50;
console.log(data, this.scrollTop);
})
.exec();
});
},
// textarea获取焦点
textareaFocus() {
this.selectModel = 0;
this.$nextTick(() => {
this.goBottom();
});
},
chatOptinos(guid, msn, type, other) {
return {
msn,
msn_type: type,
to_user_id: this.userId,
is_send: 0,
is_tourist: 0,
avatar: this.kefuInfo.avatar,
user_id: this.kefuInfo.user_id,
appid: this.kefuInfo.appid,
other: other || {},
type: 0,
guid
};
},
// 发送消息统一处理
sendMsg(msn, type) {
let guid = this.scoket.guid();
let chat = this.chatOptinos(guid, msn, type);
this.$store.commit('setChatList', {
id: this.userId,
list: chat
});
chat.msn = this.replace_em(msn);
this.pushMessageToList(chat);
http(api.sendMessage, {
guid,
msn,
msn_type: type,
to_user_id: this.userId,
}).then(data => {
this.chatEvent({
guid: data.guid
});
})
},
selectScriptLibary() {
this.$refs.scriptLibaryModel.open();
},
// 选择话术库,个人,工共
selectSpeechArtType(item) {
this.speechArtType = item.id;
this.selectScriptLibary();
},
// 选中话术
sendSpeechMessage(item) {
this.sendMessage = item.message;
this.$refs.scriptLibaryModel.close(); // 关闭话术库弹框
},
// 关闭话术弹框
closeScript() {
this.$refs.scriptLibaryModel.close();
},
// 修改话术库
editScript() {
this.$refs.scriptLibaryModel.close();
navigateTo(1, '/pages/view/customerServer/script', {
speechArtType: this.speechArtType
});
},
// 话术搜索
handleSetTitle() {
this.propstitle = serialize(this.scriptTitle);
},
// 转接
transfer() {
this.selectModel = 0;
this.$refs.transferModel.open();
},
// 确认转接
handleSubmitTransfer() {
this.$refs.transferModel.close();
navigateTo(4, '/pages/view/messageList/index');
},
// 关闭转接弹框
transferModelClose() {
this.$refs.transferModel.close();
},
// 头部方法,回到聊天列表页
goBackToMessageList() {
navigateTo(4, '/pages/view/messageList/index');
},
// 查看用户详情
selectCustomerServer(item) {
if (this.customerServerData.user_ids && this.customerServerData.user_ids.indexOf(item.user_id) === -1) {
navigateTo(1, '/pages/view/customerServer/customerMessage', item);
}
},
selectCustomerServerOfTopRight() {
navigateTo(1, '/pages/view/customerServer/customerMessage', {
user_id: this.userId
});
},
// 选择上传图片,转接
selectOption(item) {
this.selectModel = item;
},
// 聊天表情转换
replace_em(str) {
str = str.replace(/\[em-([\s\S]*)\]/g, "<span class='em em-$1'/></span>");
return str;
},
textareaChange(e) {
let strCont = e.target.value.replace(/\n\s(\s)*/gi, '\n'); // 将多个回车换行合并为 1个
strCont = strCont.replace(/^\s*/gi, ''); // 清除首行的 空格与换行
let strHtml = strCont.replace(/</gi, '&lt;'); // 将所有的 < 转义为 &lt; 防止html标签被转义
strHtml = strCont.replace(/\n(\n)*/gi, '<br>'); // 回车换行替换为 <br>
strHtml = strHtml.replace(/\s+/gi, '&nbsp;'); // 一个或过个空格 替换为 &nbsp;
strCont = strHtml.replace(/&nbsp;/gi, ' '); // 逆向处理
strCont = strCont.replace(/<br>/gi, '\n'); // 逆向处理
strCont = strCont.replace(/&lt;/gi, '<');
/** 如果 p 标签最后的字符为 <br> 并不会单独另起一行, 会导致与 textarea 的高度相差一行,
* 所以在最后添加一个字符, 这样就能保证 P 标签的高度与 textarea 的高度一致
*/
this.pCont = strHtml + '.';
},
// 点击聊天内容
// 点击图片
showImage(item) {
console.log(item);
this.selectMessageImage = item;
this.$refs.showImage.open();
},
closeImage() {
this.$refs.showImage.close();
}
}
};
</script>
<style scoped lang="less">
@import './less/customerServer.less';
@import './less/scriptLibary.less';
</style>
@@ -0,0 +1,159 @@
.selected {
color: @primaryColor !important;
font-weight: bold;
}
.content_header {
height: 150rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
background: #fff;
&_avar {
width: 110rpx;
height: 110rpx;
margin-right: 22rpx;
image {
width: 100%;
}
}
&_userName {
font-weight: 600;
font-size: 16px;
margin-right: 16rpx;
}
&_tag {
span {
display: inline-block;
font-size: 10px;
color: @primaryColor;
background: rgba(56, 117, 234, 0.14);
padding: 3rpx 10rpx;
}
}
}
.content_message {
margin-top: 14rpx;
&_item {
display: flex;
// height: 100rpx;
align-items: center;
background: #fff;
padding: 40rpx 30rpx;
font-size: 14px;
border-bottom: 1px solid #f0f2f7;
&_label {
flex: 0.25;
}
&_value {
flex: 0.75;
font-weight: 600;
input {
font-size: 14px;
}
.tag_list {
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-row-gap: 20rpx;
justify-items: self-start;
span {
font-size: 11px;
font-weight: 500;
color: @primaryColor;
display: inline-block;
padding: 4rpx 14rpx;
border-radius: 16px;
border: 1px solid @primaryColor;
}
}
}
.flex {
display: flex;
align-items: center;
}
}
.marginTop16 {
margin-top: 16rpx;
}
}
.iconfont {
font-weight: 900;
color: #c4c4c4;
font-size: 20px !important;
}
.to_link {
.iconfont {
font-weight: 400;
color: #000;
font-size: 16px !important;
}
}
.userTag_container {
padding: 30rpx 40rpx;
background: #fff;
border-radius: 12px;
width: 690rpx;
box-sizing: border-box;
&_title {
display: flex;
justify-content: flex-end;
position: relative;
&_message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
&_list {
&_item {
margin-top: 78rpx;
&_label {
color: #282828;
font-weight: bold;
margin-bottom: 26rpx;
}
&_value {
display: grid;
grid-template-columns: repeat(3, 178rpx);
grid-row-gap: 26rpx;
grid-column-gap: 26rpx;
span {
font-size: 14px;
display: inline-block;
background: #f0f0f0;
text-align: center;
padding: 12rpx 0;
border-radius: 12rpx;
}
.select {
background: @primaryColor;
color: #fff;
}
}
}
.mg0 {
margin-top: 40rpx;
}
}
&_handle {
width: 610rpx;
height: 76rpx;
background: @primaryColor;
opacity: 1;
border-radius: 43px;
margin: auto;
margin-top: 84rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: 14px;
}
}
@@ -0,0 +1,358 @@
.header {
height: 116rpx;
background: linear-gradient(90deg, #3875ea 0%, #1890fc 100%);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
color: #fff;
> view {
flex: 1;
text-align: center;
}
.header_left {
text-align: left;
}
&_center {
// position: absolute;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
font-weight: 600;
font-size: 16px;
white-space: nowrap;
}
.header_right {
text-align: right;
}
}
// 聊天对话框
.content {
padding: 36rpx 30rpx;
&_list {
display: flex;
margin-bottom: 36rpx;
color: #282828;
font-weight: 600;
&_avar {
width: 90rpx;
height: 90rpx;
margin-right: 22rpx;
border-radius: 50%;
// border: 1px solid rgba(0, 0, 0, 0.1);
overflow: hidden;
image {
width: 100%;
}
}
&_message {
max-width: 420rpx;
padding: 15rpx 28rpx;
background: #fff;
border-radius: 7px;
display: flex;
align-items: center;
&_value{
word-break:break-all;
word-wrap:break-word;
}
&_product {
.content_list_message_orderId {
padding-bottom: 15rpx;
border-bottom: 1px solid #eceff8;
}
.content_list_message_detils {
padding-top: 20rpx;
display: flex;
&_image {
width: 204rpx;
height: 204rpx;
border-radius: 6px;
overflow: hidden;
background: #eee;
image {
width: 100%;
height: 100%;
}
}
&_value {
flex: 1;
padding-left: 16rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&_shopPrice {
color: #f74c31;
margin-top: 10rpx;
}
.content_list_message_detils_value_shopMessag {
}
.content_list_message_detils_value_shopMessag_item {
display: flex;
}
}
}
}
&_image {
max-width: 400rpx;
image {
max-width: 100%;
}
}
}
}
.right_box {
flex-direction: row-reverse;
.content_list_message {
margin-right: 20rpx;
background: #cde0ff;
}
.content_list_avar {
margin-right: 0rpx !important;
}
}
}
.footer {
background: #f3f4f5;
padding-bottom: 20rpx;
&_input {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
border-top: 1px solid #dddddd;
&_icon {
// align-self: flex-start;
}
.iconfont {
font-size: 24px;
}
.mr18 {
margin-right: 18rpx;
}
&_content {
flex: 1;
position: relative;
display: flex;
align-items: center;
background: #fff;
padding: 8rpx;
border-radius: 32px;
overflow: hidden; // 隐藏超出部分的textarea
.font {
padding: 8rpx 16rpx;
}
textarea {
height: 20px;
display: block;
// position: absolute;
// top: 0rpx;
// left: 0rpx;
z-index: 10;
resize: none;
outline: none;
background: none;
color: rgba(0, 0, 0, 1);
font-weight: bold;
border: none;
width: 80%;
font-size: 14px;
}
p {
width: 100%;
display: block;
min-height: 20rpx;
opacity: 0;
}
.iconfont {
color: #e0e0e0;
}
.canSend {
color: @primaryColor;
}
}
.footer_input_icon {
display: flex;
align-items: center;
.iconfont {
font-size: 60rpx;
}
.sendMessage {
display: inline-block;
background: @primaryColor;
color: #fff;
font-size: 14px;
padding: 12rpx 25rpx;
border-radius: 8rpx;
}
}
}
&_option {
background: #fff;
height: 440rpx;
transition: 0.3s;
.option {
padding: 46rpx 65rpx;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 68rpx;
> view {
text-align: center;
image {
width: 90%;
}
> view {
color: #282828;
font-size: 14px;
font-weight: bold;
}
}
}
.emoji {
height: 100%;
.emoji-item {
display: inline-block;
width: 12.5%;
text-align: center;
margin: 20rpx 0;
// display: flex;
// align-items: center;
// justify-content: center;
.em {
width: 56rpx;
height: 56rpx;
box-sizing: border-box;
}
}
}
}
}
.translateY0 {
// transform: translateY(0);
top: 0;
}
.speechArtSelectEd {
color: @primaryColor;
}
.image_content {
max-width: 90%;
margin: auto;
position: relative;
image {
max-width: 100%;
margin: auto;
}
span {
position: absolute;
right: -30rpx;
top: -30rpx;
z-index: 100;
font-size: 60rpx;
opacity: 0.9;
}
}
.chat_result {
display: flex;
align-items: center;
padding: 0 10rpx;
}
.chat_result_ing {
width: 40rpx;
height: 40rpx;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
border-bottom: 1px solid #ccc;
animation: changeleft 1s linear infinite;
border-radius: 50%;
}
.chat_result_faile {
width: 40rpx;
height: 40rpx;
background: #FF0000;
border-radius: 50%;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
@keyframes changeleft {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(-180deg);
}
100% {
transform: rotate(-360deg);
}
}
.scriptLibary_header {
display: flex;
padding: 44rpx 32rpx;
position: relative;
justify-content: flex-end;
}
.scriptLibary_header_title[data-v-afab33f6] {
position: absolute;
top: 50%;
display: flex;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.speechArtSelectEd[data-v-afab33f6] {
color: #3875EA;
}
.scriptLibary_header_icon {
display: flex;
}
.tit {
padding: 0 40rpx;
}
.search {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
background: #f5f6f9;
margin: 0 5%;
border-radius: 30rpx;
padding: 10rpx 0;
color: #9f9f9f;
}
.icon_search {
position: absolute;
top: auto;
left: 33%;
}
.uni-input-placeholder {
color: #9f9f9f !important;
}
.icon_edit {
margin-right: 10rpx;
}
@@ -0,0 +1,59 @@
.scriptLibary {
background: #fff;
border-radius: 12rpx 12rpx 0px 0px;
&_header {
display: flex;
justify-content: flex-end;
padding: 40rpx 30rpx;
position: relative;
> div {
display: flex;
}
&_title {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
> div:nth-child(1) {
margin-right: 100rpx;
}
}
&_icon {
> div:nth-child(1) {
margin-right: 20rpx;
}
.iconfont {
font-size: 22px;
color: #c8cad0;
}
}
}
&_search {
padding-bottom: 44rpx;
border-bottom: 1px solid #F0F3FA;
> div {
display: flex;
background: #f5f6f9;
width: 690rpx;
height: 68rpx;
margin: auto;
display: flex;
align-items: center;
padding: 0 20rpx;
box-sizing: border-box;
border-radius: 39px;
> input {
flex: 1;
font-size: 14px;
margin-left: 8rpx;
}
.iconfont {
font-size: 22px;
color: #C4C4C4;
}
}
}
}
+134
View File
@@ -0,0 +1,134 @@
<template>
<div class="container">
<lay-out>
<div slot="header" class="header">
<div class="header_title">{{speechArtTypeConcat[speechArtType]}}</div>
<div @click="finish">完成</div>
</div>
<div slot="content" class="content">
<div class="scriptLibary_search">
<div>
<span class="iconfont">&#xe6d8;</span>
<input v-model="propstitle" type="text" placeholder="搜索快捷回复" />
</div>
</div>
<div class="scriptLibary_content">
<scripts-libary
:speechArtType="speechArtType"
:maxHeight="maxHeight"
icCanEdit
:tagList="speechTagList"
:title="propstitle"
></scripts-libary>
</div>
</div>
</lay-out>
</div>
</template>
<script>
import scriptsLibary from './component/scriptLibary.vue';
import { navigateTo, navigateBack } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
components: {
scriptsLibary
},
data() {
return {
maxHeight: '',
speechArtType: '',
speechArtTypeConcat: {
1: '个人库',
0: '公共库'
},
speechTagList: [], // 话术分类
propstitle: ''
};
},
onLoad(opt) {
this.speechArtType = opt.speechArtType;
this.selectScriptLibary();
console.log(this.speechArtType);
this.$nextTick(() => {
let info = uni.createSelectorQuery().select('.content');
info
.boundingClientRect((data) => {
this.maxHeight = (Math.ceil(data.height) * 1.85) + 'rpx'
})
.exec();
})
},
methods: {
// 查询话术类
selectScriptLibary() {
http(api.kefuServiceCate, {
type: this.speechArtType
}).then(res => {
this.speechTagList = res.data;
});
},
finish() {
navigateBack(1);
}
}
};
</script>
<style lang="less" scoped>
.header {
height: 96rpx;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 30rpx;
position: relative;
background: #fff;
&_title {
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
font-weight: bold;
}
> div:nth-child(2) {
font-size: 14px;
color: @primaryColor;
}
}
.scriptLibary_search {
background: #fff;
padding-bottom: 44rpx;
border-bottom: 1px solid #f0f3fa;
> div {
display: flex;
background: #f5f6f9;
width: 690rpx;
height: 68rpx;
margin: auto;
display: flex;
align-items: center;
padding: 0 20rpx;
box-sizing: border-box;
border-radius: 39px;
> input {
flex: 1;
font-size: 14px;
margin-left: 8rpx;
}
.iconfont {
font-size: 22px;
color: #c4c4c4;
}
}
}
.content {
overflow: hidden;
height: 100%;
.scriptLibary_content {
}
}
</style>