Files
crm-uni/pages/customer/followup.vue
T
浅念 5e8e44f88f Squashed commit of the following:
commit f9e9899e0e
Author: 浅念 <email@sample.com>
Date:   Fri Mar 28 11:10:56 2025 +0800

    设置默认语言为英语

commit 303121ba81
Author: 浅念 <email@sample.com>
Date:   Fri Mar 28 11:00:05 2025 +0800

    调用接口调整

commit 37e0e55d96
Author: 浅念 <email@sample.com>
Date:   Fri Mar 28 10:33:48 2025 +0800

    线上接口地址

commit 11dd7de521
Author: 浅念 <email@sample.com>
Date:   Thu Mar 27 16:54:52 2025 +0800

    修改样式

commit 8ceb98b321
Author: 浅念 <email@sample.com>
Date:   Thu Mar 27 16:47:04 2025 +0800

    隐藏我的名片

commit f58e712684
Author: 浅念 <email@sample.com>
Date:   Thu Mar 27 16:44:43 2025 +0800

    样式调整

commit 54ad6c9e37
Author: 浅念 <email@sample.com>
Date:   Thu Mar 27 16:35:21 2025 +0800

    修改语言包

commit 526f934a75
Author: 浅念 <email@sample.com>
Date:   Thu Mar 27 16:13:47 2025 +0800

    标签功能

commit 8583ff2543
Author: 浅念 <email@sample.com>
Date:   Wed Mar 26 17:02:04 2025 +0800

    标签管理页面

commit be8674fd51
Author: 浅念 <email@sample.com>
Date:   Tue Mar 25 17:29:37 2025 +0800

    样式调整

commit a5d5ce4e7c
Author: 浅念 <email@sample.com>
Date:   Tue Mar 25 17:23:40 2025 +0800

    倒计时组件

commit f88048b876
Author: 浅念 <email@sample.com>
Date:   Tue Mar 25 17:19:43 2025 +0800

    首页添加跟进提醒

commit f9a39d5aaa
Author: 浅念 <email@sample.com>
Date:   Tue Mar 25 13:13:39 2025 +0800

    提交

commit e3abe0bf18
Author: 浅念 <email@sample.com>
Date:   Tue Mar 25 13:12:24 2025 +0800

    名片部分页面。地图定位。国家下拉搜索  回收站
2025-03-28 14:46:49 +08:00

784 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_txt}}</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:(datas.follow == 0 ?'#3471FF' : datas.follow == 1 ?'#27C89C': '#FF4040')}">{{datas.follow_txt}}</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="btn1" @click="shownexttime=true">{{$t('customer.NextReminder')}}</button> -->
<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"
@confirm="confirm"
@change="change"
@cancel="cancel('show')"
@close="close('show')"
></u-datetime-picker>
<u-datetime-picker
:show="shownexttime"
closeOnClickOverlay
v-model="nexttime"
mode="datetime"
@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: 32rpx;
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-size: 32rpx;
font-weight: 500;
font-size: 32rpx;
color: #666666;
.item{
width: 90px;
}
}
.arrowright{
margin-right: -15px;
}
.cell1{
font-size: 32rpx;
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: 96rpx;
line-height: 96rpx;
margin-top: 20px;
}
.btn1{
font-size: 28rpx;
border: 1px solid #3471FF;
background: #fff;
color: #3471FF;
border-radius: 50px;
width: 45%;
height: 96rpx;
line-height: 96rpx;
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>