zksu
/
WEB
forked from yoone/WEB
1
0
Fork 0

Compare commits

...

3 Commits

Author SHA1 Message Date
tikkhun 04b9fed6f7 feat(Product/List): 添加图片列到产品列表
feat(Product/BrandSpace): 重构品牌空间页面布局和逻辑

refactor(.umirc): 调整产品路由顺序

fix(Product/CsvTool): 修复复选框事件处理并移除调试日志
2026-01-16 15:58:56 +08:00
tikkhun 49e3049681 refactor(DictItemImportButton): 移除冗余的成功检查逻辑
该逻辑已在API层处理,无需在前端重复检查
2026-01-16 15:58:55 +08:00
zhuotianyuan af1cebe29d fix(订单同步): 修复订单同步功能并添加日期范围选择
修复订单同步接口调用参数错误
在同步表单中添加日期范围选择器
调整订单来源统计接口名称拼写错误
2026-01-14 20:04:07 +08:00
8 changed files with 168 additions and 109 deletions

View File

@ -149,11 +149,6 @@ export default defineConfig({
path: '/product/list', path: '/product/list',
component: './Product/List', component: './Product/List',
}, },
{
name: '产品属性排列',
path: '/product/permutation',
component: './Product/Permutation',
},
{ {
name: '产品分类', name: '产品分类',
path: '/product/category', path: '/product/category',
@ -164,6 +159,11 @@ export default defineConfig({
path: '/product/attribute', path: '/product/attribute',
component: './Product/Attribute', component: './Product/Attribute',
}, },
{
name: '产品属性排列',
path: '/product/permutation',
component: './Product/Permutation',
},
{ {
name: '产品品牌空间', name: '产品品牌空间',
path: '/product/brandspace', path: '/product/brandspace',

View File

@ -5,8 +5,10 @@ import {
DrawerForm, DrawerForm,
ProForm, ProForm,
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 React from 'react'; import React from 'react';
// 定义SyncForm组件的props类型 // 定义SyncForm组件的props类型
@ -14,6 +16,7 @@ interface SyncFormProps {
tableRef: React.MutableRefObject<ActionType | undefined>; tableRef: React.MutableRefObject<ActionType | undefined>;
onFinish: (values: any) => Promise<void>; onFinish: (values: any) => Promise<void>;
siteId?: string; siteId?: string;
dateRange?: [dayjs.Dayjs, dayjs.Dayjs];
} }
/** /**
@ -21,7 +24,7 @@ interface SyncFormProps {
* @param {SyncFormProps} props * @param {SyncFormProps} props
* @returns {React.ReactElement} * @returns {React.ReactElement}
*/ */
const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => { const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRange }) => {
// 使用 antd 的 App 组件提供的 message API // 使用 antd 的 App 组件提供的 message API
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
@ -49,6 +52,10 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
// 返回一个抽屉表单 // 返回一个抽屉表单
return ( return (
<DrawerForm<API.ordercontrollerSyncorderParams> <DrawerForm<API.ordercontrollerSyncorderParams>
initialValues={{
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
}}
title="同步订单" title="同步订单"
// 表单的触发器,一个带图标的按钮 // 表单的触发器,一个带图标的按钮
trigger={ trigger={
@ -67,6 +74,7 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
onFinish={onFinish} onFinish={onFinish}
> >
<ProForm.Group> <ProForm.Group>
{/* 站点选择框 */} {/* 站点选择框 */}
<ProFormSelect <ProFormSelect
name="siteId" name="siteId"
@ -83,6 +91,22 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
})); }));
}} }}
/> />
<ProFormDateRangePicker
name="dateRange"
label="同步日期范围"
placeholder={['开始日期', '结束日期']}
transform={(value) => {
return {
dateRange: value,
};
}}
fieldProps={{
showTime: false,
style: { width: '100%' },
}}
/>
</ProForm.Group> </ProForm.Group>
</DrawerForm> </DrawerForm>
); );

View File

@ -52,12 +52,6 @@ const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
}); });
} }
// 检查返回结果是否包含 success 字段
if (result && result.success !== undefined && !result.success) {
throw new Error(result.message || '导入失败');
}
onSuccess?.(result);
// 显示导入结果详情 // 显示导入结果详情
showImportResult(result); showImportResult(result);

View File

@ -502,7 +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'});
if (!success) { if (!success) {
throw new Error(errMsg); throw new Error(errMsg);
} }
@ -997,6 +997,7 @@ const Detail: React.FC<{
); );
}} }}
/> />
{/* 显示 related order */} {/* 显示 related order */}
<ProDescriptions.Item <ProDescriptions.Item
label="关联" label="关联"
@ -1512,7 +1513,7 @@ const Shipping: React.FC<{
// message: '至少需要一个商品', // message: '至少需要一个商品',
// validator: (_, value) => // validator: (_, value) =>
// value && value.length > 0 // value && value.length > 0
// ? Promise.resolve() //</Col> ? Promise.resolve()
// : Promise.reject('至少需要一个商品'), // : Promise.reject('至少需要一个商品'),
// }, // },
// ]} // ]}

View File

@ -67,6 +67,7 @@ const BrandSpace: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [attributeValuesLoading, setAttributeValuesLoading] = useState(false);
// Fetch brands list // Fetch brands list
const fetchBrands = async () => { const fetchBrands = async () => {
@ -123,6 +124,7 @@ const BrandSpace: React.FC = () => {
// Fetch attribute values based on selected attribute // Fetch attribute values based on selected attribute
const fetchAttributeValues = async (attributeName: string) => { const fetchAttributeValues = async (attributeName: string) => {
setAttributeValuesLoading(true);
try { try {
const response = await request('/dict/items', { const response = await request('/dict/items', {
params: { dictId: attributeName }, params: { dictId: attributeName },
@ -132,6 +134,10 @@ const BrandSpace: React.FC = () => {
} catch (error) { } catch (error) {
console.error('Failed to fetch attribute values:', error); console.error('Failed to fetch attribute values:', error);
message.error('获取属性值列表失败'); message.error('获取属性值列表失败');
// Clear attribute values on error
setAttributeValues([]);
} finally {
setAttributeValuesLoading(false);
} }
}; };
@ -160,13 +166,13 @@ const BrandSpace: React.FC = () => {
params, params,
}); });
const productList = Array.isArray(response) const productList = response?.data?.items || [];
? response
: response?.data || [];
setProducts(productList); setProducts(productList);
} catch (error) { } catch (error) {
console.error('Failed to fetch products:', error); console.error('Failed to fetch products:', error);
message.error('获取产品列表失败'); message.error('获取产品列表失败');
// Clear products on error
setProducts([]);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -270,6 +276,7 @@ const BrandSpace: React.FC = () => {
value={selectedAttributeValue} value={selectedAttributeValue}
onChange={handleAttributeValueChange} onChange={handleAttributeValueChange}
allowClear allowClear
loading={attributeValuesLoading}
> >
{attributeValues.map((value) => ( {attributeValues.map((value) => (
<Option key={value.id} value={value.id}> <Option key={value.id} value={value.id}>
@ -340,7 +347,7 @@ const BrandSpace: React.FC = () => {
</Sider> </Sider>
{/* Main Content - Product List */} {/* Main Content - Product List */}
<Content style={{ padding: '16px' }}> <Content style={{ padding: '16px', overflowX: 'auto' }}>
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<Title level={4} style={{ margin: 0 }}> <Title level={4} style={{ margin: 0 }}>
@ -354,29 +361,65 @@ const BrandSpace: React.FC = () => {
<div style={{ textAlign: 'center', padding: '64px' }}> <div style={{ textAlign: 'center', padding: '64px' }}>
<Text>...</Text> <Text>...</Text>
</div> </div>
) : products.length > 0 ? ( ) : attributeValues.length > 0 ? (
<div style={{ minWidth: 'max-content' }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{products.map((product) => ( {attributeValues.map((attributeValue) => {
<Col xs={24} sm={12} md={8} lg={6} key={product.id}> // 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 <Card
hoverable title={
<Space>
{attributeValue.image && (
<Image
src={attributeValue.image}
style={{ style={{
height: '100%', width: 24,
display: 'flex', height: 24,
flexDirection: 'column', 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 <div
style={{ style={{
height: 200, height: 100,
overflow: 'hidden', overflow: 'hidden',
marginBottom: 12, marginBottom: 8,
}} }}
> >
<Image <Image
src={ src={
product.image || product.image ||
'https://via.placeholder.com/200x200?text=No+Image' 'https://via.placeholder.com/100x100?text=No+Image'
} }
alt={product.name} alt={product.name}
style={{ style={{
@ -386,50 +429,41 @@ const BrandSpace: React.FC = () => {
}} }}
/> />
</div> </div>
<div <div>
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
}}
>
<Text <Text
type="secondary" type="secondary"
style={{ fontSize: 12, marginBottom: 4 }} style={{ fontSize: 12, marginBottom: 4 }}
> >
{product.sku} {product.sku}
</Text> </Text>
<Title <div style={{ marginTop: 4 }}>
level={5} <Text ellipsis style={{ width: '100%', display: 'block' }}>
style={{
margin: '4px 0',
fontSize: 16,
height: 48,
overflow: 'hidden',
}}
>
{product.name} {product.name}
</Title> </Text>
<div </div>
style={{ <div style={{ marginTop: 8 }}>
marginTop: 'auto',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text <Text
strong strong
style={{ fontSize: 18, color: '#ff4d4f' }} style={{ fontSize: 14, color: '#ff4d4f' }}
> >
¥{product.price || '--'} ¥{product.price || '--'}
</Text> </Text>
</div> </div>
</div> </div>
</Card> </Card>
</Col>
))} ))}
</div>
) : (
<div style={{ textAlign: 'center', padding: '32px' }}>
<Text type="secondary"></Text>
</div>
)}
</Card>
</Col>
);
})}
</Row> </Row>
</div>
) : ( ) : (
<div <div
style={{ style={{
@ -439,7 +473,7 @@ const BrandSpace: React.FC = () => {
borderRadius: 8, borderRadius: 8,
}} }}
> >
<Text type="secondary"></Text> <Text type="secondary"></Text>
</div> </div>
)} )}
</Content> </Content>

View File

@ -418,7 +418,6 @@ const CsvTool: React.FC = () => {
if ( if (
quantity quantity
) { ) {
console.log(quantity, attributeMappings.quantities[quantity])
// 使用quantity的shortName如果没有则使用quantity但匹配 4 个零 // 使用quantity的shortName如果没有则使用quantity但匹配 4 个零
const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0'); const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0');
skuComponents.push(quantityShortName); skuComponents.push(quantityShortName);
@ -570,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)
// 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
@ -773,7 +772,7 @@ const CsvTool: React.FC = () => {
valuePropName="checked" valuePropName="checked"
initialValue={true} initialValue={true}
> >
<Checkbox onChange={setGenerateBundleSkuForSingle}> <Checkbox onChange={(e)=> setGenerateBundleSkuForSingle(e.target.checked)}>
single类型生成bundle SKU single类型生成bundle SKU
</Checkbox> </Checkbox>
</ProForm.Item> </ProForm.Item>

View File

@ -234,6 +234,12 @@ const List: React.FC = () => {
</> </>
), ),
}, },
{
title: "图片",
dataIndex: 'image',
width: 100,
valueType:'image'
},
{ {
title: '名称', title: '名称',
dataIndex: 'name', dataIndex: 'name',
@ -249,6 +255,7 @@ const List: React.FC = () => {
}, },
}, },
{ {
title: '价格', title: '价格',
dataIndex: 'price', dataIndex: 'price',

View File

@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { import {
statisticscontrollerGetinativeusersbymonth, statisticscontrollerGetinativeusersbymonth,
statisticscontrollerGetordersorce, statisticscontrollerGetordersource,
} from '@/servers/api/statistics'; } from '@/servers/api/statistics';
import { import {
ActionType, ActionType,
@ -25,7 +25,7 @@ const ListPage: React.FC = () => {
country: ['CA'], country: ['CA'],
}; };
function handleSubmit(values: typeof initialValues) { function handleSubmit(values: typeof initialValues) {
statisticscontrollerGetordersorce({params: values}).then(({ data, success }) => { statisticscontrollerGetordersource({params: values}).then(({ data, success }) => {
if (success) setData(data); if (success) setData(data);
}); });
} }