Compare commits

..

3 Commits

Author SHA1 Message Date
tikkhun 236d0a85bf refactor(product): site skus 的后端进行了分表重构,前端跟随修改。 2026-01-24 10:31:01 +00:00
tikkhun ec57d7c476 fix(Statistics/Sales): 将列标题从'产品名称'改为'产品sku' 2026-01-24 10:31:01 +00:00
tikkhun bd6a2a1509 feat(地图): 添加加拿大和澳大利亚地图组件及路由配置
添加两个新的地图组件用于展示加拿大和澳大利亚的省级地图数据
修复 ProFromSelect 拼写错误为 ProFormSelect
2026-01-24 10:31:01 +00:00
10 changed files with 27109 additions and 14 deletions

View File

@ -0,0 +1,46 @@
## 实现计划
### 1. 地图数据准备
- 获取加拿大和澳大利亚的省级地图数据JSON格式
- 将地图数据文件添加到项目的`public`目录下,命名为`canada.json`和`australia.json`
### 2. 组件实现
- 为加拿大创建地图组件:`/Users/zksu/Developer/work/workcode/WEB/src/pages/Area/Canada/index.tsx`
- 为澳大利亚创建地图组件:`/Users/zksu/Developer/work/workcode/WEB/src/pages/Area/Australia/index.tsx`
- 组件将基于现有的`Map/index.tsx`实现,修改地图数据加载和配置
### 3. 数据结构设计
- 定义省级数据接口,包含:
- 英文名称(用于匹配地图数据)
- 中文名称
- 简称
- 人口数据
### 4. 地图渲染配置
- 使用ECharts的Map组件渲染省级地图
- 配置tooltip显示中文名称、简称和人口数据
- 添加视觉映射组件,根据人口数据显示不同颜色
### 5. 数据获取与处理
- 定义本地数据结构,包含各个省的中文名称、简称和人口
- 将数据转换为ECharts需要的格式
### 6. 组件优化
- 添加加载状态
- 处理地图数据加载失败的情况
- 优化地图交互体验,如缩放、拖拽等
### 7. 实现步骤
1. 准备地图数据文件
2. 创建加拿大地图组件
3. 创建澳大利亚地图组件
4. 配置地图数据和样式
5. 添加省级数据
6. 测试地图渲染和交互
### 技术要点
- 使用ECharts的MapChart组件
- 动态加载地图JSON数据
- 数据映射和转换
- Tooltip自定义格式化
- 视觉映射配置

View File

@ -60,6 +60,16 @@ export default defineConfig({
path: '/area/map', path: '/area/map',
component: './Area/Map', component: './Area/Map',
}, },
{
name: '加拿大地图',
path: '/area/canada',
component: './Area/Canada',
},
{
name: '澳大利亚地图',
path: '/area/australia',
component: './Area/Australia',
},
], ],
}, },
{ {
@ -170,7 +180,7 @@ export default defineConfig({
component: './Product/Permutation', component: './Product/Permutation',
}, },
{ {
name: '产品品牌空间', name: '产品聚合空间',
path: '/product/groupBy', path: '/product/groupBy',
component: './Product/GroupBy', component: './Product/GroupBy',
}, },

117
public/australia.json Normal file

File diff suppressed because one or more lines are too long

26628
public/canada.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,149 @@
import { Spin, message } from 'antd';
import ReactECharts from 'echarts-for-react';
import { MapChart } from 'echarts/charts';
import { TooltipComponent, VisualMapComponent } from 'echarts/components';
import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import React, { useEffect, useState } from 'react';
// 注册 ECharts 组件
echarts.use([TooltipComponent, VisualMapComponent, MapChart, CanvasRenderer]);
interface ProvinceData {
name: string; // 英文名称(用于匹配地图)
chineseName: string; // 中文名称
shortName: string; // 简称
population: number; // 人口数据
}
const AustraliaMap: React.FC = () => {
const [option, setOption] = useState({});
const [loading, setLoading] = useState(true);
// 澳大利亚省级数据
const provinceData: ProvinceData[] = [
{ name: 'New South Wales', chineseName: '新南威尔士州', shortName: 'NSW', population: 8166369 },
{ name: 'Victoria', chineseName: '维多利亚州', shortName: 'VIC', population: 6704352 },
{ name: 'Queensland', chineseName: '昆士兰州', shortName: 'QLD', population: 5265049 },
{ name: 'Western Australia', chineseName: '西澳大利亚州', shortName: 'WA', population: 2885548 },
{ name: 'South Australia', chineseName: '南澳大利亚州', shortName: 'SA', population: 1806604 },
{ name: 'Tasmania', chineseName: '塔斯马尼亚州', shortName: 'TAS', population: 569825 },
{ name: 'Australian Capital Territory', chineseName: '澳大利亚首都领地', shortName: 'ACT', population: 453349 },
{ name: 'Northern Territory', chineseName: '北领地', shortName: 'NT', population: 249072 },
];
useEffect(() => {
const fetchAndSetMapData = async () => {
try {
// 加载澳大利亚地图数据
const australiaMapResponse = await fetch('/australia.json');
const australiaMap = await australiaMapResponse.json();
echarts.registerMap('australia', australiaMap);
// 将省级数据转换为 ECharts 需要的格式
const mapData = provinceData.map((province) => ({
name: province.name,
value: province.population,
chineseName: province.chineseName,
shortName: province.shortName,
population: province.population,
}));
// 配置 ECharts 地图选项
const mapOption = {
tooltip: {
trigger: 'item',
formatter: (params: any) => {
if (params.data) {
return `
<div>
<div><strong>${params.data.chineseName}</strong> (${params.data.shortName})</div>
<div>人口: ${params.data.population.toLocaleString()}</div>
</div>
`;
}
return `${params.name}`;
},
},
visualMap: {
left: 'left',
min: Math.min(...provinceData.map(p => p.population)),
max: Math.max(...provinceData.map(p => p.population)),
inRange: {
color: ['#f0f0f0', '#52c41a', '#389e0d'],
},
calculable: true,
orient: 'vertical',
left: 'right',
top: 'center',
text: ['人口多', '人口少'],
},
series: [
{
name: 'Australia States',
type: 'map',
map: 'australia',
roam: true,
nameProperty: 'STATE_NAME', // 指定地图数据中用于匹配的字段
label: {
show: true,
formatter: (params: any) => {
if (params.data) {
return `${params.data.shortName}\n${params.data.chineseName}\n${params.data.population.toLocaleString()}`;
}
return params.name;
},
fontSize: 12,
color: '#333',
fontWeight: 'bold',
},
emphasis: {
label: {
show: true,
},
itemStyle: {
areaColor: '#ffc107',
},
},
data: mapData,
},
],
};
setOption(mapOption);
} catch (error: any) {
message.error(`加载地图数据失败: ${error.message}`);
} finally {
setLoading(false);
}
};
fetchAndSetMapData();
}, []);
if (loading) {
return (
<Spin
size="large"
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '80vh',
}}
/>
);
}
return (
<ReactECharts
echarts={echarts}
option={option}
style={{ height: '80vh', width: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
);
};
export default AustraliaMap;

View File

@ -0,0 +1,153 @@
import { Spin, message } from 'antd';
import ReactECharts from 'echarts-for-react';
import { MapChart } from 'echarts/charts';
import { TooltipComponent, VisualMapComponent } from 'echarts/components';
import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import React, { useEffect, useState } from 'react';
// 注册 ECharts 组件
echarts.use([TooltipComponent, VisualMapComponent, MapChart, CanvasRenderer]);
interface ProvinceData {
name: string; // 英文名称(用于匹配地图)
chineseName: string; // 中文名称
shortName: string; // 简称
population: number; // 人口数据
}
const CanadaMap: React.FC = () => {
const [option, setOption] = useState({});
const [loading, setLoading] = useState(true);
// 加拿大省级数据
const provinceData: ProvinceData[] = [
{ name: 'British Columbia', chineseName: '不列颠哥伦比亚省', shortName: 'BC', population: 5147712 },
{ name: 'Alberta', chineseName: '阿尔伯塔省', shortName: 'AB', population: 4413146 },
{ name: 'Saskatchewan', chineseName: '萨斯喀彻温省', shortName: 'SK', population: 1181666 },
{ name: 'Manitoba', chineseName: '曼尼托巴省', shortName: 'MB', population: 1383765 },
{ name: 'Ontario', chineseName: '安大略省', shortName: 'ON', population: 14711827 },
{ name: 'Quebec', chineseName: '魁北克省', shortName: 'QC', population: 8501833 },
{ name: 'New Brunswick', chineseName: '新不伦瑞克省', shortName: 'NB', population: 789225 },
{ name: 'Nova Scotia', chineseName: '新斯科舍省', shortName: 'NS', population: 971395 },
{ name: 'Prince Edward Island', chineseName: '爱德华王子岛省', shortName: 'PE', population: 160302 },
{ name: 'Newfoundland and Labrador', chineseName: '纽芬兰与拉布拉多省', shortName: 'NL', population: 521365 },
{ name: 'Yukon', chineseName: '育空地区', shortName: 'YT', population: 43985 },
{ name: 'Northwest Territories', chineseName: '西北地区', shortName: 'NT', population: 45515 },
{ name: 'Nunavut', chineseName: '努纳武特地区', shortName: 'NU', population: 39430 },
];
useEffect(() => {
const fetchAndSetMapData = async () => {
try {
// 加载加拿大地图数据
const canadaMapResponse = await fetch('/canada.json');
const canadaMap = await canadaMapResponse.json();
echarts.registerMap('canada', canadaMap);
// 将省级数据转换为 ECharts 需要的格式
const mapData = provinceData.map((province) => ({
name: province.name,
value: province.population,
chineseName: province.chineseName,
shortName: province.shortName,
population: province.population,
}));
// 配置 ECharts 地图选项
const mapOption = {
tooltip: {
trigger: 'item',
formatter: (params: any) => {
if (params.data) {
return `
<div>
<div><strong>${params.data.chineseName}</strong> (${params.data.shortName})</div>
<div>人口: ${params.data.population.toLocaleString()}</div>
</div>
`;
}
return `${params.name}`;
},
},
visualMap: {
left: 'left',
min: Math.min(...provinceData.map(p => p.population)),
max: Math.max(...provinceData.map(p => p.population)),
inRange: {
color: ['#f0f0f0', '#1890ff', '#096dd9'],
},
calculable: true,
orient: 'vertical',
left: 'right',
top: 'center',
text: ['人口多', '人口少'],
},
series: [
{
name: 'Canada Provinces',
type: 'map',
map: 'canada',
roam: true,
label: {
show: true,
formatter: (params: any) => {
if (params.data) {
return `${params.data.shortName}\n${params.data.chineseName}\n${params.data.population.toLocaleString()}`;
}
return params.name;
},
fontSize: 12,
color: '#333',
fontWeight: 'bold',
},
emphasis: {
label: {
show: true,
},
itemStyle: {
areaColor: '#ffc107',
},
},
data: mapData,
},
],
};
setOption(mapOption);
} catch (error: any) {
message.error(`加载地图数据失败: ${error.message}`);
} finally {
setLoading(false);
}
};
fetchAndSetMapData();
}, []);
if (loading) {
return (
<Spin
size="large"
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '80vh',
}}
/>
);
}
return (
<ReactECharts
echarts={echarts}
option={option}
style={{ height: '80vh', width: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
);
};
export default CanadaMap;

View File

@ -238,7 +238,7 @@ const ProductGroupBy: React.FC = () => {
const { Title, Text } = Typography; const { Title, Text } = Typography;
return ( return (
<PageContainer title="品牌空间"> <PageContainer title="聚合空间">
<div style={{ padding: '16px', background: '#fff' }}> <div style={{ padding: '16px', background: '#fff' }}>
{/* Filter Section */} {/* Filter Section */}
<div style={{ marginBottom: '24px' }}> <div style={{ marginBottom: '24px' }}>

View File

@ -35,7 +35,6 @@ const EditForm: React.FC<{
const [stockStatus, setStockStatus] = useState< const [stockStatus, setStockStatus] = useState<
'in-stock' | 'out-of-stock' | null 'in-stock' | 'out-of-stock' | null
>(null); >(null);
const [sites, setSites] = useState<any[]>([]);
const [categories, setCategories] = useState<any[]>([]); const [categories, setCategories] = useState<any[]>([]);
const [activeAttributes, setActiveAttributes] = useState<any[]>([]); const [activeAttributes, setActiveAttributes] = useState<any[]>([]);
@ -44,10 +43,6 @@ const EditForm: React.FC<{
productcontrollerGetcategoriesall().then((res: any) => { productcontrollerGetcategoriesall().then((res: any) => {
setCategories(res?.data || []); setCategories(res?.data || []);
}); });
// 获取站点列表用于站点SKU选择
sitecontrollerAll().then((res: any) => {
setSites(res?.data || []);
});
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -118,9 +113,6 @@ const EditForm: React.FC<{
components: components, components: components,
type: type, type: type,
categoryId: (record as any).categoryId || (record as any).category?.id, categoryId: (record as any).categoryId || (record as any).category?.id,
// 初始化站点SKU为字符串数组
// 修改后代码:
siteSkus: (record.siteSkus || []).map((code) => ({ code })),
}; };
}, [record, components, type]); }, [record, components, type]);
return ( return (
@ -187,7 +179,7 @@ const EditForm: React.FC<{
attributes, attributes,
type: values.type, // 直接使用 type type: values.type, // 直接使用 type
categoryId: values.categoryId, categoryId: values.categoryId,
siteSkus: values.siteSkus.map((v: { code: string }) => v.code) || [], // 直接传递字符串数组 siteSkus: values.siteSkus.map((v: { sku: string }) => v.sku) || [], // 直接传递字符串数组
// 连带更新 components // 连带更新 components
components: components:
values.type === 'bundle' values.type === 'bundle'
@ -251,7 +243,7 @@ const EditForm: React.FC<{
)} )}
> >
<ProFormText <ProFormText
name="code" name="sku"
width="md" width="md"
placeholder="请输入站点SKU" placeholder="请输入站点SKU"
rules={[{ required: true, message: '请输入站点SKU' }]} rules={[{ required: true, message: '请输入站点SKU' }]}

View File

@ -228,7 +228,7 @@ const List: React.FC = () => {
<> <>
{record.siteSkus?.map((siteSku, index) => ( {record.siteSkus?.map((siteSku, index) => (
<Tag key={index} color="cyan"> <Tag key={index} color="cyan">
{siteSku} {siteSku.sku}
</Tag> </Tag>
))} ))}
</> </>

View File

@ -42,7 +42,7 @@ const ListPage: React.FC = () => {
hideInTable: true, hideInTable: true,
}, },
{ {
title: '产品名称', title: '产品sku',
dataIndex: 'sku', dataIndex: 'sku',
}, },
{ {