|
|
|
|
@ -1,12 +1,12 @@
|
|
|
|
|
import React, { useState, useMemo, useLayoutEffect } from 'react';
|
|
|
|
|
import React, { useState, useMemo } from 'react';
|
|
|
|
|
import styles from './BusinessPlanManagement.less';
|
|
|
|
|
import { Select, Space, Button, Input } from 'antd';
|
|
|
|
|
import { Select, Space, Button, Input, DatePicker } 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 topIcon from '@/assets/business_planmanage/jyjhgl1.svg';
|
|
|
|
|
import topIcon2 from '@/assets/business_planmanage/jyjhgl2.svg';
|
|
|
|
|
import topIcon3 from '@/assets/business_planmanage/jyjhgl3.svg';
|
|
|
|
|
import topIcon4 from '@/assets/business_planmanage/jyjhgl4.svg';
|
|
|
|
|
const BusinessPlanManagement = () => {
|
|
|
|
|
const [searchKeyword, setSearchKeyword] = useState('');
|
|
|
|
|
const [selectedRows, setSelectedRows] = useState([]);
|
|
|
|
|
@ -14,126 +14,98 @@ const BusinessPlanManagement = () => {
|
|
|
|
|
const [pageSize, setPageSize] = useState(10);
|
|
|
|
|
// 列表筛选与数据(演示数据,可替换为接口)
|
|
|
|
|
const [filters, setFilters] = useState({
|
|
|
|
|
customerType: '',
|
|
|
|
|
customerGrade: '',
|
|
|
|
|
cooperationStatus: '',
|
|
|
|
|
transportMethod: '',
|
|
|
|
|
transportStatus: '',
|
|
|
|
|
dateRange: [],
|
|
|
|
|
});
|
|
|
|
|
// 新增:详情页面切换状态
|
|
|
|
|
const [showDetail, setShowDetail] = useState(false);
|
|
|
|
|
const [detailData, setDetailData] = useState(null);
|
|
|
|
|
const [prevScrollY, setPrevScrollY] = useState(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// KPI数据
|
|
|
|
|
const kpiData = {
|
|
|
|
|
totalCustomers: 389,
|
|
|
|
|
highValueCustomers: 180,
|
|
|
|
|
inCooperation: 360,
|
|
|
|
|
newThisMonth: 8,
|
|
|
|
|
};
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 返回列表时恢复滚动位置,确保布局稳定后再滚动
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
if (!showDetail) {
|
|
|
|
|
// 等待本帧布局完成
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
window.scrollTo(0, prevScrollY || 0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [showDetail]);
|
|
|
|
|
// KPI数据
|
|
|
|
|
const kpiData = [
|
|
|
|
|
{ title: '本月计划总数', value: 156, icon: topIcon },
|
|
|
|
|
{ title: '待审核计划', value: 110, icon: topIcon2 },
|
|
|
|
|
{ title: '执行中计划', value: 21, icon: topIcon3 },
|
|
|
|
|
{ title: '已完成计划', value: 25, icon: topIcon4 },
|
|
|
|
|
// { title: '异常计划', value: 6, icon: topIcon2 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 表格数据
|
|
|
|
|
const tableData = [
|
|
|
|
|
{
|
|
|
|
|
id: 'CUST-2023-001',
|
|
|
|
|
customerName: '中国石化销售股份有限公司',
|
|
|
|
|
contact: '钱亚男',
|
|
|
|
|
phone: '18901563341',
|
|
|
|
|
classification: '高价值',
|
|
|
|
|
monthlyAmount: 1250000,
|
|
|
|
|
cooperationStatus: '合作中',
|
|
|
|
|
satisfaction: 4.5,
|
|
|
|
|
planNo: 'PLN-20230512-001',
|
|
|
|
|
planName: '6月首批入库计划',
|
|
|
|
|
oilType: '92#汽油',
|
|
|
|
|
quantity: 5000,
|
|
|
|
|
transportMethod: '管道运输',
|
|
|
|
|
planDate: '2023-06-01',
|
|
|
|
|
supplier: '中石化华东分公司',
|
|
|
|
|
status: '已确认',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'CUST-2023-002',
|
|
|
|
|
customerName: '中石化华东分公司',
|
|
|
|
|
contact: '郑宇雅',
|
|
|
|
|
phone: '15341731282',
|
|
|
|
|
classification: '常规客户',
|
|
|
|
|
monthlyAmount: 1250000,
|
|
|
|
|
cooperationStatus: '合作中',
|
|
|
|
|
satisfaction: 4.0,
|
|
|
|
|
planNo: 'PLN-20230510-024',
|
|
|
|
|
planName: '7月公路到货计划',
|
|
|
|
|
oilType: '0#柴油',
|
|
|
|
|
quantity: 2500,
|
|
|
|
|
transportMethod: '公路运输',
|
|
|
|
|
planDate: '2023-07-21',
|
|
|
|
|
supplier: '山东恒燃实业集团',
|
|
|
|
|
status: '审核中',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'CUST-2023-003',
|
|
|
|
|
customerName: '海南石油贸易有限公司',
|
|
|
|
|
contact: '孙向明',
|
|
|
|
|
phone: '13252257033',
|
|
|
|
|
classification: '高价值',
|
|
|
|
|
monthlyAmount: 850000,
|
|
|
|
|
cooperationStatus: '合作中',
|
|
|
|
|
satisfaction: 4.5,
|
|
|
|
|
planNo: 'PLN-20230508-017',
|
|
|
|
|
planName: '8月铁路运输计划',
|
|
|
|
|
oilType: '航空煤油',
|
|
|
|
|
quantity: 8000,
|
|
|
|
|
transportMethod: '铁路运输',
|
|
|
|
|
planDate: '2023-08-13',
|
|
|
|
|
supplier: '中原储能科技公司',
|
|
|
|
|
status: '已拒绝',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'CUST-2023-004',
|
|
|
|
|
customerName: '东莞石化有限公司',
|
|
|
|
|
contact: '何思颖',
|
|
|
|
|
phone: '18931788771',
|
|
|
|
|
classification: '高价值',
|
|
|
|
|
monthlyAmount: 980000,
|
|
|
|
|
cooperationStatus: '合作中',
|
|
|
|
|
satisfaction: 4.5,
|
|
|
|
|
planNo: 'PLN-20230505-012',
|
|
|
|
|
planName: '9月船舶运输计划',
|
|
|
|
|
oilType: '燃料油',
|
|
|
|
|
quantity: 15000,
|
|
|
|
|
transportMethod: '船舶运输',
|
|
|
|
|
planDate: '2023-09-22',
|
|
|
|
|
supplier: '中海油南方公司',
|
|
|
|
|
status: '已完成',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'CUST-2023-005',
|
|
|
|
|
customerName: '中国石油化工集团有限公司',
|
|
|
|
|
contact: '钱佳仪',
|
|
|
|
|
phone: '13743378254',
|
|
|
|
|
classification: '常规客户',
|
|
|
|
|
monthlyAmount: 980000,
|
|
|
|
|
cooperationStatus: '暂停合作',
|
|
|
|
|
satisfaction: 4.0,
|
|
|
|
|
planNo: 'PLN-20230428-008',
|
|
|
|
|
planName: '10月管道入库计划',
|
|
|
|
|
oilType: '95#汽油',
|
|
|
|
|
quantity: 6500,
|
|
|
|
|
transportMethod: '管道运输',
|
|
|
|
|
planDate: '2023-10-30',
|
|
|
|
|
supplier: '中石化华北公司',
|
|
|
|
|
status: '已确认',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 列配置(用于 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: 'planNo', key: 'planNo', width: 160 },
|
|
|
|
|
{ title: '计划名称', dataIndex: 'planName', key: 'planName', width: 200 },
|
|
|
|
|
{ title: '油品类型', dataIndex: 'oilType', key: 'oilType', width: 120 },
|
|
|
|
|
{
|
|
|
|
|
title: '交易额度(月)', dataIndex: 'monthlyAmount', key: 'monthlyAmount', width: 150,
|
|
|
|
|
render: (v) => `¥${Number(v).toLocaleString()}`
|
|
|
|
|
title: '数量(吨)', dataIndex: 'quantity', key: 'quantity', width: 120,
|
|
|
|
|
render: (v) => Number(v).toLocaleString()
|
|
|
|
|
},
|
|
|
|
|
{ title: '运输方式', dataIndex: 'transportMethod', key: 'transportMethod', width: 120 },
|
|
|
|
|
{ title: '计划日期', dataIndex: 'planDate', key: 'planDate', width: 140 },
|
|
|
|
|
{ title: '供应商/客户', dataIndex: 'supplier', key: 'supplier', width: 200 },
|
|
|
|
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 110 },
|
|
|
|
|
{
|
|
|
|
|
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,
|
|
|
|
|
title: '操作', key: 'action', fixed: 'right', width: 160,
|
|
|
|
|
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>
|
|
|
|
|
<Button type="link" size="small" className="viewDetailBtn">查看详情</Button>
|
|
|
|
|
<Button type="link" size="small" className="editBtn">编辑</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
@ -141,53 +113,39 @@ const BusinessPlanManagement = () => {
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 处理选择
|
|
|
|
|
const handleSelectRow = (id) => {
|
|
|
|
|
if (selectedRows.includes(id)) {
|
|
|
|
|
setSelectedRows(selectedRows.filter(rowId => rowId !== id));
|
|
|
|
|
const handleSelectRow = (planNo) => {
|
|
|
|
|
if (selectedRows.includes(planNo)) {
|
|
|
|
|
setSelectedRows(selectedRows.filter(rowId => rowId !== planNo));
|
|
|
|
|
} else {
|
|
|
|
|
setSelectedRows([...selectedRows, id]);
|
|
|
|
|
setSelectedRows([...selectedRows, planNo]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
setSearchKeyword('');
|
|
|
|
|
setFilters({ transportMethod: '', transportStatus: '', dateRange: [] });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles.container}>
|
|
|
|
|
<div className={styles.mainCard}>
|
|
|
|
|
{/* KPI卡片区域 */}
|
|
|
|
|
<div className={styles.kpiGrid}>
|
|
|
|
|
<div className={styles.kpiCard}>
|
|
|
|
|
<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.kpiText}>
|
|
|
|
|
<div className={styles.kpiValue}>{kpiData.highValueCustomers}</div>
|
|
|
|
|
<div className={styles.kpiTitle}>高价值客户</div>
|
|
|
|
|
{kpiData.map((item) => (
|
|
|
|
|
<div className={styles.kpiCard} key={item.title}>
|
|
|
|
|
<div className={styles.kpiText}>
|
|
|
|
|
<div className={styles.kpiValue}>{item.value}</div>
|
|
|
|
|
<div className={styles.kpiTitle}>{item.title}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<img src={item.icon} alt="icon" className={styles.kpiIconImg} />
|
|
|
|
|
</div>
|
|
|
|
|
<img src={topIcon2} alt="icon" className={styles.kpiIconImg} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.kpiCard}>
|
|
|
|
|
<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.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.bottomSection}>
|
|
|
|
|
<div className={styles.sectionHeader}>
|
|
|
|
|
<span className={styles.sectionBar} />
|
|
|
|
|
<span className={styles.sectionTitle}>客户列表</span>
|
|
|
|
|
<span className={styles.sectionTitle}>查询条件</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.filterContent}>
|
|
|
|
|
<div className={styles.filterItem}>
|
|
|
|
|
@ -197,43 +155,48 @@ const BusinessPlanManagement = () => {
|
|
|
|
|
value={searchKeyword}
|
|
|
|
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
|
|
|
|
onSearch={(val) => setSearchKeyword(val)}
|
|
|
|
|
style={{ minWidth: 150 }}
|
|
|
|
|
style={{ minWidth: 200 }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.filterItem}>
|
|
|
|
|
<span className={styles.filterLabel}>客户类型:</span>
|
|
|
|
|
<span className={styles.filterLabel}>运输方式:</span>
|
|
|
|
|
<Select
|
|
|
|
|
value={filters.customerType}
|
|
|
|
|
onChange={(v) => setFilters({ ...filters, customerType: v })}
|
|
|
|
|
value={filters.transportMethod}
|
|
|
|
|
onChange={(v) => setFilters({ ...filters, transportMethod: v })}
|
|
|
|
|
placeholder="全部"
|
|
|
|
|
options={[
|
|
|
|
|
{ label: '全部', value: '' },
|
|
|
|
|
{ label: '管道运输', value: '管道运输' },
|
|
|
|
|
{ label: '公路运输', value: '公路运输' },
|
|
|
|
|
{ label: '铁路运输', value: '铁路运输' },
|
|
|
|
|
{ label: '船舶运输', value: '船舶运输' },
|
|
|
|
|
]}
|
|
|
|
|
allowClear
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.filterItem}>
|
|
|
|
|
<span className={styles.filterLabel}>客户等级:</span>
|
|
|
|
|
<span className={styles.filterLabel}>运输状态:</span>
|
|
|
|
|
<Select
|
|
|
|
|
value={filters.customerGrade}
|
|
|
|
|
onChange={(v) => setFilters({ ...filters, customerGrade: v })}
|
|
|
|
|
value={filters.transportStatus}
|
|
|
|
|
onChange={(v) => setFilters({ ...filters, transportStatus: v })}
|
|
|
|
|
placeholder="全部"
|
|
|
|
|
options={[
|
|
|
|
|
{ label: '全部', value: '' },
|
|
|
|
|
{ label: '待审核', value: '待审核' },
|
|
|
|
|
{ label: '执行中', value: '执行中' },
|
|
|
|
|
{ label: '已完成', value: '已完成' },
|
|
|
|
|
{ label: '已拒绝', value: '已拒绝' },
|
|
|
|
|
]}
|
|
|
|
|
allowClear
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.filterItem}>
|
|
|
|
|
<span className={styles.filterLabel}>合作状态:</span>
|
|
|
|
|
<Select
|
|
|
|
|
value={filters.cooperationStatus}
|
|
|
|
|
onChange={(v) => setFilters({ ...filters, cooperationStatus: v })}
|
|
|
|
|
placeholder="全部"
|
|
|
|
|
options={[
|
|
|
|
|
{ label: '全部', value: '' },
|
|
|
|
|
]}
|
|
|
|
|
allowClear
|
|
|
|
|
<span className={styles.filterLabel}>日期范围:</span>
|
|
|
|
|
<RangePicker
|
|
|
|
|
value={filters.dateRange}
|
|
|
|
|
onChange={(val) => setFilters({ ...filters, dateRange: val || [] })}
|
|
|
|
|
style={{ minWidth: 240 }}
|
|
|
|
|
placeholder={['年/月/日', '年/月/日']}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Space className={styles.filterButtons}>
|
|
|
|
|
@ -247,29 +210,35 @@ const BusinessPlanManagement = () => {
|
|
|
|
|
<Button
|
|
|
|
|
icon={<ReloadOutlined />}
|
|
|
|
|
className={styles.resetBtn}
|
|
|
|
|
onClick={handleReset}
|
|
|
|
|
>
|
|
|
|
|
重置
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.listHeader}>
|
|
|
|
|
<div className={styles.sectionHeader}>
|
|
|
|
|
<span className={styles.sectionBar} />
|
|
|
|
|
<span className={styles.sectionTitle}>经营计划列表</span>
|
|
|
|
|
</div>
|
|
|
|
|
<Space className={styles.filterButtonsRight}>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
className={styles.addBtn}
|
|
|
|
|
>
|
|
|
|
|
新增客户
|
|
|
|
|
新增计划
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
// icon={<ExportOutlined />}
|
|
|
|
|
className={styles.exportBtn}
|
|
|
|
|
>
|
|
|
|
|
批量导出
|
|
|
|
|
<Button className={styles.exportBtn}>
|
|
|
|
|
导出
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.tableWrapper}>
|
|
|
|
|
<StandardTable
|
|
|
|
|
rowKey="id"
|
|
|
|
|
rowKey="planNo"
|
|
|
|
|
columns={columns}
|
|
|
|
|
data={{
|
|
|
|
|
list: tableData,
|
|
|
|
|
@ -282,8 +251,8 @@ const BusinessPlanManagement = () => {
|
|
|
|
|
}}
|
|
|
|
|
selectionType="checkbox"
|
|
|
|
|
selectedRowKeys={selectedRows}
|
|
|
|
|
onSelectRow={(row) => handleSelectRow(row.id)}
|
|
|
|
|
onSelectAll={(checked) => checked ? setSelectedRows(tableData.map(i => i.id)) : setSelectedRows([])}
|
|
|
|
|
onSelectRow={(row) => handleSelectRow(row.planNo)}
|
|
|
|
|
onSelectAll={(checked) => checked ? setSelectedRows(tableData.map(i => i.planNo)) : setSelectedRows([])}
|
|
|
|
|
onChangePage={(page) => setCurrentPage(page)}
|
|
|
|
|
onChangePageSize={(size) => { setPageSize(size); setCurrentPage(1); }}
|
|
|
|
|
/>
|
|
|
|
|
|