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

558 lines
22 KiB
JavaScript

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.

import React, { useState, useEffect, useRef } from 'react';
import * as echarts from 'echarts';
import styles from './CustomerInfoManagement.less';
const CustomerInfoManagement = () => {
const [searchKeyword, setSearchKeyword] = useState('');
const [customerType, setCustomerType] = useState('全部');
const [customerLevel, setCustomerLevel] = useState('全部');
const [cooperationStatus, setCooperationStatus] = useState('全部');
const [selectedRows, setSelectedRows] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
// 图表引用
const customerTypeChartRef = useRef(null);
const customerValueChartRef = useRef(null);
const customerTypeChartInstance = useRef(null);
const customerValueChartInstance = useRef(null);
// KPI数据
const kpiData = {
totalCustomers: 389,
highValueCustomers: 180,
inCooperation: 360,
newThisMonth: 8,
};
// 客户类型分布图表配置
const customerTypeChartOption = {
title: {
text: '客户类型分布',
left: 'left',
textStyle: {
fontSize: 16,
fontWeight: 600,
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: ['客户', '供应商', '第3周', '第4周'],
axisLabel: {
rotate: 0,
},
},
yAxis: {
type: 'value',
max: 100,
},
series: [
{
name: '数量',
type: 'bar',
data: [56, 32, 85, 15],
itemStyle: {
color: '#1890ff',
},
label: {
show: true,
position: 'top',
},
},
],
};
// 客户价值分布环形图配置
const customerValueChartOption = {
title: {
text: '客户价值分布',
left: 'left',
textStyle: {
fontSize: 16,
fontWeight: 600,
},
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
},
legend: {
orient: 'vertical',
left: 'right',
top: 'middle',
},
series: [
{
name: '客户价值',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
},
},
data: [
{ value: 180, name: '高价值客户', itemStyle: { color: '#5B9BD5' } },
{ value: 120, name: '中等客户', itemStyle: { color: '#FFC000' } },
{ value: 89, name: '小客户', itemStyle: { color: '#9E7CC1' } },
],
},
],
};
// 初始化图表
useEffect(() => {
// 初始化客户类型分布图表
if (customerTypeChartRef.current) {
customerTypeChartInstance.current = echarts.init(customerTypeChartRef.current);
customerTypeChartInstance.current.setOption(customerTypeChartOption);
}
// 初始化客户价值分布图表
if (customerValueChartRef.current) {
customerValueChartInstance.current = echarts.init(customerValueChartRef.current);
customerValueChartInstance.current.setOption(customerValueChartOption);
}
// 响应式调整
const handleResize = () => {
customerTypeChartInstance.current?.resize();
customerValueChartInstance.current?.resize();
};
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
customerTypeChartInstance.current?.dispose();
customerValueChartInstance.current?.dispose();
};
}, []);
// 最近活动数据
const recentActivities = [
{
id: 1,
title: '新合同签署',
description: '与中石化华东分公司签署年度供应合同',
time: '2小时前',
},
{
id: 2,
title: '订单完成',
description: '完成海南石油贸易有限公司的油品配送',
time: '昨天',
},
{
id: 3,
title: '客户反馈',
description: '收到华南能源集团的满意度调查回复',
time: '昨天',
},
{
id: 4,
title: '新客户注册',
description: '新客户"浙江能源有限公司"完成注册',
time: '3天前',
},
];
// 表格数据
const tableData = [
{
id: '1',
customerName: '中国石化销售股份有限公司',
contact: '钱亚男',
phone: '18901563341',
classification: '高价值',
monthlyAmount: 1250000,
cooperationStatus: '合作中',
satisfaction: 4.5,
},
{
id: '2',
customerName: '中石化华东分公司',
contact: '郑宇雅',
phone: '15341731282',
classification: '常规客户',
monthlyAmount: 1250000,
cooperationStatus: '合作中',
satisfaction: 4.0,
},
{
id: '3',
customerName: '海南石油贸易有限公司',
contact: '孙向明',
phone: '13252257033',
classification: '高价值',
monthlyAmount: 850000,
cooperationStatus: '合作中',
satisfaction: 4.5,
},
{
id: '4',
customerName: '东莞石化有限公司',
contact: '何思颖',
phone: '18931788771',
classification: '高价值',
monthlyAmount: 980000,
cooperationStatus: '合作中',
satisfaction: 4.5,
},
{
id: '5',
customerName: '中国石油化工集团有限公司',
contact: '钱佳仪',
phone: '13743378254',
classification: '常规客户',
monthlyAmount: 980000,
cooperationStatus: '暂停合作',
satisfaction: 4.0,
},
];
// 处理选择
const handleSelectRow = (id) => {
if (selectedRows.includes(id)) {
setSelectedRows(selectedRows.filter(rowId => rowId !== id));
} else {
setSelectedRows([...selectedRows, id]);
}
};
// 处理全选
const handleSelectAll = () => {
if (selectedRows.length === tableData.length) {
setSelectedRows([]);
} else {
setSelectedRows(tableData.map(item => item.id));
}
};
// 渲染星级评分
const renderStars = (rating) => {
const stars = [];
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 !== 0;
for (let i = 0; i < fullStars; i++) {
stars.push(<span key={i} className={styles.starFull}></span>);
}
if (hasHalfStar) {
stars.push(<span key="half" className={styles.starHalf}></span>);
}
const emptyStars = 5 - Math.ceil(rating);
for (let i = 0; i < emptyStars; i++) {
stars.push(<span key={`empty-${i}`} className={styles.starEmpty}></span>);
}
return stars;
};
// 计算总页数
const totalPages = Math.ceil(85 / pageSize);
return (
<div className={styles.container}>
{/* KPI卡片区域 */}
<div className={styles.kpiRow}>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>👥</div>
<div className={styles.kpiValue}>{kpiData.totalCustomers}</div>
<div className={styles.kpiTitle}>总客户数</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>👑</div>
<div className={styles.kpiValue}>{kpiData.highValueCustomers}</div>
<div className={styles.kpiTitle}>高价值客户</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>🤝</div>
<div className={styles.kpiValue}>{kpiData.inCooperation}</div>
<div className={styles.kpiTitle}>合作中</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}></div>
<div className={styles.kpiValue}>{kpiData.newThisMonth}</div>
<div className={styles.kpiTitle}>本月新增</div>
</div>
</div>
{/* 图表和活动区域 */}
<div className={styles.chartRow}>
{/* 客户类型分布图表 */}
<div className={styles.chartCard}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>客户类型分布</div>
<select className={styles.timeSelect} defaultValue="全部时间">
<option value="全部时间">全部时间</option>
<option value="本月">本月</option>
<option value="本季度">本季度</option>
<option value="本年">本年</option>
</select>
</div>
<div
ref={customerTypeChartRef}
className={styles.chartContainer}
/>
</div>
{/* 客户价值分布图表 */}
<div className={styles.chartCard}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>客户价值分布</div>
</div>
<div
ref={customerValueChartRef}
className={styles.chartContainer}
/>
</div>
{/* 最近活动列表 */}
<div className={styles.activityCard}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>最近活动</div>
</div>
<div className={styles.activityList}>
{recentActivities.map((activity) => (
<div key={activity.id} className={styles.activityItem}>
<div className={styles.activityIcon}>
{activity.id === 1 && '📄'}
{activity.id === 2 && '✅'}
{activity.id === 3 && '💬'}
{activity.id === 4 && '👤'}
</div>
<div className={styles.activityContent}>
<div className={styles.activityTitle}>{activity.title}</div>
<div className={styles.activityDesc}>{activity.description}</div>
<div className={styles.activityTime}>🕐 {activity.time}</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* 客户列表区域 */}
<div className={styles.tableCard}>
<div className={styles.tableHeader}>
<div className={styles.tableTitle}>客户列表</div>
</div>
{/* 筛选栏 */}
<div className={styles.filterBar}>
<div className={styles.filterGroup}>
<input
type="text"
className={styles.searchInput}
placeholder="搜索关键词"
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
<select
className={styles.filterSelect}
value={customerType}
onChange={(e) => setCustomerType(e.target.value)}
>
<option value="全部">客户类型: 全部</option>
<option value="客户">客户</option>
<option value="供应商">供应商</option>
</select>
<select
className={styles.filterSelect}
value={customerLevel}
onChange={(e) => setCustomerLevel(e.target.value)}
>
<option value="全部">客户等级: 全部</option>
<option value="高价值">高价值</option>
<option value="常规客户">常规客户</option>
</select>
<select
className={styles.filterSelect}
value={cooperationStatus}
onChange={(e) => setCooperationStatus(e.target.value)}
>
<option value="全部">合作状态: 全部</option>
<option value="合作中">合作中</option>
<option value="暂停合作">暂停合作</option>
</select>
<button className={styles.queryBtn} onClick={() => console.log('查询')}>
🔍 查询
</button>
<button className={styles.resetBtn} onClick={() => {
setSearchKeyword('');
setCustomerType('全部');
setCustomerLevel('全部');
setCooperationStatus('全部');
}}>
🔄 重置
</button>
<button className={styles.addBtn}>
新增客户
</button>
<button className={styles.exportBtn}>
📥 批量导出
</button>
</div>
</div>
{/* 表格 */}
<div className={styles.tableWrapper}>
<table className={styles.dataTable}>
<thead>
<tr>
<th>
<input
type="checkbox"
checked={selectedRows.length === tableData.length}
onChange={handleSelectAll}
/>
</th>
<th>客户名称</th>
<th>联系人</th>
<th>联系电话</th>
<th>分类</th>
<th>交易额度()</th>
<th>合作状态</th>
<th>满意度</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{tableData.map((row) => (
<tr key={row.id}>
<td>
<input
type="checkbox"
checked={selectedRows.includes(row.id)}
onChange={() => handleSelectRow(row.id)}
/>
</td>
<td>{row.customerName}</td>
<td>{row.contact}</td>
<td>{row.phone}</td>
<td>
<span className={`${styles.tag} ${row.classification === '高价值' ? styles.tagGold : styles.tagBlue}`}>
{row.classification}
</span>
</td>
<td>¥{row.monthlyAmount.toLocaleString()}</td>
<td>
<span className={`${styles.tag} ${row.cooperationStatus === '合作中' ? styles.tagGreen : styles.tagOrange}`}>
{row.cooperationStatus}
</span>
</td>
<td>
<div className={styles.stars}>
{renderStars(row.satisfaction)}
</div>
</td>
<td>
<div className={styles.actionBtns}>
<button className={styles.actionBtn}>查看详情</button>
<button className={styles.actionBtn}>修改</button>
<button className={`${styles.actionBtn} ${styles.deleteBtn}`}>删除</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* 分页 */}
<div className={styles.pagination}>
<div className={styles.paginationInfo}>
共85条
<select
className={styles.pageSizeSelect}
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
setCurrentPage(1);
}}
>
<option value="10">10 / page</option>
<option value="20">20 / page</option>
<option value="50">50 / page</option>
<option value="100">100 / page</option>
</select>
</div>
<div className={styles.paginationControls}>
<button
className={styles.pageBtn}
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
</button>
{currentPage > 3 && (
<>
<button className={styles.pageBtn} onClick={() => setCurrentPage(1)}>1</button>
<span className={styles.pageEllipsis}>...</span>
</>
)}
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNum;
if (currentPage <= 3) {
pageNum = i + 1;
} else if (currentPage >= totalPages - 2) {
pageNum = totalPages - 4 + i;
} else {
pageNum = currentPage - 2 + i;
}
if (pageNum > totalPages) return null;
return (
<button
key={pageNum}
className={`${styles.pageBtn} ${currentPage === pageNum ? styles.pageBtnActive : ''}`}
onClick={() => setCurrentPage(pageNum)}
>
{pageNum}
</button>
);
})}
{currentPage < totalPages - 2 && (
<>
<span className={styles.pageEllipsis}>...</span>
<button className={styles.pageBtn} onClick={() => setCurrentPage(totalPages)}>{totalPages}</button>
</>
)}
<button
className={styles.pageBtn}
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(currentPage + 1)}
>
</button>
</div>
</div>
</div>
</div>
);
};
export default CustomerInfoManagement;