feat: 添加产品工具, 重构产品 #31
16
.umirc.ts
16
.umirc.ts
|
|
@ -71,14 +71,19 @@ export default defineConfig({
|
|||
],
|
||||
},
|
||||
{
|
||||
name: '地点管理',
|
||||
name: '地区管理',
|
||||
path: '/area',
|
||||
access: 'canSeeArea',
|
||||
routes: [
|
||||
{
|
||||
name: '地点列表',
|
||||
name: '地区列表',
|
||||
path: '/area/list',
|
||||
component: './Area/List',
|
||||
},
|
||||
{
|
||||
name: '地区地图',
|
||||
path: '/area/map',
|
||||
component: './Area/Map',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -265,5 +270,12 @@ export default defineConfig({
|
|||
// component: './404',
|
||||
// },
|
||||
],
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: UMI_APP_API_URL,
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/api': '' },
|
||||
},
|
||||
},
|
||||
npmClient: 'pnpm',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@
|
|||
"@umijs/plugin-openapi": "^1.3.3",
|
||||
"antd": "^5.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"echarts": "^6.0.0",
|
||||
"echarts-for-react": "^3.0.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"print-js": "^1.6.0",
|
||||
"react-phone-input-2": "^2.15.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -4,8 +4,8 @@ import {
|
|||
DrawerForm,
|
||||
ProColumns,
|
||||
ProFormInstance,
|
||||
ProFormText,
|
||||
ProTable,
|
||||
ProFormSelect,
|
||||
ProTable
|
||||
} from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import { Button, message, Popconfirm, Space } from 'antd';
|
||||
|
|
@ -14,8 +14,12 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
interface AreaItem {
|
||||
id: number;
|
||||
name: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
code: string;
|
||||
}
|
||||
|
||||
interface Country {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const AreaList: React.FC = () => {
|
||||
|
|
@ -23,6 +27,7 @@ const AreaList: React.FC = () => {
|
|||
const formRef = useRef<ProFormInstance>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<AreaItem | null>(null);
|
||||
const [countries, setCountries] = useState<Country[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
|
@ -33,6 +38,20 @@ const AreaList: React.FC = () => {
|
|||
}
|
||||
}, [open, editing]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCountries = async () => {
|
||||
try {
|
||||
const resp = await request('/area/countries', { method: 'GET' });
|
||||
const { success, data, message: errMsg } = resp as any;
|
||||
if (!success) throw new Error(errMsg || '获取国家列表失败');
|
||||
setCountries(data || []);
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '获取国家列表失败');
|
||||
}
|
||||
};
|
||||
fetchCountries();
|
||||
}, []);
|
||||
|
||||
const columns: ProColumns<AreaItem>[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
|
|
@ -42,18 +61,7 @@ const AreaList: React.FC = () => {
|
|||
hideInSearch: true,
|
||||
},
|
||||
{ title: '名称', dataIndex: 'name', width: 220 },
|
||||
{
|
||||
title: '纬度',
|
||||
dataIndex: 'latitude',
|
||||
width: 160,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '经度',
|
||||
dataIndex: 'longitude',
|
||||
width: 160,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{ title: '编码', dataIndex: 'code', width: 160 },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'actions',
|
||||
|
|
@ -96,13 +104,13 @@ const AreaList: React.FC = () => {
|
|||
|
||||
const tableRequest = async (params: Record<string, any>) => {
|
||||
try {
|
||||
const { current = 1, pageSize = 10, name } = params;
|
||||
const { current = 1, pageSize = 10, keyword } = params;
|
||||
const resp = await request('/area', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
currentPage: current,
|
||||
pageSize,
|
||||
name: name || undefined,
|
||||
keyword: keyword || undefined,
|
||||
},
|
||||
});
|
||||
const { success, data, message: errMsg } = resp as any;
|
||||
|
|
@ -170,21 +178,13 @@ const AreaList: React.FC = () => {
|
|||
formRef={formRef}
|
||||
onFinish={handleSubmit}
|
||||
>
|
||||
<ProFormText
|
||||
name="name"
|
||||
label="区域名称"
|
||||
placeholder="例如:Australia"
|
||||
rules={[{ required: true, message: '区域名称为必填项' }]}
|
||||
/>
|
||||
<ProFormText
|
||||
name="latitude"
|
||||
label="纬度"
|
||||
placeholder="例如:-33.8688"
|
||||
/>
|
||||
<ProFormText
|
||||
name="longitude"
|
||||
label="经度"
|
||||
placeholder="例如:151.2093"
|
||||
<ProFormSelect
|
||||
name="code"
|
||||
label="国家/地区"
|
||||
options={countries.map(c => ({ label: c.name, value: c.code }))}
|
||||
placeholder="请选择国家/地区"
|
||||
rules={[{ required: true, message: '国家/地区为必填项' }]}
|
||||
showSearch
|
||||
/>
|
||||
</DrawerForm>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { request } from '@umijs/max';
|
||||
import { Spin, message } from 'antd';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { MapChart } from 'echarts/charts';
|
||||
import { TooltipComponent, VisualMapComponent } from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import * as countries from 'i18n-iso-countries';
|
||||
|
||||
// 注册 ECharts 组件
|
||||
echarts.use([TooltipComponent, VisualMapComponent, MapChart, CanvasRenderer]);
|
||||
|
||||
// 注册 i18n-iso-countries 语言包
|
||||
countries.registerLocale(require('i18n-iso-countries/langs/en.json'));
|
||||
|
||||
interface AreaItem {
|
||||
id: number;
|
||||
name: string; // 中文名
|
||||
code: string; // 国家代码
|
||||
}
|
||||
|
||||
const AreaMap: React.FC = () => {
|
||||
const [option, setOption] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAndSetMapData = async () => {
|
||||
try {
|
||||
// 1. 动态加载 world.json 地图数据
|
||||
const worldMapResponse = await fetch('/world.json');
|
||||
const worldMap = await worldMapResponse.json();
|
||||
echarts.registerMap('world', worldMap);
|
||||
|
||||
// 2. 从后端获取已存储的区域列表
|
||||
const areaResponse = await request('/area', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
currentPage: 1,
|
||||
pageSize: 9999,
|
||||
},
|
||||
});
|
||||
if (!areaResponse.success) {
|
||||
throw new Error(areaResponse.message || '获取区域列表失败');
|
||||
}
|
||||
const savedAreas: AreaItem[] = areaResponse.data?.list || [];
|
||||
|
||||
// 3. 将后端数据转换为 ECharts 需要的格式
|
||||
const mapData = savedAreas.map(area => {
|
||||
let nameEn = countries.getName(area.code, 'en');
|
||||
return {
|
||||
name: nameEn || area.code,
|
||||
value: 1,
|
||||
chineseName: area.name,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// 4. 配置 ECharts 地图选项
|
||||
const mapOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: (params: any) => {
|
||||
if (params.data && params.data.chineseName) {
|
||||
return `${params.data.chineseName}`;
|
||||
}
|
||||
return `${params.name}`;
|
||||
},
|
||||
},
|
||||
visualMap: {
|
||||
left: 'left',
|
||||
min: 0,
|
||||
max: 1,
|
||||
inRange: {
|
||||
color: ['#f0f0f0', '#1890ff'],
|
||||
},
|
||||
calculable: false,
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'World Map',
|
||||
type: 'map',
|
||||
map: 'world',
|
||||
roam: true,
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
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: '100%' }} />;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<ReactECharts
|
||||
echarts={echarts}
|
||||
option={option}
|
||||
style={{ height: '80vh', width: '100%' }}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AreaMap;
|
||||
Loading…
Reference in New Issue