crm-uniapp/pages/components/pages/customer/customerDetails.vue
2025-03-11 09:41:24 +08:00

1040 lines
26 KiB
Vue

<template>
<uv-navbar :fixed="false" @leftClick="$onClickLeft" bgColor="#09b4f1">
<template v-slot:left>
<uv-icon name="arrow-left" size="19" color="#ffffff"></uv-icon>
</template>
<template v-slot:center>
<text style="color:#ffffff">{{title}}</text>
</template>
</uv-navbar>
<view class="container">
<view class="bare">
<!-- 顶部导航高度 -->
<view class="statusBar"></view>
</view>
<view class="notice">
<view class="belong">
<view class="left flex-1">
<view class="store">{{customer.name}}</view>
</view>
<view class="flex align-center">
<uv-text v-if="customer.dealStatus === 1" color="#84bd00" suffixIcon="arrow-right"
iconStyle="font-size: 16px;color:#84bd00" text="成交"></uv-text>
<uv-text v-else color="#f9ae3d" suffixIcon="arrow-right" iconStyle="font-size: 16px;color:#f9ae3d"
text="未成交"></uv-text>
</view>
</view>
<!-- 标签 -->
<view class="tap" v-if="customer.tags !='' ">
<view v-for="(item,i) in customer.tags" :key="i" style="margin-right: 10rpx;">
<uv-tags :text="item" plain type="warning" size="mini" />
</view>
</view>
<view class="relation flex">
<view class="left">手机:<text class="dial" @click="call(customer.mobile)">{{customer.mobile}}</text></view>
</view>
<view class="bottom flex">
<view class="client_time">最后跟进:{{timeFormats(customer.followTime)}}</view>
<view class="right">{{customer.ownUserName ? customer.ownUserName : '--'}}</view>
</view>
<view class="flex align-center " style="margin-top: 18rpx;">
<view style="margin-right: 10rpx;">
<uv-tags :text="'总消费:' + purchaseTotalFormat " type="warning"></uv-tags>
</view>
<view>
<uv-tags :text="'下单:' + customer.purchaseTimes" type="success"></uv-tags>
</view>
</view>
<view class="more flex" @click="onLookMore">查看更多<uv-icon name="arrow-down" size="16"></uv-icon></view>
</view>
<!-- 工具项 -->
<view class="region mt-3">
<view class="border-bottom">
<uv-tabs :list="tapList" :scrollable="false" :current="current" @change="change"></uv-tabs>
</view>
<scroll-view scroll-y class="sv" :style="{height:scrollHeight+'px'}" :scroll-top="scrollTop"
@scrolltolower="reachBottom">
<view class="page-box">
<!-- 跟进记录 -->
<block v-if="current == 0">
<block v-if="recordList.length > 0">
<view class="list-view" v-for="(item, index) in recordList" :key="index">
<view class="top">
<uv-parse class="left" :content="item.content"></uv-parse>
</view>
<view class="bottom">
<view class="time">{{timeFormats(item.createTime)}}</view>
<view class="way">{{item.recordTypeName}}</view>
</view>
</view>
<uv-load-more :status="listStatus" />
</block>
<uv-empty text="暂无数据" v-else margin-top="100" mode="list"></uv-empty>
</block>
<!-- 联系列表 -->
<block v-if="current == 1">
<block v-if="contactList.length > 0">
<view class="list-view" v-for="(item,index) in contactList" :key="index">
<view class="contact">
<view class="name">{{item.name}} {{item.mobile}}</view>
<view class="dial" @click="lookDetails(item.id)">查看</view>
</view>
</view>
<uv-load-more :status="contactStatus"></uv-load-more>
</block>
<uv-empty text="暂无数据" v-else margin-top="50" mode="list"></uv-empty>
</block>
<!-- 商机 -->
<block v-if="current == 2">
<block v-if="businessList.length > 0">
<view class="list-view" v-for="(item,index) in businessList" :key="index" @click="lookDetails(item.id)">
<view class="top">
<view class="left flex-1">{{item.name}}</view>
<view class="right flex">
<uv-text v-if="item.isEnd === 1" type="success" text="成交"></uv-text>
<uv-text v-else-if="item.isEnd === 2" type="error" text="失败"></uv-text>
<uv-text v-else-if="item.isEnd === 3" type="info" text="无效"></uv-text>
<uv-text v-else type="warning" text="洽谈中"></uv-text>
<uv-icon name="arrow-right" :size="16"></uv-icon>
</view>
</view>
<view class="item">
<view class="content">
<view class="title uv-line-2" style="color: #f85a40;">
<text>¥{{item.money}}</text>
</view>
<view class="type">备注: {{item.remark}}</view>
</view>
</view>
<view class="bottom">
<view class="time">预计成交:{{timeFormats(item.dealTime)}}</view>
</view>
</view>
<uv-load-more :status="businessStatus"></uv-load-more>
</block>
<uv-empty text="暂无数据" v-else margin-top="50" mode="list"></uv-empty>
</block>
<!-- 合同 -->
<block v-if="current == 3">
<block v-if="htList.length > 0">
<view class="list-view" v-for="(item,index) in htList" :key="index" @click="lookDetails(item.id)">
<view class="top">
<view class="left flex-1">{{item.name}}</view>
<view class="right flex">
<uv-text v-if="item.checkStatus === 1" type="warning" text="审核中"></uv-text>
<uv-text v-else-if="item.checkStatus === 2" type="success" text="审核通过"></uv-text>
<uv-text v-else-if="item.checkStatus === 3" type="info" text="审核未通过"></uv-text>
<uv-text v-else type="error" text="待审核"></uv-text>
<uv-icon name="arrow-right" :size="16"></uv-icon>
</view>
</view>
<view class="item">
<view class="content">
<view class="title uv-line-2" style="color: #f85a40;">
<text class="pr-4">金额:¥{{item.money}}</text>
<text>回款:¥{{item.returnMoney}}</text>
</view>
<view class="flex">
<view class="type flex-1 mr-2">备注: {{item.remark ? item.remark : '--'}}</view>
</view>
</view>
</view>
<view class="bottom">
<view class="time">始止时间:{{ timeFormats(item.startTime) + '~' + timeFormats(item.endTime)}}</view>
</view>
</view>
<uv-load-more :status="listStatusHt"></uv-load-more>
</block>
<uv-empty text="暂无数据" v-else margin-top="50" mode="list"></uv-empty>
</block>
<!-- 回款 -->
<block v-if="current == 4">
<block v-if="hkList.length > 0">
<view class="list-view" v-for="(item,index) in hkList" :key="index" @click="lookDetails(item.id)">
<view class="top">
<view class="left flex-1">{{item.number}}</view>
<view class="right flex">
<uv-text v-if="item.checkStatus === 1" color="#fbb034" suffixIcon="arrow-right"
iconStyle="font-size: 18px;color:#fbb034" text="审核中"></uv-text>
<uv-text v-else-if="item.checkStatus === 2" color="#7ac143" suffixIcon="arrow-right"
iconStyle="font-size: 18px;color:#7ac143" text="审核通过"></uv-text>
<uv-text v-else-if="item.checkStatus === 3" color="#f85a40" suffixIcon="arrow-right"
iconStyle="font-size: 18px;color:#f85a40" text="审核未通过"></uv-text>
<uv-text v-else color="#00bce4" suffixIcon="arrow-right" iconStyle="font-size: 18px;color:#00bce4"
text="待审核"></uv-text>
</view>
</view>
<view class="item">
<view class="content flex">
<view class="type flex-1 mr-2">备注: {{item.remark ? item.remark : '--'}}</view>
</view>
</view>
<view class="bottom">
<view class="" style="color: #FF0000;">¥{{item.money}}</view>
<view class="time">提交日期:{{timeFormats(item.createTime)}}</view>
</view>
</view>
<uv-load-more :status="listStatusHk"></uv-load-more>
</block>
<uv-empty text="暂无数据" v-else margin-top="50" mode="list"></uv-empty>
</block>
</view>
</scroll-view>
</view>
<!-- 上拉菜单 -->
<view class="popup-content">
<uv-popup mode="bottom" round="38" ref="moreShowRef">
<view class="popup-title">
<view class="" @click="look()" style="width: 45px;">
</view>
<text class="">更多操作</text>
<view class="" @click="moreShowClose" style="width: 45px;">
<uv-icon name="close" color="#909399" size="20"></uv-icon>
</view>
</view>
<view class="" style="height: 300rpx;">
<uv-grid :col="4">
<uv-grid-item v-for="(item,index) in moreList" @click="moreClick(item.text)" :key="index">
<uv-icon :name="item.icon" :size="26"></uv-icon>
<view class="grid-text">{{item.text}}</view>
</uv-grid-item>
</uv-grid>
</view>
</uv-popup>
</view>
<!-- 底部按钮 -->
<view class="bottom-btn">
<view style="margin-right: 20rpx;">
<uv-button type="info" text="更多" customStyle="padding:0 50rpx;background-color:#09b4f1;color:#fff"
@click="moreShow">更多</uv-button>
</view>
<view>
<uv-button type="success" text="跟进" customStyle="padding:0 50rpx;" @click="onAdd">跟进</uv-button>
</view>
</view>
<ba-tree-picker ref="treePickerRef" :multiple='true' @select-change="userSelectChange" title="选择员工"
:localdata="userTreeList" valueKey="userId" textKey="nickname" childrenKey="items" confirmColor="#09b4f1" />
</view>
</template>
<script setup>
import {
ref,
unref,
computed
} from 'vue'
import {
onLoad,
onShow,
onReady
} from '@dcloudio/uni-app'
import {
getDictData,
getCustomer,
getCustomerDoOpen,
getRecordPage,
delCustomer,
getContactsPage,
transferCustomer,
getUserTreeList
} from '@/api/customer'
import {
getBusinessPage
} from '@/api/business'
import {
getContractPage
} from '@/api/contract'
import {
receivablesPage
} from '@/api/receivables'
import {
formatDateTime,
prePage
} from '@/utils/util'
import baTreePicker from "@/components/ba-tree-picker/ba-tree-picker.vue"
const title = ref('客户详情')
const customerId = ref(0)
const customer = ref({
purchaseTotal: 0,
})
const scrollTop = ref('')
const tapList = ref([{
name: '跟进'
},
{
name: '联系人'
},
{
name: '商机',
},
{
name: '合同',
},
{
name: '回款',
}
])
const moreList = ref([{
text: '编辑客户',
icon: 'edit-pen',
show: true,
},
{
text: '放入公海',
icon: 'trash',
show: true,
},
{
text: '转移客户',
icon: 'reload',
show: true,
},
{
text: '删除客户',
icon: 'trash',
show: true,
},
{
text: '添加联系',
icon: 'plus',
show: true,
}
])
const recordList = ref([])
const contactList = ref([])
const businessList = ref([])
const selectList = ref([])
const htList = ref([])
const hkList = ref([])
const current = ref(0)
const navbar = ref(false)
const pH = ref(0)
const scrollHeight = ref(0)
const page = ref(1)
const pageSize = ref(10)
const lastPage = ref(false)
const listStatus = ref('loadmore')
const cPage = ref(1)
const lastContact = ref(false)
const contactStatus = ref('loadmore')
const Bpage = ref(1)
const lastBusiness = ref(false)
const businessStatus = ref('loadmore')
const pageHt = ref(1)
const lastHt = ref(false)
const listStatusHt = ref('loadmore')
const pageHk = ref(0)
const lastHk = ref(false)
const listStatusHk = ref('loadmore')
onReady(() => {
uni.getSystemInfo({
success(res) {
pH.value = res.windowHeight
let scrollH = uni.createSelectorQuery().select(".sv")
scrollH.boundingClientRect(data => {
let pH0 = pH.value
scrollHeight.value = pH0 - data.top - 80
}).exec()
}
})
})
onLoad((e) => {
customerId.value = e.id
getData()
getUserList()
})
// 树形选人员
const userTreeList = ref([])
const getUserList = () => {
getUserTreeList().then(res => {
userTreeList.value = res
})
}
const treePickerRef = ref()
const userSelectChange = async (userIds) => {
let data = await transferCustomer({
customerIds: [customerId.value],
ownerAdminIds: userIds
})
moreShowClose()
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
}
onShow(() => {
getData()
switch (current.value) {
case 0:
page.value = 1
lastPage.value = false
getCustomerRecord()
break;
case 1:
cPage.value = 1
lastContact.value = false
getContactList()
break;
case 2:
Bpage.value = 1
lastBusiness.value = false
getBusinessList()
break;
case 3:
pageHt.value = 1
lastHt.value = false
getContractList()
break;
case 4:
pageHk.value = 1
lastHk.value = false
getReceivables()
break;
default:
break;
}
})
const purchaseTotalFormat = computed(() => {
if (customer.value.purchaseTotal) {
let num;
if (customer.value.purchaseTotal > 9999) {
num = (Math.floor(customer.value.purchaseTotal / 100) / 100) + '万';
} else if (customer.value.purchaseTotal <= 9999 && customer.value.purchaseTotal > -9999) {
num = customer.value.purchaseTotal
}
return num;
} else {
return 0;
}
})
// 查看更多
const onLookMore = () => {
uni.navigateTo({
url: '/pages/components/pages/customer/detail?id=' + customerId.value
});
}
// 拨打电话
const call = (p) => {
uni.makePhoneCall({
phoneNumber: p
});
}
const moreShowRef = ref()
const moreShow = () => {
unref(moreShowRef).open()
}
const moreShowClose = () => {
unref(moreShowRef).close()
}
// 更多操作
const moreClick = async (index) => {
switch (index) {
case "编辑客户":
// 编辑客户
uni.navigateTo({
url: '/pages/components/pages/customer/add?id=' + customer.value.id + '&type=edit'
});
break;
case "放入公海":
uni.showModal({
title: '提示',
content: '确定将用户放入公海吗?',
success: async function(res) {
if (res.confirm) {
await getCustomerDoOpen({
id: customerId.value
})
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
if (prePage().route == 'pages/customer/index') {
prePage().$vm.isRefresh = true
}
setTimeout(() => {
uni.navigateBack()
}, 1000)
}
}
})
break;
case "转移客户":
moreShowClose()
treePickerRef.value._show()
break;
case "删除客户":
unref(moreShowRef).close()
uni.showModal({
title: '提示',
content: '确定删除该客户吗?',
success: async function(res) {
if (res.confirm) {
await delCustomer({
id: customerId.value
})
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
if (prePage().route == 'pages/customer/index') {
prePage().$vm.isRefresh = true
}
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
});
break;
case "添加联系":
uni.navigateTo({
url: '/pages/components/pages/contacts/addPerson?id=' + customer.value.id + '&name=' + customer.value
.name
});
break;
default:
break;
}
}
// 查看详情
const lookDetails = (val) => {
switch (current.value) {
case 0:
break;
case 1:
uni.navigateTo({
url: '/pages/components/pages/contacts/detail?id=' + val
})
break;
case 2:
uni.navigateTo({
url: '/pages/components/pages/business/businessDetails?id=' + val
});
break;
case 3:
uni.navigateTo({
url: '/pages/components/pages/contract/add?id=' + val + '&type=edit'
});
break;
case 4:
uni.navigateTo({
url: '/pages/components/pages/receivables/add?id=' + val + '&type=edit'
});
break;
default:
break;
}
}
// 格式化时间
const timeFormats = (val) => {
if (val) {
return formatDateTime(val)
} else {
return '--'
}
}
// 获取客户详情
const getData = async () => {
let data = await getCustomer({
id: customerId.value
})
data.tags = data.tags.split(',')
customer.value = data
}
// 获取客户跟进记录
const getCustomerRecord = async (isNextPage, pages) => {
let data = await getRecordPage({
pageNo: page.value,
pageSize: pageSize.value,
types: 'customer',
typesId: customerId.value
})
// 不够一页
if (data.list.length < 10) {
listStatus.value = 'nomore'
}
// 最后一页
if (data.list.length == 0) {
lastPage.value = true
}
// 第二页开始
if (isNextPage) {
recordList.value = recordList.value.concat(data.list)
return
}
recordList.value = data.list
console.log('recordList.value:', recordList.value)
}
// 获取联系人
const getContactList = async (isNextPage, pages) => {
await getContactsPage({
pageNo: cPage.value,
pageSize: pageSize.value,
customerId: customerId.value
}).then(res => {
if (res) {
if (res.list.length < 10) {
contactStatus.value = 'nomore'
}
if (res.list.length == 0) {
lastContact.value = true
}
if (isNextPage) {
contactList.value = contactList.value.concat(res.list)
return
}
contactList.value = res.list
}
})
}
// 获取商机
const getBusinessList = async (isNextPage, pages) => {
await getBusinessPage({
pageNo: Bpage.value,
pageSize: pageSize.value,
customerId: customerId.value
}).then(res => {
if (res) {
if (res.list.length < 10) {
businessStatus.value = 'nomore'
}
if (res.list.length == 0) {
lastBusiness.value = true
}
if (isNextPage) {
businessList.value = businessList.value.concat(res.list)
return
}
businessList.value = res.list
}
})
}
// 获取合同
const getContractList = async (isNextPage, pages) => {
await getContractPage({
pageNo: pageHt.value,
pageSize: pageSize.value,
customerId: customerId.value
}).then(res => {
if (res) {
if (res.list.length < 10) {
listStatusHt.value = 'nomore'
}
if (res.list.length == 0) {
lastHt.value = true
}
if (isNextPage) {
htList.value = htList.value.concat(res.list)
return
}
htList.value = res.list
}
})
}
// 获取回款
const getReceivables = async (isNextPage, pages) => {
await receivablesPage({
pageNo: pageHk.value,
pageSize: pageSize.value,
customerId: customerId.value
}).then(res => {
if (res) {
if (res.list < 10) {
listStatusHk.value = 'nomore'
}
if (res.list.length == 0) {
lastHk.value = true
}
if (isNextPage) {
hkList.value = hkList.value.concat(res.list)
return
}
hkList.value = res.list
}
})
}
// 滚动到底部
const reachBottom = () => {
switch (current.value) {
case 0:
if (lastPage.value || listStatus.value == 'loading') return;
listStatus.value = 'loading'
setTimeout(() => {
if (lastPage.value) return;
getCustomerRecord(true, ++page.value)
if (recordList.value.length >= 10) listStatus.value = 'loadmore';
else listStatus.value = 'loading';
}, 1200)
break;
case 1:
if (lastContact.value || contactStatus.value == 'loading') return;
contactStatus.value = 'loading'
setTimeout(() => {
if (lastContact.value) return;
getContactList(true, ++cPage.value)
if (contactList.value.length >= 10) contactStatus.value = 'loadmore';
else contactStatus.value = 'loading';
}, 1200)
break;
case 2:
if (lastBusiness.value || businessStatus.value == 'loading') return;
businessStatus.value = 'loading'
setTimeout(() => {
if (lastBusiness.value) return;
getBusinessList(true, ++Bpage.value)
if (businessList.value.length >= 10) businessStatus.value = 'loadmore';
else businessStatus.value = 'loading';
}, 1200)
break;
case 3:
if (lastHt.value || listStatusHt.value == 'loading') return;
istStatusHt.value = 'loading'
setTimeout(() => {
if (lastHt.value) return;
getContractList(true, ++pageHt.value)
if (htList.value.length >= 10) listStatusHt.value = 'loadmore';
else listStatusHt.value = 'loading';
}, 1200)
break;
case 4:
if (lastHk.value || listStatusHk.value == 'loading') return;
listStatusHk.value = 'loading'
setTimeout(() => {
if (lastHk.value) return;
getReceivables(true, ++pageHk.value)
if (hkList.value.length >= 10) listStatusHk.value = 'loadmore';
else listStatusHk.value = 'loading';
}, 1200)
break;
default:
return '--'
break;
}
}
// 切换导航栏
const change = (e) => {
current.value = e.index
switch (e.index) {
case 0:
page.value = 1
lastPage.value = false
getCustomerRecord()
break;
case 1:
cPage.value = 1
lastContact.value = false
getContactList()
break;
case 2:
Bpage.value = 1
lastBusiness.value = false
getBusinessList()
break;
case 3:
pageHt.value = 1
lastHt.value = false
getContractList()
break;
case 4:
pageHk.value = 1
lastHk.value = false
getReceivables()
break;
default:
break;
}
}
// 商机跟进
const follow = (id, name) => {
uni.navigateTo({
url: '/pages/components/pages/customer/followUp?customerId=' + id + '&customerName=' + name
})
}
// 跟进客户
const onAdd = (e) => {
uni.navigateTo({
url: '/pages/components/pages/customer/followUp?customerId=' + customer.value.id + '&customerName=' +
customer.value.name
})
}
</script>
<style lang="scss" scoped>
.container {
background-color: #F7F7F7 !important;
}
.bare {
color: #fff;
background-color: #09b4f1;
}
.notice {
margin: -68px 10px 10px 10px;
left: 0;
right: 0;
background-color: #fff;
border-radius: 10px;
padding: 30rpx 24rpx 35rpx;
.belong {
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
padding-right: 25rpx;
.store {
font-size: 30rpx;
font-weight: bold;
word-break: break-all;
}
}
.right {
color: #FF7159;
}
.right1 {
color: #FF7159;
}
}
.tap {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 20rpx 0 0;
}
.relation {
justify-content: space-between;
margin-top: 18rpx;
font-size: 26rpx;
.dial {
color: #2979ff;
font-size: 14px;
border-bottom: 1px solid #2979ff;
padding-bottom: 0px;
}
}
.bottom {
margin-top: 25rpx;
justify-content: space-between;
.client_time {
color: #777;
font-size: 26rpx;
}
}
.more {
padding-top: 25rpx;
justify-content: center;
color: #2979ff;
font-size: 28rpx;
}
.details {
padding: 45rpx 0rpx 0rpx;
.item {
flex: 1;
margin-bottom: 25rpx;
.text {
font-size: 25rpx;
color: #777;
}
}
}
}
.statusBar {
position: relative;
z-index: -1;
height: 188rpx;
}
.page-box {
padding: 20rpx 20rpx 45rpx;
}
.list-view {
width: 710rpx;
background-color: #ffffff;
margin-bottom: 20rpx;
border-radius: 20rpx;
box-sizing: border-box;
padding: 24rpx;
font-size: 28rpx;
.top {
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
word-break: break-all;
width: 100%;
.store {
margin: 0 10rpx;
font-size: 28rpx;
font-weight: bold;
}
}
.right {
color: #F76046;
}
}
.item {
display: flex;
margin: 20rpx 0 0;
.left {
margin-right: 20rpx;
.image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
}
.video {
width: 140rpx;
height: 140rpx;
}
}
.content {
flex: 1;
.title {
font-size: 28rpx;
line-height: 50rpx;
}
.type {
margin: 20rpx 0;
font-size: 26rpx;
color: $uv-tips-color;
}
.delivery-time {
color: #e5d001;
font-size: 24rpx;
}
}
}
.contact {
padding: 25rpx 0;
display: flex;
justify-content: space-between;
.name {}
.dial {
color: #2979ff;
font-size: 14px;
border-bottom: 1px solid #2979ff;
padding-bottom: 0px;
}
}
.bottom {
display: flex;
margin-top: 10rpx;
justify-content: space-between;
align-items: center;
.time {
color: #777;
font-size: 26rpx;
}
.btn {
line-height: 60rpx;
width: 160rpx;
border-radius: 5px;
font-size: 26rpx;
text-align: center;
color: $uv-primary-dark;
}
.sky {
color: #FF6146;
background-color: #F7F7F7;
}
.entity {
color: #fff;
background-color: #09b4f1;
}
}
}
.bottom-btn {
position: fixed;
display: flex;
align-items: center;
justify-content: flex-end;
bottom: 0;
left: 0;
right: 0;
padding: 25rpx 30rpx 45rpx;
background-color: #fff;
z-index: 100;
box-sizing: border-box;
}
.popup-content {
.popup-title {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
font-size: 35rpx;
font-weight: 600;
text-align: center;
height: 50px;
padding-right: 25rpx;
}
.grid-text {
font-size: 28rpx;
margin-top: 4rpx;
margin-bottom: 20rpx;
}
}
</style>