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,329 @@
<!-- 话数库 -->
<template>
<div class="scriptLibary_container" :style="{ 'max-height': maxHeight, height: height }">
<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 authReply" :key="index" @click="selectScript(item)">
<div class="script_list_item_label">{{ item.keyword }}</div>
<div class="script_list_item_value">{{ item.content }}</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 v-if="speechData.id">编辑自动回复</div>
<div v-else>添加自动回复</div>
<div @click="closeAddGroup"><span class="iconfont">&#xe6b5;</span></div>
</div>
<div class="addGroup_form">
<div class="input_box"><input v-model="speechData.keyword" type="text" placeholder="请输入关键词,多个关键字请用逗号隔开" /></div>
<div class="input_box"><textarea v-model="speechData.content" type="text" placeholder="请输入自动回复内容"></textarea></div>
<div class="input_box" style='width: 40%;'><input v-model="speechData.sort" type="number" placeholder='排序' /></div>
<div class="button_box" :class="{ canInput: speechData.keyword && speechData.content }" @click="handleSubmiteditAdd"><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: ''
}
},
data() {
return {
authReply: [],
where: {
page: 1,
limit: 5
},
scriptList: [],
pageData: {
page: 1,
limit: '200'
},
cateId: '', // 选中的侧边栏ID
speechData: {
keyword: '',
content: '',
sort:0
},
userGroupData: {
name: '',
sort: ''
},
handleAdEdType: 1, // 1新增 2修改
handleGroupTitle: '新增分组',
groupType: 1 // 1新增 2修改
};
},
watch: {
authReply: {
handler(val) {
console.log(val);
},
deep: true
},
title: {
handler(val) {}
}
},
created() {
this.getAuthReplyList();
},
methods: {
// 查询话术类
getAuthReplyList() {
http(api.getAuthReply, this.where).then(res => {
this.authReply = res;
});
},
// 添加话术
addSpeech() {
this.handleAdEdType = 1;
this.speechData = {};
this.$refs.addEdGroup.open();
},
// 修改自动回复
editSpeech(item) {
this.handleAdEdType = 2;
this.speechData = serialize(item);
this.$refs.addEdGroup.open();
},
// 确认新增,修改自动回复
handleSubmiteditAdd() {
this.speechData.id = this.speechData.id ? this.speechData.id : 0;
http(api.saveAuthReply, this.speechData).then(res => {
Toast(this.speechData.id ? '修改成功' : '添加成功');
this.getAuthReplyList();
this.closeAddGroup();
});
},
// 删除自动回复
handleDeleteServiceSpeechcraft(item) {
Modal('温馨提示', `自动回复"${item.content}"将被删除,请问是否继续?`).then(res => {
http(api.deleteAuthReply,item).then(res => {
Toast('删除成功');
this.getAuthReplyList();
});
});
},
closeAddGroup() {
this.$refs.addEdGroup.close();
}
}
};
</script>
<style lang="less" scoped>
.scriptLibary_container {
display: flex;
overflow: hidden;
.script_group {
width: 176rpx;
background: #f5f6f9;
.script_group_scroll {
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;
.add_script {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 30rpx;
height: 109rpx;
background-color: #ffffff;
}
.script_list_scroll {
height: 100%;
.script_list_item {
padding: 25rpx 30rpx;
background-color: #ffffff;
&_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;
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;
}
}
}
}
</style>
+159
View File
@@ -0,0 +1,159 @@
<template>
<div class="container">
<lay-out>
<!-- <div slot="header" class="header"><div class="header_title">自动回复</div></div> -->
<div slot="content" class="content">
<div class="caption">
<div class="">自动回复开关:</div>
<div class="right"><switch @change="changeSwitch" :checked="authReply" /></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 {
authReply: false,
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();
});
this.getKefuInfo();
},
methods: {
getKefuInfo() {
this.$store.dispatch('getKeufuInfo').then(res => {
this.authReply = !!res.auto_reply;
});
},
changeSwitch(e) {
console.log(e);
http(api.putAuthReply, {
value: e.detail.value ? 1 : 0
}).then(res => {
this.speechTagList = res.data;
});
},
// 查询话术类
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 {
}
.caption {
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
padding: 25rpx 30rpx;
div:first-of-type {
margin-left: 5px;
}
div {
width: 50%;
}
.right {
text-align: right;
}
/deep/ .uni-switch-input {
height: 24px;
}
/deep/ .uni-switch-input:after,
/deep/ .uni-switch-input:before {
height: 22px;
}
}
}
</style>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div class="auth_login container">
<div class="auth_login_content">
<div class="userData">
<div class="avater"><image :src="userData.avatar" mode=""></image></div>
<div class="userName">
<span>{{ userData.nickname }}</span>
</div>
<div class="tipMessage">点击授权登录您的客服工作台</div>
</div>
<div class="handle">
<div class="handle_login" @click="handle_login">授权登录</div>
<div class="handle_cancel" @click="handleCancel">取消</div>
</div>
</div>
<div class="bottom"></div>
</div>
</template>
<script>
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import { navigateTo, Modal, navigateBack } from 'pages/utils/uniApi.js';
export default {
data() {
return {
key: '',
userData: {}
};
},
onLoad(opt) {
this.key = opt.key;
this.userData = this.$store.state.kefuInfo;
},
methods: {
handle_login() {
http(api.serviceCode, { code: this.key }).then(res => {
Modal('温馨提示', '登录成功', { showCancel: false }).then(() => {
navigateBack(1);
});
});
},
handleCancel() {
navigateBack(1);
}
}
};
</script>
<style lang="less" scoped>
.auth_login {
display: flex;
flex-direction: column;
justify-content: space-between;
&_content {
flex: 1;
.userData {
.avater {
width: 178rpx;
height: 178rpx;
border-radius: 50%;
overflow: hidden;
margin: 112rpx auto 20rpx auto;
image {
width: 100%;
height: 100%;
}
}
.userName {
text-align: center;
font-weight: bold;
margin-bottom: 10rpx;
}
.tipMessage {
text-align: center;
color: #9F9F9F;
font-size: 14px;
}
}
.handle {
margin-top: 96rpx;
> div {
width: 594rpx;
height: 86rpx;
border-radius: 43rpx;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
}
&_login {
background: linear-gradient(90deg, #3875ea 0%, #1890fc 100%);
color: #fff;
}
&_cancel {
margin-top: 28rpx !important;
border: 1px solid #3875EA;
color: #3875EA;
}
}
}
}
</style>
@@ -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>
+184
View File
@@ -0,0 +1,184 @@
<template>
<div class="login_container">
<!-- 头部背景图 -->
<div class="login_container_bg"><!-- <image class="login_container_bg_image" src="~static/dominName/bg.png" mode="widthFix"></image> --></div>
<!-- 头部背景图结束 -->
<div class="login_container_content">
<div class="login_container_content_title"><span>设置请求域名</span></div>
<div class="login_container_content_form">
<div class="login_container_content_form_input">
<picker mode="selector" :range="fileList" @change="chageFile" range-key="label">
<view>
<span>{{ fileListConcat[selectFile] }}</span>
<span>://</span>
</view>
</picker>
<input type="text" v-model="dominName" placeholder="请输入域名" />
<!-- <span class="iconfont">&#xe6f6;</span> -->
</div>
<!-- <div class="login_container_content_form_input">
<span>{{wsFileConcat[selectFile]}}</span>
<span>://</span>
<span>{{dominName}}</span>
</div> -->
</div>
<div class="login_container_content_handle">
<div class="login_button" @click="cancel" v-if="isShowCencel">取消</div>
<div class="login_primary_button" @click="handleSetDominName">确定</div>
</div>
</div>
</div>
</template>
<script>
import { navigateTo, Modal, navigateBack, Toast } from 'pages/utils/uniApi.js';
const app = getApp()
export default {
components: {},
data() {
return {
selectFile: 0,
dominName: '',
fileList: [
{
label: 'http',
value: 0
},
{
label: 'https',
value: 1
}
],
fileListConcat: {
0: 'http',
1: 'https'
},
wsFileConcat: {
0: 'ws',
1: 'wss'
},
isShowCencel: false
};
},
onLoad() {
this.$cache.set('pages_dominName', 1);
this.$cache.remove('accountData');
if (this.$cache.get('dominName')) {
this.dominName = this.$cache.get('dominName');
if (this.$cache.get('requestFile') == 'http') {
this.selectFile = 0;
} else {
this.selectFile = 1;
}
this.isShowCencel = true;
}
},
methods: {
handleSetDominName() {
if (!this.dominName) {
Toast('请设置域名');
return;
}
Modal('温馨提示', `您设置的域名是 "${this.dominName}", 请问是否继续?`).then(res => {
this.$store.commit('setHttp', {
wsFile: this.wsFileConcat[this.selectFile],
dominName: this.dominName,
requestFile: this.fileListConcat[this.selectFile]
});
app.globalData.http = this.$store.state.http
this.scoket.clearPing();
this.$store.commit('clearChat');
this.$store.commit('logout');
navigateTo(1, '/pages/view/login/index');
this.$cache.remove('pages_dominName');
});
},
cancel() {
navigateBack(1);
},
chageFile(e) {
console.log(e);
this.selectFile = e.detail.value;
}
}
};
</script>
<style scoped lang="less">
.login_container {
position: relative;
&_bg {
&_image {
width: 100%;
}
}
&_content {
width: 642rpx;
padding: 54rpx 79rpx 67rpx 79rpx;
// box-shadow: 0px 3px 20px rgba(0, 20, 41, 0.06);
box-sizing: border-box;
position: absolute;
top: 209rpx;
left: 0;
right: 0;
background: #fff;
margin: auto;
border-radius: 20rpx;
&_title {
text-align: center;
font-size: 16px;
font-weight: 600;
margin-bottom: 54rpx;
}
&_form {
&_input {
display: flex;
align-items: center;
height: 96rpx;
// background: #f5f5f5;
padding: 0 28rpx;
// border-radius: 12rpx;
input {
margin-left: 16rpx;
flex: 1;
font-size: 16px;
}
}
> div:nth-child(1) {
border-bottom: 1px solid #ddd;
}
}
&_handle {
margin-top: 57rpx;
display: flex;
.login_primary_button {
font-size: 14px;
width: 100%;
height: 70rpx;
background: @primaryBgColor;
border-radius: 41px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.login_button {
font-size: 14px;
width: 100%;
height: 70rpx;
border-radius: 41px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
margin-right: 32rpx;
}
}
}
}
</style>
+35
View File
@@ -0,0 +1,35 @@
<template>
<div class="container">
<lay-out>
<div slot="content" class="content">
<div v-for="(item, index) in 100" :key="index">{{index}}</div>
</div>
<div slot="bottom">asdad</div>
</lay-out>
</div>
</template>
<script>
import { navigateTo, formatDate, serialize, showWeekFirstDay, showMonthFirstDay, getQuarterStartDate } from 'pages/utils/uniApi.js';
export default {
components: {
},
data() {
return {
}
},
onLoad(opt) {
},
onShow(opt) {
},
methods: {
}
};
</script>
<style lang="less" scoped>
</style>
@@ -0,0 +1,62 @@
<template>
<div class="submit_res_container">
<div class="image_box"><image src="~static/image/feedback/submitSuccess.png" mode="widthFix"></image></div>
<div class="title">
<span>提交成功</span>
</div>
<div class="message">
<div>您的信息提交成功</div>
<div>我们会尽快与您取得联系!</div>
</div>
<div class="handle" @click="handleSubmitRes">
<span>好的</span>
</div>
</div>
</template>
<script>
export default {
methods:{
handleSubmitRes() {
this.$emit('handleSubmitRes')
}
}
}
</script>
<style lang="less" scoped>
.submit_res_container {
padding: 66rpx 111rpx;
background: #fff;
border-radius: 12px;
.image_box {
width: 344rpx;
image {
width: 100%;
}
}
.title {
color: #434343;
font-weight: 600;
text-align: center;
margin: 30rpx 0 12rpx 0;
}
.message {
text-align: center;
font-size: 14px;
color: #9F9F9F;
}
.handle {
width: 426rpx;
height: 76rpx;
background: @primaryBgColor;
opacity: 1;
border-radius: 38px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
margin-top: 54rpx;
}
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div class="container">
<lay-out headerColor="#3A3A3A" headerTextColor="#fff">
<div slot="content" class="content">
<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="feedback">
<div class="feedback_title">
<span>我要反馈</span>
</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>
</div>
</lay-out>
<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 './component/submitResponse.vue';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
components: {
submitResponse
},
data() {
return {
feedBackData: {
rela_name: '',
phone: '',
content: ''
}
}
},
onLoad() {
},
methods: {
handleSubmit() {
http(api.userFeedback, this.feedBackData).then(res => {
this.$refs.feedbackReslove.open('center');
})
},
handleSubmitRes() {
navigateBack(1);
this.$refs.feedbackReslove.close();
}
}
}
</script>
<style lang="less" scoped>
@import url('./less/feedback.less');
</style>
@@ -0,0 +1,135 @@
.container {
background: #f5f5f5;
}
.content {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.header_banner {
display: flex;
align-items: center;
padding: 30rpx;
height: 205rpx;
background: #3a3a3a;
&_content {
flex: 1;
color: #ffffff;
font-size: 16px;
&_title {
margin-bottom: 16rpx;
}
&_message {
font-size: 12px;
line-height: 1.5;
}
}
&_image {
width: 173rpx;
height: 173rpx;
&_imageBox {
width: 100%;
image {
width: 100%;
}
}
}
}
.organization_work {
display: flex;
margin-bottom: 16rpx;
&_item {
flex: 1;
padding: 24rpx 0;
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
background: #fff;
&_label {
font-size: 16px;
margin-bottom: 10rpx;
}
&_value {
color: #9f9f9f;
font-size: 12px;
}
}
.tip {
width: 2px;
height: 100%;
background: #f5f5f5;
}
}
.feedback {
padding: 28rpx 30rpx;
background: #fff;
flex: 1;
&_title {
color: #282828;
font-size: 15px;
font-weight: 600;
margin-bottom: 30rpx;
}
&_input {
height: 78rpx;
background: #f5f5f5;
margin-bottom: 20rpx;
padding-left: 20rpx;
input {
width: 100%;
height: 100%;
font-size: 14px;
}
}
&_textarea {
height: 260rpx;
padding: 19rpx 20rpx;
background: #f5f5f5;
textarea {
font-size: 14px;
}
}
.handle_button {
margin-top: 130rpx;
width: 690rpx;
height: 86rpx;
background: #3875ea;
opacity: 1;
border-radius: 43px;
display: flex;
align-items: center;
justify-content: center;
color: #FFFFFF;
}
.feedback_list {
margin-bottom: 50rpx;
&_item {
padding: 30rpx 15rpx;
display: flex;
border-bottom: 1rpx solid #ccc;
>div:nth-child(1) {
width: 40rpx;
height: 40rpx;
border: 1px solid #ccc;
border-radius: 50%;
margin-right: 15rpx;
display: flex;
align-items: center;
justify-content: center;
}
&_selected {
width: 20rpx;
height: 20rpx;
background: #3875ea;
border-radius: 50%;
}
}
}
}
+339
View File
@@ -0,0 +1,339 @@
<template>
<view class="container">
<lay-out noBottomHeight>
<div slot="header">
<view class="search-content padding">
<view class="uni-input-wrapper"><input class="uni-input" @confirm="initData" v-model="searchData.nickname" placeholder="搜索用户名称" /></view>
</view>
</div>
<div slot="content">
<!-- 通讯录导航 -->
<address-book :bookList="bookList" :letter="letter">
<template v-slot:addressBookList>
<view class="user-content">
<view class="user-list" v-for="(item, index) in Object.keys(bookList)" :key="index">
<view class="number padding">{{ item }}</view>
<view class="user" v-for="(val, i) in bookList[item]" :key="i" @click="selectUser(val)">
<view class="user-list-left"><image :src="val.avatar" mode=""></image></view>
<view class="user-list-right">{{ val.nickname }}</view>
</view>
</view>
</view>
</template>
</address-book>
</div>
</lay-out>
<uni-popup ref="userTagModel" type="bottom" animation>
<div class="userTag_container">
<div class="userTag_container_title">
<div :class="{ on: tabOn == 1 }" class="userTag_container_title_message" @click="tabOn = 1"><span>用户标签</span></div>
<div :class="{ on: tabOn == 2 }" class="userTag_container_title_message" @click="tabOn = 2"><span>分组标签</span></div>
<div class="closeModel" @click="closeUserTagModel"><span class="iconfont">&#xe6b5;</span></div>
</div>
<scroll-view scroll-y="true" class="userTag_container_list">
<div v-if="tabOn == 1" 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 v-if="tabOn == 2" class="userTag_container_list_item mg40">
<div class="userTag_container_list_item_value">
<span :class="{ select: val.disabled }" v-for="(val, i) in groupList" :key="val" @click="selectGroups(val)">{{ val.group_name }}</span>
</div>
</div>
</scroll-view>
<div class="userTag_container_handle" @click="handleSetTags">
<div class="userTag_container_handle_button"><span>确定</span></div>
</div>
</div>
</uni-popup>
</view>
</template>
<script>
import AddressBook from './addressBookNavigation/addressBookNavigation';
import { makePy } from '../../utils/utils';
import { navigateTo } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
components: { AddressBook },
data() {
return {
letter: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
list: [],
search: '',
bookList: {},
customerServerData: {}, // 客服数据
searchData: {
nickname: '',
label_id: '',
group_id: ''
},
userTagList: [],
groupList: [],
tabOn: 1,
};
},
onLoad() {
this.customerServerData = this.$store.state.kefuInfo;
this.bookList = this.$store.state.bookList;
},
onShow() {
this.searchData.label_id = '';
this.searchData.group_id = '';
this.initData();
// { id: this.queryUserData.user_id }
http(api.userLabelSelect).then(res => {
this.userTagList = res;
});
http(api.UserGroup).then(res => {
this.groupList = res;
});
},
onNavigationBarButtonTap() {
// this.toTagList();
this.$refs.userTagModel.open();
},
methods: {
// 关闭弹窗
closeUserTagModel() {
this.$refs.userTagModel.close();
},
// 选择标签
selectTags(item) {
this.$set(item, 'disabled', !item.disabled);
},
selectGroups(item) {
this.$set(item, 'disabled', !item.disabled);
},
// 设置标签
handleSetTags() {
let postData = {
label_ids: [],
group_ids: []
};
console.log(111);
this.userTagList.forEach(item => {
item.label.forEach(val => {
if (val.disabled) {
postData.label_ids.push(val.id);
}
});
});
this.groupList.forEach(item => {
if (item.disabled) {
postData.group_ids.push(item.id);
}
});
console.log(postData);
this.searchData.label_id = postData.label_ids.join(',');
this.searchData.group_id = postData.group_ids.join();
this.closeUserTagModel();
this.initData(this.searchData);
},
// 查询当前客服的客户列表
initData() {
this.$store.dispatch('getBookList', this.searchData).then(res => {
this.bookList = res;
});
},
toTagList() {
navigateTo(1, '/pages/view/setTags/index');
},
selectUser(item) {
navigateTo(1, '/pages/view/customerServer/customerMessage', { user_id: item.to_user_id });
},
// 对通讯录按字母顺序做排序
letterSrot() {
const sortList = [];
const list = this.list;
this.letter.forEach(item => {
const obj = {};
obj.key = item;
obj.list = [];
for (let i = 0; i < list.length; i++) {
if (item == makePy(list[i].username).substr(0, 1)) {
obj.list.push(list[i]);
list.splice(i, 1);
i--;
}
}
if (obj.list.length) sortList.push(obj);
});
this.bookList = sortList;
}
}
};
</script>
<style lang="less" scoped>
@padding-l-r: 30rpx;
@padding-t-p: 20rpx;
@bgc-f: #ffffff;
@fontSize: 28rpx;
@imgSize: 64rpx;
.padding {
padding: @padding-t-p @padding-l-r;
}
.search-content {
position: relative;
background-color: @bgc-f;
input {
border: none;
background-color: #f5f6f9;
color: #c4c4c4;
font-size: @fontSize;
border-radius: 39px;
padding: @padding-t-p 0 @padding-t-p @padding-l-r;
text-align: center;
}
}
.user-content {
background-color: #eeeeee;
.user-list {
.number {
color: #666666;
font-size: @fontSize - 2rpx;
}
.user {
background-color: @bgc-f;
display: flex;
align-items: center;
padding: @padding-t-p @padding-l-r;
border-bottom: 1rpx solid #f0f2f7;
.user-list-left {
width: @imgSize;
height: @imgSize;
padding-right: 20rpx;
image {
width: @imgSize;
height: @imgSize;
border-radius: 50%;
}
}
.user-list-right {
color: #282828;
}
}
}
}
.header {
&_content {
display: flex;
justify-content: flex-end;
align-items: center;
position: relative;
height: 116rpx;
background: #1890fc;
padding: 0 30rpx;
/* #ifdef APP-PLUS */
padding-top: 64rpx;
/* #endif */
color: #fff;
> div {
flex: 1;
}
> div:nth-child(2) {
text-align: center;
font-weight: bold;
}
> div:nth-child(3) {
font-size: 14px;
text-align: right;
}
}
}
.userTag_container {
padding: 30rpx 40rpx;
// padding-bottom: 140rpx;
background: #fff;
width: 100%;
box-sizing: border-box;
&_title {
display: flex;
// justify-content: flex-end;
position: relative;
&_message {
flex: 1;
text-align: center;
// position: absolute;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
&.on {
font-weight: bold;
color: #3875EA;
}
}
.closeModel {
position: absolute;
top: 0;
right: 0;
color: #c8cad0;
.iconfont {
font-size: 36rpx;
}
}
}
&_list {
height: 600rpx;
&_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: 44rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: 14px;
}
}
</style>
@@ -0,0 +1,265 @@
<template>
<view>
<!-- 右边字母选择导航条 -->
<view class="searchLetter touchClass" :style="{ height: itemH * searchLetter.length + 'px', paddingRight: paddingRight + 'upx', width: searchLetterWidth + 'upx' }">
<view
v-for="(item, index) in searchLetter"
:style="{ height: itemH + 'px' }"
:key="index"
:data-letter="item.name"
@touchstart.stop.prevent="searchStart"
@touchmove.stop.prevent="searchMove"
@touchend.stop.prevent="searchEnd"
>
{{ item.name }}
</view>
</view>
<!-- 字母选择提示框 -->
<block v-if="isShowLetter">
<view class="showSlectedLetter">{{ showLetter }}</view>
</block>
<!-- 通讯录列表 -->
<scroll-view scroll-y :scrollTop="scrollTop"><slot name="addressBookList"></slot></scroll-view>
</view>
</template>
<script>
export default {
name: 'addressBookNavigation',
props: {
//通讯录名字集合
bookList: {
type: Object,
default() {
return {};
},
required: true
},
//索引集合
letter: {
type: Array,
default() {
return ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
}
},
//通讯录索引字母显示高度 upx
bookKeyHeight: {
type: Number,
default() {
return 100;
}
},
//通讯录名字显示高度 upx
bookItemHeight: {
type: Number,
default() {
return 40;
}
},
//右边索引条右边距 upx
paddingRight: {
type: Number,
default() {
return 20;
}
},
//若有搜索框则是搜索框高度 upx
searchHeight: {
type: Number,
default() {
return 20;
}
},
//右边索引条距离搜索框位置 upx
searchLetterTop: {
type: Number,
default() {
return 60;
}
},
//右边索引条宽度 upx
searchLetterWidth: {
type: Number,
default() {
return 40;
}
},
//减去的右边导航条高度 upx
subtractiveSearchLetterHeight: {
type: Number,
default() {
return 80;
}
},
//底部tab高度 使用原生导航栏固定为50px 自定义tab传入自定义tab高度 单位为px
tabHeight: {
type: Number,
default() {
return 100;
}
},
//导航栏高度
navHeight: {
type: Number,
default() {
return 44;
}
}
},
data() {
return {
searchLetter: [],
//右边索引条单个字母元素高度
itemH: 0,
//可滑动区域高度
usableHeight: 0,
screenWidth: 0,
isShowLetter: false,
showLetter: '',
scrollTop: 0,
badPageY: uni.upx2px(this.searchHeight + this.searchLetterTop),
letterPositionTop: 0,
scrollViewPositionTop: 0
};
},
mounted() {
//#ifdef H5
this.letterPositionTop = this.searchHeight + this.searchLetterTop + 88;
this.scrollViewPositionTop = this.searchHeight + 88;
//#endif
//#ifndef H5
this.letterPositionTop = this.searchHeight + this.searchLetterTop;
this.scrollViewPositionTop = this.searchHeight;
//#endif
// #ifdef APP-PLUS
this.letterPositionTop = this.searchHeight + this.searchLetterTop;
this.scrollViewPositionTop = this.searchHeight + 260;
console.log(1);
// #endif
this.countLetterHeight();
},
methods: {
//计算和记录右边每个搜索字母的高度和位置
countLetterHeight() {
const sysInfo = uni.getSystemInfoSync();
//#ifdef H5
const usableHeight = sysInfo.screenHeight - uni.upx2px(this.searchHeight) - this.tabHeight - this.navHeight;
//#endif
//#ifndef H5
const usableHeight = sysInfo.screenHeight - uni.upx2px(this.searchHeight) - this.tabHeight - this.navHeight - sysInfo.statusBarHeight;
//#endif
this.screenWidth = sysInfo.screenWidth;
const letter = this.letter;
const itemH = (usableHeight - uni.upx2px(this.subtractiveSearchLetterHeight) - 20) / letter.length;
const tempObj = [];
for (let i = 0; i < letter.length; i++) {
tempObj.push({
name: letter[i],
tHeight: i * itemH,
bHeight: (i + 1) * itemH
});
}
this.usableHeight = usableHeight;
this.itemH = itemH;
this.searchLetter = tempObj;
},
searchEnd(e) {
const that = this;
setTimeout(() => (that.isShowLetter = false), 500);
},
//点击选择
searchStart(e) {
console.log(e);
const pageY = e.touches[0].pageY - this.badPageY;
this.nowLetter(pageY);
},
//滑动选择
searchMove(e) {
const pageY = e.touches[0].pageY - this.badPageY;
const pageX = e.touches[0].pageX;
if (this.screenWidth - pageX > uni.upx2px(this.searchLetterWidth + this.paddingRight)) {
this.isShowLetter = false;
return;
}
if (pageY < 0 || pageY > this.itemH * this.searchLetter.length) {
this.isShowLetter = false;
return;
}
this.nowLetter(pageY);
},
//计算当前选中的字母并显示
nowLetter(pageY) {
const letterData = this.searchLetter;
for (let i = 0; i < letterData.length; i++) {
if (letterData[i].tHeight <= pageY && pageY <= letterData[i].bHeight) {
this.showLetter = letterData[i].name;
this.isShowLetter = true;
break;
}
}
this.scrollPage(this.showLetter);
},
//计算要滑动的距离
countScrollTop(showLetter) {
let scrollTop = 0;
let cityCount = 0;
let initialCount = 0;
for (let item of Object.keys(this.bookList)) {
if (showLetter == item) {
scrollTop = initialCount * uni.upx2px(this.bookKeyHeight) + cityCount * uni.upx2px(this.bookItemHeight);
break;
} else {
initialCount++;
cityCount += this.bookList[item].length;
}
}
return scrollTop;
},
//页面滑动到与选中字母对应的位置
scrollPage(showLetter) {
const scroll = this.countScrollTop(showLetter);
if (scroll != 0 || showLetter == Object.keys(this.bookList)[0]) this.scrollTop = scroll;
}
}
};
</script>
<style lang="less" scoped>
.searchLetter {
position: fixed;
top: 126rpx;
right: 0;
text-align: center;
display: flex;
flex-direction: column;
color: #666;
z-index: 999;
}
.touchClass {
color: #5a5a5a;
font-size: 28upx;
}
.showSlectedLetter {
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100upx;
height: 100upx;
border-radius: 10upx;
font-size: 44upx;
z-index: 99;
color: #fff;
}
scroll-view {
// position: fixed;
width: 100%;
}
</style>
@@ -0,0 +1,68 @@
<template>
<div class="login_input">
<div class="title_icon">
<slot name="icon"></slot>
</div>
<div class="input">
<input class="input_model" :placeholder="placeholder" :type="type" v-model="value" @blur="$emit('input', $event.target.value)" />
</div>
<div class="clear" v-if="value" @click="clear">
<span class="iconfont">&#xe6b5;</span>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'input'
},
props: {
placeholder: {
type: String,
default:''
},
type: {
type: String,
default: 'text'
}
},
data() {
return {
value: ''
};
},
methods:{
clear() {
this.value = '';
this.$emit('input', '');
}
}
};
</script>
<style scoped lang="less">
.login_input {
display: flex;
align-items: center;
padding: 22rpx 0;
border-bottom: 1px solid #EDEDED;
font-size: 14px;
.title_icon {
margin-right: 12rpx;
color: @primaryColor;
}
.input {
flex: 1;
&_model {
border: none !important;
outline: none !important;
}
}
.clear {
opacity: .2;
}
}
</style>
+161
View File
@@ -0,0 +1,161 @@
<template>
<div class="login_container">
<!-- 头部背景图 -->
<div class="login_container_bg"><image class="login_container_bg_image" src="~static/image/login/bg.png" mode="widthFix"></image></div>
<!-- 头部背景图结束 -->
<div class="login_container_content">
<div class="login_container_content_title"><span>欢迎登录</span></div>
<div class="login_container_content_form">
<div class="login_container_content_form_input">
<login-input v-model="loginData.account" type="text" placeholder="请输入账号"><span slot="icon" class="iconfont">&#xe6bc;</span></login-input>
</div>
<div class="login_container_content_form_input">
<login-input v-model="loginData.password" type="password" placeholder="请输入密码"><span slot="icon" class="iconfont">&#xe6bd;</span></login-input>
</div>
<div class="login_container_content_form_input privacy_agreement">
<div class="privacy_agreement_content">
<checkbox-group @change="checkoutPrivaey">
<checkbox class="checkbox" :value="isCheckEd" />
<span>请阅读并勾选</span>
<text @click="showPrivaey">隐私协议</text>
</checkbox-group>
</div>
</div>
</div>
<div class="login_container_content_handle"><div class="login_primary_button" @click="handleLogin">登录</div></div>
<div v-if='isDomainName' class="login-to-set-domin-name"><navigator hover-class="none" url="/pages/view/dominName/index">修改域名</navigator></div>
</div>
</div>
</template>
<script>
import loginInput from './component/loginInput.vue';
import { navigateTo, Modal, Toast } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import config from 'pages/config/app.js';
export default {
components: {
loginInput
},
data() {
return {
loginData: {
account: '',
password: ''
},
cid: '',
isCheckEd: '1', // 是否选中隐私协议
isCheckEdArr: [],
isDomainName: config.isDomainName
};
},
onLoad() {
this.$cache.remove('accountData');
this.$cache.set('pages_login', 1);
this.scoket.clearPing()
},
onUnload() {
this.$cache.remove('pages_login');
},
methods: {
async handleLogin() {
if (!this.isCheckEdArr.length) {
Toast('请勾选隐私协议');
return;
}
//#ifdef APP-PLUS
let info = plus.push.getClientInfo();
this.cid = info.clientid;
this.$cache.set('cid', this.cid);
//#endif
let loginRes = await http(api.login, { ...this.loginData, client_id: this.cid, is_app: 1 });
this.$cache.set('userData', loginRes);
this.$store.commit('setToken', loginRes.token);
this.$cache.set('accountData', this.loginData);
//连接长连接
this.scoket.startConnect();
this.$cache.remove('pages_login');
navigateTo(4, '/pages/view/messageList/index');
},
// 勾选隐私协议
checkoutPrivaey(e) {
this.isCheckEdArr = e.detail.value;
},
// 跳转隐私协议
showPrivaey() {
navigateTo(1, '/pages/view/privacyAgreement/index');
}
}
};
</script>
<style scoped lang="less">
.login_container {
position: relative;
&_bg {
&_image {
width: 100%;
}
}
&_content {
width: 642rpx;
padding: 54rpx 79rpx 120rpx 79rpx;
box-shadow: 0px 3px 20px rgba(0, 20, 41, 0.06);
box-sizing: border-box;
position: absolute;
top: 349rpx;
left: 0;
right: 0;
background: #fff;
margin: auto;
border-radius: 20rpx;
&_title {
text-align: center;
font-size: 21px;
font-weight: 600;
margin-bottom: 54rpx;
}
&_handle {
margin-top: 115rpx;
.login_primary_button {
font-size: 15px;
width: 100%;
height: 82rpx;
background: @primaryBgColor;
border-radius: 41px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
}
}
}
.privacy_agreement {
margin-top: 30rpx;
&_content {
font-size: 28rpx;
.checkbox {
transform: scale(0.7);
}
text {
color: @primaryColor;
}
}
}
.login-to-set-domin-name {
font-size: 20rpx;
padding-top: 40rpx;
text-align: center;
}
</style>
+433
View File
@@ -0,0 +1,433 @@
<template>
<div class="container">
<lay-out @goBottom="goBottom" @goTop="goTop" @refresherrefresh="refresherrefresh"
:refresherTriggered="refresherTriggered" @refresherrestore="refresherrestore"
@refresherabort="refresherabort" :scroll-top="scrollTop">
<div slot="header">
<!-- 搜索框 -->
<div class="content_search">
<div class="content_search_box">
<div class="icon" @click="searchUser"><span class="iconfont">&#xe6bf;</span></div>
<div class="input">
<input class="uni-input" @confirm="searchUser" @focus="searchFocus" @blur="searchBlur"
v-model="nickname" type="text" placeholder="搜索用户名称" />
</div>
</div>
</div>
</div>
<div slot="content" class="content">
<!-- 聊天列表 -->
<div class="content_userMessgae">
<uni-swipe-action>
<uni-swipe-action-item v-for="(item, index) in userList" :right-options="options"
@click="deleteUserRecord($event, index)" :key="item.id">
<div :id="`content_userMessgae_item${item.id}`" class="content_userMessgae_item"
@click="connentServerForUser(item)">
<div class="content_userMessgae_item_avar">
<image :src="item.avatar" mode="widthFix"></image>
<span class="online_status" :class="{ off: item.online }"></span>
</div>
<div class="content_userMessgae_item_value">
<div class="content_userMessgae_item_value_user">
<div class="content_userMessgae_item_value_user_nickName">{{ item.nickname }}
</div>
<div class="content_userMessgae_item_value_user_form" v-if="item.type"
:class="formType[item.type].className">
<span>{{ formType[item.type].label }}</span>
</div>
</div>
<!-- 用户消息 -->
<div class="content_userMessgae_item_value_message">
<span>{{ item.message }}</span>
</div>
<!-- 用户消息结束 -->
</div>
<!-- 聊天时间 -->
<div class="content_userMessgae_item_createTime">
<div class="content_userMessgae_item_createTime_value">
<span>{{ item.update_time | toDay }}</span>
</div>
<div class="content_userMessgae_item_createTime_count" v-if="item.mssage_num">
<span>{{ item.mssage_num }}</span>
</div>
</div>
<!-- 聊天时间结束 -->
<div class="content_userMessgae_item_del">删除</div>
</div>
</uni-swipe-action-item>
</uni-swipe-action>
</div>
<!-- <div v-if="!userList.length" class="no_message">
<span>暂无客户!</span>
</div> -->
<!-- 聊天列表结束 -->
</div>
</lay-out>
<div class="fixed_model" v-if="showSearchModel"></div>
<download-progress v-show='download' @clearAbort='clearAbort' :percent='percent'></download-progress>
</div>
</template>
<script>
import {
navigateTo,
Modal,
formatDate,
Toast,
parseToObject
} from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import {
mapState
} from 'vuex';
import DownloadProgress from '../../../components/DownloadProgress.vue';
export default {
filters: {
toDay(value) {
if (!value) return '';
return formatDate(value * 1000, 'M月d日 hh:mm');
}
},
components: {
DownloadProgress
},
data() {
return {
selectOnlineStatus: 1,
onlineStatus: [{
label: '在线',
value: 1
},
{
label: '下线',
value: 0
}
],
customerServerHandleModel: false, // 客服操作开关
customerServerData: {},
pageData: {
page: 1,
limit: 15
},
formType: {
0: {
className: 'pc',
label: 'pc端'
},
1: {
className: 'gozhonghao',
label: '公众号'
},
2: {
className: 'wchat',
label: '小程序'
},
3: {
className: 'h5',
label: 'H5'
}
},
refresherTriggered: false, // 下拉刷新状态
nickname: '',
is_tourist: '',
userList: [], // 用户列表
audioFun: '', // 音频对象
showSearchModel: false,
lastTapTime: 0,
scrollTop: 0,
count: 0,
userListTemp: {},
options: [{
text: '取消',
style: {
backgroundColor: '#007aff'
}
}, {
text: '删除',
style: {
backgroundColor: '#dd524d'
}
}],
download: false,
percent: 0
};
},
watch: {
chatInfo(data) {
if (data.id) {
let ids = [];
this.userList.map(item => {
ids.push(item.id);
});
if (ids.indexOf(data.id) === -1) {
this.userList.unshift(data);
}
this.$store.commit('setChatInfo', {});
}
}
},
onLoad(opt) {
this.initSocket();
this.userList = this.$store.getters.userRecord.userList;
this.appVersionLevel();
},
onShow(opt) {
this.pageData.page = 1;
// this.initData(this.pageData.page > 1 ? 'concat' : '');
this.initData();
this.getKefuInfo();
this.updataUserCount();
},
onUnload() {
uni.$off('mssage_num', this.mssageNumEvent);
uni.$off('rm_transfer', this.rmTransferEvent);
uni.$off('transfer', this.transferEvent)
},
onTabItemTap(e) {
},
onNavigationBarButtonTap(e) {
this.handleScanCode(); // 吊起相机,扫描二维码
},
computed: {
...mapState(['chatInfo', 'kefuInfo', 'config', 'userRecord', 'userLists'])
},
methods: {
appVersionLevel() {
// #ifdef APP-PLUS
let that = this;
plus.runtime.getProperty(plus.runtime.appid, ({
version,
name
}) => {
//升级过一次就不要在提醒
if (this.$cache.get('update_' + version)) {
return;
}
http(api.appVersion, {
version,
name
}).then(data => {
if (data.update === false) {
return;
}
uni.showModal({
title: '检测到新版本',
content: data.info,
confirmText: '立即升级',
success: (res) => {
if (res.confirm && data.update && data.url) {
that.downloadFile(data.url);
}
},
complete: () => {
this.$cache.set('update_' + version, 1);
}
})
}).catch(res => {})
});
// #endif
},
downloadFile(url) {
this.downloadTask = uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath, {
force: false
}, () => {
plus.runtime.restart();
}, err => {
console.error(err);
});
}
},
complete: () => {
this.download = false
this.percent = 0;
}
});
this.download = true;
this.downloadTask.onProgressUpdate(res => {
this.percent = res.progress
});
},
clearAbort() {
if (this.downloadTask) {
this.downloadTask.abort();
this.downloadTask = null;
}
},
// 删除聊天列表用户
deleteUserRecord(e, index) {
if (e.index == 1) {
this.$store.dispatch('deleteUserRecord', {
id: this.userList[index].id
}).then(res => {
this.userList.splice(index, 1);
});
}
},
mssageNumEvent(data) {
console.log('mssage_num', data);
this.getMssageNum(data);
},
//被转走事件
rmTransferEvent(data) {
let rmIndex = -1;
this.userList.map((item, index) => {
if (item.id === data.recored.id) {
rmIndex = index;
}
});
if (rmIndex !== -1) {
this.userList.splice(rmIndex, 1);
this.updataUserCount();
}
},
//转接到当前客服事件
transferEvent(data) {
let pushIndex = -1;
this.userList.map((item, index) => {
if (item.id === data.recored.id) {
pushIndex = index;
}
});
if (pushIndex === -1) {
this.userList.unshift(data.recored);
this.updataUserCount();
}
},
initSocket() {
uni.$on('mssage_num', this.mssageNumEvent);
uni.$on('rm_transfer', this.rmTransferEvent);
uni.$on('transfer', this.transferEvent);
},
getKefuInfo() {
this.$store.dispatch('getKeufuInfo').then(res => {
uni.setNavigationBarTitle({
title: res.site_title
});
});
},
// 搜索框获得焦点
searchFocus() {
this.showSearchModel = true;
},
// 搜索框失去焦点
searchBlur() {
this.searchUser();
this.showSearchModel = false;
},
// 搜索用户
searchUser() {
this.pageData.page = 1;
this.initData();
},
// 获取客户列表, 客服信息
initData(type) {
this.pageData.limit = 10;
this.$store.dispatch('getUserRecord', {
nickname: this.nickname,
pageData: this.pageData,
is_tourist: this.is_tourist
}).then(res => {
if (res.length > 0) {
this.pageData.page++;
}
if (type == 'concat') {
this.userList = this.userList.concat(res);
} else {
this.userList = res;
// this.$set(this.userListTemp, this.pageData.page, res);
}
if (this.refresherTriggered) {
this.refresherTriggered = false;
}
});
},
// 下拉刷新被触发
refresherrefresh() {
if (this.refresherTriggered) {
this.refresherTriggered = false;
return;
}
if (!this.refresherTriggered) {
this.refresherTriggered = true;
}
// this.refresherTriggered = true;
this.pageData.page = 1;
this.initData();
},
// 下拉刷新复位
refresherrestore() {
this.refresherTriggered = false;
},
//
refresherabort() {
this.refresherTriggered = false;
},
// 滑动到页面底部
goBottom() {
this.initData('concat');
},
goTop() {
this.pageData.page = 1;
this.initData();
},
updataUserCount() {
this.$store.dispatch('updataUserCount').then(res => {
this.scoket.setBadgeNumber(res.count);
});
},
// 通过sockect连接收取新的消息
getMssageNum(data) {
if (data.num < 1) {
return;
}
this.userList.forEach((item, index) => {
// 如果发送方的user_id 与 列表中的 to_uid 一致
if (item.to_user_id == data.user_id) {
this.userList.splice(index, 1);
}
});
this.userList.unshift(data.recored);
this.updataUserCount();
},
// 扫描二维码
handleScanCode() {
// let str = "http://192.168.31.192:8081/pages/users/scan_login/index?key=463aca66113f65396dc28e3f0041a2f2";
uni.scanCode({
onlyFromCamera: true, // 是否只允许相机扫码,不允许相机选择图片
success(res) {
console.log(res);
navigateTo(1, '/pages/view/authorizedLogin/index', {
key: res.result.split('=')[1]
});
},
fail: () => {}
});
},
connentServerForUser(item) {
item.mssage_num = 0;
navigateTo(1, '/pages/view/customerServer/index', {
to_user_id: item.to_user_id
});
}
}
};
</script>
<style lang="less" scoped>
@import './less/messageList.less';
</style>
@@ -0,0 +1,305 @@
.header {
background: @primaryBgColor;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15rpx;
position: relative;
height: 116rpx;
&_customer {
display: flex;
align-items: center;
&_avar {
margin-right: 10rpx;
border-radius: 50%;
overflow: hidden;
width: 80rpx;
height: 80rpx;
image {
width: 100%;
height: 100%;
}
}
&_status {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
display: flex;
align-items: center;
&_name {
margin-right: 10rpx;
}
&_item {
&_value {
display: flex;
align-items: center;
.tip_primary {
display: inline-block;
width: 12rpx;
height: 12rpx;
background: linear-gradient(143deg, #27f2cb 0%, #14e3b4 100%);
border-radius: 50%;
margin-right: 6rpx;
}
.tip_danger {
background: #f74c31 !important;
}
.tip_type {
font-size: 20rpx;
white-space: nowrap;
}
}
}
}
}
&_handle {
display: flex;
color: rgba(255, 255, 255, 0.8);
align-items: center;
.font {
// display: inline-block;
// width: 38rpx;
// height: 38rpx;
// background: rgba(255, 255, 255, 0.22);
text-align: center;
line-height: 38rpx;
margin-right: 8rpx;
font-size: 16px;
// border-radius: 50%;
}
.loginOut {
font-size: 14px;
}
}
&_checkout_status {
position: absolute;
width: 214rpx;
height: 173rpx;
background: #434343;
top: 140rpx;
left: 43rpx;
z-index: 90;
border-radius: 10px;
&_sanjiao {
background: #434343;
position: absolute;
top: -15rpx;
left: 30rpx;
width: 30rpx;
height: 30rpx;
transform: rotate(45deg);
}
&_select {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
&_item {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
color: #fff;
padding: 0 30rpx;
border-bottom: 1px solid #fff;
&_tip {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: linear-gradient(180deg, #bcbcbc 0%, #919191 100%);
}
.tip_select {
background: linear-gradient(143deg, #27f2cb 0%, #14e3b4 100%);
}
.tip_select_outline {
background: #f74c31;
}
&_status {
flex: 1;
padding-left: 15rpx;
font-size: 14px;
}
&_icon {
color: #b9b9b9;
}
}
&_line {
height: 1px;
width: 100%;
background: #f0f1f2;
}
}
}
}
// 内容部分样式
.content {
&_search {
padding: 20rpx 30rpx;
border-bottom: 1px solid #eceff8;
background: #fff;
display: flex;
align-items: center;
&_box {
flex: 1;
// margin-right: 20rpx;
display: flex;
padding: 14rpx 22rpx;
background: #f5f6f9;
border-radius: 39rpx;
.icon {
margin-right: 12rpx;
color: #c4c4c4;
}
.input {
width: 100%;
align-items: center;
input {
font-size: 14px;
}
}
}
.reload {
}
}
// 聊天内容
&_userMessgae {
padding: 0 30rpx;
background: #fff;
&_item {
position: relative;
display: flex;
padding: 28rpx 0;
border-bottom: 1px solid #f6f6f6;
transition: transform 0.3s;
&_avar {
border-radius: 50%;
// overflow: hidden;
width: 96rpx;
height: 96rpx;
margin-right: 22rpx;
// border: 1px solid rgba(0, 0, 0, 0.1);
// background: @primaryBgColor;
position: relative;
image {
width: 100%;
}
.online_status {
position: absolute;
right: 0rpx;
bottom: 0rpx;
z-index: 10;
display: inline-block;
width: 15rpx;
height: 15rpx;
background: #ccc;
border-radius: 50%;
}
.off {
background: linear-gradient(143deg, #27f2cb 0%, #14e3b4 100%);
}
}
&_value {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
padding-top: 8rpx;
&_user {
display: flex;
align-items: center;
&_nickName {
font-size: 14px;
color: rgba(0, 0, 0, 0.7);
font-weight: 600;
margin-right: 16rpx;
}
&_form {
font-size: 26rpx;
padding: 0 10rpx;
}
.wchat {
background: rgba(56, 117, 234, 0.14);
color: rgba(56, 117, 234, 1);
}
.h5 {
background: rgba(255, 162, 0, 0.18);
color: #d08800;
}
.gozhonghao {
background: rgba(0, 186, 100, 0.14);
color: #00a219;
}
.pc {
color: #820adb;
background: rgba(133, 64, 227, 0.14);
}
}
// 用户消息
&_message {
color: rgba(159, 159, 159, 1);
font-size: 24rpx;
width: 100%;
.overText;
}
}
// 聊天时间
&_createTime {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 8rpx;
&_value {
color: rgba(159, 159, 159, 1);
font-size: 22rpx;
}
&_count {
align-self: flex-end;
display: inline-block;
width: 38rpx;
height: 38rpx;
line-height: 38rpx;
text-align: center;
background: rgba(247, 76, 49, 1);
color: #fff;
border-radius: 50%;
font-size: 24rpx;
}
}
&_del {
position: absolute;
top: 0;
right: -30rpx;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
background-color: rgba(255, 0, 0, 1);
font-size: 28rpx;
color: rgba(255, 255, 255, 1);
transform: translateX(100%);
}
&.on {
transform: translateX(-100rpx);
}
}
}
}
.no_message {
text-align: center;
padding: 60rpx 0;
font-size: 14px;
color: #ccc;
}
@@ -0,0 +1,33 @@
<template>
<view class="agreement">
<view class="content" v-html="agreement"></view>
</view>
</template>
<script>
import api from 'pages/api/api.js';
import http from 'pages/api/index';
export default {
data() {
return {
agreement: ''
};
},
mounted() {
this.getAgreement();
},
methods: {
getAgreement() {
http(api.userAgreement, {}).then(res => {
console.log(res);
this.agreement = res.content;
});
}
}
};
</script>
<style>
.agreement .content {
margin: 10rpx;
}
</style>
+137
View File
@@ -0,0 +1,137 @@
<template>
<div class="container">
<lay-out>
<div slot="content">
<div class="content">
<div class="content_tag"><span>标签名称</span></div>
<!-- <div class="content_input"><input type="text" placeholder="请输入标签名称" /></div> -->
<div class="content_input">
<span>{{ tagsData.label }}</span>
</div>
<div class="content_tag"><span>标签成员</span></div>
<div class="content_group">
<!-- <div class="content_group_handle">
<span class="iconfont">&#xe6c1;</span>
<span>添加成员</span>
</div> -->
<view class="user-content">
<view class="user-list" v-for="item in canEachUserList" :key="item.key">
<div class="content_tag">
<span>{{ item.key }}</span>
</div>
<uni-swipe-action v-for="items in item.list" :key="items.id">
<uni-swipe-action-item>
<view class="user" @click="toUserMessage(items)">
<view class="user-list-left"><image :src="items.avatar" mode=""></image></view>
<view class="user-list-right">{{ items.nickname }}</view>
</view>
<template v-slot:right>
<div class="delete_tags" @click="deleteUser(items)"><text>删除</text></div>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</view>
</div>
</div>
</div>
</lay-out>
</div>
</template>
<script>
import { navigateTo, navigateBack, Modal, Toast } from 'pages/utils/uniApi.js';
import { makePy } from 'pages/utils/utils';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
data() {
return {
userList: [],
canEachUserList: [],
tagsData: {}
};
},
onLoad(opt) {
this.tagsData = this.$cache.get('userTagData');
this.canEachUserList = this.letterSrot(this.tagsData.user);
},
methods: {
// 前往用户详情界面
toUserMessage(item) {
navigateTo(1, '/pages/view/customerServer/customerMessage', { ...item, user_id: item.id });
},
// 删除用户
deleteUser(item) {
Modal('温馨提示', `"${item.nickname}"将被删除,请问是否继续?`).then(() => {
http(api.deleteUserLabel, {
userId: item.id,
labelId: this.tagsData.id
}).then(res => {
this.canEachUserList.forEach(key => {
key.list.forEach((val, index, arr) => {
if (item.id == val.id) {
arr.splice(index, 1);
}
});
});
Toast('删除成功');
});
});
},
cencel() {
navigateBack(1);
},
// 对通讯录按字母顺序做排序
letterSrot(userList) {
const sortList = [];
const list = userList;
const letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
letter.forEach(item => {
const obj = {};
obj.key = item;
obj.list = [];
for (let i = 0; i < list.length; i++) {
if (item == makePy(list[i].nickname).substr(0, 1)) {
obj.list.push(list[i]);
list.splice(i, 1);
i--;
}
}
if (obj.list.length) sortList.push(obj);
});
return sortList;
}
}
};
</script>
<style scoped lang="less">
@import url('./less/addTag.less');
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 96rpx;
padding: 0 30rpx;
font-size: 16px;
&_title {
font-weight: 600;
}
.finish {
width: 92rpx;
height: 56rpx;
background: #dddddd;
opacity: 1;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
color: #aaaaaa;
opacity: 0;
}
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div class="container">
<lay-out>
<div slot="content" class="content">
<!-- <div class="handle_title" @click="createdTag">
<span class="iconfont">&#xe6c1;</span>
<span class="handle_title_message">新建标签</span>
</div> -->
<!-- <uni-swipe-action>
<uni-swipe-action-item>
<div class="tag_container">
<div class="tag_container_name">
<span class="name">意向客户</span>
<span class="num">(9)</span>
</div>
<div class="tag_container_user">
<div class="userList">
阿萨大大啊水水水水水水水水水水水水水水水水水水水啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊反反复复反反复复反反复复方法方法分分分3
</div>
</div>
</div>
<template v-slot:right>
<div class="delete_tags">
<text>删除</text>
</div>
</template>
</uni-swipe-action-item>
</uni-swipe-action> -->
<div class="tag_container" v-for="(item, index) in tagList" :key="index" @click="toTagDetils(item)">
<div class="tag_container_name">
<span class="name">{{item.label}}</span>
<span class="num" v-if="item.user.length">({{ item.user.length }})</span>
</div>
<div class="tag_container_user">
<div class="userList" v-if="item.user.length">
{{item.user.map(item=> item.nickname).join('')}}
</div>
</div>
</div>
</div>
</lay-out>
</div>
</template>
<script>
import { navigateTo} from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
data() {
return {
userData: {},
tagList: []
};
},
onLoad(opt) {
this.userData = opt;
},
onShow() {
this.initData();
},
methods: {
initData() {
http(api.userLabe, { id: this.userData.user_id }).then(res => {
console.log(res);
this.tagList = res;
})
},
toTagDetils(item) {
this.$cache.get('userTagData', item);
navigateTo(1, '/pages/view/setTags/addTag');
},
createdTag() {
navigateTo(1, '/pages/view/setTags/addTag');
}
}
};
</script>
<style lang="less" scoped>
@import url('./less/setTags.less');
</style>
@@ -0,0 +1,63 @@
@padding-l-r:30rpx;
@padding-t-p:20rpx;
@bgc-f:#FFFFFF;
@fontSize:28rpx;
@imgSize:64rpx;
.content_tag {
padding: 14rpx 30rpx;
font-size: 12px;
color: #999999;
background: #eeeeee;
}
.content_input {
padding: 24rpx 30rpx;
input {
font-size: 14px;
}
}
.content_group {
&_handle {
color: #3875EA;
height: 88rpx;
display: flex;
align-items: center;
border-bottom: 1px solid #F0F2F7;
padding: 0 30rpx;
}
}
.user-content{
background-color: #EEEEEE;
.user-list{
.number{
color: #666666;
font-size:@fontSize - 2rpx;
}
.user{
background-color: @bgc-f;
display: flex;
align-items: center;
padding:30rpx 28rpx;
border-bottom: 1rpx solid #F0F2F7;
.user-list-left{
width: @imgSize;
height: @imgSize;
padding-right: 20rpx;
image{
width: @imgSize;
height: @imgSize;
border-radius: 50%;
}
}
.user-list-right{
color: #282828;
}
}
}
}
.delete_tags {
padding: 27rpx 37rpx;
background: #F74C31;
color: #fff;
}
@@ -0,0 +1,33 @@
.bg {
background: #fff;
border-bottom: 1px solid #f0f2f7;
}
.handle_title {
color: @primaryColor;
padding: 35rpx 30rpx;
.bg;
}
.tag_container {
.bg;
padding: 15rpx 30rpx;
font-size: 15px;
&_name {
color: #282828;
font-weight: 600;
}
&_user {
color: #999999;
margin-top: 6rpx;
.userList {
.overText;
}
}
}
.delete_tags {
padding: 39rpx;
background: #F74C31;
color: #fff;
font-size: 14px;
}
+256
View File
@@ -0,0 +1,256 @@
<template>
<div class="container">
<lay-out>
<!-- <div slot="header" class="header">
<div class="title_name">统计</div>
</div> -->
<div slot="content">
<div class="statistics"></div>
<div class="statistics_list">
<div class="statistics_list_item">
<div class="statistics_list_item_value">{{ statisticsAll.all }}</div>
<div class="statistics_list_item_label">全部客户</div>
</div>
<div class="statistics_list_item">
<div class="statistics_list_item_value">{{ statisticsAll.toDayKefu }}</div>
<div class="statistics_list_item_label">今日客户</div>
</div>
<div class="statistics_list_item">
<div class="statistics_list_item_value">{{ statisticsAll.month }}</div>
<div class="statistics_list_item_label">本月客户</div>
</div>
<div class="statistics_list_item">
<div class="statistics_list_item_value">{{ statisticsAll.toDayTourist }}</div>
<div class="statistics_list_item_label">今日游客</div>
</div>
</div>
<div class="content">
<div class="content_charts">
<div class="content_charts_title">
<div class="content_charts_title_select">
<scroll-view scroll-x class="content_charts_title_select_scroll">
<view class="scroll_content">
<view class="scroll_content_item" v-for="(item, index) in monthList" :key="index" @click="handleSelectMonth(item)">
<div class="scroll_content_item_label" :class="{ selectEd: selectMonth == item.value }">{{ item.label }}</div>
<div class="tip" v-if="selectMonth == item.value"></div>
</view>
</view>
</scroll-view>
</div>
<div class="content_charts_title_date">
<biaofun-datetime-picker
class="positionPicker"
:class="{ primaryColor: searchChartsData.value }"
placeholder="请选择年月"
:defaultValue="searchChartsData.value"
start="2000"
end="2100"
fields="month"
@change="changeDatetimePicker"
:key="searchChartsData.value"
></biaofun-datetime-picker>
<!-- <span>{{searchChartsData.value}}</span> -->
<span class="iconfont">&#xe6c5;</span>
</div>
</div>
<div class="content_charts_value">
<view class="charts-box">
<div class="title">新增()</div>
<qiun-data-charts type="area" :inScrollView="true" canvasId="scrolllineid" :opts="opts" :chartData="chartData" :ontouch="true" :canvas2d="true" />
</view>
</div>
</div>
</div>
</div>
</lay-out>
</div>
</template>
<script>
import biaofunDatetimePicker from '@/components/biaofun-datetime-picker/biaofun-datetime-picker.vue';
import { formatDate } from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
export default {
components: {
biaofunDatetimePicker
},
data() {
return {
selectMonth: 'yesterday',
chartData: {
categories: [],
series: [
{
name: '客户',
data: [],
color: '#3875EA',
legendShape: 'square' // 长方形
},
{
name: '游客',
data: [],
color: '#3FC7A9',
legendShape: 'square'
}
]
},
opts: {
enableScroll: true,
extra: {
area: { type: 'curve', addLine: true, gradient: true }
},
xAxis: {
type: 'grid',
gridType: 'solid',
itemCount: 5, //x轴单屏显示数据的数量,默认为5个
scrollShow: false, //新增是否显示滚动条,默认false
scrollAlign: 'left', //滚动条初始位置
scrollBackgroundColor: '#F7F7FF', //默认为 #EFEBEF
scrollColor: '#DEE7F7' //默认为 #A6A6A6
},
legend: {
show: true,
position: 'top',
float: 'right',
padding: 2,
margin: 20,
backgroundColor: 'rgba(0,0,0,0)',
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
fontSize: 13,
fontColor: '#666666',
lineHeight: 11,
hiddenColor: '#CECECE',
itemGap: 10
}
},
statisticsAll: {}, // 客户总统计数据
searchChartsData: {
year: String(new Date().getFullYear() + '-' + new Date().getMonth() + 1),
month: new Date().getMonth() + 1,
value: ''
}
};
},
computed: {
monthList() {
let arr = [
{
label: '昨天',
value: 'yesterday'
},
{
label: '今天',
value: 'today'
},
{
label: '7天',
value: 'lately7'
},
{
label: '30天',
value: 'lately30'
},
{
label: '半年',
value: 'lately180'
}
];
// for (let i = 1; i < new Date().getMonth(); i++) {
// arr.push({
// month: i,
// label: i + '月'
// });
// }
// let monthArr = [
// {
// label: '当月',
// month: new Date().getMonth() + 1
// },
// {
// label: '上月',
// month: new Date().getMonth()
// }
// ];
// arr.push(...monthArr);
// arr.reverse();
return arr;
}
},
watch: {
// searchChartsData: {
// handler() {
// this.getChartsData();
// },
// deep: true,
// immediate: true
// }
},
onShow() {
// this.searchChartsData.value = formatDate(new Date().getTime(), 'yyyy-MM');
// this.selectMonth = this.searchChartsData.month; // 自动选择本月
this.initData();
this.getChartsData();
},
methods: {
// 获取数据总统计
initData() {
http(api.statisticsAll, {}).then(res => {
this.statisticsAll = res;
});
},
// 获取图标统计数据
getChartsData() {
let categories = [];
http(api.statisticsIndex, { time: this.selectMonth }).then(res => {
// 构建 X 轴数据
// 将两条年份数据拼合在一起, 给对应的条目赋值
// if (res.list.length) {
res.list.forEach(item => {
categories.push(item.time);
});
this.chartData.series[0].data = res.list.map(item => item.number);
// }
// if (res.tourist.length) {
res.tourist.forEach(item => {
categories.push(item.time);
});
this.chartData.series[1].data = res.tourist.map(item => item.number);
// }
// 数组去重
categories = Array.from(new Set(categories));
// 数组从小到大排序
categories.sort((val1, val2) => {
return Number(val1.replace(/-/g, '')) - Number(val2.replace(/-/g, ''));
});
this.chartData.categories = categories;
});
},
// 选择年份
changeDatetimePicker(e) {
this.searchChartsData.value = formatDate(e.dt, 'yyyy/MM');
this.selectMonth = this.searchChartsData.value;
this.getChartsData();
},
// 选择月份
handleSelectMonth(item) {
this.selectMonth = item.value;
this.searchChartsData.value = '';
this.getChartsData();
}
}
};
</script>
<style lang="less" scoped>
@import url('./less/statistics.less');
</style>
@@ -0,0 +1,141 @@
.header {
height: 201rpx;
background: @primaryBgColor;
.title_name {
text-align: center;
padding: 20rpx;
font-size: 16px;
font-weight: 600;
color: #fff;
}
}
.statistics {
background: #1890FC;
padding: 43rpx 30rpx;
position: absolute;
top: 0;
left: 0;
width: 100%;
box-sizing: border-box;
}
.statistics_list {
position: relative;
z-index: 10;
display: flex;
justify-content: space-between;
width: 690rpx;
box-sizing: border-box;
margin: auto;
text-align: center;
padding: 43rpx 30rpx;
background: #fff;
border-radius: 12px;
&_item {
&_value {
font-size: 20px;
font-weight: bold;
margin-bottom: 8rpx;
}
&_label {
color: #999999;
font-size: 12px;
}
}
}
.content {
padding-top: 36.2rpx;
&_charts {
width: 690rpx;
// padding: 34rpx 26rpx;
box-sizing: border-box;
margin: auto;
background: #fff;
// border-radius: 12px;
// overflow: hidden;
&_title {
display: flex;
justify-content: space-between;
padding: 34rpx 26rpx;
padding-bottom: 0;
&_select {
flex: 1;
display: flex;
align-items: center;
&_scroll {
.scroll_content {
display: flex;
// width: 100%;
justify-content: space-around;
&_item {
// margin-right: 52rpx;
&_label {
font-size: 15px;
white-space: nowrap;
color: #999999;
text-align: center;
margin-bottom: 6rpx;
}
.selectEd {
color: @primaryColor;
font-weight: bold;
}
.tip {
width: 40rpx;
height: 4rpx;
background: #3875ea;
opacity: 1;
margin: auto;
}
}
}
}
}
.content_charts_title_date {
// width: 110rpx;
// height: 110rpx;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
border-left: 1px solid #ccc;
padding-left: 30rpx;
// white-space: nowrap;
// position: relative;
.positionPicker {
// position: absolute;
// top: 0;
// left: 0;
// opacity: 0;
}
}
}
&_value {
.charts-box {
position: relative;
.title {
position: absolute;
top: 70rpx;
left: 30rpx;
font-size: 11px;
color: #cccccc;
z-index: 999;
}
}
}
}
}
.content_charts_title_date:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 1px;
height: 100%;
background: #000000;
opacity: 0.08;
}
.primaryColor {
color: @primaryColor;
}
+387
View File
@@ -0,0 +1,387 @@
<template>
<div class="container">
<lay-out>
<div slot="content">
<div class="header"></div>
<div class="box">
<div class="img" @click="setting">
<image :src="customerServerData.avatar" mode=""></image>
</div>
<div>
<div class="box_font1">
<span>{{ customerServerData.nickname }}</span>
<span class="isOnline" :class="{ online: customerServerData.online == '1' }"
@click="handleChangeOnline">
<span></span>
<span>{{ customerServerData.online == '1' ? '在线' : '离线' }}</span>
<!-- 切换上线下线弹框开始 -->
<span class="onLineModel" v-if="onlineModel">
<span class="sanjiao"></span>
<span class="model_text" @click="online(1)">
<span class="dian online"></span>
<span>在线</span>
<span class="iconfont"
:class="{ opacity0: !(customerServerData.online == '1') }">&#xe6d1;</span>
</span>
<span class="xian"></span>
<span class="model_text" @click="online(0)">
<span class="dian"></span>
<span>离线</span>
<span class="iconfont"
:class="{ opacity0: !(!customerServerData.online || customerServerData.online == '0') }">&#xe6d1;</span>
</span>
</span>
<!-- 切换上线下线弹框结束 -->
</span>
</div>
<div class="box_font2">手机号{{ customerServerData.phone }}</div>
</div>
</div>
<div class="content">
<div class="content_box" @click="setting">
<div class="icon"><span class="iconfont iconPrimaryColor">&#xe6bc;</span></div>
<div class="text">个人信息</div>
<div>
<image src="~static/images/right.png" alt=""></image>
</div>
</div>
<div class="line"></div>
<div class="content_box" @click="authReply">
<div class="icon"><span class="iconfont iconPrimaryColor">&#xe6d9;</span></div>
<div class="text">自动回复</div>
<div>
<image src="~static/images/right.png" alt=""></image>
</div>
</div>
<div class="line"></div>
<div class="content_box" @click="feedback">
<div class="icon"><span class="iconfont iconPrimaryColor">&#xe6d9;</span></div>
<div class="text">意见反馈</div>
<div>
<image src="~static/images/right.png" alt=""></image>
</div>
</div>
<div class="line" v-if='isDomainName'></div>
<div class="content_box" v-if='isDomainName' @click="editDominName">
<div class="icon"><span class="iconfont iconPrimaryColor">&#xe6f9;</span></div>
<div class="text">域名设置</div>
<div class="message">
<span>{{ dominName }}</span>
<image src="~static/images/right.png" alt=""></image>
</div>
</div>
</div>
<div class="content">
<div class="content_box" @click="loginOut">
<div class="icon"><span class="iconfont iconPrimaryColor">&#xe6f8;</span></div>
<div class="text">退出登录</div>
<div>
<image src="~static/images/right.png" alt=""></image>
</div>
</div>
</div>
<div class="copyright"><span class="iconfont">&#xe6f7;</span></div>
</div>
</lay-out>
</div>
</template>
<script>
import {
navigateTo,
Modal
} from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import config from 'pages/config/app.js';
export default {
components: {},
data() {
return {
customerServerData: {},
dominName: '',
use: false,
onlineModel: false,
isDomainName: config.isDomainName
};
},
onLoad(opt) {
this.dominName = this.$cache.get('dominName');
this.customerServerData = this.$store.getters.kefuInfo;
},
onShow() {
this.initData();
},
onHide() {
this.onlineModel = false;
},
onUnload() {
this.onlineModel = false;
},
methods: {
// 获取当前登录客服信息
initData() {
this.$store.dispatch('getKeufuInfo').then(res => {
this.customerServerData = res;
});
},
// 意见反馈
feedback() {
navigateTo(1, '/pages/view/feedback/index');
},
authReply() {
navigateTo(1, '/pages/view/authReply/index');
},
// 设置
setting() {
navigateTo(1, '/pages/view/user/setting/setting', this.customerServerData);
},
// 修改域名
editDominName() {
navigateTo(1, '/pages/view/dominName/index');
},
// 退出登录
loginOut() {
Modal('温馨提示', '您确定要退出登录吗?').then(() => {
http(api.userLogout)
.then(res => {
//关闭长连接
this.scoket.clearPing();
this.$store.commit('clearChat');
this.$store.commit('logout');
navigateTo(3, '/pages/view/login/index');
})
.catch(res => {
this.scoket.clearPing();
this.$store.commit('clearChat');
this.$store.commit('logout');
navigateTo(3, '/pages/view/login/index');
});
});
},
// 更改在线状态
handleChangeOnline() {
this.onlineModel = !this.onlineModel;
},
online(val) {
this.scoket.send({
data: {
online: val
},
type: 'online'
});
this.customerServerData.online = val;
}
}
};
</script>
<style lang="less" scoped>
.header {
background: #1890fc;
padding: 43rpx 30rpx;
// height: 83rpx;
position: absolute;
top: 0;
left: 0;
width: 100%;
box-sizing: border-box;
}
.box {
width: 690rpx;
height: 180rpx;
background: #ffffff;
border-radius: 12px;
margin: 0 auto;
margin-bottom: 24rpx;
display: flex;
justify-content: left;
align-items: center;
position: relative;
z-index: 10;
image {
width: 100rpx;
height: 100rpx;
margin-left: 34rpx;
margin-right: 20rpx;
border-radius: 50%;
}
.box_font1 {
font-size: 30rpx;
font-weight: 600;
line-height: 42rpx;
color: #282828;
margin-bottom: 8rpx;
display: flex;
align-items: center;
}
.box_font2 {
font-size: 26rpx;
font-weight: 400;
line-height: 37rpx;
color: #999999;
}
}
.content {
width: 690rpx;
margin: 0 auto;
margin-top: 40rpx;
background: #fff;
border-radius: 10rpx;
.line {
width: 90%;
height: 1px;
background: #eeeeee;
position: relative;
left: 10%;
}
.content_box {
padding: 28rpx 30rpx;
// height: 88rpx;
background: #ffffff;
border-radius: 12rpx;
// margin-bottom: 24rpx;
display: flex;
justify-content: space-between;
align-items: center;
.icon {
margin-right: 20rpx;
}
.text {
flex: 1;
// width: 84%;
}
image {
width: 33rpx;
height: 33rpx;
}
.message {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
}
}
}
.copyright {
display: flex;
justify-content: center;
margin-top: 300rpx;
.iconfont {
font-size: 250rpx;
color: #cccccc;
text-align: center;
}
}
.iconPrimaryColor {
// color: @primaryColor;
}
.isOnline {
width: 100rpx;
height: 40rpx;
border-radius: 18rpx;
background: #999999;
display: flex;
margin-left: 9rpx;
color: #fff;
align-items: center;
justify-content: center;
text-align: center;
font-size: 20rpx;
position: relative;
}
.isOnline>span:nth-child(1) {}
.dian {
display: inline-block;
width: 8rpx;
height: 8rpx;
background: #ffffff;
border-radius: 50%;
margin-right: 6rpx;
}
.online {
background: #20c93b;
}
.onLineModel {
position: absolute;
top: 50rpx;
left: 43rpx;
z-index: 999 !important;
background: #434343;
border-radius: 6px;
width: 180rpx;
height: 150rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.sanjiao {
background: #434343;
position: absolute;
top: -15rpx;
left: 30rpx;
width: 30rpx;
height: 30rpx;
transform: rotate(45deg);
}
.model_text {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
white-space: nowrap;
font-size: 24rpx;
.dian {
margin-right: 8rpx;
}
}
.xian {
display: inline-block;
width: 100%;
height: 1px;
background: #f0f1f2;
}
.opacity0 {
opacity: 0;
}
}
.onlineFlag {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 0;
background: rgba(0, 0, 0, 0.3);
}
</style>
+342
View File
@@ -0,0 +1,342 @@
<template>
<div class="container">
<lay-out titleName="设置">
<div slot="content">
<view class='personal-data'>
<view class='list'>
<view class='item'>
<view>头像</view>
<view class="avatar-box" @click='uploadpic'>
<image :src="customerServerData.avatar"></image>
</view>
</view>
<view class='item'>
<view>昵称</view>
<view class='input'><input v-model="customerServerData.nickname" type='text' placeholder="请输入客服名称"></input></view>
</view>
<view class='item'>
<view>手机号</view>
<view class='input'>
<view class='input'><input v-model="customerServerData.phone" type='number' placeholder="请填写联系方式"></input></view>
</view>
</view>
<view class="item">
<view>密码</view>
<view class='input'>
<view class='input'><input v-model="customerServerData.password" type='number' placeholder="请输入密码"></input></view>
</view>
</view>
</view>
</view>
</div>
</lay-out>
</div>
</template>
<script>
import { navigateTo, chooseImage, pathToBase64 , Toast, Modal, Loading, hideLoading} from 'pages/utils/uniApi.js';
import http from 'pages/api/index';
import api from 'pages/api/api.js';
import { mapState } from 'vuex'
const app = getApp()
export default {
data() {
return {
dominName: '',
customerServerData: {}
}
},
computed:{
...mapState(['token'])
},
onLoad(opt) {
this.initData()
this.dominName = this.$cache.get('dominName');
},
onNavigationBarButtonTap() {
this.handleEdit();
},
methods: {
edit(){
uni.navigateTo({
url:'/pages/view/example'
})
},
// 修改域名
editDominName() {
navigateTo(3, '/pages/view/dominName/index');
},
// 获取当前登录客服信息
initData() {
http(api.userUserInfo).then(res => {
this.customerServerData = res;
console.log(this.customerServerData);
});
},
// 发送图片
uploadpic() {
let that = this;
chooseImage(1).then(res => {
//#ifndef H5
uni.compressImage({
src: res.tempFilePaths[0],
width: 'auto',
height: 'auto',
success(res) {
console.log('ok');
that.upload(res.tempFilePath);
},
fail(e) {
console.log(e,'error');
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.token
},
success: uploadFileRes => {
console.log(uploadFileRes)
let unloadResponse = JSON.parse(uploadFileRes.data);
if (unloadResponse.status == 200) {
this.customerServerData.avatar = unloadResponse.data.url;
}
},
fail: res => {
Toast('上传图片失败');
},
complete: res => {
console.log(res);
}
});
},
// 确定修改
handleEdit() {
let postData = this.customerServerData;
if(!postData.password) {
postData.password = '******'
}
http(api.putKefuUserUserInfo, postData).then(res => {
Toast('修改成功');
this.initData();
})
},
// 退出登录
loginOut() {
http(api.userLogout).then(res => {
Modal('温馨提示', '您确定要退出登录吗?').then(() => {
navigateTo(3, '/pages/view/login/index');
});
});
}
}
}
</script>
<style scoped lang="less">
.personal-data {
background-color: #fff;
// padding-bottom: 60rpx;
}
.personal-data .title {
margin-bottom: 30rpx;
font-size: 32rpx;
color: #282828;
}
.personal-data .wrapList .item {
width: 690rpx;
height: 160rpx;
background-color: #f8f8f8;
border-radius: 20rpx;
margin-bottom: 22rpx;
padding: 0 30rpx;
position: relative;
border: 2rpx solid #f8f8f8;
box-sizing: border-box;
}
.personal-data .wrapList .item.on {
border-color: #e93323;
border-radius: 20rpx;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArIAAACgCAYAAADw+I85AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0QzNkY3NzlCNzJCMTFFOTgyNEU4QzhGQTRFRUY2REQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0QzNkY3N0FCNzJCMTFFOTgyNEU4QzhGQTRFRUY2REQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozRDM2Rjc3N0I3MkIxMUU5ODI0RThDOEZBNEVFRjZERCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozRDM2Rjc3OEI3MkIxMUU5ODI0RThDOEZBNEVFRjZERCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pn3rJMAAAArUSURBVHja7N3NXuLIGsDhqigK2Ou+grmEuf/t2fT+bOYKZn9aW5Q6qaQSIoKfoCQ8z29QRBSBzX+q31RiSikAAMDYVF4CAACELAAACFkAABCyAAAIWQAAELIAACBkAQAQsgAAIGQBAEDIAgCAkAUAQMgCAICQBQAAIQsAgJAFAAAhCwAAQhYAACELAABCFgAAhCwAAAhZAACELAAACFkAABCyAAAIWQAAELIAACBkAQAQsgAAIGQBAEDIAgCAkAUAQMgCAICQBQAAIQsAgJAFAAAhCwAAQhYAACELAABCFgAAhCwAAAhZAACELAAACFkAABCyAAAIWQAAELIAACBkAQAQsgAAIGQBAEDIAgCAkAUAQMgCAICQBQAAIQsAgJAFAAAhCwAAQhYAACELAABCFgAAhCwAAAhZAACELAAACFkAABCyAAAIWQAAELIAACBkAQBAyAIAIGQBAEDIAgCAkAUAQMgCAMAJuPQSAABMy79///XaXfJi5qy0YFUuqVzW9eWhvqzK9b1+/vpHyAIAcMjCqxs1tldj/zHl/6oU4rz+ctY2a3tzjO2n0F6tUqobMYZ5fX1V337XBm0MMbX3SuXnvv1peqcBAKYlXl+VSI2lZJuIzSuwi7pUY3/HFPsijYMPcVOps9hG7W19fRVT+50YT6TXvdUAABML2at5V6rdTdfNSmzXquX2FOKTr7trsVvBjeVOISzLyuyfNnNTOIWWFbIAAFNzfd2umjYrsmlWR+i8KuusXbhurudZgTZpU6w/p82Ka0oldJvb47z+cp3HDU5kQVbIAgBMTVwsmzitr1V1ni5C07Pd5EAXtCVlm3BNTfS27dvGbAiDcYPUr9TWvys91jetT2BEVsgCAEwuZOeLJkDr/+Z5sbXdb7UdCIixb9M2WDdjss2n4X274YN2LraJ3fzjeUTh9yk8TyELADC1kM0rsjHVTRpnTYam2I8LNBOuaRO0TbaWbQhidyRYKveLmz0P+vu223ZV8ZWtuYQsAADvD9nlTTMb23/dxelg9TUM4nSzRLvZsSANf274u9uvZnXm/hGyAAAcVHWzzKusl5uDtvq9YtvvpzZJmwGC+GS1tR83iHuGYMuPXtbfF7IAABxWXP7IyVkNT4awGQ/Y7FswHBkIW9e7W1Kfv0/GDKpTeJ5CFgBgapbLPAJQxX5X2DIuEPsdYtsSTak/nKv5Xir7GQxWZNvvlZGC/pReUcgCAHB41c2PnbfHrc+v3bbv61MhZAEAJibmkE1pXRdo9SRDuxXVuJWp3XBsGYDdfL9frx38jub767LVgZAFAOCAIdvsWpBjs5tlHZx4tvmQNhsVdH1bAjYO9pTtrlX9cEJvfQrPU8gCAExMdXOTPz3knQvCk/1iU4iDhO3HCuKT8yK0v6P/mfL9wTFf9W0PpzBvIGQBACYmLm7yOMCqDtB5f6hXak94UFo0lPMklO22ykFfg71mNyu3/ZkUNltz1b+7vYOQBQDgkCG7vMmxmWdkVyGfiWvH3rD9yWeb22O/KVdfuqVy29HZOBwuWKVmbEHIAgBw6JBdLMqKaryLMV3GwRFcqRykVXWt2g0V9KfyimV7rsEEbTkILLbDCXftqIGDvQAAOLTFsjtxwbrOzds6PJcpPT8pQnctlV6N/XlsBwd9lZXcsp/sbZXiuszJClkAAA4rzuclUpsl11UdoXcxxXm709Zg7rUp1fJ13KzKDnbfGhwQFu/qr1fdoGwUsgAAHD5kF32JlhD9E5ots+KiCv0JvAZzr3GzPUGJ235lNo8TpHjbBnF373QSz1PIAgBMLWSvrtoQTf3ga5YP0nqsP89jPgCs7dz2Q4xhu03T5mfuYnNyhTjYzSAE228BALDXv3//9aGf+/mf/5ai3Zy0q4wOrGOIv1NoznEwq0P3sv66yl+XLs0ztfV9wkOO2NieVKFP29SeKqyP2I/+fUIWAIDdZrP+6nDhdDMa0JyZ60+57LvPM9+0CJsfttq6NMetCVkAgIn57pXST0Zr7tOLEqzd552ELAAA3x2u3aV6zw8LWQAAvlKO1Vm5XHzmFwlZAABGE69CFgDgDb5z1vTnr3+m8BLmcL06VnMKWQCAwzRVt9rYHVWf5c2r8g4Bef/WVWi3tZq6WF6L6/DOmVchCwDwdcGWY+0q7N+ZKpa4vSj3y2F7H9ptr9IZvh5CFgDgm+UVx8UHgm0Ye7ehXaUVsEIWAOBLLEq0fTb+lqFdnb0d8WtxXS7fcq4EIQsA8HY5Pmc7bs9jAt0MbJ6HXZe460YLuhna7eDrVjF/j+x1yM9lHo48AytkAQAOY7EnYu9Cu7KadsRtd7DXqtzvqgTgdhTm3z2Gldmq/K0n0ZBCFgDgdd02UkM5UPNK6uMbf0eO2nyQV161XYanq5lX5fZTnpn91jGCfVUNAMB+OdwWOyL2f++I2KHH8rPrrds/cvDYV/XiTWhXkuOp/WEAAOy3axXy944QfY9uNXc7mK9P7Lnnlegf4UT/FV/IAgC8bHukII8HPB7g9z6W3/XSY32nvEK8DKe5SixkAQBecbkVcmlHfH7G9okRYvj+1c/chz9OLKqFLADAO23vUrAKhz0jV7dt10uP+dXhniP2YgxvjpAFANhvO+gejvAYD6885lfJK7D5oK44ljfH9lsAAPttL/o9HuExHl95zK+QdyS4HtubI2QBAPbbXp1cH+Ex1q885rEd4pS7J/F/GQAAvD1sx260EStkAQBelr4gZKtXHvNYlmOOWCELAPCy7X/2P8aBWBevPOYx5JXY2djfHCELALDf9oFYxzi+6PKVxzxGxF5N4c0RsgAA++3a4/WQ4wUxPF8ZfTji85lPJWKFLADAy3JUbp9565DbVF2H52cOWx3puczCCLfYErIAAB93vyM+DzEre7EjLO+P9Bzy+MJyam+MkAUAeNmf8HwngeUnO6raEZapPNYxem85xTdGyAIAvCwH5u2Ohsqnc/3IyuxF+dntDrsNh996K5aIjVN8Y4QsAMDr8tzq/Y6O+hHaA6jeEoqx3PfHjga7D8eZjZ2H42wZdhKcohYA4G1uw+5dBvKc61UJ0XxZh81esFW5zML+HQ9W4fmK7yHMwoR2KBCyAACf8zvs3oc1ltveG473R4rYqvydkyZkAQDeJ4fnQwnFj86ednO3x9pq6zN/m5AFAJiwVYnZbqzgrdGYAzavwu7aCeFQrs6l8YQsAMDH5BC9K5fcVHkmNR9YVQ3CNt8nz8s+DuL3mPJjz8/lDRCyAACf9/AFkfoWZzFSIGQBAF7x89c/Y/pzZ+fWdvaRBQCYhvm5PWEhCwAwftfn2HVCFgBg3GIJ2bMjZAEAxu06nNEBXkIWAGAaujOKnSUhCwAwXme7GitkAQDG66xXY2tJyAIAjNMsnPFqbG0tZAEAxun6zJ+/kAUAGKF8Bq9z77hHIQsAMD5XXoLwIGQBAMYlz8XOzvw1WAcrsgAAo2M1NoRV/iBkAQDGZeYlCPdCFgBgXHK7XYjYZrRAyAIAjMi5r8am+nI3rHoAAITsGNyWmBWyAAAjkncrOOexgjxSsBreIGQBAMbh8oyfew7Y2+0bhSwAgJA9ZQ+7Ivbcyx4AQMietvt9EStkAQDGIc/HntO/pKcSsCtlDwAwbufUbHkV9i4MdifwogAAjNfUdyvIJzhYhcHJDtQ9AMA0TGmsIJVYzZfH0B7M9fiRX/R/AQYA1i4UF+HkevkAAAAASUVORK5CYII=");
background-size: 100% 100%;
background-color: #fff9f9;
background-repeat: no-repeat;
}
.personal-data .wrapList .item .picTxt {
width: 445rpx;
}
.personal-data .wrapList .item .picTxt .pictrue {
width: 96rpx;
height: 96rpx;
position: relative;
}
.personal-data .wrapList .item .picTxt .pictrue image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.personal-data .wrapList .item .picTxt .pictrue .alter {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
position: absolute;
bottom: 0;
right: 0;
}
.personal-data .wrapList .item .picTxt .text {
width: 325rpx;
}
.personal-data .wrapList .item .picTxt .text .name {
width: 100%;
font-size: 30rpx;
color: #282828;
}
.personal-data .wrapList .item .picTxt .text .phone {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
.personal-data .wrapList .item .bnt {
font-size: 24rpx;
background-color: #fff;
border-radius: 27rpx;
width: 140rpx;
height: 54rpx;
border: 2rpx solid #e93323;
}
.personal-data .wrapList .item .currentBnt {
position: absolute;
right: 0;
top: 0;
font-size: 26rpx;
background-color: rgba(233, 51, 35, 0.1);
width: 140rpx;
height: 48rpx;
border-radius: 0 20rpx 0 20rpx;
}
.personal-data .list {
// margin-top: 15rpx;
background-color: #fff;
}
.personal-data .list .item {
padding: 30rpx 30rpx 30rpx 0;
border-bottom: 1rpx solid #f2f2f2;
margin-left: 30rpx;
font-size: 32rpx;
color: #282828;
display: flex;
justify-content: space-between;
align-items: center;
}
.personal-data .list .item .phone {
width: 160rpx;
height: 56rpx;
font-size: 24rpx;
color: #fff;
line-height: 56rpx;
border-radius: 32rpx
}
.personal-data .list .item .pictrue {
width: 88rpx;
height: 88rpx;
}
.personal-data .list .item .pictrue image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.personal-data .list .item .input {
width: 415rpx;
text-align: right;
color: #868686;
}
.personal-data .list .item .inputs {
// width: 415rpx;
text-align: right;
color: #868686;
display: flex;
justify-content: right;
align-items: center;
}
.personal-data .list .item .inputs .iconfont {
font-size: 35rpx;
margin-left: 30rpx;
width: 28rpx;
height: 28rpx;
}
.personal-data .modifyBnt {
font-size: 32rpx;
color: #fff;
width: 690rpx;
height: 90rpx;
text-align: center;
line-height: 90rpx;
margin: 76rpx auto 0 auto;
background: #3875EA;
border-radius: 45rpx;
}
.personal-data .logOut {
font-size: 32rpx;
text-align: center;
width: 690rpx;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
margin: 30rpx auto 0 auto;
border: 1rpx solid #3875EA;
color: #3875EA;
}
.avatar-box {
width: 96rpx;
height: 96rpx;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
</style>