You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

830 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view>
<cpn-navbar title="客户列表" :is-back="true"></cpn-navbar>
<view>
<view>
<u-button :custom-style="{'margin':'24rpx 24rpx 0 24rpx'}" type="warning" @click="refresh"></u-button>
</view>
<!-- 搜索同一行 -->
<view class="search-row">
<view class="search small">
<view class="input-content">
<u-input :clearable="false" :value="select.keyword" placeholder="关键词" height="30" :custom-style="inputStyle"
@input="searchInput"></u-input>
</view>
<view class="icon">
<u-icon name="search" size="46" color="#ABAEBE"></u-icon>
</view>
</view>
<view class="search small">
<view class="input-content">
<u-input :clearable="true" :value="select.village_name" placeholder="市/区/社区街道/镇/村" height="30" :custom-style="inputStyle"
@input="villageInput"></u-input>
</view>
<!-- <view class="icon">
<u-icon name="home" size="46" color="#ABAEBE"></u-icon>
</view> -->
</view>
</view>
<!-- 回访/生存状态 选择器同一行 -->
<view class="drop-down" style="padding: 0 24rpx;">
<view class="picker-row">
<view class="picker-item" @click="showCallbackPicker = true">
<text class="picker-label">回访</text>
<text class="picker-value">{{ getCallbackLabel(select.has_quality_callbacks) }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
<view class="picker-item" @click="showDeadPicker = true">
<text class="picker-label">状态</text>
<text class="picker-value">{{ getDeadLabel(select.is_dead) }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
</view>
</view>
<!-- 产品类型 选择器 -->
<view class="drop-down" style="padding: 0 24rpx; margin-top: 16rpx;">
<view class="picker-item full-width" @click="showProductTypePicker = true">
<text class="picker-label">产品类型</text>
<text class="picker-value">{{ getProductTypeLabel(select.product_type_id) }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
</view>
<!-- 护理列表 -->
<view class="nursing-list">
<view v-if="nursingList && nursingList.length > 0">
<view v-for="(item,index) in nursingList" :key="index" class="list-item">
<view class="top">
<view v-if="item.quality_callbacks_count>0" @click="pageTo('/package_sub/pages/quality/qualityHistory?customer_id='+item.id)" class="time">
回访次数 <text style="color:#2979ff">{{ item.quality_callbacks_count }}</text> 次</view>
<view v-else class="time">回访次数 {{ 0 }} 次</view>
<view class="status">
<view class="status-text">{{ item.product_type_name }}</view>
</view>
</view>
<view class="line"></view>
<view class="center">
<view class="avatar">
<u-image :src="item.sex === '男' ? vuex_male_img : vuex_female_img" height="104"
width="104" shape="circle"></u-image>
</view>
<view class="user-info">
<view class="name">{{item.name}}</view>
<view class="tel">
<view>
<u-icon name="phone" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{item.phone || '无'}}</view>
</view>
<view class="address">
<view>
<u-icon name="map" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{defaultAddress(item.customer_address).address || '无'}}</view>
</view>
<view class="age">
<view>
<u-icon name="calendar" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{getAgeByIdcard(item.idcard) || '-'}}岁</view>
</view>
<view class="dislevel">
<view>
<u-icon name="eye-off" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">失能等级{{ item.level_type_detail ? item.level_type_detail.value : '' }} {{ item.level_detail ? item.level_detail.value : '' }}</view>
</view>
<view class="contact">
<view>
<u-icon name="man-add-fill" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">联系人 {{ item.contact_name?item.contact_name:'' }}</view>
</view>
</view>
<view class="sex">
<view class="sex-text">
{{item.sex || '无'}}
</view>
</view>
</view>
<view class="bottom">
<view class="button-group">
<u-button style="width: 45%;margin: 10rpx auto 0;"
type="primary"
ripple
@click="pageTo('/package_sub/pages/addQuality/addQuality?customer_id='+item.id+'&customer_name='+item.name)">质控回访</u-button>
<u-button v-if="defaultAddress(item.customer_address).lat && defaultAddress(item.customer_address).lng"
style="width: 45%;margin: 10rpx auto 0;"
type="default"
ripple
@click="openMapNavigation(item.customer_address)">去这里</u-button>
</view>
</view>
</view>
</view>
<view v-else>
<u-empty mode="list" text="暂无记录"></u-empty>
</view>
</view>
</view>
<u-loadmore :status="status" :load-text='loadText' @loadmore='select.page++,getList()' />
<u-back-top :scroll-top="scrollTop"></u-back-top>
<!-- 底部弹出选择器 -->
<u-picker
v-model="showCallbackPicker"
mode="selector"
:range="callbackPickerData"
range-key="label"
@confirm="onCallbackConfirm"
@cancel="showCallbackPicker = false"
></u-picker>
<u-picker
v-model="showDeadPicker"
mode="selector"
:range="deadPickerData"
range-key="label"
@confirm="onDeadConfirm"
@cancel="showDeadPicker = false"
></u-picker>
<u-picker
v-model="showProductTypePicker"
mode="selector"
:range="productTypePickerData"
range-key="label"
@confirm="onProductTypeConfirm"
@cancel="showProductTypePicker = false"
></u-picker>
<!-- 附近客户 悬浮按钮 -->
<view class="nearby-fab" @click="fetchNearby">
<text class="nearby-fab-text">附近客户</text>
</view>
</view>
</template>
<script>
import { getAgeByIdcard } from "@/common/util";
export default {
data() {
return {
scrollTop: 0,
isShowCalendar: false,
inputStyle: {
width: "100%",
fontSize: "28rpx",
fontWeight: "500"
},
callbackDropdownOptions: [
{ label: '全部', value: 'all' },
{ label: '无回访', value: 0 },
{ label: '有回访', value: 1 },
],
deadDropdownOptions: [
{ label: '全部', value: 'all' },
{ label: '正常', value: 0 },
{ label: '死亡', value: 1 },
],
productTypeOptions: [
{ label: '全部', value: 'all' }
],
// 底部弹出选择器相关
showCallbackPicker: false,
showDeadPicker: false,
showProductTypePicker: false,
callbackPickerData: [
{ label: '全部', value: 'all' },
{ label: '无回访', value: 0 },
{ label: '有回访', value: 1 },
],
deadPickerData: [
{ label: '全部', value: 'all' },
{ label: '正常', value: 0 },
{ label: '死亡', value: 1 },
],
productTypePickerData: [
{ label: '全部', value: 'all' }
],
optionsStatus: [{
label: '全部',
value: ''
},
{
label: '待护理',
value: '0',
},
{
label: '护理中',
value: 1,
},
{
label: '已护理',
value: 2,
},
],
optionsSex: [{
label: '全部',
value: ''
},
{
label: '男',
value: '男',
},
{
label: '女',
value: '女',
},
],
status: 'loadmore',
loadText: {
loadmore: '轻轻上拉或点击',
loading: '努力加载中',
nomore: '实在没有了'
},
nursingList: [],
select: {
page_size: 10,
page: 1,
keyword: '',
village_name: '',
has_quality_callbacks: 'all',
is_dead: 'all',
lat: '',
lng: '',
product_type_id: 'all'
},
}
},
methods: {
getAgeByIdcard,
async loadProductTypes() {
try {
const res = await this.$u.api.adminProductTypeList({ page: 1, page_size: 99,sort_type:'desc',sort_name:'sortnumber' })
const list = (res && res.data && res.data.data) ? res.data.data : (res && res.data ? res.data : [])
this.productTypeOptions = [{ label: '全部', value: 'all' }].concat(
(list || []).map(it => ({ label: it.name, value: it.id }))
)
this.productTypePickerData = this.productTypeOptions
} catch (e) {
// ignore
}
},
refresh(){
this.select = {
page_size: 10,
page: 1,
keyword: '',
village_name: '',
has_quality_callbacks: 'all',
is_dead: 'all',
lat: '',
lng: '',
product_type_id: 'all'
}
this.nursingList = []
this.getList()
},
searchInput(e) {
this.select.keyword = e
this.nursingList = []
this.select.page = 1
this.$u.debounce(this.getList, 1000)
},
villageInput(e) {
this.select.village_name = e
this.nursingList = []
this.select.page = 1
this.$u.debounce(this.getList, 1000)
},
async fetchNearby() {
try {
const res = await uni.getLocation({ type: 'gcj02', isHighAccuracy: true })
// uni.getLocation Promise 风格返回 [err, data]
const location = Array.isArray(res) ? res[1] : res
if (location && location.latitude && location.longitude) {
this.select.lat = location.latitude
this.select.lng = location.longitude
this.nursingList = []
this.select.page = 1
this.getList()
} else {
uni.showToast({ icon: 'none', title: '定位失败,请稍后重试' })
}
} catch (e) {
uni.showToast({ icon: 'none', title: '未能获取定位权限' })
}
},
async getList() {
const params = { ...this.select }
if (params.has_quality_callbacks === 'all') params.has_quality_callbacks = ''
if (params.is_dead === 'all') params.is_dead = ''
if (params.product_type_id === 'all') params.product_type_id = ''
const response = await this.$u.api.adminCustomerList(params)
console.log("response",response)
let res = response.data
if (res.data.length > 0 && res.data) {
this.nursingList.push(...res.data)
this.status = 'loadmore'
} else {
this.status = 'nomore'
if (this.select.page > 1) {
this.select.page--
}
}
},
pageTo (url) {
uni.navigateTo({
url
})
},
// 获取标签显示文本
getCallbackLabel(value) {
const item = this.callbackPickerData.find(item => item.value === value)
return item ? item.label : '全部'
},
getDeadLabel(value) {
const item = this.deadPickerData.find(item => item.value === value)
return item ? item.label : '全部'
},
getProductTypeLabel(value) {
const item = this.productTypePickerData.find(item => item.value === value)
return item ? item.label : '全部'
},
// 选择器确认回调
onCallbackConfirm(e) {
const selectedItem = this.callbackPickerData[e[0]]
this.select.has_quality_callbacks = selectedItem.value
this.showCallbackPicker = false
},
onDeadConfirm(e) {
const selectedItem = this.deadPickerData[e[0]]
this.select.is_dead = selectedItem.value
this.showDeadPicker = false
},
onProductTypeConfirm(e) {
const selectedItem = this.productTypePickerData[e[0]]
this.select.product_type_id = selectedItem.value
this.showProductTypePicker = false
},
// 打开地图导航
openMapNavigation(customerAddress) {
const defaultAddr = this.defaultAddress(customerAddress)
const lat = defaultAddr.lat
const lng = defaultAddr.lng
if (!lat || !lng) {
uni.showToast({
icon: 'none',
title: '地址缺少经纬度信息,无法导航'
})
return
}
// 打开地图导航
uni.openLocation({
latitude: parseFloat(lat),
longitude: parseFloat(lng),
name: defaultAddr.address || '客户地址',
success: () => {
console.log('打开地图成功')
},
fail: (err) => {
console.error('打开地图失败', err)
uni.showToast({
icon: 'none',
title: '打开地图失败'
})
}
})
}
},
computed: {
statusComputed() {
return function(status) {
switch (status) {
case '0':
return "待护理"
break;
case 1:
return "护理中"
break;
case 2:
return "已护理"
break;
default:
return '全部'
}
}
},
defaultAddress () {
return function (address) {
return address&&address.length>0?address[0]: {}
}
}
},
watch: {
'select.has_quality_callbacks'(val) {
this.nursingList = []
this.select.page = 1
this.getList()
},
'select.is_dead'(val) {
this.nursingList = []
this.select.page = 1
this.getList()
},
'select.product_type_id'(val) {
this.nursingList = []
this.select.page = 1
this.getList()
}
},
onReachBottom() {
this.select.page++
this.status = 'loading';
this.getList()
},
onShow() {
this.select.page = 1
this.nursingList = []
this.loadProductTypes()
this.getList()
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
}
}
</script>
<style scoped lang="scss">
//
/deep/.u-dropdown__menu {
justify-content: space-between !important;
}
/deep/.u-dropdown__menu__item {
width: 340rpx !important; //
flex: none !important;
height: 70rpx;
background: #FFFFFF;
border-radius: 10rpx;
justify-content: space-between !important;
}
/deep/.u-dropdown__menu__item .u-flex {
flex: 1;
}
/deep/.u-dropdown__menu__item__text {
flex: 1 !important;
font-size: 32rpx !important;
font-weight: 500 !important;
color: #333333 !important;
text-align: center;
padding: 12rpx 0rpx !important;
}
/deep/.u-dropdown__menu__item__arrow {
width: 70rpx !important;
height: 70rpx !important;
background: rgba(20, 121, 255, 0.1) !important;
border-radius: 10rpx !important;
border: 2rpx solid #FFFFFF !important;
display: flex;
justify-content: center;
align-items: center;
}
/deep/.u-load-more-wrap {
padding: 10px 0;
}
.search {
width: 710rpx;
height: 70rpx;
background: #FFFFFF;
border: 2rpx solid #FAFBFC;
display: flex;
align-items: center;
justify-content: space-between;
margin: 24rpx 24rpx 0 24rpx;
.input-content {
margin-left: 30rpx;
}
.icon {
margin-right: 30rpx;
}
}
.search-row {
width: 710rpx;
display: flex;
justify-content: space-between;
margin: 24rpx 24rpx 0 24rpx;
}
.search.small {
width: 340rpx;
margin: 0;
}
.drop-down {
margin-top: 20rpx;
}
.picker-row {
display: flex;
justify-content: space-between;
}
.picker-item {
width: 340rpx;
height: 70rpx;
background: #FFFFFF;
border: 2rpx solid #FAFBFC;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
box-sizing: border-box;
}
.picker-item.full-width {
width: 100%;
}
.picker-label {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.picker-value {
font-size: 28rpx;
color: #666;
flex: 1;
text-align: center;
}
.nursing-list {
margin-top: 10rpx;
.list-item {
width: 710rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(219, 218, 218, 0.5);
margin: 0 20rpx 26rpx 22rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 22rpx 0;
.time {
font-size: 32rpx;
font-weight: 500;
color: #36596A;
line-height: 32rpx;
padding-left: 20rpx;
}
.status {
display: flex;
align-items: center;
.status-icon {
margin-right: 10rpx;
}
.status-text {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
margin-right: 20rpx;
}
}
}
.line {
width: 670rpx;
height: 2rpx;
border: 2rpx solid #EEEFF5;
margin: 0 auto;
}
.center {
display: flex;
justify-content: space-between;
padding-top: 24rpx;
.avatar {
padding-top: 4rpx;
padding-left: 20rpx;
}
.user-info {
flex: 1;
padding-left: 24rpx;
.name {
width: 270rpx;
height: 48rpx;
font-size: 32rpx;
font-weight: 500;
color: #333333;
}
.tel {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 18rpx;
.text {
margin-left: 10rpx;
}
}
.address {
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
align-items: center;
margin-top: 14rpx;
.text {
flex: 1;
margin-left: 10rpx;
}
.nav-btn {
margin-left: 10rpx;
padding: 8rpx;
border-radius: 50%;
background: rgba(20, 121, 255, 0.1);
}
}
}
.sex {
width: 40rpx;
height: 40rpx;
background: #FDECEC;
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
.sex-text {
width: 28rpx;
height: 34rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 34rpx;
}
}
.age {
@extend .address;
}
.dislevel {
@extend .address;
}
.contact {
@extend .address;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-bottom: 26rpx;
.button-group {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
}
.distance {
align-items: center;
display: flex;
.distance-icon {
padding-left: 20rpx;
}
.distance-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
padding-left: 20rpx;
}
}
.to-there {
display: flex;
align-items: center;
margin-right: 20rpx;
.to-there-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
margin-right: 14rpx;
}
.to-there-icon {
margin-left: 3rpx;
}
}
}
}
}
.nearby-fab {
position: fixed;
right: 32rpx;
bottom: 320rpx;
width: 140rpx;
height: 140rpx;
background: #1479FF;
color: #FFFFFF;
border-radius: 70rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 20rpx rgba(20, 121, 255, 0.35);
z-index: 9;
}
.nearby-fab-text {
font-size: 26rpx;
font-weight: 600;
}
</style>