客户信息管理统计列表页面开发

main
jiangxucong 1 day ago
parent ef0771a4c4
commit c7c5181dec

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 177 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 175 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 179 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 176 KiB

@ -1,16 +1,25 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef, useMemo, useLayoutEffect } from 'react';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import styles from './CustomerInfoManagement.less'; import styles from './CustomerInfoManagement.less';
import { Select, Space, Button, Input } from 'antd';
import { PlusOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons';
import StandardTable from '@/components/StandardTable';
import topIcon from '@/assets/business_basic/top_icon1.svg';
import topIcon2 from '@/assets/business_basic/top_icon2.svg';
import topIcon3 from '@/assets/business_basic/top_icon3.svg';
import topIcon4 from '@/assets/business_basic/top_icon4.svg';
import CustomerInfoManagementDetail from './CustomerInfoManagementDetail'; import CustomerInfoManagementDetail from './CustomerInfoManagementDetail';
const CustomerInfoManagement = () => { const CustomerInfoManagement = () => {
const [searchKeyword, setSearchKeyword] = useState(''); const [searchKeyword, setSearchKeyword] = useState('');
const [customerType, setCustomerType] = useState('全部');
const [customerLevel, setCustomerLevel] = useState('全部');
const [cooperationStatus, setCooperationStatus] = useState('全部');
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
// 列表筛选与数据(演示数据,可替换为接口)
const [filters, setFilters] = useState({
customerType: '',
customerGrade: '',
cooperationStatus: '',
});
// 新增:详情页面切换状态 // 新增:详情页面切换状态
const [showDetail, setShowDetail] = useState(false); const [showDetail, setShowDetail] = useState(false);
const [detailData, setDetailData] = useState(null); const [detailData, setDetailData] = useState(null);
@ -32,21 +41,27 @@ const CustomerInfoManagement = () => {
// 客户类型分布图表配置 // 客户类型分布图表配置
const customerTypeChartOption = { const customerTypeChartOption = {
title: { // title: {
text: '客户类型分布', // text: '客户类型分布',
left: 'left', // left: 'left',
textStyle: { // textStyle: {
fontSize: 16, // fontSize: 16,
fontWeight: 600, // fontWeight: 600,
}, // color: '#333',
}, // },
// },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow', type: 'shadow',
}, },
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#e0e0e0',
borderWidth: 1,
textStyle: { color: '#333' },
}, },
grid: { grid: {
top: 16,
left: '3%', left: '3%',
right: '4%', right: '4%',
bottom: '3%', bottom: '3%',
@ -57,23 +72,44 @@ const CustomerInfoManagement = () => {
data: ['客户', '供应商', '第3周', '第4周'], data: ['客户', '供应商', '第3周', '第4周'],
axisLabel: { axisLabel: {
rotate: 0, rotate: 0,
color: '#4E5856',
}, },
axisLine: {
lineStyle: { color: '#C9E3DE' },
},
axisTick: { show: false },
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
max: 100, max: 100,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: '#4E5856' },
splitLine: {
show: true,
lineStyle: { color: '#E9F3F1', type: 'dashed' },
},
}, },
series: [ series: [
{ {
name: '数量', name: '数量',
type: 'bar', type: 'bar',
data: [56, 32, 85, 15], data: [56, 32, 85, 15],
barWidth: 26,
itemStyle: { itemStyle: {
color: '#1890ff', color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#7FC8B6' },
{ offset: 1, color: '#008F8E' },
]),
// barBorderRadius: [6, 6, 0, 0],
shadowColor: 'rgba(0,0,0,0.08)',
shadowBlur: 6,
shadowOffsetY: 2,
}, },
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
color: '#4E5856',
}, },
}, },
], ],
@ -81,81 +117,113 @@ const CustomerInfoManagement = () => {
// 客户价值分布环形图配置 // 客户价值分布环形图配置
const customerValueChartOption = { const customerValueChartOption = {
title: { // title: {
text: '客户价值分布', // text: '客户价值分布',
left: 'left', // left: 'left',
textStyle: { // textStyle: {
fontSize: 16, // fontSize: 16,
fontWeight: 600, // fontWeight: 600,
}, // },
}, // },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)', formatter: '{a} <br/>{b}: {c} ({d}%)',
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
left: 'right', right: '6%',
top: 'middle', top: '32%',
itemWidth: 16,
itemHeight: 4,
itemGap: 16,
textStyle: {
fontSize: 12,
color: '#666',
fontWeight: 'normal'
}
}, },
series: [ series: [
{ {
name: '客户价值', name: '客户价值',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['40%', '65%'],
avoidLabelOverlap: false, center: ['30%', '50%'],
// avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, // borderRadius: 10,
borderColor: '#fff', // borderColor: '#fff',
borderWidth: 2, // borderWidth: 2,
}, },
label: { label: {
show: false, show: false,
}, },
emphasis: { // emphasis: {
label: { // label: {
show: true, // show: true,
fontSize: 16, // fontSize: 16,
fontWeight: 'bold', // fontWeight: 'bold',
}, // },
}, // },
data: [ data: [
{ value: 180, name: '高价值客户', itemStyle: { color: '#5B9BD5' } }, { value: 180, name: '高价值客户', itemStyle: { color: '#5CB3FF' } },
{ value: 120, name: '中等客户', itemStyle: { color: '#FFC000' } }, { value: 120, name: '中等客户', itemStyle: { color: '#FFDE73' } },
{ value: 89, name: '小客户', itemStyle: { color: '#9E7CC1' } }, { value: 89, name: '小客户', itemStyle: { color: '#A990EA' } },
], ],
}, },
], ],
}; };
// 初始化图表 // 图表初始化与销毁:根据详情模式切换,返回列表后重新初始化
useEffect(() => { useEffect(() => {
// 初始化客户类型分布图表 if (!showDetail) {
if (customerTypeChartRef.current) { if (customerTypeChartRef.current) {
customerTypeChartInstance.current = echarts.init(customerTypeChartRef.current); customerTypeChartInstance.current = echarts.init(customerTypeChartRef.current);
customerTypeChartInstance.current.setOption(customerTypeChartOption); customerTypeChartInstance.current.setOption(customerTypeChartOption);
} // 额外一次 resize确保初始化后尺寸正确
requestAnimationFrame(() => {
// 初始化客户价值分布图表 customerTypeChartInstance.current?.resize();
if (customerValueChartRef.current) { });
customerValueChartInstance.current = echarts.init(customerValueChartRef.current); }
customerValueChartInstance.current.setOption(customerValueChartOption); if (customerValueChartRef.current) {
} customerValueChartInstance.current = echarts.init(customerValueChartRef.current);
customerValueChartInstance.current.setOption(customerValueChartOption);
// 额外一次 resize确保初始化后尺寸正确
requestAnimationFrame(() => {
customerValueChartInstance.current?.resize();
});
}
// 响应式调整 const handleResize = () => {
const handleResize = () => { customerTypeChartInstance.current?.resize();
customerTypeChartInstance.current?.resize(); customerValueChartInstance.current?.resize();
customerValueChartInstance.current?.resize(); };
}; window.addEventListener('resize', handleResize);
window.addEventListener('resize', handleResize);
// 清理函数 return () => {
return () => { window.removeEventListener('resize', handleResize);
window.removeEventListener('resize', handleResize); customerTypeChartInstance.current?.dispose();
customerTypeChartInstance.current = null;
customerValueChartInstance.current?.dispose();
customerValueChartInstance.current = null;
};
} else {
// 进入详情时,释放实例以避免无容器绑定
customerTypeChartInstance.current?.dispose(); customerTypeChartInstance.current?.dispose();
customerTypeChartInstance.current = null;
customerValueChartInstance.current?.dispose(); customerValueChartInstance.current?.dispose();
}; customerValueChartInstance.current = null;
}, []); }
}, [showDetail]);
// 返回列表时恢复滚动位置,确保布局稳定后再滚动
useLayoutEffect(() => {
if (!showDetail) {
// 等待本帧布局完成
requestAnimationFrame(() => {
window.scrollTo(0, prevScrollY || 0);
});
}
}, [showDetail]);
// 最近活动数据 // 最近活动数据
const recentActivities = [ const recentActivities = [
@ -239,6 +307,48 @@ const CustomerInfoManagement = () => {
}, },
]; ];
// 列配置(用于 StandardTable
const columns = useMemo(() => {
return [
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 220 },
{ title: '联系人', dataIndex: 'contact', key: 'contact', width: 120 },
{ title: '联系电话', dataIndex: 'phone', key: 'phone', width: 140 },
{
title: '分类', dataIndex: 'classification', key: 'classification', width: 110,
render: (val) => (
<span className={`${styles.tag} ${val === '高价值' ? styles.tagGold : styles.tagBlue}`}>{val}</span>
)
},
{
title: '交易额度(月)', dataIndex: 'monthlyAmount', key: 'monthlyAmount', width: 150,
render: (v) => `¥${Number(v).toLocaleString()}`
},
{
title: '合作状态', dataIndex: 'cooperationStatus', key: 'cooperationStatus', width: 110,
render: (val) => (
<span className={`${styles.tag} ${val === '合作中' ? styles.tagGreen : styles.tagOrange}`}>{val}</span>
)
},
{
title: '满意度', dataIndex: 'satisfaction', key: 'satisfaction', width: 120,
},
{
title: '操作', key: 'action', fixed: 'right', width: 200,
render: (_, row) => (
<Space size={12}>
<Button type="link" size="small" className="viewDetailBtn" onClick={() => {
setPrevScrollY(window.pageYOffset || document.documentElement.scrollTop || 0);
setDetailData(row);
setShowDetail(true);
}}>查看详情</Button>
<Button type="link" size="small" className="editBtn">修改</Button>
<Button type="link" size="small" danger className="deleteBtn">删除</Button>
</Space>
)
},
];
}, []);
// 处理选择 // 处理选择
const handleSelectRow = (id) => { const handleSelectRow = (id) => {
if (selectedRows.includes(id)) { if (selectedRows.includes(id)) {
@ -262,7 +372,7 @@ const CustomerInfoManagement = () => {
const stars = []; const stars = [];
const fullStars = Math.floor(rating); const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 !== 0; const hasHalfStar = rating % 1 !== 0;
for (let i = 0; i < fullStars; i++) { for (let i = 0; i < fullStars; i++) {
stars.push(<span key={i} className={styles.starFull}></span>); stars.push(<span key={i} className={styles.starFull}></span>);
} }
@ -282,7 +392,7 @@ const CustomerInfoManagement = () => {
if (showDetail) { if (showDetail) {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div style={{marginBottom:10}}> <div style={{ marginBottom: 10 }}>
<button <button
className={styles.backBtn} className={styles.backBtn}
onClick={() => { onClick={() => {
@ -292,7 +402,7 @@ const CustomerInfoManagement = () => {
window.scrollTo(0, prevScrollY); window.scrollTo(0, prevScrollY);
}, 0); }, 0);
}} }}
style={{padding:'6px 12px',border:'1px solid #d9d9d9',borderRadius:6,background:'#fff',fontSize:12,cursor:'pointer'}} style={{ padding: '6px 12px', border: '1px solid #d9d9d9', borderRadius: 6, background: '#fff', fontSize: 12, cursor: 'pointer' }}
> >
返回列表 返回列表
</button> </button>
@ -304,286 +414,198 @@ const CustomerInfoManagement = () => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
{/* KPI卡片区域 */} <div className={styles.mainCard}>
<div className={styles.kpiRow}> {/* KPI卡片区域 */}
<div className={styles.kpiCard}> <div className={styles.kpiGrid}>
<div className={styles.kpiIcon}>👥</div> <div className={styles.kpiCard}>
<div className={styles.kpiValue}>{kpiData.totalCustomers}</div> <div className={styles.kpiText}>
<div className={styles.kpiTitle}>总客户数</div> <div className={styles.kpiValue}>{kpiData.totalCustomers}</div>
</div> <div className={styles.kpiTitle}>总客户数</div>
<div className={styles.kpiCard}> </div>
<div className={styles.kpiIcon}>👑</div> <img src={topIcon} alt="icon" className={styles.kpiIconImg} />
<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>
<div <div className={styles.kpiCard}>
ref={customerTypeChartRef} <div className={styles.kpiText}>
className={styles.chartContainer} <div className={styles.kpiValue}>{kpiData.highValueCustomers}</div>
/> <div className={styles.kpiTitle}>高价值客户</div>
</div> </div>
<img src={topIcon2} alt="icon" className={styles.kpiIconImg} />
{/* 客户价值分布图表 */}
<div className={styles.chartCard}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>客户价值分布</div>
</div> </div>
<div <div className={styles.kpiCard}>
ref={customerValueChartRef} <div className={styles.kpiText}>
className={styles.chartContainer} <div className={styles.kpiValue}>{kpiData.inCooperation}</div>
/> <div className={styles.kpiTitle}>合作中</div>
</div> </div>
<img src={topIcon3} alt="icon" className={styles.kpiIconImg} />
{/* 最近活动列表 */}
<div className={styles.activityCard}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>最近活动</div>
</div> </div>
<div className={styles.activityList}> <div className={styles.kpiCard}>
{recentActivities.map((activity) => ( <div className={styles.kpiText}>
<div key={activity.id} className={styles.activityItem}> <div className={styles.kpiValue}>{kpiData.newThisMonth}</div>
<div className={styles.activityIcon}> <div className={styles.kpiTitle}>本月新增</div>
{activity.id === 1 && '📄'} </div>
{activity.id === 2 && '✅'} <img src={topIcon4} alt="icon" className={styles.kpiIconImg} />
{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>
</div>
{/* 客户列表区域 */} {/* 图表和活动区域 */}
<div className={styles.tableCard}> <div className={styles.chartRow}>
<div className={styles.tableHeader}> {/* 客户类型分布图表 */}
<div className={styles.tableTitle}>客户列表</div> <div className={styles.chartCard} style={{ flex: 9 }}>
</div> <div className={styles.chartHeader}>
<div className={styles.chartTitle}>客户类型分布</div>
<Select
size="small"
className={styles.timeSelect}
defaultValue="全部时间"
options={[
{ value: '全部时间', label: '全部时间' },
{ value: '本月', label: '本月' },
{ value: '本季度', label: '本季度' },
{ value: '本年', label: '本年' },
]}
onChange={(v) => {/* 可根据需要触发筛选 */ }}
/>
</div>
<div
ref={customerTypeChartRef}
className={styles.chartContainer}
/>
</div>
{/* 筛选栏 */} {/* 客户价值分布图表 */}
<div className={styles.filterBar}> <div className={styles.chartCard} style={{ flex: 6 }}>
<div className={styles.filterGroup}> <div className={styles.chartHeader}>
<input <div className={styles.chartTitle}>客户价值分布</div>
type="text" </div>
className={styles.searchInput} <div
placeholder="搜索关键词" ref={customerValueChartRef}
value={searchKeyword} className={styles.chartContainer}
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={() => {
setCustomerType('全部');
setCustomerLevel('全部');
setCooperationStatus('全部');
}}>
🔄 重置
</button>
<button className={styles.addBtn}>
新增客户
</button>
<button className={styles.exportBtn}>
📥 批量导出
</button>
</div> </div>
</div>
{/* 表格 */} {/* 最近活动列表 */}
<div className={styles.tableWrapper}> <div className={styles.activityCard} style={{ flex: 9 }}>
<table className={styles.dataTable}> <div className={styles.chartHeader}>
<thead> <div className={styles.chartTitle}>最近活动</div>
<tr> </div>
<th> <div className={styles.activityGrid}>
<input {recentActivities.map((activity) => (
type="checkbox" <div key={activity.id} className={styles.activityCardItem}>
checked={selectedRows.length === tableData.length} <div className={styles.activityCardHeader}>
onChange={handleSelectAll} <div className={styles.activityCardTitle}>{activity.title}</div>
/> <div className={styles.activityCardTime}>{activity.time}</div>
</th> </div>
<th>客户名称</th> <div className={styles.activityCardDesc}>{activity.description}</div>
<th>联系人</th> </div>
<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}
onClick={() => {
// 记录当前滚动位置
setPrevScrollY(window.pageYOffset || document.documentElement.scrollTop || 0);
setDetailData(row);
setShowDetail(true);
}}
>
查看详情
</button>
<button className={styles.actionBtn}>修改</button>
<button className={`${styles.actionBtn} ${styles.deleteBtn}`}>删除</button>
</div>
</td>
</tr>
))} ))}
</tbody> </div>
</table> </div>
</div> </div>
{/* 分页 */} {/* 客户列表区域 */}
<div className={styles.pagination}> <div className={styles.bottomSection}>
<div className={styles.paginationInfo}> <div className={styles.sectionHeader}>
共85条 <span className={styles.sectionBar} />
<select <span className={styles.sectionTitle}>客户列表</span>
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>
<div className={styles.paginationControls}> <div className={styles.filterContent}>
<button <div className={styles.filterItem}>
className={styles.pageBtn} <Input.Search
disabled={currentPage === 1} placeholder="搜索关键词"
onClick={() => setCurrentPage(currentPage - 1)} allowClear
> value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
</button> onSearch={(val) => setSearchKeyword(val)}
{currentPage > 3 && ( style={{ minWidth: 150 }}
<> />
<button className={styles.pageBtn} onClick={() => setCurrentPage(1)}>1</button> </div>
<span className={styles.pageEllipsis}>...</span> <div className={styles.filterItem}>
</> <span className={styles.filterLabel}>客户类型:</span>
)} <Select
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { value={filters.customerType}
let pageNum; onChange={(v) => setFilters({ ...filters, customerType: v })}
if (currentPage <= 3) { placeholder="全部"
pageNum = i + 1; options={[
} else if (currentPage >= totalPages - 2) { { label: '全部', value: '' },
pageNum = totalPages - 4 + i; ]}
} else { allowClear
pageNum = currentPage - 2 + i; />
} </div>
if (pageNum > totalPages) return null; <div className={styles.filterItem}>
return ( <span className={styles.filterLabel}>客户等级:</span>
<button <Select
key={pageNum} value={filters.customerGrade}
className={`${styles.pageBtn} ${currentPage === pageNum ? styles.pageBtnActive : ''}`} onChange={(v) => setFilters({ ...filters, customerGrade: v })}
onClick={() => setCurrentPage(pageNum)} placeholder="全部"
> options={[
{pageNum} { label: '全部', value: '' },
</button> ]}
); allowClear
})} />
{currentPage < totalPages - 2 && ( </div>
<> <div className={styles.filterItem}>
<span className={styles.pageEllipsis}>...</span> <span className={styles.filterLabel}>合作状态:</span>
<button className={styles.pageBtn} onClick={() => setCurrentPage(totalPages)}>{totalPages}</button> <Select
</> value={filters.cooperationStatus}
)} onChange={(v) => setFilters({ ...filters, cooperationStatus: v })}
<button placeholder="全部"
className={styles.pageBtn} options={[
disabled={currentPage === totalPages} { label: '全部', value: '' },
onClick={() => setCurrentPage(currentPage + 1)} ]}
> allowClear
/>
</button> </div>
<Space className={styles.filterButtons}>
<Button
type="primary"
icon={<SearchOutlined />}
className={styles.queryBtn}
>
查询
</Button>
<Button
icon={<ReloadOutlined />}
className={styles.resetBtn}
>
重置
</Button>
</Space>
<Space className={styles.filterButtonsRight}>
<Button
type="primary"
icon={<PlusOutlined />}
className={styles.addBtn}
>
新增客户
</Button>
<Button
// icon={<ExportOutlined />}
className={styles.exportBtn}
>
批量导出
</Button>
</Space>
</div>
<div className={styles.tableWrapper}>
<StandardTable
rowKey="id"
columns={columns}
data={{
list: tableData,
pagination: {
total: tableData.length,
pageSize,
current: currentPage,
showTotal: (total) => `${total}`,
},
}}
selectionType="checkbox"
selectedRowKeys={selectedRows}
onSelectRow={(row) => handleSelectRow(row.id)}
onSelectAll={(checked) => checked ? setSelectedRows(tableData.map(i => i.id)) : setSelectedRows([])}
onChangePage={(page) => setCurrentPage(page)}
onChangePageSize={(size) => { setPageSize(size); setCurrentPage(1); }}
/>
</div> </div>
</div> </div>
</div> </div>

@ -3,41 +3,81 @@
background: #f5f5f5; background: #f5f5f5;
min-height: 100vh; min-height: 100vh;
// KPI卡片区域 .mainCard {
.kpiRow { background: #fff;
display: flex; border-radius: 0px;
// box-shadow: 0 4px 16px rgba(0,0,0,0.08);
// border: 1px solid #eaeaea;
padding: 16px;
}
// KPI卡片区域改为贴近截图样式
.kpiGrid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px; gap: 16px;
margin-bottom: 20px; margin-bottom: 20px;
.kpiCard { .kpiCard {
flex: 1; display: flex;
background: #fff; align-items: center;
border-radius: 8px; justify-content: space-between;
padding: 24px; padding: 16px 20px;
text-align: center; border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); background: linear-gradient(180deg, #A1DED4 0%, #CBEDE74D 70%);
transition: all 0.3s; // border: 1px solid transparent;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: border-color 0.2s, box-shadow 0.2s;
&.kpiCardActive {
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.2);
}
&:hover { &:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
} }
.kpiIcon { .kpiText {
font-size: 32px; display: flex;
margin-bottom: 12px; flex-direction: column;
gap: 6px;
} }
.kpiValue { .kpiValue {
font-size: 32px; font-size: 24px;
font-weight: 600; font-weight: 600;
color: #1890ff; color: #333;
margin-bottom: 8px;
} }
.kpiTitle { .kpiTitle {
font-size: 14px; font-size: 12px;
color: #666; color: #4E5856;
}
.kpiIconBox {
width: 40px;
height: 40px;
border-radius: 8px;
background: #1D6E6A;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
flex-shrink: 0;
border: 1px solid rgba(0, 0, 0, 0.06);
}
.kpiIcon {
font-size: 20px;
line-height: 1;
}
.kpiIconImg {
width: 40px;
height: 40px;
display: block;
object-fit: contain;
} }
} }
} }
@ -51,364 +91,360 @@
.chartCard, .chartCard,
.activityCard { .activityCard {
flex: 1; flex: 1;
background: #fff; background: #F3F9F8;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #76C2B5;
.chartHeader { .chartHeader {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 8px;
padding-bottom: 12px; padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
.chartTitle { .chartTitle {
position: relative;
padding-left: 10px;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
} }
.chartTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 18px;
background-color: #1D6E6A;
border-radius: 2px;
}
.timeSelect { .timeSelect {
padding: 4px 8px; :global(.ant-select-selector) {
border: 1px solid #d9d9d9; border-color: #2C9E9D !important;
border-radius: 4px; border-radius: 2px;
font-size: 12px; padding: 0 8px;
cursor: pointer; background: transparent !important;
font-size: 13px;
}
&:hover { :global(.ant-select-focused .ant-select-selector),
border-color: #40a9ff; :global(.ant-select-selector:hover) {
border-color: #2C9E9D !important;
box-shadow: 0 0 0 2px rgba(29, 110, 106, 0.15);
} }
} }
} }
.chartContainer { .chartContainer {
height: 300px; height: 200px;
width: 100%; width: 100%;
} }
} }
.activityCard { .activityCard {
.activityList { .activityGrid {
.activityItem { display: grid;
display: flex; grid-template-columns: repeat(2, 1fr);
gap: 12px; gap: 12px;
padding: 12px 0; }
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.activityIcon { .activityCardItem {
width: 40px; background: #fff;
height: 40px; border: 1px solid #D6EEE9;
display: flex; // border-radius: 10px;
align-items: center; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
justify-content: center; padding: 12px 14px;
background: #f5f5f5; }
border-radius: 8px;
font-size: 20px;
flex-shrink: 0;
}
.activityContent { .activityCardHeader {
flex: 1; display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.activityTitle { .activityCardTitle {
font-size: 14px; color: #006665;
font-weight: 500; font-size: 14px;
color: #333; font-weight: 500;
margin-bottom: 4px; }
}
.activityDesc { .activityCardTime {
font-size: 13px; color: #9AA3A1;
color: #666; font-size: 12px;
margin-bottom: 4px; flex-shrink: 0;
} }
.activityTime { .activityCardDesc {
font-size: 12px; color: #4E5856;
color: #999; font-size: 12px;
} line-height: 1.6;
}
}
} }
} }
} }
// 表格区域 // 底部区域 - 客户列表
.tableCard { .bottomSection {
flex: 1;
display: flex;
flex-direction: column;
// border: 1px solid @section-border;
background: #fff; background: #fff;
border-radius: 8px; padding: 5px 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.tableHeader { // 区域头部
margin-bottom: 16px; .sectionHeader {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
// 装饰条
.sectionBar {
width: 2px;
height: 16px;
background: rgba(0, 102, 101, 1);
border-radius: 1px;
}
.tableTitle { // 标题
.sectionTitle {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
} }
} }
.filterBar { // 筛选内容区域
margin-bottom: 16px; .filterContent {
padding: 16px; display: flex;
background: #fafafa; align-items: center;
border-radius: 4px; gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
// justify-content: space-between;
.filterGroup { // 筛选项
.filterItem {
display: flex; display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center; align-items: center;
gap: 12px;
.searchInput { // 筛选标签
padding: 8px 12px; .filterLabel {
border: 1px solid #d9d9d9; color: #333;
border-radius: 4px;
font-size: 14px; font-size: 14px;
width: 200px; }
:global {
&:focus { // 统一 Input.Search 边框颜色为绿主题
outline: none; .ant-input-affix-wrapper {
border-color: #40a9ff; border-radius: 0px !important;
border-color: rgba(44, 158, 157, 1) !important;
} }
}
.filterSelect { .ant-input-search-button {
padding: 8px 12px; border-radius: 0px !important;
border: 1px solid #d9d9d9; border-color: rgba(44, 158, 157, 1) !important;
border-radius: 4px; }
font-size: 14px;
width: 150px;
cursor: pointer;
background: #fff;
&:hover { .ant-select {
border-color: #40a9ff; min-width: 90px;
// 选择框样式
.ant-select-selector {
border-color: rgba(44, 158, 157, 1) !important;
border-radius: 2px !important;
}
&:hover .ant-select-selector {
border-color: rgba(44, 158, 157, 1) !important;
}
&.ant-select-focused .ant-select-selector {
border-color: rgba(44, 158, 157, 1) !important;
box-shadow: 0 0 0 2px rgba(44, 158, 157, 0.2) !important;
}
} }
} }
}
.queryBtn, // 筛选按钮组
.resetBtn, .filterButtons {
.addBtn, display: flex;
.exportBtn { gap: 12px;
padding: 8px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
background: #fff;
transition: all 0.3s;
&:hover { .queryBtn {
border-color: #40a9ff; background-image: url('@/assets/business_basic/Bt_bg1.png');
color: #40a9ff; background-position: center;
} background-repeat: no-repeat;
background-size: cover;
border: none;
}
.resetBtn {
background-image: url('@/assets/business_basic/Bt_bg2.png');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border: none;
color: #006665;
} }
}
// 右侧按钮组
.filterButtonsRight {
display: flex;
gap: 12px;
margin-left: auto;
.queryBtn,
.addBtn { .addBtn {
background: #1890ff; background-image: url('@/assets/business_basic/Bt_bg1.png');
color: #fff; background-position: center;
border-color: #1890ff; background-repeat: no-repeat;
background-size: cover;
border: none;
}
&:hover { .exportBtn {
background: #40a9ff; background-image: url('@/assets/business_basic/Bt_bg2.png');
border-color: #40a9ff; background-position: center;
} background-repeat: no-repeat;
background-size: cover;
border: none;
color: #006665;
} }
} }
} }
// 表格包装器
.tableWrapper { .tableWrapper {
overflow-x: auto; flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
.dataTable { :global(.ant-table-wrapper) {
width: 100%; flex: 1;
border-collapse: collapse; display: flex;
flex-direction: column;
thead { }
background: #fafafa;
// 表头样式
th { :global {
padding: 12px; .ant-table-thead>tr>th {
text-align: left; color: rgba(51, 51, 51, 1) !important;
font-weight: 600; font-weight: 450 !important;
color: #333; background-color: rgba(240, 247, 247, 1) !important;
border-bottom: 2px solid #f0f0f0; text-align: center !important;
white-space: nowrap;
}
} }
tbody { // 表体样式
tr { .ant-table-tbody>tr>td {
border-bottom: 1px solid #f0f0f0; color: rgba(78, 88, 86, 1) !important;
transition: background 0.3s; font-weight: 400 !important;
text-align: center !important;
}
&:hover { // 操作列按钮样式
background: #fafafa; .viewDetailBtn {
} color: rgba(0, 102, 101, 1) !important;
td { &:hover {
padding: 12px; color: rgba(0, 102, 101, 1) !important;
color: #666;
white-space: nowrap;
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.tagGold {
background: #fffbe6;
color: #faad14;
border: 1px solid #ffe58f;
}
.tagBlue {
background: #e6f7ff;
color: #1890ff;
border: 1px solid #91d5ff;
}
.tagGreen {
background: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.tagOrange {
background: #fff7e6;
color: #fa8c16;
border: 1px solid #ffd591;
}
.stars {
display: flex;
gap: 2px;
.starFull {
color: #faad14;
font-size: 16px;
}
.starHalf {
color: #faad14;
font-size: 16px;
opacity: 0.5;
}
.starEmpty {
color: #d9d9d9;
font-size: 16px;
}
}
.actionBtns {
display: flex;
gap: 8px;
.actionBtn {
padding: 4px 8px;
border: none;
background: transparent;
color: #1890ff;
cursor: pointer;
font-size: 12px;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background: #e6f7ff;
}
&.deleteBtn {
color: #ff4d4f;
&:hover {
background: #fff1f0;
}
}
}
}
}
} }
} }
}
}
.pagination { .editBtn {
display: flex; color: rgba(45, 158, 157, 1) !important;
justify-content: space-between;
align-items: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
.paginationInfo {
display: flex;
align-items: center;
gap: 8px;
color: #666;
font-size: 14px;
.pageSizeSelect { &:hover {
padding: 4px 8px; color: rgba(45, 158, 157, 1) !important;
border: 1px solid #d9d9d9; }
border-radius: 4px;
font-size: 14px;
cursor: pointer;
margin-left: 8px;
} }
}
.paginationControls { .deleteBtn {
display: flex; color: rgba(255, 130, 109, 1) !important;
gap: 4px;
align-items: center;
.pageBtn { &:hover {
min-width: 32px; color: rgba(255, 130, 109, 1) !important;
height: 32px; }
padding: 0 8px; }
border: 1px solid #d9d9d9;
border-radius: 4px; // 状态列 Switch 样式
background: #fff; .statusSwitch {
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
&:hover:not(:disabled) { // 启用状态背景色
border-color: #40a9ff; &.ant-switch-checked {
color: #40a9ff; background-color: rgba(20, 106, 89, 1) !important;
} }
&:disabled { // 停用状态背景色
opacity: 0.5; &:not(.ant-switch-checked) {
cursor: not-allowed; background-color: rgba(153, 153, 153, 1) !important;
} }
}
&.pageBtnActive { // 复选框样式
background: #1890ff; .ant-checkbox-inner {
color: #fff; border-color: rgba(0, 102, 101, 1) !important;
border-color: #1890ff; &::after {
background-color: rgba(0, 102, 101, 1) !important;
} }
} }
.ant-checkbox-wrapper:hover .ant-checkbox-inner,
.ant-checkbox:hover .ant-checkbox-inner {
border-color: rgba(0, 102, 101, 1) !important;
}
.pageEllipsis { .ant-checkbox-checked .ant-checkbox-inner {
padding: 0 8px; background-color: rgba(0, 102, 101, 1) !important;
color: #666; border-color: rgba(0, 102, 101, 1) !important;
} }
// 分页样式(绿色主题)
// .ant-pagination {
// .ant-pagination-total-text {
// color: rgba(78, 88, 86, 1) !important;
// }
// .ant-pagination-item {
// border-radius: 2px;
// border-color: rgba(44, 158, 157, 1) !important;
// }
// .ant-pagination-item a {
// color: rgba(78, 88, 86, 1) !important;
// }
// .ant-pagination-item-active {
// border-color: rgba(0, 102, 101, 1) !important;
// background: rgba(240, 247, 247, 1) !important;
// }
// .ant-pagination-item-active a {
// color: rgba(0, 102, 101, 1) !important;
// }
// .ant-pagination-item:hover {
// border-color: rgba(0, 102, 101, 1) !important;
// }
// .ant-pagination-prev .ant-pagination-item-link,
// .ant-pagination-next .ant-pagination-item-link {
// border-radius: 2px;
// border-color: rgba(44, 158, 157, 1) !important;
// color: rgba(78, 88, 86, 1) !important;
// }
// .ant-select-selector {
// border-color: rgba(44, 158, 157, 1) !important;
// border-radius: 2px !important;
// }
// }
} }
} }
} }
} }

@ -175,19 +175,17 @@
:global(.ant-btn:not(.ant-btn-primary)) { :global(.ant-btn:not(.ant-btn-primary)) {
background-color: rgba(183, 229, 213, 0.2) !important; background-color: rgba(183, 229, 213, 0.2) !important;
border: 1px solid transparent !important; border: 1px solid transparent !important;
border-image-source: conic-gradient( border-image-source: conic-gradient(from 102.75deg at 50% 52.91%,
from 102.75deg at 50% 52.91%, rgba(249, 249, 249, 0.5) -32.95deg,
rgba(249, 249, 249, 0.5) -32.95deg, rgba(140, 160, 156, 0.5) 10.52deg,
rgba(140, 160, 156, 0.5) 10.52deg, rgba(140, 160, 156, 0.35) 32.12deg,
rgba(140, 160, 156, 0.35) 32.12deg, rgba(255, 255, 255, 0.5) 60.28deg,
rgba(255, 255, 255, 0.5) 60.28deg, rgba(255, 255, 255, 0.5) 107.79deg,
rgba(255, 255, 255, 0.5) 107.79deg, rgba(140, 160, 156, 0.35) 187.59deg,
rgba(140, 160, 156, 0.35) 187.59deg, #F9F9F9 207.58deg,
#F9F9F9 207.58deg, rgba(255, 255, 255, 0.5) 287.31deg,
rgba(255, 255, 255, 0.5) 287.31deg, rgba(249, 249, 249, 0.5) 327.05deg,
rgba(249, 249, 249, 0.5) 327.05deg, rgba(140, 160, 156, 0.5) 370.52deg) !important;
rgba(140, 160, 156, 0.5) 370.52deg
) !important;
border-image-slice: 1 !important; border-image-slice: 1 !important;
&:hover { &:hover {
@ -308,7 +306,7 @@
// 表头样式 // 表头样式
:global { :global {
.ant-table-thead > tr > th { .ant-table-thead>tr>th {
color: rgba(51, 51, 51, 1) !important; color: rgba(51, 51, 51, 1) !important;
font-weight: 450 !important; font-weight: 450 !important;
background-color: rgba(240, 247, 247, 1) !important; background-color: rgba(240, 247, 247, 1) !important;
@ -316,7 +314,7 @@
} }
// 表体样式 // 表体样式
.ant-table-tbody > tr > td { .ant-table-tbody>tr>td {
color: rgba(78, 88, 86, 1) !important; color: rgba(78, 88, 86, 1) !important;
font-weight: 400 !important; font-weight: 400 !important;
text-align: center !important; text-align: center !important;
@ -349,6 +347,7 @@
// 状态列 Switch 样式 // 状态列 Switch 样式
.statusSwitch { .statusSwitch {
// 启用状态背景色 // 启用状态背景色
&.ant-switch-checked { &.ant-switch-checked {
background-color: rgba(20, 106, 89, 1) !important; background-color: rgba(20, 106, 89, 1) !important;
@ -363,6 +362,10 @@
// 复选框样式 // 复选框样式
.ant-checkbox-inner { .ant-checkbox-inner {
border-color: rgba(0, 102, 101, 1) !important; border-color: rgba(0, 102, 101, 1) !important;
&::after {
background-color: rgba(0, 102, 101, 1) !important;
}
} }
.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox-wrapper:hover .ant-checkbox-inner,

Loading…
Cancel
Save