OfficeSystem/pages/customer/detail/index.vue
2025-11-10 10:49:55 +08:00

580 lines
14 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-detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<text class="nav-btn" @click="handleBack"></text>
<text class="nav-title">{{ customerDetail.name || '客户详情' }}</text>
<text class="nav-btn" style="opacity: 0;">占位</text>
</view>
</view>
<!-- 客户摘要卡片 -->
<view class="customer-summary-card">
<view class="summary-row">
<view class="summary-item">
<text class="summary-label">客户昵称</text>
<text class="summary-value">{{ customerDetail.contactName || customerDetail.name || '--' }}</text>
</view>
<view class="summary-item">
<text class="summary-label">客户状态</text>
<view class="status-badge" :class="getStatusClass(customerDetail.status)">
<text>{{ getStatusText(customerDetail.status) }}</text>
</view>
</view>
</view>
<view class="summary-row">
<view class="summary-item">
<text class="summary-label">客户意向</text>
<text class="summary-value">{{ formatIntents(customerDetail.intents) }}</text>
</view>
<view class="summary-item">
<text class="summary-label">意向强度</text>
<text class="summary-value">{{ getIntentStrengthText(customerDetail.intentLevel) }}</text>
</view>
</view>
<view class="summary-row">
<view class="summary-item">
<text class="summary-label">客户归属</text>
<text class="summary-value">{{ customerDetail.followName || '未分配' }}</text>
</view>
<view class="summary-item">
<text class="summary-label">最近跟进</text>
<text class="summary-value">{{ formatDateTime(customerDetail.lastFollowTime) }}</text>
</view>
</view>
<view class="summary-row">
<view class="summary-item">
<text class="summary-label">微信号</text>
<text class="summary-value">{{ customerDetail.wechat || '--' }}</text>
</view>
<view class="summary-item">
<text class="summary-label">微信好友</text>
<text class="summary-value">{{ customerDetail.wechatId || '--' }}</text>
</view>
</view>
</view>
<!-- 标签页导航 -->
<view class="tab-navigation">
<view
class="tab-item"
:class="{ active: activeTab === 'followup' }"
@click="switchTab('followup')"
>
<text>跟进动态</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'projects' }"
@click="switchTab('projects')"
>
<text>项目列表</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'info' }"
@click="switchTab('info')"
>
<text>客户信息</text>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 跟进动态标签页 -->
<FollowupTab
v-show="activeTab === 'followup'"
:followup-list="followupList"
@followup-click="handleFollowupClick"
/>
<!-- 项目列表标签页 -->
<ProjectsTab
v-show="activeTab === 'projects'"
:project-list="projectList"
@project-more="handleProjectMore"
/>
<!-- 客户信息标签页 -->
<InfoTab
v-show="activeTab === 'info'"
:customer-detail="customerDetail"
/>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="action-btn" @click="handleNewFollowup">
<text class="action-icon">✎</text>
<text class="action-text">写新跟进</text>
</view>
<view class="action-btn" @click="handleNewTask">
<text class="action-icon">☑</text>
<text class="action-text">新建任务</text>
</view>
<view class="action-btn" @click="handleCall">
<text class="action-icon">☎</text>
<text class="action-text">拨打电话</text>
</view>
<view class="action-btn" @click="handleMore">
<text class="action-icon">⋯</text>
<text class="action-text">更多操作</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { getCustomerDetail, getCustomerFollowupList, getCustomerProjects, deleteCustomer } from '@/common/api';
import FollowupTab from '@/components/customer-detail/FollowupTab.vue';
import ProjectsTab from '@/components/customer-detail/ProjectsTab.vue';
import InfoTab from '@/components/customer-detail/InfoTab.vue';
// 页面参数
const customerId = ref('');
const customerDetail = ref({});
const activeTab = ref('followup');
const followupList = ref([]);
const projectList = ref([]);
const loading = ref(false);
// 获取页面参数
onLoad((options) => {
if (options && options.id) {
customerId.value = options.id;
loadCustomerDetail();
loadFollowupList();
}
});
// 切换标签页
const switchTab = (tab) => {
activeTab.value = tab;
if (tab === 'followup' && followupList.value.length === 0) {
loadFollowupList();
} else if (tab === 'projects' && projectList.value.length === 0) {
loadProjectList();
}
};
// 加载客户详情
const loadCustomerDetail = async () => {
if (!customerId.value) return;
loading.value = true;
try {
const res = await getCustomerDetail(customerId.value);
if (res) {
customerDetail.value = res;
}
} catch (error) {
console.error('加载客户详情失败:', error);
uni.$uv.toast('加载客户详情失败');
} finally {
loading.value = false;
}
};
// 加载跟进动态列表
const loadFollowupList = async () => {
if (!customerId.value) return;
try {
const res = await getCustomerFollowupList(customerId.value);
if (res && Array.isArray(res)) {
followupList.value = res;
} else if (res && res.rows && Array.isArray(res.rows)) {
followupList.value = res.rows;
}
} catch (error) {
console.error('加载跟进动态失败:', error);
}
};
// 加载项目列表
const loadProjectList = async () => {
if (!customerId.value) return;
try {
const res = await getCustomerProjects(customerId.value);
if (res && Array.isArray(res)) {
projectList.value = res;
} else if (res && res.rows && Array.isArray(res.rows)) {
projectList.value = res.rows;
} else if (res && res.data && res.data.rows && Array.isArray(res.data.rows)) {
projectList.value = res.data.rows;
}
} catch (error) {
console.error('加载项目列表失败:', error);
uni.$uv.toast('加载项目列表失败');
}
};
// 获取状态样式类
const getStatusClass = (status) => {
return {
'status-following': status === '1',
'status-pending': status === '2'
};
};
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'1': '正在跟进',
'2': '待跟进',
'3': '其他',
'4': '无效客户'
};
return statusMap[status] || '未知';
};
// 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return '暂无';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (e) {
return dateTime;
}
};
// 格式化客户意向(数组转字符串)
const formatIntents = (intents) => {
if (!intents) return '--';
// 如果是数组直接join
if (Array.isArray(intents)) {
return intents.length > 0 ? intents.join('、') : '--';
}
// 如果是字符串(逗号分隔),直接返回
if (typeof intents === 'string') {
return intents || '--';
}
return '--';
};
// 获取意向强度文本
const getIntentStrengthText = (intentLevel) => {
const levelMap = {
'1': '高',
'2': '中',
'3': '低'
};
return levelMap[intentLevel] || '--';
};
// 返回
const handleBack = () => {
uni.navigateBack();
};
// 跟进项点击
const handleFollowupClick = (item) => {
// 可以跳转到跟进详情
console.log('点击跟进:', item);
};
// 项目更多操作
const handleProjectMore = (project) => {
uni.showActionSheet({
itemList: ['查看详情', '编辑项目', '删除项目'],
success: (res) => {
console.log('选择了第' + (res.tapIndex + 1) + '个选项');
}
});
};
// 写新跟进
const handleNewFollowup = () => {
if (!customerId.value) {
uni.$uv.toast('客户信息不完整');
return;
}
uni.navigateTo({
url: `/pages/customer/follow/add/index?customerId=${customerId.value}&customerName=${encodeURIComponent(customerDetail.value.name || '')}`
});
};
// 新建任务
const handleNewTask = () => {
uni.navigateTo({
url: `/pages/customer-tasks/index?customerId=${customerId.value}&customerName=${customerDetail.value.name}`
});
};
// 拨打电话
const handleCall = () => {
if (customerDetail.value.mobile) {
uni.makePhoneCall({
phoneNumber: customerDetail.value.mobile,
fail: (err) => {
console.error('拨打电话失败:', err);
uni.$uv.toast('拨打电话失败');
}
});
} else {
uni.$uv.toast('客户未设置电话号码');
}
};
// 更多操作
const handleMore = () => {
uni.showActionSheet({
itemList: ['编辑客户', '删除客户', '分享客户'],
success: (res) => {
if (res.tapIndex === 0) {
// 编辑客户
uni.navigateTo({
url: `/pages/customer/edit/index?id=${customerId.value}`
});
} else if (res.tapIndex === 1) {
// 删除客户
uni.showModal({
title: '确认删除',
content: `确定要删除客户"${customerDetail.value.name || '该客户'}"吗?`,
success: async (modalRes) => {
if (modalRes.confirm) {
try {
// 显示加载提示
uni.showLoading({
title: '删除中...',
mask: true
});
// 调用删除API
await deleteCustomer(customerId.value);
// 隐藏加载提示
uni.hideLoading();
// 显示成功提示
uni.$uv.toast('删除成功');
// 发送全局事件,通知客户列表刷新
uni.$emit('customerListRefresh');
// 设置刷新标志,确保返回页面时也能刷新
uni.setStorageSync('customerListNeedRefresh', true);
// 延迟一下再跳转,让用户看到成功提示
setTimeout(() => {
// 跳转回客户列表页面
uni.navigateBack({
delta: 999 // 返回到客户列表页面
});
}, 500);
} catch (error) {
// 隐藏加载提示
uni.hideLoading();
// 显示错误提示
console.error('删除客户失败:', error);
uni.$uv.toast(error?.message || '删除失败,请重试');
}
}
}
});
} else if (res.tapIndex === 2) {
// 分享客户
// TODO: 实现分享客户功能
uni.$uv.toast('分享功能待实现');
}
}
});
};
// 组件挂载时的初始化(如果需要)
onMounted(() => {
// 数据已在 onLoad 中加载
});
// 页面显示时刷新数据(从编辑页返回时)
onShow(() => {
if (customerId.value) {
loadCustomerDetail();
// 如果当前在跟进动态标签页,也刷新跟进列表
if (activeTab.value === 'followup') {
loadFollowupList();
}
}
});
</script>
<style lang="scss" scoped>
.customer-detail-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.custom-navbar {
background-color: #fff;
padding-top: var(--status-bar-height, 0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
z-index: 100;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
}
.nav-btn {
font-size: 24px;
color: #333;
font-weight: bold;
min-width: 44px;
text-align: center;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
}
.customer-summary-card {
background-color: #fff;
padding: 16px;
margin-bottom: 8px;
}
.summary-row {
display: flex;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.summary-item {
flex: 1;
display: flex;
flex-direction: column;
&:first-child {
margin-right: 16px;
}
}
.summary-label {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.summary-value {
font-size: 14px;
color: #333;
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
&.status-following {
background-color: #e3f2fd;
color: #1976d2;
}
&.status-pending {
background-color: #f5f5f5;
color: #666;
}
}
.tab-navigation {
display: flex;
background-color: #fff;
border-bottom: 1px solid #eee;
padding: 0 16px;
}
.tab-item {
flex: 1;
padding: 12px 0;
text-align: center;
position: relative;
text {
font-size: 14px;
color: #666;
}
&.active {
text {
color: #1976d2;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: #1976d2;
}
}
}
.content-scroll {
flex: 1;
overflow-y: auto;
}
// 底部操作栏
.bottom-actions {
display: flex;
background-color: #fff;
border-top: 1px solid #eee;
padding: 8px 0;
padding-bottom: calc(8px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
.action-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px;
}
.action-icon {
font-size: 20px;
color: #1976d2;
margin-bottom: 4px;
}
.action-text {
font-size: 12px;
color: #666;
}
</style>