Files
crm-uni/pages/customer/followup.vue
T
2025-04-08 09:30:48 +08:00

775 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" :title="$t('navbar.addfollowup')"></uni-nav-bar>
<view class="contents" >
<view class="top">
<view class="name">{{userdatas.contacts}}</view>
<view class="right">
<text class="medal" :style="{color:(userdatas.magnitude == 0 ?'#F5AB00' : userdatas.magnitude == 1 ?'#8495B8': '#9B7200')}">{{userdatas.magnitude_txt}}</text>
<text class="tag" :style="{background:(userdatas.follow == 0 ?'#3471FF' : userdatas.follow == 1 ?'#27C89C': '#FF4040')}">{{userdatas.follow==0?$t('followup'):userdatas.follow==1?$t('deal'):$t('lost')}}</text>
</view>
</view>
<u-line margin="20rpx 0px" color="#fff" :hairline="false"></u-line>
<view class="cont">
<view class="title">
<view class=""><text class="name">{{userdatas.phone}}</text></view>
<view class="ststus">{{$t('channel')}}:{{userdatas.channel_name}}</view>
</view>
<view class="info"><text class="sex">{{userdatas.sex_txt}}</text> | <text class="addr">{{userdatas.country_name}}</text><text class="type">{{userdatas.car_type_name}}</text>
</view>
<view v-if="userdatas.email!=''">Email: {{userdatas.email}}</view>
<view v-if="userdatas.WhatsApp!=''">WhatsAPP: {{userdatas.WhatsApp}}</view>
<view v-if="userdatas.address!=''">{{$t('customer.Company')}}: {{userdatas.address}}</view>
<view v-if="userdatas.describe!=''">{{$t('remarks')}}: {{userdatas.describe}}</view>
</view>
</view>
<view class="cell2">
<view class="item">{{$t('customer.Cstomerfj')}}</view>
<view class="right select" @click="showgrade = true">
<view class="" :class="['content2', { placeholder: !grade_name }]">{{grade_name || $t('customer.Cstomerfjinput')}}</view>
<u-icon class="arrowright" name="arrow-right" size="18" color="#747474"></u-icon>
</view>
</view>
<u-gap height="10" bgColor="#F6F6F6"></u-gap>
<view class="content">
<view class="cell">
<view class="item">{{$t('customer.followtype')}}</view>
<view class="right">
<u-radio-group v-model="form.type">
<u-radio name="1" class="u-m-lr-10">{{$t('customer.Online')}}</u-radio>
<u-radio name="0">{{$t('customer.Visit')}}</u-radio>
</u-radio-group>
</view>
</view>
<u-line></u-line>
<view class="cell" @click="show = true" >
<view class="item">{{$t('customer.followtime')}} </view>
<view class="right" >
<!-- <u-input v-model="form.firstFollowtime" :border="false" /> -->
<view class="">{{form.firstFollowtime || $t('customer.followtime')}}</view>
<!-- <input :placeholder="$t('customer.followtime')" v-model="form.firstFollowtime" disabled /> -->
</view>
</view>
<u-line></u-line>
<view class="cell1" v-if="form.type==1">
<view>{{$t('customer.updataimg')}}</view>
<view style="margin-top: 10px;">
<uni-file-picker fileMediatype="image" limit="1" :del-icon="false" disable-preview @select="select" @delete="delfn('picture')">
<image src="/static/image/customer/upimg.png" mode="" class="addimg"></image>
</uni-file-picker>
</view>
</view>
<view class="cell" v-if="form.type==0">
<view class="item">{{$t('customer.followlocation')}}</view>
<view class="right">
<input :placeholder="$t('customer.followlocation')" disabled v-model="form.address" />
</view>
</view>
<u-line v-if="form.type==0"></u-line>
<view class="cell1" v-if="form.type==0">
<view>{{$t('customer.locationimg')}}</view>
<view style="margin-top: 10px;">
<uni-file-picker fileMediatype="image" limit="1" :del-icon="false" disable-preview @select="select1" @delete="delfn('picture')">
<image src="/static/image/customer/upimg.png" mode="" class="addimg"></image>
</uni-file-picker>
</view>
</view>
<u-line v-if="form.type==0"></u-line>
<view class="cell1" v-if="form.type==0">
<view>{{$t('customer.Visitcontent')}}</view>
<view class="yuyin">
<textarea auto-height v-model="form.audio_text" :placeholder="$t('customer.Visiinputhd')"/>
<!-- <image src="/static/image/customer/yuyin.png" mode="" @tap="startRecognizebd"></image> -->
</view>
</view>
<u-line v-if="form.type==0"></u-line>
<view class="cell1" v-if="form.type==0">
<view>{{$t('customer.Remarks')}}</view>
<view class="yuyin">
<textarea auto-height v-model="form.remark" :placeholder="$t('customer.Remarksinputhd')"/>
<!-- <image src="/static/image/customer/yuyin.png" mode="" @tap="startRecognize"></image> -->
</view>
</view>
</view>
<u-gap height="10" bgColor="#F6F6F6"></u-gap>
<view class="steps">
<view class="title">
{{$t('customer.followrecord')}} <text class="status" :style="{color:(userdatas.follow == 0 ?'#3471FF' : userdatas.follow == 1 ?'#27C89C': '#FF4040')}">{{userdatas.follow==0?$t('followup'):userdatas.follow==1?$t('deal'):$t('lost')}}</text>
</view>
<u-steps v-if="followList.length>0" :current="followList.length" direction="column" dot>
<u-steps-item :title="item.type==1? $t('customer.Online') : $t('customer.Visit') " v-for="(item, index) in followList" :key="index">
<view slot="desc" class="desc">
<view class="flex" v-if="item.type==0">
<view class="">
<view class="lingh">
{{$t('times')}} :{{item.created_at}}
</view>
<view class="">{{$t('address')}}:{{item.address}}</view>
<view class="lingh">{{$t('customer.Visitcontent')}}:{{item.audio_text}}</view>
</view>
<view class="">
<!-- <image :src="item.oss_picture" mode=""></image> -->
<u-album
:urls="isurls(item.oss_picture)"
:singleSize="80"
keyName="src"
></u-album>
</view>
</view>
<view class="" v-else>
<view class="lingh">
{{$t('times')}}:{{item.created_at}}
</view>
<view class="">
<!-- <image :src="item.oss_picture" mode=""></image> -->
<u-album
:urls="isurls(item.oss_picture)"
:singleSize="80"
keyName="src"
></u-album>
</view>
</view>
</view>
</u-steps-item>
</u-steps>
<empty :list="followList"></empty>
</view>
<u-gap height="100" bgColor="#fff"></u-gap>
<view class="foot">
<button class="btn" @click="nextFn">{{$t('submit')}}</button>
</view>
<u-picker closeOnClickOverlay :cancel-text="$t('cancel')" :confirm-text="$t('confirm')" :defaultIndex="defaultIndexfj" :show="showgrade" @cancel="cancel('showgrade')" @close="close('showgrade')" :columns="getGradeList" keyName="name" @confirm="gradeHandler"></u-picker>
<u-datetime-picker
:show="show"
closeOnClickOverlay
v-model="firstFollow"
mode="datetime"
:cancel-text="$t('cancel')"
:confirm-text="$t('confirm')"
@confirm="confirm"
@change="change"
@cancel="cancel('show')"
@close="close('show')"
></u-datetime-picker>
<u-datetime-picker
:show="shownexttime"
closeOnClickOverlay
v-model="nexttime"
mode="datetime"
:cancel-text="$t('cancel')"
:confirm-text="$t('confirm')"
@confirm="confirmnext"
@cancel="cancelnext"
@close="close('shownexttime')"
></u-datetime-picker>
</view>
</template>
<script>
import permision from "@/common/permission.js"
import { request,showToast } from '@/api/apiT.js'
export default {
data() {
return {
id:'',
show:false,
showgrade:false,
shownexttime:false,
firstFollow: Number(new Date()),
nexttime: Number(new Date()),
followList:[],
getGradeList1:[],
getGradeList:[],
grade_name:'',
defaultIndexfj:[0],
grade:'',
form: {
customer_id:'',
type: '1',
address: '',
lat: '',
lng: '',
firstFollowtime:'',
audio_text:'',
remark:'',
picture:''
},
datas:'',
userdatas:''
}
},
onLoad(options) {
console.log(options);
this.getlocahost()
this.id = options.id;
this.form.customer_id = options.id;
var that = this
this.getGrade()
this.customerdetailfn()
this.followlistfn()
},
methods: {
leftClick(){
uni.navigateBack({})
},
isurls(url){
var urls=[]
var obj={}
obj.src = url
urls.push(obj)
return urls
},
close(val){
this[val]=false
},
selectgd(){
var that=this;
that.getGradeList1.forEach((item,index) => {
if(item.id == that.userdatas.grade){
that.grade_name = item.name
that.defaultIndexfj=[index]
}
})
},
//客户等级
getGrade(){
request({
url:'customer/getGrade',
method:'get',
}).then(res => {
var list = res.data.data
this.getGradeList = [list]
this.getGradeList1 = list
// this.selectgd()
})
},
//客户等级切换事件
gradeHandler(val){
this.grade = val.value[0].id
this.grade_name = val.value[0].name
this.showgrade = false
request({
url:'customer/grade',
method:'post',
data:{
id:this.id,
grade:this.grade
}
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
})
},
cancel(val){
this[val] = false
},
close(val){
this[val] = false
},
//客户详情
customerdetailfn(){
var that=this;
request({
url:'customer/detail',
method:'get',
data: {
id:this.id,
}
}).then(res => {
that.userdatas = res.data.data
that.selectgd()
})
},
// 获取当前位置
getarrd(longitude,latitude){
const apiKey = 'AIzaSyBGvV-vX7C72PEbcYrTdmUj3qrubejWzNw';
uni.request({
url: `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${apiKey}`,
success: (res) => {
if (res.data.results && res.data.results.length > 0) {
this.form.address = res.data.results[0].formatted_address; // 获取格式化地址
console.log(res.data.results[0].formatted_address)
} else {
console.error('未找到地址');
}
},
fail: (err) => {
console.error('请求失败:', err);
},
});
},
unigetLocation(){
var that = this
uni.getLocation({
type: 'wgs84', // 默认为 wgs84 坐标系,可选 gcj02
success: function (res) {
console.log('当前位置的经度:' + res.longitude);
console.log('当前位置的纬度:' + res.latitude);
that.getarrd(res.longitude,res.latitude)
// 如果需要将经纬度转换为具体地址,可以使用逆地理编码
// 这里调用 uni.chooseLocation 或第三方地图服务(如高德、腾讯地图)
},
fail: function (err) {
console.log('获取位置失败:', err);
},
complete: function () {
console.log('获取位置完成');
}
});
},
getlocahost() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('当前位置的经度:' + position.coords.longitude);
console.log('当前位置的纬度:' + position.coords.latitude);
this.getarrd(position.coords.longitude,position.coords.latitude)
this.error = '';
console.log("获取位置成功:", position);
},
(error) => {
this.unigetLocation()
// this.error = this.getErrorMessage(error);
// uni.showToast({
// title: this.error,
// icon: 'none'
// });
console.error("获取位置失败:", error);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
this.unigetLocation()
// this.error = "浏览器不支持 Geolocation API";
// uni.showToast({
// title: this.error,
// icon: 'none'
// });
console.error("浏览器不支持 Geolocation API");
}
},
getErrorMessage(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
return "用户拒绝了地理位置权限";
case error.POSITION_UNAVAILABLE:
return "无法获取当前位置";
case error.TIMEOUT:
return "获取位置超时";
default:
return "未知错误";
}
},
// 跟进记录
followlistfn(){
request({
url:'follow/list',
method:'get',
data: {
customer_id:this.id,
}
}).then(res => {
this.datas = res.data.data
this.followList = res.data.data
console.log(res.data.data)
})
},
//保存跟进
nextFn(){
request({
url:'follow/save',
method:'post',
data:this.form
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code ==0){
setTimeout(() => {
uni.navigateTo({
url: '/pages/customer/followsuccess?id='+this.id
});
}, 1000);
}
})
},
result(time, mode) {
const timeFormat = uni.$u.timeFormat
switch (mode) {
case 'datetime':
return timeFormat(time, 'yyyy-mm-dd hh:MM')
case 'date':
return timeFormat(time, 'yyyy-mm-dd')
case 'year-month':
return timeFormat(time, 'yyyy-mm')
case 'time':
return time
default:
return ''
}
},
confirm(e) {
this.show = false
this.form.firstFollowtime = this.result(e.value, e.mode)
console.log(this.result(e.value, e.mode))
},
cancelnext() {
this.shownexttime = false
},
confirmnext(e) {
this.shownexttime = false
this.nexttime = this.result(e.value, e.mode)
console.log(this.nexttime)
request({
url:'customer/followNext',
method:'post',
data:{
id:this.id,
follow_next:e.value
}
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code ==0){
setTimeout(() => {
uni.navigateBack({})
}, 1000);
}
})
},
change(e) {
// console.log('change', e)
},
delfn(name){
console.log(name)
that.carinfo[name] =''
},
uploadfn(imgUrl,name){
var that = this;
uni.uploadFile({
// url: 'http://api.crm.hnxinyouhe.com/upload',
url: 'http://219.157.255.213:9007/upload/uploadImage',
filePath: imgUrl,
name: 'files',
header:{'Authorization': 'Bearer ' + uni.getStorageSync('token')},
success: (uploadFileRes) => {
const path=JSON.parse(uploadFileRes.data)
if(path.code==0){
that.form[name] = path.data.path
console.log(that.form)
}else{
uni.showToast({
title: path.message,
icon: 'none'
});
}
}
});
},
select(e){
const tempFilePaths = e.tempFilePaths;
const imgUrl=tempFilePaths[0]
this.uploadfn(imgUrl,'picture')
},
select1(e){
const tempFilePaths = e.tempFilePaths;
const imgUrl=tempFilePaths[0]
this.uploadfn(imgUrl,'picture')
},
async startRecognizebd () {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
// TODO ios 在没有请求过权限之前无法得知是否有相关权限,这种状态下需要直接调用语音,会弹出正在识别的toast
var options = {};
var that = this;
options.engine = 'baidu';
that.form.audio_text = "";
plus.speech.startRecognize(options, function (s) {
console.log(s);
that.form.audio_text += s;
}, function (e) {
console.log("语音识别失败:" + e.message);
});
},
async startRecognize () {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
// TODO ios 在没有请求过权限之前无法得知是否有相关权限,这种状态下需要直接调用语音,会弹出正在识别的toast
var options = {};
var that = this;
that.form.remark = "";
options.engine = 'iFly';
options.punctuation = false; // 是否需要标点符号
options.timeout = 10 * 1000; //语音录入持续时长
plus.speech.startRecognize(options, function(s) {
console.log(s);
that.form.remark += s;
// 关闭语音
plus.speech.stopRecognize();
}, function (e) {
console.log("语音识别失败:" + e.message);
});
},
// #ifdef APP-PLUS
async checkPermission() {
let status = permision.isIOS ? await permision.requestIOS('record') :
await permision.requestAndroid('android.permission.RECORD_AUDIO');
if (status === null || status === 1) {
status = 1;
} else if (status === 2) {
uni.showModal({
content: "系统麦克风已关闭",
confirmText: "确定",
showCancel: false,
success: function(res) {
}
})
} else {
uni.showModal({
content: "需要麦克风权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
// #endif
}
}
</script>
<style lang="scss">
.u-m-lr-10{
margin: 0px 10px;
}
textarea {
min-height: 200rpx;// 设置默认高度 ,解决设置自适应高度之后高度无效问题
margin-top: 15px;
background-color: #F6F6F6;
padding: 15px;
border-radius: 10px;
}
.u-divider{
margin: 0px 0px !important;
}
.contents{
background-color:#F6F6F6;
border-radius: 10px;
margin: 0px 15px 10px;
.edt{
display: flex;
justify-content: center;
}
.btn{
background: linear-gradient(to right, #3471FF, #6776FF);
color: white;
text-align: center;
border-radius: 20px;
width: 100px;
margin-bottom: 10px;
padding: 4px 0px;
}
.top{
padding: 10px 15px 0px;
display: flex;
align-items: center;
justify-content: space-between;
.tag{
color: white;
border-radius: 20px 0px 0px 20px;
padding: 3px 10px;
margin-right: -15px;
}
.right{
display: flex;
align-items: center;
.medal{
margin-right: 10px;
}
}
}
.name{
font-size: 30rpx;
font-weight: bold;
margin-right: 10px;
}
.cont{
padding: 0px 15px 10px;
font-size: 24rpx;
.title{
display: flex;
align-items: center;
justify-content: space-between;
color: #222222;
}
.ststus{
color: #666666;
}
.info,.desc{
color: #78758E;
line-height: 26px;
}
.sex{
margin-right: 5px;
}
.addr{
margin-left: 5px;
}
.type{
margin-left: 5px;
color: #3471FF;
}
}
}
.content{
padding-top: 10px;
background: #FFFFFF;
// height: calc(100vh - 44px);
}
.cell2{
position: relative;
display: flex;
justify-content:space-between;
padding: 18px 20px;
align-items: center;
font-weight: 500;
margin: 0px 15px 10px;
.item{
width: 90px;
color: #666666;
}
background: #f6f6f6;
border-radius: 10px;
.right{
text-align: right;
}
.select{
display: flex;
position: relative;
margin-right: 10px;
}
}
.cell{
position: relative;
display: flex;
justify-content:space-between;
padding: 18px 20px;
align-items: center;
font-weight: 500;
font-size: 24rpx;
color: #666666;
.item{
width: 90px;
}
}
.arrowright{
margin-right: -15px;
}
.cell1{
font-size: 24rpx;
padding: 18px 20px;
align-items: center;
font-weight: 500;
color: #666666;
}
.right{
text-align: right;
color: #222222;
}
.foot{
position: fixed;
bottom: 0px;
width: 100%;
height: 90px;
background: #FFFFFF;
z-index: 1;
display: flex;
padding-top: 20px;
}
.btn{
font-size: 28rpx;
background: linear-gradient(to right, #3471FF, #6776FF);
color: white;
border-radius: 50px;
width: 93%;
height:88rpx;
line-height: 88rpx;
margin-top: 20px;
}
.addimg{
width: 110px;
height:110px;
}
.yuyin{
position: relative;
image{
width: 20px;
height:24px;
position: absolute;
right: 15px;
bottom: 10px;
}
}
.steps{
padding: 15px;
background: #fff;
image{
width: 44px;
height:44px;
}
.desc{
display: flex;
justify-content: space-between;
align-items: center;
color: #999999;
.lingh{
line-height: 40px;
}
.flex{
display: flex;
justify-content: space-between;
align-items: center;
}
}
.title{
color: #666666;
font-size: 32rpx;
margin-bottom: 15px;
.status{
font-size: 28rpx;
color: #3471FF;
margin-left: 10px;
}
}
}
</style>