Compare commits

...

5 Commits

@ -0,0 +1,21 @@
<svg width="224" height="127" viewBox="0 0 224 127" fill="none" xmlns="http://www.w3.org/2000/svg">
<foreignObject x="-3" y="-9" width="234" height="137"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(7.5px);clip-path:url(#bgblur_0_3804_30382_clip_path);height:100%;width:100%"></div></foreignObject><g filter="url(#filter0_d_3804_30382)" data-figma-bg-blur-radius="15">
<mask id="path-1-inside-1_3804_30382" fill="white">
<path d="M12 10C12 7.79086 13.7909 6 16 6H212C214.209 6 216 7.79086 216 10V109C216 111.209 214.209 113 212 113H16C13.7909 113 12 111.209 12 109V10Z"/>
</mask>
<path d="M12 10C12 7.79086 13.7909 6 16 6H212C214.209 6 216 7.79086 216 10V109C216 111.209 214.209 113 212 113H16C13.7909 113 12 111.209 12 109V10Z" fill="#F0F7F7" fill-opacity="0.5"/>
<g clip-path="url(#paint0_angular_3804_30382_clip_path)" data-figma-skip-parse="true" mask="url(#path-1-inside-1_3804_30382)"><g transform="matrix(-0.028937 0.0441283 -0.116795 -0.00833659 119.61 47.4625)"><foreignObject x="-1608.22" y="-1608.22" width="3216.45" height="3216.45"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(60, 137, 136, 0.5) 0deg,rgba(0, 102, 101, 0.5) 10.5206deg,rgba(0, 102, 101, 0.35) 32.1212deg,rgba(255, 255, 255, 1) 60.2813deg,rgba(255, 255, 255, 0.5) 107.788deg,rgba(0, 102, 101, 0.35) 187.591deg,rgba(249, 249, 249, 1) 207.58deg,rgba(255, 255, 255, 1) 287.308deg,rgba(249, 249, 249, 0.5) 327.047deg,rgba(60, 137, 136, 0.5) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path d="M16 6V7H212V6V5H16V6ZM216 10H215V109H216H217V10H216ZM212 113V112H16V113V114H212V113ZM12 109H13V10H12H11V109H12ZM16 113V112C14.3431 112 13 110.657 13 109H12H11C11 111.761 13.2386 114 16 114V113ZM216 109H215C215 110.657 213.657 112 212 112V113V114C214.761 114 217 111.761 217 109H216ZM212 6V7C213.657 7 215 8.34315 215 10H216H217C217 7.23858 214.761 5 212 5V6ZM16 6V5C13.2386 5 11 7.23858 11 10H12H13C13 8.34315 14.3431 7 16 7V6Z" data-figma-gradient-fill="{&#34;type&#34;:&#34;GRADIENT_ANGULAR&#34;,&#34;stops&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.50},&#34;position&#34;:0.029223963618278503},{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.34999999403953552},&#34;position&#34;:0.089225620031356812},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.16744795441627502},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:0.50},&#34;position&#34;:0.29941025376319885},{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.34999999403953552},&#34;position&#34;:0.52108556032180786},{&#34;color&#34;:{&#34;r&#34;:0.97664386034011841,&#34;g&#34;:0.97664386034011841,&#34;b&#34;:0.97664386034011841,&#34;a&#34;:1.0},&#34;position&#34;:0.57661086320877075},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.79807692766189575},{&#34;color&#34;:{&#34;r&#34;:0.97647058963775635,&#34;g&#34;:0.97647058963775635,&#34;b&#34;:0.97647058963775635,&#34;a&#34;:0.50},&#34;position&#34;:0.90846514701843262}],&#34;stopsVar&#34;:[{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.50},&#34;position&#34;:0.029223963618278503},{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.34999999403953552},&#34;position&#34;:0.089225620031356812},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.16744795441627502},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:0.50},&#34;position&#34;:0.29941025376319885},{&#34;color&#34;:{&#34;r&#34;:0.0,&#34;g&#34;:0.40000000596046448,&#34;b&#34;:0.39607843756675720,&#34;a&#34;:0.34999999403953552},&#34;position&#34;:0.52108556032180786},{&#34;color&#34;:{&#34;r&#34;:0.97664386034011841,&#34;g&#34;:0.97664386034011841,&#34;b&#34;:0.97664386034011841,&#34;a&#34;:1.0},&#34;position&#34;:0.57661086320877075},{&#34;color&#34;:{&#34;r&#34;:1.0,&#34;g&#34;:1.0,&#34;b&#34;:1.0,&#34;a&#34;:1.0},&#34;position&#34;:0.79807692766189575},{&#34;color&#34;:{&#34;r&#34;:0.97647058963775635,&#34;g&#34;:0.97647058963775635,&#34;b&#34;:0.97647058963775635,&#34;a&#34;:0.50},&#34;position&#34;:0.90846514701843262}],&#34;transform&#34;:{&#34;m00&#34;:-57.873920440673828,&#34;m01&#34;:-233.59097290039062,&#34;m02&#34;:265.34243774414062,&#34;m10&#34;:88.256553649902344,&#34;m11&#34;:-16.673183441162109,&#34;m12&#34;:11.670814514160156},&#34;opacity&#34;:1.0,&#34;blendMode&#34;:&#34;NORMAL&#34;,&#34;visible&#34;:true}" mask="url(#path-1-inside-1_3804_30382)"/>
</g>
<defs>
<filter id="filter0_d_3804_30382" x="-3" y="-9" width="234" height="137" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-2" dy="4"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.568627 0 0 0 0 0.568627 0 0 0 0 0.568627 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3804_30382"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3804_30382" result="shape"/>
</filter>
<clipPath id="bgblur_0_3804_30382_clip_path" transform="translate(3 9)"><path d="M12 10C12 7.79086 13.7909 6 16 6H212C214.209 6 216 7.79086 216 10V109C216 111.209 214.209 113 212 113H16C13.7909 113 12 111.209 12 109V10Z"/>
</clipPath><clipPath id="paint0_angular_3804_30382_clip_path"><path d="M16 6V7H212V6V5H16V6ZM216 10H215V109H216H217V10H216ZM212 113V112H16V113V114H212V113ZM12 109H13V10H12H11V109H12ZM16 113V112C14.3431 112 13 110.657 13 109H12H11C11 111.761 13.2386 114 16 114V113ZM216 109H215C215 110.657 213.657 112 212 112V113V114C214.761 114 217 111.761 217 109H216ZM212 6V7C213.657 7 215 8.34315 215 10H216H217C217 7.23858 214.761 5 212 5V6ZM16 6V5C13.2386 5 11 7.23858 11 10H12H13C13 8.34315 14.3431 7 16 7V6Z" mask="url(#path-1-inside-1_3804_30382)"/></clipPath></defs>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7.5" cy="7.5" r="7.5" fill="#C3E7E1"/>
<circle cx="7.5" cy="7.5" r="3.5" fill="#006665"/>
</svg>

After

Width:  |  Height:  |  Size: 205 B

@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import * as echarts from 'echarts';
import styles from './CustomerInfoManagement.less';
import CustomerInfoManagementDetail from './CustomerInfoManagementDetail';
const CustomerInfoManagement = () => {
const [searchKeyword, setSearchKeyword] = useState('');
@ -10,6 +11,10 @@ const CustomerInfoManagement = () => {
const [selectedRows, setSelectedRows] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
// 新增:详情页面切换状态
const [showDetail, setShowDetail] = useState(false);
const [detailData, setDetailData] = useState(null);
const [prevScrollY, setPrevScrollY] = useState(0);
// 图表引用
const customerTypeChartRef = useRef(null);
@ -183,7 +188,7 @@ const CustomerInfoManagement = () => {
// 表格数据
const tableData = [
{
id: '1',
id: 'CUST-2023-001',
customerName: '中国石化销售股份有限公司',
contact: '钱亚男',
phone: '18901563341',
@ -193,7 +198,7 @@ const CustomerInfoManagement = () => {
satisfaction: 4.5,
},
{
id: '2',
id: 'CUST-2023-002',
customerName: '中石化华东分公司',
contact: '郑宇雅',
phone: '15341731282',
@ -203,7 +208,7 @@ const CustomerInfoManagement = () => {
satisfaction: 4.0,
},
{
id: '3',
id: 'CUST-2023-003',
customerName: '海南石油贸易有限公司',
contact: '孙向明',
phone: '13252257033',
@ -213,7 +218,7 @@ const CustomerInfoManagement = () => {
satisfaction: 4.5,
},
{
id: '4',
id: 'CUST-2023-004',
customerName: '东莞石化有限公司',
contact: '何思颖',
phone: '18931788771',
@ -223,7 +228,7 @@ const CustomerInfoManagement = () => {
satisfaction: 4.5,
},
{
id: '5',
id: 'CUST-2023-005',
customerName: '中国石油化工集团有限公司',
contact: '钱佳仪',
phone: '13743378254',
@ -273,6 +278,29 @@ const CustomerInfoManagement = () => {
// 计算总页数
const totalPages = Math.ceil(85 / pageSize);
// 当进入详情模式时,直接渲染详情页并提供返回按钮
if (showDetail) {
return (
<div className={styles.container}>
<div style={{marginBottom:10}}>
<button
className={styles.backBtn}
onClick={() => {
setShowDetail(false);
// 恢复滚动位置
setTimeout(() => {
window.scrollTo(0, prevScrollY);
}, 0);
}}
style={{padding:'6px 12px',border:'1px solid #d9d9d9',borderRadius:6,background:'#fff',fontSize:12,cursor:'pointer'}}
>
返回列表
</button>
</div>
<CustomerInfoManagementDetail data={detailData} />
</div>
);
}
return (
<div className={styles.container}>
@ -402,7 +430,6 @@ const CustomerInfoManagement = () => {
🔍 查询
</button>
<button className={styles.resetBtn} onClick={() => {
setSearchKeyword('');
setCustomerType('全部');
setCustomerLevel('全部');
setCooperationStatus('全部');
@ -471,7 +498,17 @@ const CustomerInfoManagement = () => {
</td>
<td>
<div className={styles.actionBtns}>
<button className={styles.actionBtn}>查看详情</button>
<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>

@ -1,5 +1,5 @@
.container {
padding: 20px;
padding: 10px 5px;
background: #f5f5f5;
min-height: 100vh;

@ -0,0 +1,101 @@
import React, { useState } from 'react';
import { Select } from 'antd';
import styles from './CustomerInfoManagementDetail.less';
import BasicInfo from './second_customer_components/BasicInfo';
import BusinessInfo from './second_customer_components/BusinessInfo';
import LogisticsInfo from './second_customer_components/LogisticsInfo';
import CooperateRecord from './second_customer_components/CooperateRecord';
const CustomerInfoManagementDetail = ({ data }) => {
// 顶部一行选择:第一个为“基本信息/理化性质”,其余占位
const [firstMenu, setFirstMenu] = useState('basic');
const [secondMenu, setSecondMenu] = useState(undefined);
const [thirdMenu, setThirdMenu] = useState(undefined);
const [fourthMenu, setFourthMenu] = useState(undefined);
const [fifthMenu, setFifthMenu] = useState(undefined);
const firstMenuLabelMap = {
basic: '基础信息',
business: '业务信息',
logistics: '物流信息',
cooperate: '合作记录',
};
const renderFirstMenuContent = () => {
switch (firstMenu) {
case 'basic':
return <BasicInfo data={data} />;
case 'business':
return <BusinessInfo data={data} />;
case 'logistics':
return <LogisticsInfo data={data} />;
case 'cooperate':
return <CooperateRecord data={data} />;
default:
return null;
}
};
return (
<div className={styles.container}>
{/* 客户基本信息展示 */}
{!data ? (
<div className={styles.emptyInfo}>
暂无客户数据请从列表页选择一个客户查看详情
</div>
) : (
<div className={styles.detailHeader}>
<div className={styles.detailTitle}>{data?.customerName ?? '-'}</div>
<div className={styles.detailSubTitle}>客户ID{data?.customerId ?? data?.id ?? '-'}</div>
</div>
)}
<div className={styles.topBar}>
<div className={styles.currentLabel}>
当前{firstMenuLabelMap[firstMenu] || '未选择'}
</div>
<div className={styles.selectsWrapper}>
<Select
className={styles.topSelect}
value={firstMenu}
onChange={setFirstMenu}
options={[
{ label: '基础信息', value: 'basic' },
{ label: '业务信息', value: 'business' },
{ label: '物流信息', value: 'logistics' },
{ label: '合作记录', value: 'cooperate' },
]}
/>
<Select
className={styles.topSelect}
value={secondMenu}
onChange={setSecondMenu}
placeholder="业务信息"
options={[]}
disabled
/>
<Select
className={styles.topSelect}
value={thirdMenu}
onChange={setThirdMenu}
placeholder="物流信息"
options={[]}
disabled
/>
<Select
className={styles.topSelect}
value={fourthMenu}
onChange={setFourthMenu}
placeholder="合作记录"
options={[]}
disabled
/>
</div>
</div>
{renderFirstMenuContent()}
</div>
);
};
export default CustomerInfoManagementDetail;

@ -0,0 +1,169 @@
.container {
min-height: 100%;
.topBar {
display: flex;
height: 46px;
margin-bottom: 16px;
.currentLabel {
border-top-left-radius: 20px;
height: 100%;
width: 230px;
display: flex;
align-items: center;
padding-left: 22px;
color: #fff;
font-size: 17px;
font-weight: 450;
background: pink;
background-image: url('@/assets/business_basic/bgOil.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center center;
}
.selectsWrapper {
height: 100%;
// width: 230px;
background-color: #fff;
border: 1px solid transparent;
border-image-source: linear-gradient(96.54deg, #FFFFFF -0.94%, rgba(255, 255, 255, 0) 25.28%, rgba(167, 229, 228, 0) 59.69%, #A7E5E4 79.76%);
border-image-slice: 1;
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.topSelect {
width: 130px;
display: flex;
align-items: center;
text-align: center;
box-shadow: none;
:global(.ant-select-selector) {
border: transparent !important;
background-image: url('@/assets/business_basic/Union.svg');
background-repeat: no-repeat;
background-position: center center;
font-size: 12px;
color: rgba(0, 102, 101, 1);
background-size: 100% 100%;
box-shadow: none !important;
}
:global(.ant-select-focused .ant-select-selector),
:global(.ant-select-selector:hover) {
border: transparent !important;
box-shadow: none !important;
}
:global(.ant-select-arrow) {
right: 25px;
color: rgba(0, 102, 101, 1);
}
}
}
.section {
margin-bottom: 16px;
.blockTitle {
font-size: 16px;
font-weight: 600;
color: #2f4f4f;
margin-bottom: 12px;
}
.formGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 16px;
grid-row-gap: 12px;
.formItem {
display: flex;
flex-direction: column;
.label {
color: #666;
font-size: 12px;
margin-bottom: 6px;
}
}
.span2 {
grid-column: 1 / span 2;
}
}
.actionsRight {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
.listHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.filterRow {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 12px;
.filterItem {
display: flex;
align-items: center;
gap: 8px;
.filterLabel {
color: #666;
font-size: 12px;
white-space: nowrap;
}
}
}
}
.detailHeader {
margin: 0 0 16px 0;
padding: 12px 20px;
background: #fff;
border: 1px solid #eaeef2;
border-radius: 8px;
box-shadow: 0 0 0 1px rgba(240, 240, 240, 0.6) inset;
.detailTitle {
font-size: 16px;
line-height: 28px;
color: #333333;
font-weight: 600;
margin-bottom: 6px;
}
.detailSubTitle {
font-size: 12px;
line-height: 20px;
color: #666666;
}
}
.emptyInfo {
margin-bottom: 16px;
padding: 12px 20px;
background: #fffbe6;
border: 1px solid #ffe58f;
border-radius: 8px;
color: #614700;
}
}

@ -0,0 +1,67 @@
import React from 'react';
import styles from './BasicInfo.less';
const BasicInfo = ({ data }) => {
const mockData = {
// 基本信息
companyName: '中国石化销售股份有限公司',
contactName: '张经理',
address: '北京市朝阳区朝阳门北大街22号',
creditCode: '9110000100012345X',
email: 'zhang@sinopec.com',
legalEntity: '中国石油化工股份有限公司',
customerId: 'CUST-001',
phone: '13800138000',
// 交易信息
annualPurchase: 15680000,
paymentTerm: '30 天',
cooperationStart: '2020-03-15',
paymentMethod: '银行转账',
lastTransaction: '2023-10-28',
creditRating: 'A+'
}
const safe = (v) => (v ?? '-')
const currency = (n) => {
if (typeof n === 'number') return `¥${n.toLocaleString()}`;
if (typeof n === 'string') return n;
return '-';
}
return (
<div className={styles.container}>
<div className={styles.section}>
<div className={styles.sectionTitle}>基本信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>公司名称</span><span className={styles.value}>{safe(mockData?.companyName)}</span></div>
<div className={styles.item}><span className={styles.label}>联系人</span><span className={styles.value}>{safe(mockData?.contactName)}</span></div>
<div className={styles.item}><span className={styles.label}>地址</span><span className={styles.value}>{safe(mockData?.address)}</span></div>
<div className={styles.item}><span className={styles.label}>统一信用代码</span><span className={styles.value}>{safe(mockData?.creditCode)}</span></div>
<div className={styles.item}><span className={styles.label}>电子邮箱</span><span className={styles.value}>{safe(mockData?.email)}</span></div>
<div className={styles.item}><span className={styles.label}>法定实体</span><span className={styles.value}>{safe(mockData?.legalEntity)}</span></div>
<div className={styles.item}><span className={styles.label}>客户 ID</span><span className={styles.value}>{safe(mockData?.customerId)}</span></div>
<div className={styles.item}><span className={styles.label}>联系电话</span><span className={styles.value}>{safe(mockData?.phone)}</span></div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>交易信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>年度采购额</span><span className={styles.value}>{currency(mockData?.annualPurchase)}</span></div>
<div className={styles.item}><span className={styles.label}>账期</span><span className={styles.value}>{safe(mockData?.paymentTerm)}</span></div>
<div className={styles.item}><span className={styles.label}>合作起始</span><span className={styles.value}>{safe(mockData?.cooperationStart)}</span></div>
<div className={styles.item}><span className={styles.label}>支付方式</span><span className={styles.value}>{safe(mockData?.paymentMethod)}</span></div>
<div className={styles.item}><span className={styles.label}>最近交易</span><span className={styles.value}>{safe(mockData?.lastTransaction)}</span></div>
<div className={styles.item}><span className={styles.label}>信用等级</span><span className={styles.value}>{safe(mockData?.creditRating)}</span></div>
</div>
</div>
</div>
);
};
export default BasicInfo;

@ -0,0 +1,54 @@
.container {
width: 100%;
margin: 0 auto;
padding: 24px 32px;
background: #fff;
}
.section {
margin-bottom: 24px;
}
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
position: relative;
padding-left: 8px;
}
.sectionTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 18px;
background-color: #006665; /* 蓝色竖线 */
border-radius: 2px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 48px;
grid-row-gap: 16px;
}
.item {
display: flex;
align-items: baseline;
}
.label {
color: #666666;
font-size: 12px;
min-width: 96px;
}
.value {
color: #333333;
font-size: 12px;
}

@ -0,0 +1,89 @@
import React, { useMemo } from 'react';
import styles from './BusinessInfo.less';
import StandardTable from '@/components/StandardTable';
const BusinessInfo = ({ data }) => {
const mockData = {
// 合同信息
currentContract: 'CONTRACT-2023-001',
contractPeriod: '2023-01-01 至 2023-12-31',
paymentTerms: '月结30天',
contractAmount: 18000000,
// 财务信息
creditLimit: 2000000,
taxRegistrationNo: '91110000100012345X',
bankAccount: '0200 0045 0910 1234 567',
bankName: '中国工商银行北京分行'
}
const safe = (v) => (v ?? '-')
const currency = (n) => {
if (typeof n === 'number') return `¥${n.toLocaleString()}`;
if (typeof n === 'string') return n;
return '-';
}
// 近期交易记录 - 列定义与数据(参考 second_oil_components/BasicInfo.less 的表格样式)
const columns = useMemo(() => ([
{ title: '订单号', dataIndex: 'orderId', key: 'orderId', width: 160 },
{ title: '日期', dataIndex: 'date', key: 'date', width: 200 },
{ title: '产品', dataIndex: 'product', key: 'product', width: 140 },
{ title: '数量(吨)', dataIndex: 'quantity', key: 'quantity', width: 120 },
{ title: '金额(元)', dataIndex: 'amount', key: 'amount', width: 140, render: (val) => val.toLocaleString() },
]), []);
const tableData = [
{ key: '1', orderId: 'ORDER-2023-1025', date: '2025-11-15 20:02:14', product: '92#汽油', quantity: 500, amount: 3850000 },
{ key: '2', orderId: 'ORDER-2023-1012', date: '2025-11-15 18:22:58', product: '92#汽油', quantity: 200, amount: 2050000 },
{ key: '3', orderId: 'ORDER-2023-1005', date: '2025-11-15 15:38:26', product: '95#汽油', quantity: 300, amount: 1620000 },
{ key: '4', orderId: 'ORDER-2023-1021', date: '2025-11-10 10:42:08', product: '92#汽油', quantity: 500, amount: 3850000 },
];
return (
<div className={styles.container}>
<div className={styles.section}>
<div className={styles.sectionTitle}>合同信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>当前合同</span><span className={styles.value}>{safe(mockData?.currentContract)}</span></div>
<div className={styles.item}><span className={styles.label}>合同有效期</span><span className={styles.value}>{safe(mockData?.contractPeriod)}</span></div>
<div className={styles.item}><span className={styles.label}>付款条款</span><span className={styles.value}>{safe(mockData?.paymentTerms)}</span></div>
<div className={styles.item}><span className={styles.label}>合同金额</span><span className={styles.value}>{currency(mockData?.contractAmount)}</span></div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>财务信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>信用额度</span><span className={styles.value}>{currency(mockData?.creditLimit)}</span></div>
<div className={styles.item}><span className={styles.label}>税务登记号</span><span className={styles.value}>{safe(mockData?.taxRegistrationNo)}</span></div>
<div className={styles.item}><span className={styles.label}>开户银行</span><span className={styles.value}>{safe(mockData?.bankName)}</span></div>
<div className={styles.item}><span className={styles.label}>银行账户</span><span className={styles.value}>{safe(mockData?.bankAccount)}</span></div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>近期交易记录</div>
<div className={styles.tableWrapper}>
<StandardTable
rowKey="key"
columns={columns}
data={{
list: tableData,
pagination: {
total: tableData.length,
pageSize: 10,
current: 1,
showTotal: (total) => `${total}`,
},
}}
/>
</div>
</div>
</div>
);
};
export default BusinessInfo;

@ -0,0 +1,142 @@
.container {
width: 100%;
margin: 0 auto;
padding: 24px 32px;
background: #fff;
}
.section {
margin-bottom: 24px;
}
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
position: relative;
padding-left: 8px;
}
.sectionTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 18px;
background-color: #006665;
/* 蓝色竖线 */
border-radius: 2px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 48px;
grid-row-gap: 16px;
}
.item {
display: flex;
align-items: baseline;
}
.label {
color: #666666;
font-size: 12px;
min-width: 96px;
}
.value {
color: #333333;
font-size: 12px;
}
// 表格包装器
.tableWrapper {
width: 60%;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
:global(.ant-table-wrapper) {
flex: 1;
display: flex;
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;
}
// 表体样式
.ant-table-tbody>tr>td {
color: rgba(78, 88, 86, 1) !important;
font-weight: 400 !important;
text-align: center !important;
}
// 操作列按钮样式
.viewDetailBtn {
color: rgba(0, 102, 101, 1) !important;
&:hover {
color: rgba(0, 102, 101, 1) !important;
}
}
.editBtn {
color: rgba(45, 158, 157, 1) !important;
&:hover {
color: rgba(45, 158, 157, 1) !important;
}
}
.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;
}
.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;
}
}
}

@ -0,0 +1,67 @@
import React from 'react';
import { Progress, Steps } from 'antd';
import stepsIcon from '@/assets/business_basic/steps_icon.svg';
import styles from './CooperateRecord.less';
const CooperateRecord = ({ data }) => {
const perfGrade = { level: 'A 级', score: 95 };
const satisfactionCards = [
{ score: 4.8, label: '产品质量' },
{ score: 4.6, label: '交付及时性' },
{ score: 4.7, label: '客户服务' },
{ score: 4.5, label: '价格合理性' },
];
const history = [
{ date: '2025-07-16', text: '完成500吨92#汽油交付,客户反馈良好' },
{ date: '2025-03-24', text: '参加客户组织的供应商大会,获得“优秀供应商”称号' },
{ date: '2025-02-11', text: '客户满意度调查综合得分4.7分' },
{ date: '2024-08-29', text: '签订年度框架合作协议合同金额5000万元' },
];
return (
<div className={styles.container}>
<div className={styles.section}>
<div className={styles.sectionTitle}>合作绩效评级</div>
<div className={styles.perfWrap}>
<div className={styles.perfRow}>
<div className={styles.perfLevel}>{perfGrade.level}</div>
<div className={styles.perfBarHolder}>
<Progress percent={perfGrade.score} showInfo={false} strokeColor={{ from: 'rgba(0, 102, 101, 1)', to: 'rgba(0, 102, 101, 1)' }} />
</div>
<div className={styles.perfScore}>{perfGrade.score} </div>
</div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>满意度调查结果</div>
<div className={styles.satisfactionGrid}>
{satisfactionCards.map((c, i) => (
<div key={i} className={styles.card}>
<div className={styles.cardScore}>{c.score}</div>
<div className={styles.cardLabel}>{c.label}</div>
</div>
))}
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>合作历史记录</div>
<div className={styles.historyStepsWrap}>
<Steps
direction="vertical"
progressDot={(dot, { status, index }) => (
<img src={stepsIcon} alt="step" style={{ width: 15, height: 15 }} />
)}
items={history.map((s) => ({
title: <span className={styles.historyDate}>{s.date}</span>,
description: <span className={styles.historyText}>{s.text}</span>,
}))}
/>
</div>
</div>
</div>
);
};
export default CooperateRecord;

@ -0,0 +1,211 @@
.container {
width: 100%;
margin: 0 auto;
padding: 24px 32px;
background: #fff;
box-sizing: border-box;
}
.section {
margin-bottom: 24px;
}
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
position: relative;
padding-left: 8px;
}
.sectionTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 18px;
background-color: #006665;
/* 蓝色竖线 */
border-radius: 2px;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-column-gap: 48px;
grid-row-gap: 16px;
max-width: 100%;
}
.item {
display: flex;
align-items: baseline;
max-width: 100%;
}
.label {
color: #666666;
font-size: 12px;
min-width: 96px;
}
.value {
color: #333333;
font-size: 12px;
}
/* 合作绩效评级 */
.perfWrap {
display: flex;
flex-direction: column;
gap: 8px;
}
.perfRow {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
}
.perfBarHolder {
flex: 1;
}
.perfLevel {
color: #666;
}
.perfBar {
width: 100%;
height: 10px;
background: rgba(0, 0, 0, 0.06);
border-radius: 5px;
position: relative;
}
.perfBarFill {
height: 100%;
background: rgba(0, 102, 101, 1);
border-radius: 5px;
}
.perfScore {
color: #333;
white-space: nowrap;
}
/* 满意度调查结果 */
.satisfactionGrid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
.card {
border-radius: 4px;
padding: 16px 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
max-width: 100%;
background-image: url('@/assets/business_basic/cooperate_bg.svg');
background-repeat: no-repeat;
background-position: center;
background-size: contain; // 完整显示背景
min-height: 120px; // 保证背景有足够展示空间
// aspect-ratio: 224 / 127; // 与SVG比例一致
position: relative;
box-sizing: border-box;
}
.card > * { position: relative; z-index: 1; }
.cardScore {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.cardLabel {
font-size: 12px;
color: #666;
word-break: break-word;
overflow-wrap: anywhere;
}
/* 合作历史记录 */
.historyList {
display: block;
}
.historyItem {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
}
.historyDate {
color: #333333;
font-weight: 600;
}
.historyText {
color: #666666;
}
/* Steps 样式适配 */
.historyStepsWrap {
padding-left: 10px;
:global {
.ant-steps-item-title {
color: #333333;
font-weight: 600;
}
.ant-steps-item-description {
color: #666666;
}
.ant-steps-item-icon .ant-steps-icon {
color: rgba(0, 102, 101, 1);
}
.ant-steps-item-tail::after {
background-color: rgba(0, 0, 0, 0.06);
}
/* progressDot 使用自定义图片时的尺寸与对齐 */
.ant-steps-item-icon {
width: 15px;
height: 15px;
margin-top: 3px; /* 再下移 1px让基线更齐 */
display: flex;
align-items: center;
justify-content: center;
}
.ant-steps-item-tail {
left: 7.5px; /* 居中对齐 15px 节点 */
top: 7.5px; /* 让尾线从圆点正中开始,紧贴节点 */
}
.ant-steps-item-tail::after {
width: 1px; /* 尾线更细,贴合视觉 */
border-radius: 0; /* 直线效果 */
}
/* 调整进度条高度与圆角 */
.ant-progress {
margin-top: 4px;
}
.ant-progress-bg {
height: 10px !important;
border-radius: 5px !important;
}
.ant-progress-inner {
border-radius: 5px !important;
background: rgba(0, 0, 0, 0.06);
}
}
}

@ -0,0 +1,74 @@
import React, { useMemo } from 'react';
import styles from './LogisticsInfo.less';
const LogisticsInfo = ({ data }) => {
const mockData = {
// 承运商信息
approvedCarriers: '中远物流、顺丰速运、德邦物流',
vehicleRequirement: '危险品运输专用车辆',
driverQualification: '危险品运输资格证',
// 交付信息
preferredDeliveryLocation: '北京市大兴区亦庄开发区',
deliveryTime: '工作日 9:00-17:00',
receiverContact: '李主管(13900139001)',
// 资质证书
hazardousChemBusinessLicense: '已上传(有效期至2025-12-31)',
safetyProductionLicense: '已上传(有效期至2024-06-30)'
}
const safe = (v) => (v ?? '-')
const currency = (n) => {
if (typeof n === 'number') return `¥${n.toLocaleString()}`;
if (typeof n === 'string') return n;
return '-';
}
// 近期交易记录 - 列定义与数据(参考 second_oil_components/BasicInfo.less 的表格样式)
const columns = useMemo(() => ([
{ title: '订单号', dataIndex: 'orderId', key: 'orderId', width: 160 },
{ title: '日期', dataIndex: 'date', key: 'date', width: 200 },
{ title: '产品', dataIndex: 'product', key: 'product', width: 140 },
{ title: '数量(吨)', dataIndex: 'quantity', key: 'quantity', width: 120 },
{ title: '金额(元)', dataIndex: 'amount', key: 'amount', width: 140, render: (val) => val.toLocaleString() },
]), []);
const tableData = [
{ key: '1', orderId: 'ORDER-2023-1025', date: '2025-11-15 20:02:14', product: '92#汽油', quantity: 500, amount: 3850000 },
{ key: '2', orderId: 'ORDER-2023-1012', date: '2025-11-15 18:22:58', product: '92#汽油', quantity: 200, amount: 2050000 },
{ key: '3', orderId: 'ORDER-2023-1005', date: '2025-11-15 15:38:26', product: '95#汽油', quantity: 300, amount: 1620000 },
{ key: '4', orderId: 'ORDER-2023-1021', date: '2025-11-10 10:42:08', product: '92#汽油', quantity: 500, amount: 3850000 },
];
return (
<div className={styles.container}>
<div className={styles.section}>
<div className={styles.sectionTitle}>承运商信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>核准承运商</span><span className={styles.value}>{safe(mockData?.approvedCarriers)}</span></div>
<div className={styles.item}><span className={styles.label}>车辆要求</span><span className={styles.value}>{safe(mockData?.vehicleRequirement)}</span></div>
<div className={styles.item}><span className={styles.label}>司机资质</span><span className={styles.value}>{safe(mockData?.driverQualification)}</span></div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>交付信息</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>首选交付地点</span><span className={styles.value}>{safe(mockData?.preferredDeliveryLocation)}</span></div>
<div className={styles.item}><span className={styles.label}>交付时间</span><span className={styles.value}>{safe(mockData?.deliveryTime)}</span></div>
<div className={styles.item}><span className={styles.label}>接收联系人</span><span className={styles.value}>{safe(mockData?.receiverContact)}</span></div>
</div>
</div>
<div className={styles.section}>
<div className={styles.sectionTitle}>资质证书</div>
<div className={styles.grid}>
<div className={styles.item}><span className={styles.label}>危险化学品经营许可证</span><span className={styles.value}>{safe(mockData?.hazardousChemBusinessLicense)}</span></div>
<div className={styles.item}><span className={styles.label}>安全生产许可证</span><span className={styles.value}>{safe(mockData?.safetyProductionLicense)}</span></div>
</div>
</div>
</div>
);
};
export default LogisticsInfo;

@ -0,0 +1,55 @@
.container {
width: 100%;
margin: 0 auto;
padding: 24px 32px;
background: #fff;
}
.section {
margin-bottom: 24px;
}
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
position: relative;
padding-left: 8px;
}
.sectionTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 18px;
background-color: #006665;
/* 蓝色竖线 */
border-radius: 2px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 48px;
grid-row-gap: 16px;
}
.item {
display: flex;
align-items: baseline;
}
.label {
color: #666666;
font-size: 12px;
min-width: 96px;
}
.value {
color: #333333;
font-size: 12px;
}
Loading…
Cancel
Save