Files
smartDriveEEUniApp/pages/furniture_customer/components/CustomerList.vue
2026-01-10 11:12:23 +08:00

458 lines
12 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 class="customer-list-container">
<view class="service-status-toolbar">
<text class="toolbar-total">{{ customerListTotal || list.length }}</text>
<input
class="toolbar-input"
v-model="salesName"
placeholder="所属销售姓名"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="handleRefresh"
@input="onInputChange"
/>
<input
class="toolbar-input"
v-model="customerName"
placeholder="客户姓名"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="handleRefresh"
@input="onInputChange"
/>
<view class="toolbar-actions">
<view class="toolbar-btn toolbar-btn--refresh" @click="handleRefresh">
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
<text>刷新</text>
</view>
</view>
</view>
<scroll-view class="customer-list" scroll-y>
<view
class="customer-card"
v-for="(item, index) in list"
:key="index"
@click.stop="$emit('itemClick', item)">
<view class="customer-header">
<text class="customer-name-text">{{ item.name }}</text>
<view class="intent-badge" :class="item.intent === '高' ? 'intent-high' : 'intent-medium'">
<text>意向{{ item.intent }}</text>
</view>
</view>
<view class="customer-service-info">
<text>服务人员:{{ item.serviceStaff }} 服务{{ item.serviceCount }} 上次服务:{{ item.lastService }}</text>
</view>
<view class="customer-tags">
<view
class="tag-item"
:class="'tag-' + tag.color"
v-for="(tag, tagIndex) in item.tags"
:key="tagIndex">
<text>{{ tag.text }}</text>
</view>
</view>
<view class="customer-footer" v-if="item.phone || item.budget">
<text class="customer-phone" v-if="item.phone">{{ item.phone }}</text>
<text class="customer-budget" v-if="item.budget">{{ item.budget }}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { getApiUrl } from '@/common/config.js';
export default {
name: 'CustomerList',
props: {},
data() {
return {
salesName: '',
customerName: '',
customerList: [],
customerListTotal: 0,
customerListPage: {
current: 1,
size: 10
}
}
},
computed: {
list() {
return this.customerList;
}
},
mounted() {
// 组件挂载时自动加载数据
this.loadCustomerList();
},
methods: {
onInputChange() {
// 输入框变化时,可以在这里添加实时搜索逻辑(如果需要)
},
handleRefresh() {
// 刷新客户列表
this.refreshCustomerList({
salesName: this.salesName.trim(),
customerName: this.customerName.trim()
});
},
refreshCustomerList(filterParams = {}) {
// 重置分页到第一页
this.customerListPage.current = 1;
// 重新加载数据,传递查询参数
this.loadCustomerList(filterParams);
},
// 加载客户列表
async loadCustomerList(params = {}) {
try {
// 构建查询参数对象(后端使用 @RequestParam参数需要作为 URL 查询参数传递)
const queryParams = {
current: this.customerListPage.current,
size: this.customerListPage.size
};
// 添加查询参数:客户姓名、所属销售姓名、销售电话、联系方式
const trimmedCustomerName = params.customerName?.trim();
const trimmedSalesName = params.salesName?.trim();
const trimmedSalesPhone = params.salesPhone?.trim();
const trimmedContact = params.contact?.trim();
if (trimmedCustomerName) {
queryParams.customerName = trimmedCustomerName;
}
if (trimmedSalesName) {
queryParams.salesName = trimmedSalesName;
}
if (trimmedSalesPhone) {
queryParams.salesPhone = trimmedSalesPhone;
}
if (trimmedContact) {
queryParams.contact = trimmedContact;
}
// 将参数转换为 URL 查询字符串(因为后端使用 @RequestParam
const queryString = Object.keys(queryParams)
.filter(key => queryParams[key] !== null && queryParams[key] !== undefined && queryParams[key] !== '')
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
.join('&');
// 调试:打印请求参数
console.log('客户列表查询参数:', JSON.stringify(queryParams));
console.log('查询字符串:', queryString);
// 后端使用 @GetMapping 和 @RequestParam参数需要作为 URL 查询参数传递
const baseUrl = getApiUrl('/api/customerManagement/list');
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
// 获取认证信息
let tenantId = '';
let token = '';
try {
tenantId = uni.getStorageSync('backend-tenant-id') || '';
token = uni.getStorageSync('backend-token') || '';
} catch (e) {
console.error('获取认证信息失败:', e);
}
// 构建请求头
const headers = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const res = await uni.request({
url,
method: 'GET',
header: headers,
timeout: 10000
});
if (res.statusCode === 200 && res.data && res.data.success) {
// 转换后端客户数据为显示格式
const customerRecords = res.data.data || [];
// 将 CustomerManagement 数据映射为显示格式
const mappedList = customerRecords.map(item => {
// 格式化创建时间
let formattedTime = '';
if (item.createTime) {
const date = new Date(item.createTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
formattedTime = `${year}-${month}-${day}`;
}
// 构建标签数组(根据业务需求可以从不同字段提取)
const tags = [];
if (item.remark) {
// 从备注中提取标签信息
if (item.remark.includes('优惠') || item.remark.includes('活动')) {
tags.push({ text: '关注优惠活动', color: 'orange' });
}
if (item.remark.includes('颜色') || item.remark.includes('搭配')) {
tags.push({ text: '关注颜色搭配', color: 'orange' });
}
}
// 根据录音条数或联系次数判断意向程度
let intent = '中';
const recordingCount = item.recordingCount || 0;
const contactCount = item.contactCount || 0;
if (recordingCount > 3 || contactCount > 5) {
intent = '高';
}
return {
name: item.customerName || '',
intent: intent,
serviceStaff: item.salesName || '',
serviceCount: item.contactCount || item.recordingCount || 0,
lastService: formattedTime,
lastServiceTime: item.updateTime || item.createTime,
tags: tags,
phone: item.contact || item.salesPhone || '',
budget: item.intendedModel || '', // 意向车型作为预算信息
// 保留原始数据字段,方便后续使用
rawData: item
};
});
// 按更新时间倒序排序
mappedList.sort((a, b) => {
if (!a.lastServiceTime) return 1;
if (!b.lastServiceTime) return -1;
return new Date(b.lastServiceTime) - new Date(a.lastServiceTime);
});
// 分页:第一页或刷新时重置,其他情况追加
if (this.customerListPage.current === 1) {
this.customerList = mappedList;
} else {
this.customerList = this.customerList.concat(mappedList);
}
this.customerListTotal = res.data.total || mappedList.length;
} else {
uni.showToast({
title: res.data.message || '获取客户列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取客户列表失败:', error);
uni.showToast({
title: `获取客户列表失败: ${error.errMsg || error.message || '未知错误'}`,
icon: 'none',
duration: 3000
});
}
}
}
}
</script>
<style scoped>
.customer-list-container {
/* 使用绝对定位从tab页底部开始到底部导航栏结束 */
position: absolute;
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
display: flex;
flex-direction: column;
background-color: #F5F5F5;
padding: 24rpx 16rpx;
box-sizing: border-box;
}
.service-status-toolbar {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 8rpx; /* 增加间距,避免元素被挤压 */
padding: 12rpx 8rpx; /* 增加内边距,给搜索框更多空间 */
margin-bottom: 24rpx; /* 与列表项间距保持一致24rpx */
background-color: #F8F8FA;
border-radius: 8rpx;
border: 1px solid #EFEFF2;
overflow-x: auto;
min-height: 64rpx; /* 设置最小高度,确保搜索区域不被挤压 */
box-sizing: border-box;
flex-shrink: 0; /* 防止被压缩 */
}
.toolbar-total {
font-size: 24rpx;
color: #666;
white-space: nowrap;
flex-shrink: 0;
margin-right: 8rpx; /* 增加右边距,与搜索框保持距离 */
line-height: 1.2;
padding: 0 4rpx; /* 增加左右内边距 */
}
.toolbar-input {
flex: 1;
min-width: 120rpx; /* 增加最小宽度,避免被挤压变形 */
max-width: 200rpx; /* 设置最大宽度,保持合理比例 */
height: 56rpx; /* 稍微增加高度,提升可用性 */
line-height: 56rpx;
background-color: #F5F6FA;
border-radius: 8rpx;
padding: 0 12rpx; /* 增加左右内边距 */
font-size: 24rpx;
border: 1px solid transparent;
box-sizing: border-box;
flex-shrink: 1; /* 允许适当收缩,但保持最小宽度 */
}
.toolbar-actions {
display: flex;
align-items: center;
margin-left: auto;
flex-shrink: 0;
gap: 8rpx; /* 增加按钮之间的间距 */
}
.toolbar-btn {
padding: 0 12rpx; /* 增加左右内边距 */
height: 56rpx; /* 与输入框高度保持一致 */
min-width: 56rpx;
color: #2A68FF;
display: flex;
align-items: center;
justify-content: center;
gap: 4rpx; /* 增加图标和文字之间的间距 */
font-size: 24rpx;
font-weight: 400;
line-height: 1;
box-sizing: border-box;
}
.toolbar-btn text {
color: #2A68FF;
}
.customer-list {
/* 使用flex填充剩余空间 */
flex: 1;
background-color: transparent;
box-sizing: border-box;
overflow-y: auto;
}
.customer-card {
width: 100%;
box-sizing: border-box;
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.customer-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.customer-name-text {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.intent-badge {
padding: 6rpx 16rpx;
border-radius: 8rpx;
}
.intent-high {
background-color: #E3F2FD;
}
.intent-high text {
font-size: 24rpx;
color: #1976D2;
font-weight: 500;
}
.intent-medium {
background-color: #FFF3E0;
}
.intent-medium text {
font-size: 24rpx;
color: #F57C00;
font-weight: 500;
}
.customer-service-info {
margin-bottom: 20rpx;
}
.customer-service-info text {
font-size: 24rpx;
color: #999;
}
.customer-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
gap: 12rpx;
}
.tag-item {
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
.tag-orange {
background-color: #FFF3E0;
}
.tag-orange text {
font-size: 24rpx;
color: #F57C00;
}
.tag-blue {
background-color: #E3F2FD;
}
.tag-blue text {
font-size: 24rpx;
color: #1976D2;
}
.customer-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 16rpx;
border-top: 1px solid #F0F0F0;
}
.customer-phone {
font-size: 28rpx;
color: #333;
}
.customer-budget {
font-size: 26rpx;
color: #1976D2;
}
</style>