Compare commits
No commits in common. "c8064f20e3af3a01d668f8c2381eeafb92c89789" and "85ad42278b113401b2c8542668822bdfbce1d0fb" have entirely different histories.
c8064f20e3
...
85ad42278b
|
|
@ -1,18 +1,15 @@
|
|||
## 实现计划
|
||||
|
||||
### 1. 地图数据准备
|
||||
|
||||
- 获取加拿大和澳大利亚的省级地图数据(JSON 格式)
|
||||
- 获取加拿大和澳大利亚的省级地图数据(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. 数据结构设计
|
||||
|
||||
- 定义省级数据接口,包含:
|
||||
- 英文名称(用于匹配地图数据)
|
||||
- 中文名称
|
||||
|
|
@ -20,24 +17,20 @@
|
|||
- 人口数据
|
||||
|
||||
### 4. 地图渲染配置
|
||||
|
||||
- 使用 ECharts 的 Map 组件渲染省级地图
|
||||
- 配置 tooltip 显示中文名称、简称和人口数据
|
||||
- 使用ECharts的Map组件渲染省级地图
|
||||
- 配置tooltip显示中文名称、简称和人口数据
|
||||
- 添加视觉映射组件,根据人口数据显示不同颜色
|
||||
|
||||
### 5. 数据获取与处理
|
||||
|
||||
- 定义本地数据结构,包含各个省的中文名称、简称和人口
|
||||
- 将数据转换为 ECharts 需要的格式
|
||||
- 将数据转换为ECharts需要的格式
|
||||
|
||||
### 6. 组件优化
|
||||
|
||||
- 添加加载状态
|
||||
- 处理地图数据加载失败的情况
|
||||
- 优化地图交互体验,如缩放、拖拽等
|
||||
|
||||
### 7. 实现步骤
|
||||
|
||||
1. 准备地图数据文件
|
||||
2. 创建加拿大地图组件
|
||||
3. 创建澳大利亚地图组件
|
||||
|
|
@ -46,9 +39,8 @@
|
|||
6. 测试地图渲染和交互
|
||||
|
||||
### 技术要点
|
||||
|
||||
- 使用 ECharts 的 MapChart 组件
|
||||
- 动态加载地图 JSON 数据
|
||||
- 使用ECharts的MapChart组件
|
||||
- 动态加载地图JSON数据
|
||||
- 数据映射和转换
|
||||
- Tooltip 自定义格式化
|
||||
- 视觉映射配置
|
||||
- Tooltip自定义格式化
|
||||
- 视觉映射配置
|
||||
26482
public/australia.json
26482
public/australia.json
File diff suppressed because one or more lines are too long
32482
public/canada.json
32482
public/canada.json
File diff suppressed because it is too large
Load Diff
|
|
@ -22,54 +22,14 @@ const AustraliaMap: React.FC = () => {
|
|||
|
||||
// 澳大利亚省级数据
|
||||
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,
|
||||
},
|
||||
{ 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(() => {
|
||||
|
|
@ -97,9 +57,7 @@ const AustraliaMap: React.FC = () => {
|
|||
if (params.data) {
|
||||
return `
|
||||
<div>
|
||||
<div><strong>${params.data.chineseName}</strong> (${
|
||||
params.data.shortName
|
||||
})</div>
|
||||
<div><strong>${params.data.chineseName}</strong> (${params.data.shortName})</div>
|
||||
<div>人口: ${params.data.population.toLocaleString()}</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -109,8 +67,8 @@ const AustraliaMap: React.FC = () => {
|
|||
},
|
||||
visualMap: {
|
||||
left: 'left',
|
||||
min: Math.min(...provinceData.map((p) => p.population)),
|
||||
max: Math.max(...provinceData.map((p) => p.population)),
|
||||
min: Math.min(...provinceData.map(p => p.population)),
|
||||
max: Math.max(...provinceData.map(p => p.population)),
|
||||
inRange: {
|
||||
color: ['#f0f0f0', '#52c41a', '#389e0d'],
|
||||
},
|
||||
|
|
@ -131,9 +89,7 @@ const AustraliaMap: React.FC = () => {
|
|||
show: true,
|
||||
formatter: (params: any) => {
|
||||
if (params.data) {
|
||||
return `${params.data.shortName}\n${
|
||||
params.data.chineseName
|
||||
}\n${params.data.population.toLocaleString()}`;
|
||||
return `${params.data.shortName}\n${params.data.chineseName}\n${params.data.population.toLocaleString()}`;
|
||||
}
|
||||
return params.name;
|
||||
},
|
||||
|
|
@ -190,4 +146,4 @@ const AustraliaMap: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default AustraliaMap;
|
||||
export default AustraliaMap;
|
||||
|
|
@ -22,84 +22,19 @@ const CanadaMap: React.FC = () => {
|
|||
|
||||
// 加拿大省级数据
|
||||
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,
|
||||
},
|
||||
{ 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(() => {
|
||||
|
|
@ -127,9 +62,7 @@ const CanadaMap: React.FC = () => {
|
|||
if (params.data) {
|
||||
return `
|
||||
<div>
|
||||
<div><strong>${params.data.chineseName}</strong> (${
|
||||
params.data.shortName
|
||||
})</div>
|
||||
<div><strong>${params.data.chineseName}</strong> (${params.data.shortName})</div>
|
||||
<div>人口: ${params.data.population.toLocaleString()}</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -139,8 +72,8 @@ const CanadaMap: React.FC = () => {
|
|||
},
|
||||
visualMap: {
|
||||
left: 'left',
|
||||
min: Math.min(...provinceData.map((p) => p.population)),
|
||||
max: Math.max(...provinceData.map((p) => p.population)),
|
||||
min: Math.min(...provinceData.map(p => p.population)),
|
||||
max: Math.max(...provinceData.map(p => p.population)),
|
||||
inRange: {
|
||||
color: ['#f0f0f0', '#1890ff', '#096dd9'],
|
||||
},
|
||||
|
|
@ -160,9 +93,7 @@ const CanadaMap: React.FC = () => {
|
|||
show: true,
|
||||
formatter: (params: any) => {
|
||||
if (params.data) {
|
||||
return `${params.data.shortName}\n${
|
||||
params.data.chineseName
|
||||
}\n${params.data.population.toLocaleString()}`;
|
||||
return `${params.data.shortName}\n${params.data.chineseName}\n${params.data.population.toLocaleString()}`;
|
||||
}
|
||||
return params.name;
|
||||
},
|
||||
|
|
@ -219,4 +150,4 @@ const CanadaMap: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default CanadaMap;
|
||||
export default CanadaMap;
|
||||
|
|
@ -4,4 +4,4 @@ export default function Statistic() {
|
|||
<h1>客户统计</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,6 @@ const DictItemModal: React.FC<DictItemModalProps> = ({
|
|||
label="名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Input placeholder="名称 (e.g., zyn)" />
|
||||
</Form.Item>
|
||||
|
|
@ -74,36 +73,19 @@ const DictItemModal: React.FC<DictItemModalProps> = ({
|
|||
label="标题"
|
||||
name="title"
|
||||
rules={[{ required: true, message: '请输入标题' }]}
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Input placeholder="标题 (e.g., ZYN)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="中文标题"
|
||||
name="titleCN"
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Form.Item label="中文标题" name="titleCN">
|
||||
<Input placeholder="中文标题 (e.g., 品牌)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="简称 (可选)"
|
||||
name="shortName"
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Form.Item label="简称 (可选)" name="shortName">
|
||||
<Input placeholder="简称 (可选)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="图片 (可选)"
|
||||
name="image"
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Form.Item label="图片 (可选)" name="image">
|
||||
<Input placeholder="图片链接 (可选)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="值 (可选)"
|
||||
name="value"
|
||||
normalize={(value) => (value || '').trim()}
|
||||
>
|
||||
<Form.Item label="值 (可选)" name="value">
|
||||
<Input placeholder="值 (可选)" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ const CsvTool: React.FC = () => {
|
|||
const [selectedSites, setSelectedSites] = useState<Site[]>([]); // 现在使用多选
|
||||
const [generateBundleSkuForSingle, setGenerateBundleSkuForSingle] =
|
||||
useState(true); // 是否为type为single的记录生成包含quantity的bundle SKU
|
||||
const [generateSkuForProducts, setGenerateSkuForProducts] = useState(true); // 是否为产品生成SKU
|
||||
const [generateNameForProducts, setGenerateNameForProducts] = useState(true); // 是否为产品生成名称
|
||||
const [config, setConfig] = useState<SkuConfig>({
|
||||
brands: [],
|
||||
categories: [],
|
||||
|
|
@ -530,43 +528,39 @@ const CsvTool: React.FC = () => {
|
|||
// 获取产品类型
|
||||
const type = row.type || '';
|
||||
|
||||
// 根据选项决定是否生成SKU
|
||||
let baseSku = row.sku; // 默认为原有SKU
|
||||
if (generateSkuForProducts) {
|
||||
baseSku = generateSku(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
type,
|
||||
);
|
||||
}
|
||||
// 生成基础SKU(不包含站点前缀)
|
||||
const baseSku = generateSku(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
type,
|
||||
);
|
||||
const name = generateName(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
type,
|
||||
);
|
||||
// 为所有站点生成带前缀的siteSkus
|
||||
const siteSkus = generateSiteSkus(baseSku);
|
||||
|
||||
// 根据选项决定是否生成名称
|
||||
let generatedName = row.name; // 默认为原有名称
|
||||
if (generateNameForProducts) {
|
||||
generatedName = generateName(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
type,
|
||||
);
|
||||
}
|
||||
|
||||
// 返回包含新SKU的行数据,将SKU直接保存到sku栏
|
||||
// 返回包含新SKU和siteSkus的行数据,将SKU直接保存到sku栏
|
||||
return {
|
||||
...row,
|
||||
sku: baseSku, // 根据选项决定是否更新SKU
|
||||
name: generateNameForProducts ? generatedName : row.name, // 根据选项决定是否更新名称
|
||||
sku: baseSku, // 直接生成在sku栏
|
||||
generatedName: name,
|
||||
// name: name, // 生成的产品名称
|
||||
siteSkus,
|
||||
attribute_quantity: quantity, // 确保quantity保存到attribute_quantity
|
||||
};
|
||||
});
|
||||
|
|
@ -582,94 +576,63 @@ const CsvTool: React.FC = () => {
|
|||
);
|
||||
|
||||
// Get quantity values from the config (same source as other attributes like brand)
|
||||
const quantityValues = config.quantities
|
||||
.sort((a, b) => Number(a.name) - Number(b.name))
|
||||
.map((quantity) => quantity.name);
|
||||
// 获取源文件中已有的所有 SKU 列表(包括 single 和 bundle),用于检查重复
|
||||
const existingSkus = new Set(
|
||||
csvData.map((row) => row.sku).filter((sku) => sku),
|
||||
);
|
||||
|
||||
// 获取源文件中已有的 bundle 类型记录的 SKU,用于进一步检查
|
||||
const existingBundleSkus = new Set(
|
||||
csvData
|
||||
.filter((row) => row.type === 'bundle')
|
||||
.map((row) => row.sku)
|
||||
.filter((sku) => sku),
|
||||
const quantityValues = config.quantities.map(
|
||||
(quantity) => quantity.name,
|
||||
);
|
||||
|
||||
// Generate bundle products for each single record and quantity
|
||||
const generatedBundleRecords = singleRecords
|
||||
.flatMap((singleRecord) => {
|
||||
return quantityValues.map((quantity) => {
|
||||
// Extract all necessary attributes from the single record
|
||||
const brand = singleRecord.attribute_brand || '';
|
||||
const version = singleRecord.attribute_version || '';
|
||||
const category = singleRecord.category || '';
|
||||
const flavor = singleRecord.attribute_flavor || '';
|
||||
const strength = singleRecord.attribute_strength || '';
|
||||
const humidity = singleRecord.attribute_humidity || '';
|
||||
const size =
|
||||
singleRecord.attribute_size || singleRecord.size || '';
|
||||
// 根据选项决定是否生成bundle SKU
|
||||
let bundleSku;
|
||||
if (generateSkuForProducts) {
|
||||
bundleSku = generateSku(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
'bundle',
|
||||
);
|
||||
} else {
|
||||
// 如果不生成SKU,则使用基于原有SKU和数量的组合
|
||||
bundleSku = `${singleRecord.sku}-bundle-${quantity}`;
|
||||
}
|
||||
const generatedBundleRecords = singleRecords.flatMap((singleRecord) => {
|
||||
return quantityValues.map((quantity) => {
|
||||
// Extract all necessary attributes from the single record
|
||||
const brand = singleRecord.attribute_brand || '';
|
||||
const version = singleRecord.attribute_version || '';
|
||||
const category = singleRecord.category || '';
|
||||
const flavor = singleRecord.attribute_flavor || '';
|
||||
const strength = singleRecord.attribute_strength || '';
|
||||
const humidity = singleRecord.attribute_humidity || '';
|
||||
const size = singleRecord.attribute_size || singleRecord.size || '';
|
||||
// Generate bundle SKU with the quantity
|
||||
const bundleSku = generateSku(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
'bundle',
|
||||
);
|
||||
|
||||
// 检查 bundle SKU 是否已存在于源文件中(包括所有类型的记录)
|
||||
if (
|
||||
existingSkus.has(bundleSku) ||
|
||||
existingBundleSkus.has(bundleSku)
|
||||
) {
|
||||
return null; // 跳过已存在的 SKU
|
||||
}
|
||||
// Generate bundle name with the quantity
|
||||
const bundleName = generateName(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
'bundle',
|
||||
);
|
||||
|
||||
// 根据选项决定是否生成bundle名称
|
||||
let bundleName;
|
||||
if (generateNameForProducts) {
|
||||
bundleName = generateName(
|
||||
brand,
|
||||
version,
|
||||
category,
|
||||
flavor,
|
||||
strength,
|
||||
humidity,
|
||||
size,
|
||||
quantity,
|
||||
'bundle',
|
||||
);
|
||||
} else {
|
||||
// 如果不生成名称,则使用基于原有名称和数量的组合
|
||||
bundleName = `${singleRecord.name} Bundle x ${quantity}`;
|
||||
}
|
||||
// Create the bundle record
|
||||
return {
|
||||
...singleRecord,
|
||||
type: 'bundle', // Change type to bundle
|
||||
sku: bundleSku, // Use the new bundle SKU
|
||||
name: bundleName, // Use the new bundle name
|
||||
// siteSkus: bundleSiteSkus,
|
||||
attribute_quantity: quantity, // Set the attribute_quantity
|
||||
component_1_sku: singleRecord.sku, // Set component_1_sku to the single product's sku
|
||||
component_1_quantity: Number(quantity), // Set component_1_quantity to the same as attribute_quantity
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter(Boolean); // 过滤掉 null 值
|
||||
// Generate siteSkus for the bundle
|
||||
const bundleSiteSkus = generateSiteSkus(bundleSku);
|
||||
|
||||
// Create the bundle record
|
||||
return {
|
||||
...singleRecord,
|
||||
type: 'bundle', // Change type to bundle
|
||||
sku: bundleSku, // Use the new bundle SKU
|
||||
name: bundleName, // Use the new bundle name
|
||||
siteSkus: bundleSiteSkus,
|
||||
attribute_quantity: quantity, // Set the attribute_quantity
|
||||
component_1_sku: singleRecord.sku, // Set component_1_sku to the single product's sku
|
||||
component_1_quantity: Number(quantity), // Set component_1_quantity to the same as attribute_quantity
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Combine original dataWithSku with generated bundle records
|
||||
finalData = [...dataWithSku, ...generatedBundleRecords];
|
||||
|
|
@ -801,6 +764,22 @@ const CsvTool: React.FC = () => {
|
|||
}))}
|
||||
fieldProps={{ allowClear: true }}
|
||||
/>
|
||||
|
||||
<ProForm.Item
|
||||
name="generateBundleSkuForSingle"
|
||||
label="为type=single生成bundle产品数据行"
|
||||
tooltip="为类型为single的记录生成包含quantity的bundle SKU"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={(e) =>
|
||||
setGenerateBundleSkuForSingle(e.target.checked)
|
||||
}
|
||||
>
|
||||
启用为single类型生成bundle SKU
|
||||
</Checkbox>
|
||||
</ProForm.Item>
|
||||
</ProForm>
|
||||
</Card>
|
||||
|
||||
|
|
@ -890,50 +869,6 @@ const CsvTool: React.FC = () => {
|
|||
</label>
|
||||
<Input value={file ? file.name : '暂未选择文件'} readOnly />
|
||||
</div>
|
||||
<ProForm.Item
|
||||
name="generateSkuForProducts"
|
||||
label="为产品生成SKU"
|
||||
tooltip="为所有产品记录生成SKU"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={(e) => setGenerateSkuForProducts(e.target.checked)}
|
||||
>
|
||||
启用为产品生成SKU
|
||||
</Checkbox>
|
||||
</ProForm.Item>
|
||||
|
||||
<ProForm.Item
|
||||
name="generateNameForProducts"
|
||||
label="为产品生成名称"
|
||||
tooltip="为所有产品记录生成名称"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={(e) => setGenerateNameForProducts(e.target.checked)}
|
||||
>
|
||||
启用为产品生成名称
|
||||
</Checkbox>
|
||||
</ProForm.Item>
|
||||
|
||||
<ProForm.Item
|
||||
name="generateBundleSkuForSingle"
|
||||
label="为type=single生成bundle产品数据行"
|
||||
tooltip="为类型为single的记录生成包含quantity的bundle SKU"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={(e) =>
|
||||
setGenerateBundleSkuForSingle(e.target.checked)
|
||||
}
|
||||
>
|
||||
启用为single类型生成bundle SKU
|
||||
</Checkbox>
|
||||
</ProForm.Item>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleProcessData}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,9 @@
|
|||
import { categorycontrollerGetall } from '@/servers/api/category';
|
||||
import { dictcontrollerGetdictitems } from '@/servers/api/dict';
|
||||
import { productcontrollerGetproductlistgrouped } from '@/servers/api/product';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { PageContainer, ProFormSelect } from '@ant-design/pro-components';
|
||||
import {
|
||||
Card,
|
||||
Collapse,
|
||||
Divider,
|
||||
Image,
|
||||
Select,
|
||||
Space,
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Card, Collapse, Divider, Image, Select, Space, Typography, message } from 'antd';
|
||||
import { categorycontrollerGetall } from '@/servers/api/category';
|
||||
import { productcontrollerGetproductlistgrouped } from '@/servers/api/product';
|
||||
import { dictcontrollerGetdictitems } from '@/servers/api/dict';
|
||||
|
||||
// Define interfaces
|
||||
interface Category {
|
||||
|
|
@ -64,22 +55,13 @@ const ProductCard: React.FC<{ product: Product }> = ({ product }) => {
|
|||
/>
|
||||
</div> */}
|
||||
<div>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12, display: 'block', marginBottom: '4px' }}
|
||||
>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: '4px' }}>
|
||||
{product.sku}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
ellipsis
|
||||
style={{ width: '100%', display: 'block', marginBottom: '8px' }}
|
||||
>
|
||||
<Typography.Text ellipsis style={{ width: '100%', display: 'block', marginBottom: '8px' }}>
|
||||
{product.name}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
strong
|
||||
style={{ fontSize: 16, color: '#ff4d4f', display: 'block' }}
|
||||
>
|
||||
<Typography.Text strong style={{ fontSize: 16, color: '#ff4d4f', display: 'block' }}>
|
||||
¥{product.price || '--'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
|
@ -108,11 +90,7 @@ const ProductGroup: React.FC<{
|
|||
)}
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
<span>
|
||||
{attributeValue?.titleCN ||
|
||||
attributeValue?.title ||
|
||||
attributeValue?.name ||
|
||||
attributeValueId ||
|
||||
'未知'}
|
||||
{attributeValue?.titleCN || attributeValue?.title || attributeValue?.name || attributeValueId||'未知'}
|
||||
(共 {groupProducts.length} 个产品)
|
||||
</span>
|
||||
</Typography.Title>
|
||||
|
|
@ -130,14 +108,7 @@ const ProductGroup: React.FC<{
|
|||
key: attributeValueId,
|
||||
label: panelHeader,
|
||||
children: (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '16px',
|
||||
paddingTop: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', paddingTop: '8px' }}>
|
||||
{groupProducts.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
|
|
@ -155,9 +126,7 @@ const ProductGroupBy: React.FC = () => {
|
|||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
// Store selected values for each attribute
|
||||
const [attributeFilters, setAttributeFilters] = useState<{
|
||||
[key: string]: number | null;
|
||||
}>({});
|
||||
const [attributeFilters, setAttributeFilters] = useState<{ [key: string]: number | null }>({});
|
||||
|
||||
// Group by attribute
|
||||
const [groupByAttribute, setGroupByAttribute] = useState<string | null>(null);
|
||||
|
|
@ -170,16 +139,12 @@ const ProductGroupBy: React.FC = () => {
|
|||
// Extract all unique attributes from categories
|
||||
const categoryAttributes = useMemo(() => {
|
||||
if (!selectedCategory) return [];
|
||||
const categoryItem = categories.find(
|
||||
(category: any) => category.name === selectedCategory,
|
||||
);
|
||||
const categoryItem = categories.find((category: any) => category.name === selectedCategory);
|
||||
if (!categoryItem) return [];
|
||||
const attributesList: Attribute[] = categoryItem.attributes.map(
|
||||
(attribute: any, index) => ({
|
||||
...attribute.attributeDict,
|
||||
id: index + 1,
|
||||
}),
|
||||
);
|
||||
const attributesList: Attribute[] = categoryItem.attributes.map((attribute: any, index) => ({
|
||||
...attribute.attributeDict,
|
||||
id: index + 1,
|
||||
}));
|
||||
return attributesList;
|
||||
}, [selectedCategory]);
|
||||
|
||||
|
|
@ -187,16 +152,12 @@ const ProductGroupBy: React.FC = () => {
|
|||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await categorycontrollerGetall();
|
||||
const rawCategories = Array.isArray(response)
|
||||
? response
|
||||
: response?.data || [];
|
||||
const rawCategories = Array.isArray(response) ? response : response?.data || [];
|
||||
setCategories(rawCategories);
|
||||
|
||||
// Set default category
|
||||
if (rawCategories.length > 0) {
|
||||
const defaultCategory = rawCategories.find(
|
||||
(category: any) => category.name === 'nicotine-pouches',
|
||||
);
|
||||
const defaultCategory = rawCategories.find((category: any) => category.name === 'nicotine-pouches');
|
||||
setSelectedCategory(defaultCategory?.name || rawCategories[0].name);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -209,17 +170,16 @@ const ProductGroupBy: React.FC = () => {
|
|||
useEffect(() => {
|
||||
if (!selectedCategory) return;
|
||||
|
||||
const category = categories.find((cat) => cat.name === selectedCategory);
|
||||
const category = categories.find(cat => cat.name === selectedCategory);
|
||||
if (!category) return;
|
||||
|
||||
// Get attributes for this category
|
||||
const attributesForCategory = categoryAttributes.filter(
|
||||
(attr) =>
|
||||
attr.name === 'brand' || category.attributes.includes(attr.name),
|
||||
const attributesForCategory = categoryAttributes.filter(attr =>
|
||||
attr.name === 'brand' || category.attributes.includes(attr.name)
|
||||
);
|
||||
// Reset attribute filters when category changes
|
||||
const newFilters: { [key: string]: number | null } = {};
|
||||
attributesForCategory.forEach((attr) => {
|
||||
attributesForCategory.forEach(attr => {
|
||||
newFilters[attr.name] = null;
|
||||
});
|
||||
setAttributeFilters(newFilters);
|
||||
|
|
@ -231,11 +191,8 @@ const ProductGroupBy: React.FC = () => {
|
|||
}, [selectedCategory, categories, categoryAttributes]);
|
||||
|
||||
// Handle attribute filter change
|
||||
const handleAttributeFilterChange = (
|
||||
attributeName: string,
|
||||
value: number | null,
|
||||
) => {
|
||||
setAttributeFilters((prev) => ({ ...prev, [attributeName]: value }));
|
||||
const handleAttributeFilterChange = (attributeName: string, value: number | null) => {
|
||||
setAttributeFilters(prev => ({ ...prev, [attributeName]: value }));
|
||||
};
|
||||
|
||||
// Fetch products based on filters
|
||||
|
|
@ -244,15 +201,16 @@ const ProductGroupBy: React.FC = () => {
|
|||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
category: selectedCategory,
|
||||
groupBy: groupByAttribute,
|
||||
const params: any = {
|
||||
category: selectedCategory,
|
||||
groupBy: groupByAttribute
|
||||
};
|
||||
|
||||
|
||||
const response = await productcontrollerGetproductlistgrouped(params);
|
||||
const grouped = response?.data || {};
|
||||
setGroupedProducts(grouped);
|
||||
|
||||
|
||||
// Flatten grouped products to get all products
|
||||
const allProducts = Object.values(grouped).flat() as Product[];
|
||||
setProducts(allProducts);
|
||||
|
|
@ -284,9 +242,7 @@ const ProductGroupBy: React.FC = () => {
|
|||
<div style={{ padding: '16px', background: '#fff' }}>
|
||||
{/* Filter Section */}
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>
|
||||
筛选条件
|
||||
</Title>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>筛选条件</Title>
|
||||
<Space direction="vertical" size="large">
|
||||
{/* Category Filter */}
|
||||
<div>
|
||||
|
|
@ -300,7 +256,7 @@ const ProductGroupBy: React.FC = () => {
|
|||
showSearch
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
{categories.map(category => (
|
||||
<Option key={category.id} value={category.name}>
|
||||
{category.title}
|
||||
</Option>
|
||||
|
|
@ -312,61 +268,41 @@ const ProductGroupBy: React.FC = () => {
|
|||
{categoryAttributes.length > 0 && (
|
||||
<div>
|
||||
<Text strong>属性筛选:</Text>
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{ marginTop: '8px', width: '100%' }}
|
||||
>
|
||||
{categoryAttributes.map((attr) => (
|
||||
<div
|
||||
key={attr.id}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Text style={{ width: '100px' }}>{attr.title}:</Text>
|
||||
<ProFormSelect
|
||||
placeholder={`请选择${attr.title}`}
|
||||
style={{ width: 300 }}
|
||||
value={attributeFilters[attr.name] || null}
|
||||
onChange={(value) =>
|
||||
handleAttributeFilterChange(attr.name, value)
|
||||
}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
request={async (params) => {
|
||||
try {
|
||||
console.log('params', params, attr);
|
||||
const response = await dictcontrollerGetdictitems({
|
||||
dictId: attr.name,
|
||||
});
|
||||
const rawValues = Array.isArray(response)
|
||||
? response
|
||||
: response?.data?.items || [];
|
||||
const filteredValues = rawValues.filter(
|
||||
(value: any) =>
|
||||
value.dictId === attr.name ||
|
||||
value.dict?.id === attr.name ||
|
||||
value.dict?.name === attr.name,
|
||||
);
|
||||
return {
|
||||
options: filteredValues.map((value: any) => ({
|
||||
label: `${value.name}${
|
||||
value.titleCN || value.title
|
||||
}`,
|
||||
value: value.id,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to fetch ${attr.title} values:`,
|
||||
error,
|
||||
);
|
||||
message.error(`获取${attr.title}属性值失败`);
|
||||
return { options: [] };
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Space direction="vertical" style={{ marginTop: '8px', width: '100%' }}>
|
||||
{categoryAttributes.map(attr => (
|
||||
<div key={attr.id} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Text style={{ width: '100px' }}>{attr.title}:</Text>
|
||||
<ProFormSelect
|
||||
placeholder={`请选择${attr.title}`}
|
||||
style={{ width: 300 }}
|
||||
value={attributeFilters[attr.name] || null}
|
||||
onChange={value => handleAttributeFilterChange(attr.name, value)}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
request={async (params) => {
|
||||
try {
|
||||
console.log('params', params,attr);
|
||||
const response = await dictcontrollerGetdictitems({ dictId: attr.name });
|
||||
const rawValues = Array.isArray(response) ? response : response?.data?.items || [];
|
||||
const filteredValues = rawValues.filter((value: any) =>
|
||||
value.dictId === attr.name || value.dict?.id === attr.name || value.dict?.name === attr.name
|
||||
);
|
||||
return {
|
||||
options: filteredValues.map((value: any) => ({
|
||||
label: `${value.name}${value.titleCN || value.title}`,
|
||||
value: value.id
|
||||
}))
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch ${attr.title} values:`, error);
|
||||
message.error(`获取${attr.title}属性值失败`);
|
||||
return { options: [] };
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -383,7 +319,7 @@ const ProductGroupBy: React.FC = () => {
|
|||
showSearch
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{categoryAttributes.map((attr) => (
|
||||
{categoryAttributes.map(attr => (
|
||||
<Option key={attr.id} value={attr.name}>
|
||||
{attr.title}
|
||||
</Option>
|
||||
|
|
@ -398,41 +334,28 @@ const ProductGroupBy: React.FC = () => {
|
|||
|
||||
{/* Products Section */}
|
||||
<div>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>
|
||||
产品列表 ({products.length} 个产品)
|
||||
</Title>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>产品列表 ({products.length} 个产品)</Title>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '64px' }}>
|
||||
<Text>加载中...</Text>
|
||||
</div>
|
||||
) : groupByAttribute && Object.keys(groupedProducts).length > 0 ? (
|
||||
<div
|
||||
style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}
|
||||
>
|
||||
{Object.entries(groupedProducts).map(
|
||||
([attrValueId, groupProducts]) => {
|
||||
return (
|
||||
<ProductGroup
|
||||
key={attrValueId}
|
||||
attributeValueId={attrValueId}
|
||||
groupProducts={groupProducts}
|
||||
// attributeValue={}
|
||||
attributeName={groupByAttribute!}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||
{Object.entries(groupedProducts).map(([attrValueId, groupProducts]) => {
|
||||
return (
|
||||
<ProductGroup
|
||||
key={attrValueId}
|
||||
attributeValueId={attrValueId}
|
||||
groupProducts={groupProducts}
|
||||
// attributeValue={}
|
||||
attributeName={groupByAttribute!}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '64px',
|
||||
background: '#fafafa',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '64px', background: '#fafafa', borderRadius: 8 }}>
|
||||
<Text type="secondary">暂无产品</Text>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { productcontrollerCreateproduct, productcontrollerGetproductlist } from '@/servers/api/product';
|
||||
import {
|
||||
productcontrollerCreateproduct,
|
||||
productcontrollerGetproductlist,
|
||||
} from '@/servers/api/product';
|
||||
import { ModalForm, ProFormSelect } from '@ant-design/pro-components';
|
||||
ModalForm,
|
||||
ProFormSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
|
|
@ -31,12 +31,12 @@ const BatchCreateBundleModal: React.FC<{
|
|||
modalProps={{ destroyOnClose: true }}
|
||||
onFinish={async (values) => {
|
||||
const { products, quantity } = values;
|
||||
|
||||
|
||||
if (!products || products.length === 0) {
|
||||
message.error('请选择至少一个单品');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 生成批量创建的 Promise 数组
|
||||
const createPromises = products.flatMap((product: any) => {
|
||||
return quantity.map((q: number) => {
|
||||
|
|
@ -46,10 +46,10 @@ const BatchCreateBundleModal: React.FC<{
|
|||
name: `套装 ${product.name} x ${q}`,
|
||||
type: 'bundle',
|
||||
components: [
|
||||
{
|
||||
sku: product.sku,
|
||||
quantity: q,
|
||||
},
|
||||
{
|
||||
sku: product.sku,
|
||||
quantity: q
|
||||
}
|
||||
],
|
||||
attributes: [],
|
||||
});
|
||||
|
|
@ -59,10 +59,10 @@ const BatchCreateBundleModal: React.FC<{
|
|||
try {
|
||||
// 并行执行批量创建
|
||||
const results = await Promise.all(createPromises);
|
||||
|
||||
|
||||
// 检查是否所有创建都成功
|
||||
const allSuccess = results.every((result) => result.success);
|
||||
|
||||
|
||||
if (allSuccess) {
|
||||
const totalCreated = createPromises.length;
|
||||
message.success(`成功创建 ${totalCreated} 个套装产品`);
|
||||
|
|
@ -88,7 +88,9 @@ const BatchCreateBundleModal: React.FC<{
|
|||
const params = keyWords
|
||||
? { sku: keyWords, name: keyWords, type: 'single' }
|
||||
: { pageSize: 9999, type: 'single' };
|
||||
const { data } = await productcontrollerGetproductlist(params as any);
|
||||
const { data } = await productcontrollerGetproductlist(
|
||||
params as any,
|
||||
);
|
||||
if (!data || !data.items) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -113,4 +115,4 @@ const BatchCreateBundleModal: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
export default BatchCreateBundleModal;
|
||||
export default BatchCreateBundleModal;
|
||||
|
|
@ -6,6 +6,7 @@ import {
|
|||
productcontrollerGetproductlist,
|
||||
productcontrollerUpdateproduct,
|
||||
} from '@/servers/api/product';
|
||||
import { sitecontrollerAll } from '@/servers/api/site';
|
||||
import { stockcontrollerGetstocks as getStocks } from '@/servers/api/stock';
|
||||
import {
|
||||
ActionType,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ import {
|
|||
import { request } from '@umijs/max';
|
||||
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import BatchCreateBundleModal from './BatchCreateBundleModal';
|
||||
import CreateForm from './CreateForm';
|
||||
import EditForm from './EditForm';
|
||||
import SyncToSiteModal from './SyncToSiteModal';
|
||||
import BatchCreateBundleModal from './BatchCreateBundleModal';
|
||||
|
||||
const NameCn: React.FC<{
|
||||
id: number;
|
||||
|
|
@ -189,8 +189,7 @@ const List: React.FC = () => {
|
|||
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
|
||||
const [syncProducts, setSyncProducts] = useState<API.Product[]>([]);
|
||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||
const [batchCreateBundleModalVisible, setBatchCreateBundleModalVisible] =
|
||||
useState(false);
|
||||
const [batchCreateBundleModalVisible, setBatchCreateBundleModalVisible] = useState(false);
|
||||
|
||||
const { message } = App.useApp();
|
||||
// 导出产品 CSV(带认证请求)
|
||||
|
|
@ -467,7 +466,9 @@ const List: React.FC = () => {
|
|||
批量修改
|
||||
</Button>,
|
||||
// 批量创建 bundle 产品按钮
|
||||
<Button onClick={() => setBatchCreateBundleModalVisible(true)}>
|
||||
<Button
|
||||
onClick={() => setBatchCreateBundleModalVisible(true)}
|
||||
>
|
||||
批量创建套装
|
||||
</Button>,
|
||||
// 批量同步按钮
|
||||
|
|
|
|||
|
|
@ -58,9 +58,7 @@ export interface SiteItem {
|
|||
const SiteList: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<
|
||||
(SiteItem & { areas: string[] }) | null
|
||||
>(null);
|
||||
const [editing, setEditing] = useState<SiteItem & { areas: string[] } | null>(null);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [batchEditOpen, setBatchEditOpen] = useState(false);
|
||||
const [batchEditForm] = Form.useForm();
|
||||
|
|
@ -294,11 +292,11 @@ const SiteList: React.FC = () => {
|
|||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
function normalEditing(row: SiteItem) {
|
||||
function normalEditing(row:SiteItem){
|
||||
return {
|
||||
...row,
|
||||
areas: row.areas?.map((area) => area.code) || [],
|
||||
};
|
||||
areas: row.areas?.map(area=>area.code) || [],
|
||||
}
|
||||
}
|
||||
setEditing(normalEditing(row));
|
||||
setOpen(true);
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ const ShopLayout: React.FC = () => {
|
|||
const location = useLocation();
|
||||
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editingSite, setEditingSite] = useState<
|
||||
(SiteItem & { areas: string[] }) | null
|
||||
>(null);
|
||||
const [editingSite, setEditingSite] = useState<SiteItem & { areas: string[] } | null>(null);
|
||||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
|
|
@ -97,12 +95,7 @@ const ShopLayout: React.FC = () => {
|
|||
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
|
||||
<Col span={4} style={{ height: '100%' }}>
|
||||
<Sider
|
||||
style={{
|
||||
background: 'white',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
zIndex: 1,
|
||||
}}
|
||||
style={{ background: 'white', height: '100%', overflow: 'hidden', zIndex: 1 }}
|
||||
>
|
||||
<div style={{ padding: '0 10px 16px' }}>
|
||||
<div
|
||||
|
|
@ -110,7 +103,7 @@ const ShopLayout: React.FC = () => {
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '4px',
|
||||
gap: '4px'
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
|
|
@ -135,8 +128,8 @@ const ShopLayout: React.FC = () => {
|
|||
function normalizeEditing(site: SiteItem) {
|
||||
return {
|
||||
...site,
|
||||
areas: site.areas?.map((area) => area.code) || [],
|
||||
};
|
||||
areas: site.areas?.map(area => area.code) || [],
|
||||
}
|
||||
}
|
||||
setEditingSite(normalizeEditing(currentSite));
|
||||
setEditModalOpen(true);
|
||||
|
|
|
|||
|
|
@ -238,9 +238,11 @@ const OrdersPage: React.FC = () => {
|
|||
? `快递方式: ${item.shipping_provider}`
|
||||
: ''}
|
||||
</span>
|
||||
{item.shipping_method
|
||||
? `发货方式: ${item.shipping_method}`
|
||||
: ''}
|
||||
{
|
||||
item.shipping_method
|
||||
? `发货方式: ${item.shipping_method}`
|
||||
: ''
|
||||
}
|
||||
<span>
|
||||
{item.tracking_number
|
||||
? `物流单号: ${item.tracking_number}`
|
||||
|
|
|
|||
Loading…
Reference in New Issue