Compare commits
No commits in common. "d78b5870359737e7c80390f8da337305225aff3a" and "d2a84a9a4ae7e1ff5c40e63ed26edd7526c3e503" have entirely different histories.
d78b587035
...
d2a84a9a4a
|
|
@ -166,8 +166,8 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '产品品牌空间',
|
name: '产品品牌空间',
|
||||||
path: '/product/groupBy',
|
path: '/product/brandspace',
|
||||||
component: './Product/GroupBy',
|
component: './Product/BrandSpace',
|
||||||
},
|
},
|
||||||
// sync
|
// sync
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import {
|
||||||
ActionType,
|
ActionType,
|
||||||
DrawerForm,
|
DrawerForm,
|
||||||
ProForm,
|
ProForm,
|
||||||
ProFormDateRangePicker,
|
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
|
ProFormDateRangePicker,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
@ -24,12 +24,7 @@ interface SyncFormProps {
|
||||||
* @param {SyncFormProps} props 组件属性
|
* @param {SyncFormProps} props 组件属性
|
||||||
* @returns {React.ReactElement} 抽屉表单
|
* @returns {React.ReactElement} 抽屉表单
|
||||||
*/
|
*/
|
||||||
const SyncForm: React.FC<SyncFormProps> = ({
|
const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRange }) => {
|
||||||
tableRef,
|
|
||||||
onFinish,
|
|
||||||
siteId,
|
|
||||||
dateRange,
|
|
||||||
}) => {
|
|
||||||
// 使用 antd 的 App 组件提供的 message API
|
// 使用 antd 的 App 组件提供的 message API
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
|
@ -57,10 +52,11 @@ const SyncForm: React.FC<SyncFormProps> = ({
|
||||||
// 返回一个抽屉表单
|
// 返回一个抽屉表单
|
||||||
return (
|
return (
|
||||||
<DrawerForm<API.ordercontrollerSyncorderParams>
|
<DrawerForm<API.ordercontrollerSyncorderParams>
|
||||||
initialValues={{
|
initialValues={{
|
||||||
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
|
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
|
||||||
}}
|
}}
|
||||||
title="同步订单"
|
|
||||||
|
title="同步订单"
|
||||||
// 表单的触发器,一个带图标的按钮
|
// 表单的触发器,一个带图标的按钮
|
||||||
trigger={
|
trigger={
|
||||||
<Button key="syncSite" type="primary">
|
<Button key="syncSite" type="primary">
|
||||||
|
|
@ -78,6 +74,7 @@ const SyncForm: React.FC<SyncFormProps> = ({
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
>
|
>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
|
|
||||||
{/* 站点选择框 */}
|
{/* 站点选择框 */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="siteId"
|
name="siteId"
|
||||||
|
|
@ -96,19 +93,20 @@ const SyncForm: React.FC<SyncFormProps> = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProFormDateRangePicker
|
<ProFormDateRangePicker
|
||||||
name="dateRange"
|
name="dateRange"
|
||||||
label="同步日期范围"
|
label="同步日期范围"
|
||||||
placeholder={['开始日期', '结束日期']}
|
placeholder={['开始日期', '结束日期']}
|
||||||
transform={(value) => {
|
|
||||||
return {
|
transform={(value) => {
|
||||||
dateRange: value,
|
return {
|
||||||
};
|
dateRange: value,
|
||||||
}}
|
};
|
||||||
fieldProps={{
|
}}
|
||||||
showTime: false,
|
fieldProps={{
|
||||||
style: { width: '100%' },
|
showTime: false,
|
||||||
}}
|
style: { width: '100%' },
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
</DrawerForm>
|
</DrawerForm>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -502,10 +502,7 @@ const ListPage: React.FC = () => {
|
||||||
success,
|
success,
|
||||||
message: errMsg,
|
message: errMsg,
|
||||||
data,
|
data,
|
||||||
} = await ordercontrollerSyncorders(values, {
|
} = await ordercontrollerSyncorders(values,{after:values.dateRange?.[0]+'T00:00:00Z',before:values.dateRange?.[1]+'T23:59:59Z'});
|
||||||
after: values.dateRange?.[0] + 'T00:00:00Z',
|
|
||||||
before: values.dateRange?.[1] + 'T23:59:59Z',
|
|
||||||
});
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
|
@ -1510,16 +1507,16 @@ const Shipping: React.FC<{
|
||||||
<ProFormList
|
<ProFormList
|
||||||
label="发货产品"
|
label="发货产品"
|
||||||
name="sales"
|
name="sales"
|
||||||
// rules={[
|
// rules={[
|
||||||
// {
|
// {
|
||||||
// required: true,
|
// required: true,
|
||||||
// message: '至少需要一个商品',
|
// message: '至少需要一个商品',
|
||||||
// validator: (_, value) =>
|
// validator: (_, value) =>
|
||||||
// value && value.length > 0
|
// value && value.length > 0
|
||||||
//</Col> ? Promise.resolve()
|
//</Col> ? Promise.resolve()
|
||||||
// : Promise.reject('至少需要一个商品'),
|
// : Promise.reject('至少需要一个商品'),
|
||||||
// },
|
// },
|
||||||
// ]}
|
// ]}
|
||||||
>
|
>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Image,
|
||||||
|
Layout,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const { Sider, Content } = Layout;
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
// Define interfaces
|
||||||
|
interface Brand {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
shortName?: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Attribute {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttributeValue {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
titleCN?: string;
|
||||||
|
value?: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: number;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
image?: string;
|
||||||
|
brandId: number;
|
||||||
|
brandName: string;
|
||||||
|
attributes: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
const BrandSpace: React.FC = () => {
|
||||||
|
// State management
|
||||||
|
const [brands, setBrands] = useState<Brand[]>([]);
|
||||||
|
const [selectedBrand, setSelectedBrand] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const [attributes, setAttributes] = useState<Attribute[]>([]);
|
||||||
|
const [selectedAttribute, setSelectedAttribute] = useState<string | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [attributeValues, setAttributeValues] = useState<AttributeValue[]>([]);
|
||||||
|
const [selectedAttributeValue, setSelectedAttributeValue] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const [products, setProducts] = useState<Product[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [attributeValuesLoading, setAttributeValuesLoading] = useState(false);
|
||||||
|
|
||||||
|
// Fetch brands list
|
||||||
|
const fetchBrands = async () => {
|
||||||
|
try {
|
||||||
|
const response = await request('/dict/items', {
|
||||||
|
params: { dictId: 'brand' }, // Assuming brand is a dict
|
||||||
|
});
|
||||||
|
const brandList = Array.isArray(response)
|
||||||
|
? response
|
||||||
|
: response?.data || [];
|
||||||
|
setBrands(brandList);
|
||||||
|
|
||||||
|
// Set default brand to "yoone" if exists
|
||||||
|
const defaultBrand = brandList.find((brand) => brand.name === 'yoone');
|
||||||
|
if (defaultBrand) {
|
||||||
|
setSelectedBrand(defaultBrand.id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch brands:', error);
|
||||||
|
message.error('获取品牌列表失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch attributes list
|
||||||
|
const fetchAttributes = async () => {
|
||||||
|
try {
|
||||||
|
// Get all dicts that are attributes (excluding non-attribute dicts)
|
||||||
|
const response = await request('/dict/list');
|
||||||
|
const dictList = Array.isArray(response)
|
||||||
|
? response
|
||||||
|
: response?.data || [];
|
||||||
|
|
||||||
|
// Filter out non-attribute dicts (assuming attributes are specific dicts)
|
||||||
|
const attributeDicts = dictList.filter((dict: any) =>
|
||||||
|
['strength', 'flavor', 'humidity', 'size', 'version'].includes(
|
||||||
|
dict.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
setAttributes(attributeDicts);
|
||||||
|
|
||||||
|
// Set default attribute to strength if exists
|
||||||
|
const defaultAttribute = attributeDicts.find(
|
||||||
|
(attr) => attr.name === 'strength',
|
||||||
|
);
|
||||||
|
if (defaultAttribute) {
|
||||||
|
setSelectedAttribute(defaultAttribute.name);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch attributes:', error);
|
||||||
|
message.error('获取属性列表失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch attribute values based on selected attribute
|
||||||
|
const fetchAttributeValues = async (attributeName: string) => {
|
||||||
|
setAttributeValuesLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await request('/dict/items', {
|
||||||
|
params: { dictId: attributeName },
|
||||||
|
});
|
||||||
|
const values = Array.isArray(response) ? response : response?.data || [];
|
||||||
|
setAttributeValues(values);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch attribute values:', error);
|
||||||
|
message.error('获取属性值列表失败');
|
||||||
|
// Clear attribute values on error
|
||||||
|
setAttributeValues([]);
|
||||||
|
} finally {
|
||||||
|
setAttributeValuesLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch products based on filters
|
||||||
|
const fetchProducts = async () => {
|
||||||
|
if (!selectedBrand) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
brandId: selectedBrand,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add attribute filter if selected
|
||||||
|
if (selectedAttribute) {
|
||||||
|
// If attribute value is selected, filter by both attribute and value
|
||||||
|
if (selectedAttributeValue) {
|
||||||
|
params[selectedAttribute] = selectedAttributeValue;
|
||||||
|
} else {
|
||||||
|
// If only attribute is selected, filter by attribute presence
|
||||||
|
params[selectedAttribute] = 'hasValue';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await request('/product/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
const productList = response?.data?.items || [];
|
||||||
|
setProducts(productList);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch products:', error);
|
||||||
|
message.error('获取产品列表失败');
|
||||||
|
// Clear products on error
|
||||||
|
setProducts([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial data fetch
|
||||||
|
useEffect(() => {
|
||||||
|
fetchBrands();
|
||||||
|
fetchAttributes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fetch attribute values when attribute changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedAttribute) {
|
||||||
|
fetchAttributeValues(selectedAttribute);
|
||||||
|
setSelectedAttributeValue(null); // Reset selected value when attribute changes
|
||||||
|
}
|
||||||
|
}, [selectedAttribute]);
|
||||||
|
|
||||||
|
// Fetch products when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProducts();
|
||||||
|
}, [selectedBrand, selectedAttribute, selectedAttributeValue]);
|
||||||
|
|
||||||
|
// Handle brand selection change
|
||||||
|
const handleBrandChange = (value: number) => {
|
||||||
|
setSelectedBrand(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle attribute selection change
|
||||||
|
const handleAttributeChange = (value: string) => {
|
||||||
|
setSelectedAttribute(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle attribute value selection change
|
||||||
|
const handleAttributeValueChange = (value: number) => {
|
||||||
|
setSelectedAttributeValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer title="品牌空间">
|
||||||
|
<Layout style={{ minHeight: 'calc(100vh - 64px)', background: '#fff' }}>
|
||||||
|
{/* Top Brand Selection */}
|
||||||
|
<div style={{ padding: '16px', borderBottom: '1px solid #f0f0f0' }}>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Title level={4} style={{ margin: 0 }}>
|
||||||
|
选择品牌
|
||||||
|
</Title>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择品牌"
|
||||||
|
style={{ width: 300 }}
|
||||||
|
value={selectedBrand}
|
||||||
|
onChange={handleBrandChange}
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
{brands.map((brand) => (
|
||||||
|
<Option key={brand.id} value={brand.id}>
|
||||||
|
{brand.name}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
{/* Left Attribute Selection */}
|
||||||
|
<Sider
|
||||||
|
width={240}
|
||||||
|
style={{ background: '#fafafa', borderRight: '1px solid #f0f0f0' }}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '16px' }}>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
|
选择属性
|
||||||
|
</Title>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择属性类型"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={selectedAttribute}
|
||||||
|
onChange={handleAttributeChange}
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
{attributes.map((attr) => (
|
||||||
|
<Option key={attr.id} value={attr.name}>
|
||||||
|
{attr.title}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{selectedAttribute && (
|
||||||
|
<>
|
||||||
|
<Title level={5} style={{ margin: '16px 0 8px 0' }}>
|
||||||
|
属性值
|
||||||
|
</Title>
|
||||||
|
<Select
|
||||||
|
placeholder={`请选择${
|
||||||
|
attributes.find((a) => a.name === selectedAttribute)
|
||||||
|
?.title
|
||||||
|
}`}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={selectedAttributeValue}
|
||||||
|
onChange={handleAttributeValueChange}
|
||||||
|
allowClear
|
||||||
|
loading={attributeValuesLoading}
|
||||||
|
>
|
||||||
|
{attributeValues.map((value) => (
|
||||||
|
<Option key={value.id} value={value.id}>
|
||||||
|
<Space>
|
||||||
|
{value.image && (
|
||||||
|
<Image
|
||||||
|
src={value.image}
|
||||||
|
style={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
objectFit: 'cover',
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{value.titleCN || value.title || value.name}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Filter Summary */}
|
||||||
|
{selectedBrand && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
padding: 12,
|
||||||
|
background: '#fff',
|
||||||
|
borderRadius: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text strong>当前筛选条件:</Text>
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<Text type="secondary">品牌: </Text>
|
||||||
|
<Text>
|
||||||
|
{brands.find((b) => b.id === selectedBrand)?.name}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
{selectedAttribute && (
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
<Text type="secondary">
|
||||||
|
{
|
||||||
|
attributes.find((a) => a.name === selectedAttribute)
|
||||||
|
?.title
|
||||||
|
}
|
||||||
|
:
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{selectedAttributeValue
|
||||||
|
? attributeValues.find(
|
||||||
|
(v) => v.id === selectedAttributeValue,
|
||||||
|
)?.titleCN ||
|
||||||
|
attributeValues.find(
|
||||||
|
(v) => v.id === selectedAttributeValue,
|
||||||
|
)?.title
|
||||||
|
: '所有值'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Sider>
|
||||||
|
|
||||||
|
{/* Main Content - Product List */}
|
||||||
|
<Content style={{ padding: '16px', overflowX: 'auto' }}>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Title level={4} style={{ margin: 0 }}>
|
||||||
|
产品列表
|
||||||
|
<Text type="secondary" style={{ fontSize: 16, marginLeft: 8 }}>
|
||||||
|
({products.length} 个产品)
|
||||||
|
</Text>
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '64px' }}>
|
||||||
|
<Text>加载中...</Text>
|
||||||
|
</div>
|
||||||
|
) : attributeValues.length > 0 ? (
|
||||||
|
<div style={{ minWidth: 'max-content' }}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{attributeValues.map((attributeValue) => {
|
||||||
|
// Filter products for this attribute value
|
||||||
|
if(!Array.isArray(products)){
|
||||||
|
console.log('products',products)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const attributeValueProducts = selectedAttribute
|
||||||
|
? (products || []).filter((product) =>
|
||||||
|
product.attributes &&
|
||||||
|
product.attributes[selectedAttribute] === attributeValue.id
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col key={attributeValue.id} style={{ minWidth: 300 }}>
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
{attributeValue.image && (
|
||||||
|
<Image
|
||||||
|
src={attributeValue.image}
|
||||||
|
style={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
objectFit: 'cover',
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{attributeValue.titleCN || attributeValue.title || attributeValue.name}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
style={{ width: 300 }}
|
||||||
|
>
|
||||||
|
{attributeValueProducts.length > 0 ? (
|
||||||
|
<div style={{ maxHeight: 600, overflowY: 'auto' }}>
|
||||||
|
{attributeValueProducts.map((product) => (
|
||||||
|
<Card
|
||||||
|
key={product.id}
|
||||||
|
size="small"
|
||||||
|
hoverable
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 100,
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={
|
||||||
|
product.image ||
|
||||||
|
'https://via.placeholder.com/100x100?text=No+Image'
|
||||||
|
}
|
||||||
|
alt={product.name}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
type="secondary"
|
||||||
|
style={{ fontSize: 12, marginBottom: 4 }}
|
||||||
|
>
|
||||||
|
{product.sku}
|
||||||
|
</Text>
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
<Text ellipsis style={{ width: '100%', display: 'block' }}>
|
||||||
|
{product.name}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{ fontSize: 14, color: '#ff4d4f' }}
|
||||||
|
>
|
||||||
|
¥{product.price || '--'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ textAlign: 'center', padding: '32px' }}>
|
||||||
|
<Text type="secondary">暂无产品</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '64px',
|
||||||
|
background: '#fafafa',
|
||||||
|
borderRadius: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text type="secondary">暂无属性值或产品</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BrandSpace;
|
||||||
|
|
@ -415,11 +415,11 @@ const CsvTool: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果type为single且启用了生成bundle SKU,则添加quantity
|
// 如果type为single且启用了生成bundle SKU,则添加quantity
|
||||||
if (quantity) {
|
if (
|
||||||
|
quantity
|
||||||
|
) {
|
||||||
// 使用quantity的shortName,如果没有则使用quantity但匹配 4 个零
|
// 使用quantity的shortName,如果没有则使用quantity但匹配 4 个零
|
||||||
const quantityShortName =
|
const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0');
|
||||||
attributeMappings.quantities[quantity] ||
|
|
||||||
Number(quantity).toString().padStart(4, '0');
|
|
||||||
skuComponents.push(quantityShortName);
|
skuComponents.push(quantityShortName);
|
||||||
}
|
}
|
||||||
// 合并所有组件,使用短横线分隔
|
// 合并所有组件,使用短横线分隔
|
||||||
|
|
@ -463,7 +463,9 @@ const CsvTool: React.FC = () => {
|
||||||
if (size) nameComponents.push(size);
|
if (size) nameComponents.push(size);
|
||||||
|
|
||||||
// 如果有数量且类型为bundle或者生成bundle的single产品,则添加数量
|
// 如果有数量且类型为bundle或者生成bundle的single产品,则添加数量
|
||||||
if (type === 'bundle' && quantity) {
|
if (
|
||||||
|
type==='bundle' && quantity
|
||||||
|
) {
|
||||||
nameComponents.push(String(quantity));
|
nameComponents.push(String(quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,7 +569,7 @@ const CsvTool: React.FC = () => {
|
||||||
|
|
||||||
// Determine which data to use for processing and download
|
// Determine which data to use for processing and download
|
||||||
let finalData = dataWithSku;
|
let finalData = dataWithSku;
|
||||||
console.log('generateBundleSkuForSingle', generateBundleSkuForSingle);
|
console.log('generateBundleSkuForSingle',generateBundleSkuForSingle)
|
||||||
// If generateBundleSkuForSingle is enabled, generate bundle products for single products
|
// If generateBundleSkuForSingle is enabled, generate bundle products for single products
|
||||||
if (generateBundleSkuForSingle) {
|
if (generateBundleSkuForSingle) {
|
||||||
// Filter out single records
|
// Filter out single records
|
||||||
|
|
@ -576,9 +578,7 @@ const CsvTool: React.FC = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get quantity values from the config (same source as other attributes like brand)
|
// Get quantity values from the config (same source as other attributes like brand)
|
||||||
const quantityValues = config.quantities.map(
|
const quantityValues = config.quantities.map(quantity=>quantity.name)
|
||||||
(quantity) => quantity.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate bundle products for each single record and quantity
|
// Generate bundle products for each single record and quantity
|
||||||
const generatedBundleRecords = singleRecords.flatMap((singleRecord) => {
|
const generatedBundleRecords = singleRecords.flatMap((singleRecord) => {
|
||||||
|
|
@ -772,11 +772,7 @@ const CsvTool: React.FC = () => {
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
initialValue={true}
|
initialValue={true}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox onChange={(e)=> setGenerateBundleSkuForSingle(e.target.checked)}>
|
||||||
onChange={(e) =>
|
|
||||||
setGenerateBundleSkuForSingle(e.target.checked)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
启用为single类型生成bundle SKU
|
启用为single类型生成bundle SKU
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</ProForm.Item>
|
</ProForm.Item>
|
||||||
|
|
|
||||||
|
|
@ -1,368 +0,0 @@
|
||||||
import React, { useEffect, useState, useMemo } from 'react';
|
|
||||||
import { PageContainer, ProFromSelect } from '@ant-design/pro-components';
|
|
||||||
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 {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
attributes: string[]; // List of attribute names for this category
|
|
||||||
}
|
|
||||||
interface Attribute {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AttributeValue {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
titleCN?: string;
|
|
||||||
value?: string;
|
|
||||||
image?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Product {
|
|
||||||
id: number;
|
|
||||||
sku: string;
|
|
||||||
name: string;
|
|
||||||
image?: string;
|
|
||||||
brandId: number;
|
|
||||||
brandName: string;
|
|
||||||
attributes: { [key: string]: number }; // attribute name to attribute value id mapping
|
|
||||||
price?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grouped products by attribute value
|
|
||||||
interface GroupedProducts {
|
|
||||||
[attributeValueId: string]: Product[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProductCard component for displaying single product
|
|
||||||
const ProductCard: React.FC<{ product: Product }> = ({ product }) => {
|
|
||||||
return (
|
|
||||||
<Card hoverable style={{ width: 240 }}>
|
|
||||||
{/* <div style={{ height: 180, overflow: 'hidden', marginBottom: '12px' }}>
|
|
||||||
<Image
|
|
||||||
src={product.image || 'https://via.placeholder.com/240x180?text=No+Image'}
|
|
||||||
alt={product.name}
|
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
||||||
<div>
|
|
||||||
<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' }}>
|
|
||||||
{product.name}
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text strong style={{ fontSize: 16, color: '#ff4d4f', display: 'block' }}>
|
|
||||||
¥{product.price || '--'}
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ProductGroup component for displaying grouped products
|
|
||||||
const ProductGroup: React.FC<{
|
|
||||||
attributeValueId: string;
|
|
||||||
groupProducts: Product[];
|
|
||||||
attributeValue: AttributeValue | undefined;
|
|
||||||
attributeName: string;
|
|
||||||
}> = ({ attributeValueId, groupProducts, attributeValue }) => {
|
|
||||||
// State for collapse control
|
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
||||||
|
|
||||||
// Create collapse panel header
|
|
||||||
const panelHeader = (
|
|
||||||
<Space>
|
|
||||||
{attributeValue?.image && (
|
|
||||||
<Image
|
|
||||||
src={attributeValue.image}
|
|
||||||
style={{ width: 24, height: 24, objectFit: 'cover', borderRadius: 4 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
|
||||||
<span>
|
|
||||||
{attributeValue?.titleCN || attributeValue?.title || attributeValue?.name || attributeValueId||'未知'}
|
|
||||||
(共 {groupProducts.length} 个产品)
|
|
||||||
</span>
|
|
||||||
</Typography.Title>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
activeKey={isCollapsed ? [] : [attributeValueId]}
|
|
||||||
onChange={(key) => setIsCollapsed(Array.isArray(key) && key.length === 0)}
|
|
||||||
ghost
|
|
||||||
bordered={false}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: attributeValueId,
|
|
||||||
label: panelHeader,
|
|
||||||
children: (
|
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', paddingTop: '8px' }}>
|
|
||||||
{groupProducts.map((product) => (
|
|
||||||
<ProductCard key={product.id} product={product} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main ProductGroupBy component
|
|
||||||
const ProductGroupBy: React.FC = () => {
|
|
||||||
// State management
|
|
||||||
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 }>({});
|
|
||||||
|
|
||||||
// Group by attribute
|
|
||||||
const [groupByAttribute, setGroupByAttribute] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Products
|
|
||||||
const [products, setProducts] = useState<Product[]>([]);
|
|
||||||
const [groupedProducts, setGroupedProducts] = useState<GroupedProducts>({});
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// Extract all unique attributes from categories
|
|
||||||
const categoryAttributes = useMemo(() => {
|
|
||||||
if (!selectedCategory) return [];
|
|
||||||
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,
|
|
||||||
}));
|
|
||||||
return attributesList;
|
|
||||||
}, [selectedCategory]);
|
|
||||||
|
|
||||||
// Fetch categories list
|
|
||||||
const fetchCategories = async () => {
|
|
||||||
try {
|
|
||||||
const response = await categorycontrollerGetall();
|
|
||||||
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');
|
|
||||||
setSelectedCategory(defaultCategory?.name || rawCategories[0].name);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch categories:', error);
|
|
||||||
message.error('获取分类列表失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update category attributes when selected category changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selectedCategory) return;
|
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
// Reset attribute filters when category changes
|
|
||||||
const newFilters: { [key: string]: number | null } = {};
|
|
||||||
attributesForCategory.forEach(attr => {
|
|
||||||
newFilters[attr.name] = null;
|
|
||||||
});
|
|
||||||
setAttributeFilters(newFilters);
|
|
||||||
|
|
||||||
// Set default group by attribute
|
|
||||||
if (attributesForCategory.length > 0) {
|
|
||||||
setGroupByAttribute(attributesForCategory[0].name);
|
|
||||||
}
|
|
||||||
}, [selectedCategory, categories, categoryAttributes]);
|
|
||||||
|
|
||||||
// Handle attribute filter change
|
|
||||||
const handleAttributeFilterChange = (attributeName: string, value: number | null) => {
|
|
||||||
setAttributeFilters(prev => ({ ...prev, [attributeName]: value }));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch products based on filters
|
|
||||||
const fetchProducts = async () => {
|
|
||||||
if (!selectedCategory || !groupByAttribute) return;
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch grouped products:', error);
|
|
||||||
message.error('获取分组产品列表失败');
|
|
||||||
setProducts([]);
|
|
||||||
setGroupedProducts({});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial data fetch
|
|
||||||
useEffect(() => {
|
|
||||||
fetchCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch products when filters change
|
|
||||||
useEffect(() => {
|
|
||||||
fetchProducts();
|
|
||||||
}, [selectedCategory, attributeFilters, groupByAttribute]);
|
|
||||||
|
|
||||||
// Destructure antd components
|
|
||||||
const { Title, Text } = Typography;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContainer title="品牌空间">
|
|
||||||
<div style={{ padding: '16px', background: '#fff' }}>
|
|
||||||
{/* Filter Section */}
|
|
||||||
<div style={{ marginBottom: '24px' }}>
|
|
||||||
<Title level={4} style={{ marginBottom: '16px' }}>筛选条件</Title>
|
|
||||||
<Space direction="vertical" size="large">
|
|
||||||
{/* Category Filter */}
|
|
||||||
<div>
|
|
||||||
<Text strong>选择分类:</Text>
|
|
||||||
<Select
|
|
||||||
placeholder="请选择分类"
|
|
||||||
style={{ width: 300, marginLeft: '8px' }}
|
|
||||||
value={selectedCategory}
|
|
||||||
onChange={setSelectedCategory}
|
|
||||||
allowClear
|
|
||||||
showSearch
|
|
||||||
optionFilterProp="children"
|
|
||||||
>
|
|
||||||
{categories.map(category => (
|
|
||||||
<Option key={category.id} value={category.name}>
|
|
||||||
{category.title}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Attribute Filters */}
|
|
||||||
{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>
|
|
||||||
<ProFromSelect
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Group By Attribute */}
|
|
||||||
{categoryAttributes.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<Text strong>分组依据:</Text>
|
|
||||||
<Select
|
|
||||||
placeholder="请选择分组属性"
|
|
||||||
style={{ width: 300, marginLeft: '8px' }}
|
|
||||||
value={groupByAttribute}
|
|
||||||
onChange={setGroupByAttribute}
|
|
||||||
showSearch
|
|
||||||
optionFilterProp="children"
|
|
||||||
>
|
|
||||||
{categoryAttributes.map(attr => (
|
|
||||||
<Option key={attr.id} value={attr.name}>
|
|
||||||
{attr.title}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{/* Products Section */}
|
|
||||||
<div>
|
|
||||||
<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>
|
|
||||||
) : (
|
|
||||||
<div style={{ textAlign: 'center', padding: '64px', background: '#fafafa', borderRadius: 8 }}>
|
|
||||||
<Text type="secondary">暂无产品</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PageContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProductGroupBy;
|
|
||||||
|
|
@ -235,10 +235,10 @@ const List: React.FC = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '图片',
|
title: "图片",
|
||||||
dataIndex: 'image',
|
dataIndex: 'image',
|
||||||
width: 100,
|
width: 100,
|
||||||
valueType: 'image',
|
valueType:'image'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
|
|
@ -255,6 +255,7 @@ const List: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '价格',
|
title: '价格',
|
||||||
dataIndex: 'price',
|
dataIndex: 'price',
|
||||||
|
|
|
||||||
|
|
@ -202,11 +202,7 @@ const SiteList: React.FC = () => {
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{ title: 'webhook地址', dataIndex: 'webhookUrl', width: 280, hideInSearch: true },
|
||||||
title: 'webhook地址',
|
|
||||||
dataIndex: 'webhookUrl',
|
|
||||||
hideInSearch: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'SKU 前缀',
|
title: 'SKU 前缀',
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
label="区域"
|
label="区域"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
|
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ORDER_STATUS_ENUM } from '@/constants';
|
||||||
import { AddTag } from '@/pages/Customer/List';
|
import { AddTag } from '@/pages/Customer/List';
|
||||||
import { customercontrollerDeltag } from '@/servers/api/customer';
|
import { customercontrollerDeltag } from '@/servers/api/customer';
|
||||||
import { sitecontrollerAll } from '@/servers/api/site';
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
import * as countries from 'i18n-iso-countries';
|
||||||
import {
|
import {
|
||||||
statisticscontrollerGetorderbydate,
|
statisticscontrollerGetorderbydate,
|
||||||
statisticscontrollerGetorderbyemail,
|
statisticscontrollerGetorderbyemail,
|
||||||
|
|
@ -21,7 +22,6 @@ import { Button, Space, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
dayjs.extend(weekOfYear);
|
dayjs.extend(weekOfYear);
|
||||||
|
|
@ -40,15 +40,15 @@ const highlightText = (text: string, keyword: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
// 获取所有国家/地区的选项
|
||||||
const getCountryOptions = () => {
|
const getCountryOptions = () => {
|
||||||
// 获取所有国家的 ISO 代码
|
// 获取所有国家的 ISO 代码
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
const countryCodes = countries.getAlpha2Codes();
|
||||||
// 将国家代码转换为选项数组
|
// 将国家代码转换为选项数组
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
return Object.keys(countryCodes).map((code) => ({
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
||||||
value: code,
|
value: code,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const [xAxis, setXAxis] = useState([]);
|
const [xAxis, setXAxis] = useState([]);
|
||||||
|
|
@ -635,17 +635,18 @@ const ListPage: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="country"
|
name="country"
|
||||||
label="区域"
|
label="区域"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) =>
|
showSearch
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
filterOption={(input, option) =>
|
||||||
}
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
options={getCountryOptions()}
|
}
|
||||||
/>
|
options={getCountryOptions()}
|
||||||
|
/>
|
||||||
{/* <ProFormSelect
|
{/* <ProFormSelect
|
||||||
label="类型"
|
label="类型"
|
||||||
name="purchaseType"
|
name="purchaseType"
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,16 @@ import {
|
||||||
ActionType,
|
ActionType,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
|
ProTable,
|
||||||
ProForm,
|
ProForm,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
ProTable,
|
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Space, Tag } from 'antd';
|
import { Space, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import { HistoryOrder } from '../Order';
|
||||||
import * as countries from 'i18n-iso-countries';
|
import * as countries from 'i18n-iso-countries';
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
import zhCN from 'i18n-iso-countries/langs/zh';
|
||||||
import { HistoryOrder } from '../Order';
|
|
||||||
countries.registerLocale(zhCN);
|
countries.registerLocale(zhCN);
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState({});
|
||||||
|
|
@ -25,14 +25,12 @@ const ListPage: React.FC = () => {
|
||||||
country: ['CA'],
|
country: ['CA'],
|
||||||
};
|
};
|
||||||
function handleSubmit(values: typeof initialValues) {
|
function handleSubmit(values: typeof initialValues) {
|
||||||
statisticscontrollerGetordersource({ params: values }).then(
|
statisticscontrollerGetordersource({params: values}).then(({ data, success }) => {
|
||||||
({ data, success }) => {
|
if (success) setData(data);
|
||||||
if (success) setData(data);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleSubmit(initialValues);
|
handleSubmit(initialValues)
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const option = useMemo(() => {
|
const option = useMemo(() => {
|
||||||
|
|
@ -289,6 +287,7 @@ const ListPage: React.FC = () => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -298,6 +297,7 @@ const ListPage: React.FC = () => {
|
||||||
layout="inline"
|
layout="inline"
|
||||||
onFinish={handleSubmit}
|
onFinish={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="country"
|
name="country"
|
||||||
label="区域"
|
label="区域"
|
||||||
|
|
@ -343,6 +343,8 @@ const ListPage: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
// 获取所有国家/地区的选项
|
||||||
const getCountryOptions = () => {
|
const getCountryOptions = () => {
|
||||||
// 获取所有国家的 ISO 代码
|
// 获取所有国家的 ISO 代码
|
||||||
|
|
|
||||||
|
|
@ -623,21 +623,6 @@ export async function productcontrollerGetproductlist(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /product/list/grouped */
|
|
||||||
export async function productcontrollerGetproductlistgrouped(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.productcontrollerGetproductlistgroupedParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/product/list/grouped', {
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /product/search */
|
/** 此处后端没有提供注释 GET /product/search */
|
||||||
export async function productcontrollerSearchproducts(
|
export async function productcontrollerSearchproducts(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -963,7 +963,6 @@ declare namespace API {
|
||||||
orderId?: number;
|
orderId?: number;
|
||||||
siteId?: number;
|
siteId?: number;
|
||||||
externalOrderItemId?: string;
|
externalOrderItemId?: string;
|
||||||
parentProductId?: number;
|
|
||||||
productId?: number;
|
productId?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
/** sku */
|
/** sku */
|
||||||
|
|
@ -994,7 +993,6 @@ declare namespace API {
|
||||||
orderId?: number;
|
orderId?: number;
|
||||||
siteId?: number;
|
siteId?: number;
|
||||||
externalOrderItemId?: string;
|
externalOrderItemId?: string;
|
||||||
parentProductId?: number;
|
|
||||||
productId?: number;
|
productId?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
/** sku */
|
/** sku */
|
||||||
|
|
@ -1229,25 +1227,6 @@ declare namespace API {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type productcontrollerGetproductlistgroupedParams = {
|
|
||||||
/** 页码 */
|
|
||||||
page?: number;
|
|
||||||
/** 每页数量 */
|
|
||||||
per_page?: number;
|
|
||||||
/** 查询时间范围开始 */
|
|
||||||
after?: string;
|
|
||||||
/** 查询时间范围结束 */
|
|
||||||
before?: string;
|
|
||||||
/** 搜索关键词 */
|
|
||||||
search?: string;
|
|
||||||
/** 过滤条件对象 */
|
|
||||||
where?: any;
|
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
|
||||||
orderBy?: any;
|
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type productcontrollerGetproductlistParams = {
|
type productcontrollerGetproductlistParams = {
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
@ -1263,8 +1242,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type productcontrollerGetproductsiteskusParams = {
|
type productcontrollerGetproductsiteskusParams = {
|
||||||
|
|
@ -1807,8 +1784,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1827,8 +1802,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1847,8 +1820,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1867,8 +1838,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1887,8 +1856,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1907,8 +1874,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1927,8 +1892,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
customerId: number;
|
customerId: number;
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
@ -1953,8 +1916,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1977,8 +1938,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2012,8 +1971,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2037,8 +1994,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2057,8 +2012,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2077,8 +2030,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2102,8 +2053,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3055,8 +3004,6 @@ declare namespace API {
|
||||||
where?: any;
|
where?: any;
|
||||||
/** 排序对象,例如 { "sku": "desc" } */
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
/** 分组字段,例如 "categoryId" */
|
|
||||||
groupBy?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type UnifiedShippingLineDTO = {
|
type UnifiedShippingLineDTO = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue