OfficeSystem/pages/customer/detail/index.vue
2025-11-07 17:14:18 +08:00

516 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-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>
<view class="status-badge" :class="getStatusClass(customerDetail.status)">
<text>{{ getStatusText(customerDetail.status) }}</text>
</view>
</view>
<view class="summary-item">
<text class="summary-label">客户星级</text>
<view class="stars">
<text
class="star"
v-for="i in 5"
:key="i"
:class="{ 'filled': i <= getRatingFromIntentLevel(customerDetail.intentLevel) }"
>★</text>
</view>
</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">{{ getCustomerTypeText(customerDetail.type) }}</text>
</view>
</view>
<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>
<text class="summary-value">{{ formatDateTime(customerDetail.lastFollowTime) }}</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 } from '@dcloudio/uni-app';
import { getCustomerDetail, getCustomerFollowupList, getCustomerProjects } 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 getRatingFromIntentLevel = (intentLevel) => {
const levelMap = {
'1': 5,
'2': 3
};
return levelMap[intentLevel] || 0;
};
// 获取客户类型文本
const getCustomerTypeText = (type) => {
const typeMap = {
'1': '企业客户',
'2': '个人客户'
};
return typeMap[type] || '--';
};
// 返回
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 = () => {
uni.navigateTo({
url: `/pages/customer-follow/index?customerId=${customerId.value}&customerName=${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) => {
console.log('选择了第' + (res.tapIndex + 1) + '个选项');
if (res.tapIndex === 0) {
// 编辑客户
uni.navigateTo({
url: `/pages/edit-customer/index?id=${customerId.value}`
});
}
}
});
};
// 组件挂载时的初始化(如果需要)
onMounted(() => {
// 数据已在 onLoad 中加载
});
</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;
}
}
.stars {
display: flex;
gap: 2px;
}
.star {
font-size: 14px;
color: #ddd;
&.filled {
color: #ffc107;
}
}
.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>