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

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 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';
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 [filters, setFilters] = useState({
customerType: '',
customerGrade: '',
cooperationStatus: '',
});
// 新增:详情页面切换状态
const [showDetail, setShowDetail] = useState(false);
const [detailData, setDetailData] = useState(null);
@ -32,21 +41,27 @@ const CustomerInfoManagement = () => {
// 客户类型分布图表配置
const customerTypeChartOption = {
title: {
text: '客户类型分布',
left: 'left',
textStyle: {
fontSize: 16,
fontWeight: 600,
},
},
// title: {
// text: '客户类型分布',
// left: 'left',
// textStyle: {
// fontSize: 16,
// fontWeight: 600,
// color: '#333',
// },
// },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#e0e0e0',
borderWidth: 1,
textStyle: { color: '#333' },
},
grid: {
top: 16,
left: '3%',
right: '4%',
bottom: '3%',
@ -57,23 +72,44 @@ const CustomerInfoManagement = () => {
data: ['客户', '供应商', '第3周', '第4周'],
axisLabel: {
rotate: 0,
color: '#4E5856',
},
axisLine: {
lineStyle: { color: '#C9E3DE' },
},
axisTick: { show: false },
},
yAxis: {
type: 'value',
max: 100,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: '#4E5856' },
splitLine: {
show: true,
lineStyle: { color: '#E9F3F1', type: 'dashed' },
},
},
series: [
{
name: '数量',
type: 'bar',
data: [56, 32, 85, 15],
barWidth: 26,
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: {
show: true,
position: 'top',
color: '#4E5856',
},
},
],
@ -81,81 +117,113 @@ const CustomerInfoManagement = () => {
// 客户价值分布环形图配置
const customerValueChartOption = {
title: {
text: '客户价值分布',
left: 'left',
textStyle: {
fontSize: 16,
fontWeight: 600,
},
},
// 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',
right: '6%',
top: '32%',
itemWidth: 16,
itemHeight: 4,
itemGap: 16,
textStyle: {
fontSize: 12,
color: '#666',
fontWeight: 'normal'
}
},
series: [
{
name: '客户价值',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
radius: ['40%', '65%'],
center: ['30%', '50%'],
// avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
// borderRadius: 10,
// borderColor: '#fff',
// borderWidth: 2,
},
label: {
show: false,
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
},
},
// 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' } },
{ value: 180, name: '高价值客户', itemStyle: { color: '#5CB3FF' } },
{ value: 120, name: '中等客户', itemStyle: { color: '#FFDE73' } },
{ value: 89, name: '小客户', itemStyle: { color: '#A990EA' } },
],
},
],
};
// 初始化图表
// 图表初始化与销毁:根据详情模式切换,返回列表后重新初始化
useEffect(() => {
// 初始化客户类型分布图表
if (!showDetail) {
if (customerTypeChartRef.current) {
customerTypeChartInstance.current = echarts.init(customerTypeChartRef.current);
customerTypeChartInstance.current.setOption(customerTypeChartOption);
// 额外一次 resize确保初始化后尺寸正确
requestAnimationFrame(() => {
customerTypeChartInstance.current?.resize();
});
}
// 初始化客户价值分布图表
if (customerValueChartRef.current) {
customerValueChartInstance.current = echarts.init(customerValueChartRef.current);
customerValueChartInstance.current.setOption(customerValueChartOption);
// 额外一次 resize确保初始化后尺寸正确
requestAnimationFrame(() => {
customerValueChartInstance.current?.resize();
});
}
// 响应式调整
const handleResize = () => {
customerTypeChartInstance.current?.resize();
customerValueChartInstance.current?.resize();
};
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
customerTypeChartInstance.current?.dispose();
customerTypeChartInstance.current = null;
customerValueChartInstance.current?.dispose();
customerValueChartInstance.current = null;
};
}, []);
} else {
// 进入详情时,释放实例以避免无容器绑定
customerTypeChartInstance.current?.dispose();
customerTypeChartInstance.current = null;
customerValueChartInstance.current?.dispose();
customerValueChartInstance.current = null;
}
}, [showDetail]);
// 返回列表时恢复滚动位置,确保布局稳定后再滚动
useLayoutEffect(() => {
if (!showDetail) {
// 等待本帧布局完成
requestAnimationFrame(() => {
window.scrollTo(0, prevScrollY || 0);
});
}
}, [showDetail]);
// 最近活动数据
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) => {
if (selectedRows.includes(id)) {
@ -304,42 +414,57 @@ const CustomerInfoManagement = () => {
return (
<div className={styles.container}>
<div className={styles.mainCard}>
{/* KPI卡片区域 */}
<div className={styles.kpiRow}>
<div className={styles.kpiGrid}>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>👥</div>
<div className={styles.kpiText}>
<div className={styles.kpiValue}>{kpiData.totalCustomers}</div>
<div className={styles.kpiTitle}>总客户数</div>
</div>
<img src={topIcon} alt="icon" className={styles.kpiIconImg} />
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>👑</div>
<div className={styles.kpiText}>
<div className={styles.kpiValue}>{kpiData.highValueCustomers}</div>
<div className={styles.kpiTitle}>高价值客户</div>
</div>
<img src={topIcon2} alt="icon" className={styles.kpiIconImg} />
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}>🤝</div>
<div className={styles.kpiText}>
<div className={styles.kpiValue}>{kpiData.inCooperation}</div>
<div className={styles.kpiTitle}>合作中</div>
</div>
<img src={topIcon3} alt="icon" className={styles.kpiIconImg} />
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiIcon}></div>
<div className={styles.kpiText}>
<div className={styles.kpiValue}>{kpiData.newThisMonth}</div>
<div className={styles.kpiTitle}>本月新增</div>
</div>
<img src={topIcon4} alt="icon" className={styles.kpiIconImg} />
</div>
</div>
{/* 图表和活动区域 */}
<div className={styles.chartRow}>
{/* 客户类型分布图表 */}
<div className={styles.chartCard}>
<div className={styles.chartCard} style={{ flex: 9 }}>
<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>
<Select
size="small"
className={styles.timeSelect}
defaultValue="全部时间"
options={[
{ value: '全部时间', label: '全部时间' },
{ value: '本月', label: '本月' },
{ value: '本季度', label: '本季度' },
{ value: '本年', label: '本年' },
]}
onChange={(v) => {/* 可根据需要触发筛选 */ }}
/>
</div>
<div
ref={customerTypeChartRef}
@ -348,7 +473,7 @@ const CustomerInfoManagement = () => {
</div>
{/* 客户价值分布图表 */}
<div className={styles.chartCard}>
<div className={styles.chartCard} style={{ flex: 6 }}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>客户价值分布</div>
</div>
@ -359,24 +484,18 @@ const CustomerInfoManagement = () => {
</div>
{/* 最近活动列表 */}
<div className={styles.activityCard}>
<div className={styles.activityCard} style={{ flex: 9 }}>
<div className={styles.chartHeader}>
<div className={styles.chartTitle}>最近活动</div>
</div>
<div className={styles.activityList}>
<div className={styles.activityGrid}>
{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 key={activity.id} className={styles.activityCardItem}>
<div className={styles.activityCardHeader}>
<div className={styles.activityCardTitle}>{activity.title}</div>
<div className={styles.activityCardTime}>{activity.time}</div>
</div>
<div className={styles.activityCardDesc}>{activity.description}</div>
</div>
))}
</div>
@ -384,206 +503,109 @@ const CustomerInfoManagement = () => {
</div>
{/* 客户列表区域 */}
<div className={styles.tableCard}>
<div className={styles.tableHeader}>
<div className={styles.tableTitle}>客户列表</div>
<div className={styles.bottomSection}>
<div className={styles.sectionHeader}>
<span className={styles.sectionBar} />
<span className={styles.sectionTitle}>客户列表</span>
</div>
{/* 筛选栏 */}
<div className={styles.filterBar}>
<div className={styles.filterGroup}>
<input
type="text"
className={styles.searchInput}
<div className={styles.filterContent}>
<div className={styles.filterItem}>
<Input.Search
placeholder="搜索关键词"
allowClear
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
onSearch={(val) => setSearchKeyword(val)}
style={{ minWidth: 150 }}
/>
<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 className={styles.tableWrapper}>
<table className={styles.dataTable}>
<thead>
<tr>
<th>
<input
type="checkbox"
checked={selectedRows.length === tableData.length}
onChange={handleSelectAll}
<div className={styles.filterItem}>
<span className={styles.filterLabel}>客户类型:</span>
<Select
value={filters.customerType}
onChange={(v) => setFilters({ ...filters, customerType: v })}
placeholder="全部"
options={[
{ label: '全部', value: '' },
]}
allowClear
/>
</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}
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 className={styles.filterItem}>
<span className={styles.filterLabel}>客户等级:</span>
<Select
value={filters.customerGrade}
onChange={(v) => setFilters({ ...filters, customerGrade: v })}
placeholder="全部"
options={[
{ label: '全部', value: '' },
]}
allowClear
/>
</div>
</td>
</tr>
))}
</tbody>
</table>
<div className={styles.filterItem}>
<span className={styles.filterLabel}>合作状态:</span>
<Select
value={filters.cooperationStatus}
onChange={(v) => setFilters({ ...filters, cooperationStatus: v })}
placeholder="全部"
options={[
{ label: '全部', value: '' },
]}
allowClear
/>
</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);
}}
<Space className={styles.filterButtons}>
<Button
type="primary"
icon={<SearchOutlined />}
className={styles.queryBtn}
>
<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>
<Button
icon={<ReloadOutlined />}
className={styles.resetBtn}
>
</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)}
重置
</Button>
</Space>
<Space className={styles.filterButtonsRight}>
<Button
type="primary"
icon={<PlusOutlined />}
className={styles.addBtn}
>
{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>
<Button
// icon={<ExportOutlined />}
className={styles.exportBtn}
>
</button>
批量导出
</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>

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

Loading…
Cancel
Save