feat: 添加部门管理功能、主题切换和多语言支持
refactor(dashboard): 重构用户管理页面和路由结构 feat(server): 实现部门管理API和RBAC增强功能 docs: 更新用户手册和管理员指南文档 style: 统一图标使用和组件命名规范 test: 添加部门服务和数据隔离测试用例 chore: 更新依赖和配置文件
This commit is contained in:
@@ -214,11 +214,11 @@ const ProductPublishForm: FC = () => {
|
||||
|
||||
// 模拟保存到本地数据库
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
message.success('Product saved to local system');
|
||||
message.success(t('product.publish.productSaved', { name: values.name }));
|
||||
|
||||
// Step 2: 合规检查(本地进行)
|
||||
if (values.complianceCheck) {
|
||||
message.info('Running compliance check...');
|
||||
message.info(t('product.publish.runComplianceCheck'));
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
}
|
||||
|
||||
@@ -226,14 +226,14 @@ const ProductPublishForm: FC = () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
|
||||
Modal.success({
|
||||
title: 'Product Created Successfully',
|
||||
title: t('product.publish.productCreated'),
|
||||
content: (
|
||||
<div>
|
||||
<p>Product "{values.name}" has been saved to local system.</p>
|
||||
<p>Target platforms: {values.platform.join(', ')}</p>
|
||||
<p>Status: {values.autoPublish ? 'Will be auto-published after review' : 'Pending review'}</p>
|
||||
<p>{t('product.publish.productSaved', { name: values.name })}</p>
|
||||
<p>{t('product.publish.targetPlatformsList', { platforms: values.platform.join(', ') })}</p>
|
||||
<p>Status: {values.autoPublish ? t('product.publish.statusAuto') : t('product.publish.statusPending')}</p>
|
||||
<p style={{ color: '#1890ff', marginTop: 8 }}>
|
||||
The product will be synced to platforms according to the sync schedule.
|
||||
{t('product.publish.syncSchedule')}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -244,7 +244,7 @@ const ProductPublishForm: FC = () => {
|
||||
setVariants([]);
|
||||
setFileList([]);
|
||||
} catch (error) {
|
||||
message.error('Please fill in required fields');
|
||||
message.error(t('product.publish.fillRequiredFields'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -252,31 +252,31 @@ const ProductPublishForm: FC = () => {
|
||||
|
||||
const variantColumns = [
|
||||
{
|
||||
title: 'SKU',
|
||||
title: t('product.publish.sku'),
|
||||
dataIndex: 'sku',
|
||||
key: 'sku',
|
||||
render: (value: string, record: ProductVariant) => (
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => handleVariantChange(record.id, 'sku', e.target.value)}
|
||||
placeholder="SKU"
|
||||
placeholder={t('product.publish.sku')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
title: t('product.publish.variantName'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (value: string, record: ProductVariant) => (
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => handleVariantChange(record.id, 'name', e.target.value)}
|
||||
placeholder="Variant Name"
|
||||
placeholder={t('product.publish.variantName')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Price',
|
||||
title: t('product.publish.price'),
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
render: (value: number, record: ProductVariant) => (
|
||||
@@ -290,7 +290,7 @@ const ProductPublishForm: FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Stock',
|
||||
title: t('product.publish.stock'),
|
||||
dataIndex: 'stock',
|
||||
key: 'stock',
|
||||
render: (value: number, record: ProductVariant) => (
|
||||
@@ -303,7 +303,7 @@ const ProductPublishForm: FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
title: t('product.publish.action'),
|
||||
key: 'action',
|
||||
render: (_: any, record: ProductVariant) => (
|
||||
<Button
|
||||
@@ -311,6 +311,7 @@ const ProductPublishForm: FC = () => {
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleRemoveVariant(record.id)}
|
||||
title={t('common.delete')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -318,7 +319,7 @@ const ProductPublishForm: FC = () => {
|
||||
|
||||
return (
|
||||
<div className="product-publish-form-page">
|
||||
<Card title="Product Publish Form">
|
||||
<Card title={t('product.publish.formTitle')}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
@@ -333,25 +334,25 @@ const ProductPublishForm: FC = () => {
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={[
|
||||
{ key: 'basic', label: 'Basic Info', children: (
|
||||
{ key: 'basic', label: t('product.publish.basicInfoTab'), children: (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Product Name"
|
||||
rules={[{ required: true, message: 'Please enter product name' }]}
|
||||
label={t('product.publish.productName')}
|
||||
rules={[{ required: true, message: t('product.publish.enterProductName') }]}
|
||||
>
|
||||
<Input placeholder="Enter product name" maxLength={200} showCount />
|
||||
<Input placeholder={t('product.publish.enterProductName')} maxLength={200} showCount />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="platform"
|
||||
label="Target Platforms"
|
||||
rules={[{ required: true, message: 'Please select at least one platform' }]}
|
||||
label={t('product.publish.targetPlatforms')}
|
||||
rules={[{ required: true, message: t('product.publish.selectPlatform') }]}
|
||||
>
|
||||
<Select mode="multiple" placeholder="Select platforms">
|
||||
<Select mode="multiple" placeholder={t('product.publish.selectPlatform')}>
|
||||
{PLATFORMS.map(p => (
|
||||
<Option key={p.value} value={p.value}>{p.label}</Option>
|
||||
))}
|
||||
@@ -364,10 +365,10 @@ const ProductPublishForm: FC = () => {
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="category"
|
||||
label="Category"
|
||||
rules={[{ required: true, message: 'Please select category' }]}
|
||||
label={t('product.publish.category')}
|
||||
rules={[{ required: true, message: t('product.publish.selectCategory') }]}
|
||||
>
|
||||
<Select placeholder="Select category" showSearch>
|
||||
<Select placeholder={t('product.publish.selectCategory')} showSearch>
|
||||
{CATEGORIES.map(c => (
|
||||
<Option key={c.value} value={c.value}>{c.label}</Option>
|
||||
))}
|
||||
@@ -375,18 +376,18 @@ const ProductPublishForm: FC = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="brand" label="Brand">
|
||||
<Input placeholder="Enter brand name" />
|
||||
<Form.Item name="brand" label={t('product.publish.brand')}>
|
||||
<Input placeholder={t('product.publish.brand')} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="Description"
|
||||
rules={[{ required: true, message: 'Please enter description' }]}
|
||||
label={t('product.publish.description')}
|
||||
rules={[{ required: true, message: t('product.publish.enterDescription') }]}
|
||||
>
|
||||
<TextArea rows={4} placeholder="Enter product description" maxLength={5000} showCount />
|
||||
<TextArea rows={4} placeholder={t('product.publish.enterDescription')} maxLength={5000} showCount />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t('product.publish.productImages')}</Divider>
|
||||
@@ -521,34 +522,34 @@ const ProductPublishForm: FC = () => {
|
||||
</Row>
|
||||
</>
|
||||
)},
|
||||
{ key: 'inventory', label: <><BoxPlotOutlined /> Inventory</>, children: (
|
||||
{ key: 'inventory', label: <><BoxPlotOutlined /> {t('product.publish.inventoryTab')}</>, children: (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="sku"
|
||||
label="SKU"
|
||||
rules={[{ required: true, message: 'Please enter SKU' }]}
|
||||
label={t('product.publish.sku')}
|
||||
rules={[{ required: true, message: t('product.publish.enterSku') }]}
|
||||
>
|
||||
<Input placeholder="Enter SKU" />
|
||||
<Input placeholder={t('product.publish.enterSku')} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="stock"
|
||||
label="Initial Stock"
|
||||
rules={[{ required: true, message: 'Please enter stock quantity' }]}
|
||||
label={t('product.publish.stock')}
|
||||
rules={[{ required: true, message: t('product.publish.enterStock') }]}
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} placeholder="0" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider>Variants</Divider>
|
||||
<Divider>{t('product.publish.variantName')}</Divider>
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="dashed" icon={<PlusOutlined />} onClick={handleAddVariant}>
|
||||
Add Variant
|
||||
{t('product.publish.addVariant')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -558,23 +559,23 @@ const ProductPublishForm: FC = () => {
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
size="small"
|
||||
locale={{ emptyText: 'No variants added' }}
|
||||
locale={{ emptyText: t('product.publish.noVariants') }}
|
||||
/>
|
||||
</>
|
||||
)},
|
||||
{ key: 'settings', label: 'Publish Settings', children: (
|
||||
{ key: 'settings', label: t('product.publish.settingsTab'), children: (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="Status"
|
||||
label={t('product.publish.status')}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select>
|
||||
<Option value="DRAFT">Draft</Option>
|
||||
<Option value="PENDING">Pending Review</Option>
|
||||
<Option value="ACTIVE">Active</Option>
|
||||
<Option value="DRAFT">{t('common.draft')}</Option>
|
||||
<Option value="PENDING">{t('product.publish.statusPending')}</Option>
|
||||
<Option value="ACTIVE">{t('common.active')}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@@ -582,29 +583,29 @@ const ProductPublishForm: FC = () => {
|
||||
|
||||
<Form.Item
|
||||
name="complianceCheck"
|
||||
label="Run Compliance Check"
|
||||
label={t('product.publish.runComplianceCheck')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="Yes" unCheckedChildren="No" />
|
||||
<Switch checkedChildren={t('common.yes')} unCheckedChildren={t('common.no')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="autoPublish"
|
||||
label="Auto Publish After Compliance Check"
|
||||
label={t('product.publish.autoPublish')}
|
||||
valuePropName="checked"
|
||||
extra="Product will be automatically published if compliance check passes"
|
||||
extra={t('product.publish.autoPublishDesc')}
|
||||
>
|
||||
<Switch checkedChildren="Yes" unCheckedChildren="No" />
|
||||
<Switch checkedChildren={t('common.yes')} unCheckedChildren={t('common.no')} />
|
||||
</Form.Item>
|
||||
|
||||
<Alert
|
||||
message="Publishing Rules"
|
||||
message={t('product.publish.publishingRules')}
|
||||
description={
|
||||
<ul style={{ marginBottom: 0, paddingLeft: 20 }}>
|
||||
<li>Products are first saved to local system, then synced to platforms</li>
|
||||
<li>All products must pass compliance check before syncing</li>
|
||||
<li>Products with profit margin below threshold will require manual approval</li>
|
||||
<li>Platform sync happens according to configured schedule</li>
|
||||
<li>{t('product.publish.rule1')}</li>
|
||||
<li>{t('product.publish.rule2')}</li>
|
||||
<li>{t('product.publish.rule3')}</li>
|
||||
<li>{t('product.publish.rule4')}</li>
|
||||
</ul>
|
||||
}
|
||||
type="warning"
|
||||
@@ -618,12 +619,12 @@ const ProductPublishForm: FC = () => {
|
||||
<Divider />
|
||||
|
||||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||
<Button onClick={() => form.resetFields()}>Reset</Button>
|
||||
<Button onClick={() => form.resetFields()}>{t('product.publish.reset')}</Button>
|
||||
<Button icon={<SaveOutlined />} onClick={handleSaveDraft} loading={loading}>
|
||||
Save Draft
|
||||
{t('product.publish.saveDraft')}
|
||||
</Button>
|
||||
<Button type="primary" icon={<SendOutlined />} onClick={handleSubmit} loading={loading}>
|
||||
Save to System
|
||||
{t('product.publish.saveToSystem')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
@@ -32,21 +32,10 @@ import {
|
||||
FC,
|
||||
} from '@/imports';
|
||||
import CrossPlatformManage from './CrossPlatformManage';
|
||||
|
||||
interface Product {
|
||||
id: string;
|
||||
name: string;
|
||||
sku: string;
|
||||
price: number;
|
||||
cost: number;
|
||||
stock: number;
|
||||
status: 'draft' | 'published' | 'synced';
|
||||
platform: string;
|
||||
createdAt: string;
|
||||
}
|
||||
import { productDataSource, Product as ProductType } from '@/services/productDataSource';
|
||||
|
||||
const ProductManagement: FC = () => {
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [products, setProducts] = useState<ProductType[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@@ -54,11 +43,11 @@ const ProductManagement: FC = () => {
|
||||
const [sortField, setSortField] = useState<string>('');
|
||||
const [sortOrder, setSortOrder] = useState<'ascend' | 'descend'>('ascend');
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
||||
const [editingProduct, setEditingProduct] = useState<ProductType | null>(null);
|
||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||
const [viewingProduct, setViewingProduct] = useState<Product | null>(null);
|
||||
const [viewingProduct, setViewingProduct] = useState<ProductType | null>(null);
|
||||
const [isPricingModalVisible, setIsPricingModalVisible] = useState(false);
|
||||
const [pricingProduct, setPricingProduct] = useState<Product | null>(null);
|
||||
const [pricingProduct, setPricingProduct] = useState<ProductType | null>(null);
|
||||
const [activeTab, setActiveTab] = useState('local');
|
||||
const [form] = Form.useForm();
|
||||
const [pricingForm] = Form.useForm();
|
||||
@@ -70,9 +59,8 @@ const ProductManagement: FC = () => {
|
||||
const fetchProducts = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/products');
|
||||
const data = await response.json();
|
||||
setProducts(data.data || []);
|
||||
const data = await productDataSource.fetchProducts();
|
||||
setProducts(data || []);
|
||||
} catch (error) {
|
||||
message.error('获取商品列表失败');
|
||||
} finally {
|
||||
@@ -99,33 +87,31 @@ const ProductManagement: FC = () => {
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEdit = (record: Product) => {
|
||||
const handleEdit = (record: ProductType) => {
|
||||
setEditingProduct(record);
|
||||
form.setFieldsValue(record);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleView = (record: Product) => {
|
||||
const handleView = (record: ProductType) => {
|
||||
setViewingProduct(record);
|
||||
setIsDetailModalVisible(true);
|
||||
};
|
||||
|
||||
const handlePricing = (record: Product) => {
|
||||
const handlePricing = (record: ProductType) => {
|
||||
setPricingProduct(record);
|
||||
pricingForm.setFieldsValue({
|
||||
cost: record.cost,
|
||||
cost: record.costPrice,
|
||||
price: record.price,
|
||||
profit: record.price - record.cost,
|
||||
profitMargin: ((record.price - record.cost) / record.price * 100).toFixed(2)
|
||||
profit: record.profit,
|
||||
profitMargin: ((record.profit / record.price) * 100).toFixed(2)
|
||||
});
|
||||
setIsPricingModalVisible(true);
|
||||
};
|
||||
|
||||
const handlePublish = async (record: Product) => {
|
||||
const handlePublish = async (record: ProductType) => {
|
||||
try {
|
||||
await fetch(`/api/v1/products/${record.id}/publish`, {
|
||||
method: 'POST'
|
||||
});
|
||||
await productDataSource.updateProductStatus(record.id, 'LISTED');
|
||||
message.success('商品上架成功');
|
||||
fetchProducts();
|
||||
} catch (error) {
|
||||
@@ -133,11 +119,9 @@ const ProductManagement: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSync = async (record: Product) => {
|
||||
const handleSync = async (record: ProductType) => {
|
||||
try {
|
||||
await fetch(`/api/v1/products/${record.id}/sync`, {
|
||||
method: 'POST'
|
||||
});
|
||||
await productDataSource.syncProduct(record.id);
|
||||
message.success('商品同步成功');
|
||||
fetchProducts();
|
||||
} catch (error) {
|
||||
@@ -151,11 +135,10 @@ const ProductManagement: FC = () => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetch('/api/v1/products/batch-sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: selectedRowKeys })
|
||||
});
|
||||
// 逐个同步商品
|
||||
for (const id of selectedRowKeys) {
|
||||
await productDataSource.syncProduct(id as string);
|
||||
}
|
||||
message.success('批量同步成功');
|
||||
fetchProducts();
|
||||
} catch (error) {
|
||||
@@ -165,9 +148,7 @@ const ProductManagement: FC = () => {
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await fetch(`/api/v1/products/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
await productDataSource.deleteProduct(id);
|
||||
message.success('商品删除成功');
|
||||
fetchProducts();
|
||||
} catch (error) {
|
||||
@@ -179,17 +160,24 @@ const ProductManagement: FC = () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
if (editingProduct) {
|
||||
await fetch(`/api/v1/products/${editingProduct.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(values)
|
||||
});
|
||||
await productDataSource.updateProduct(editingProduct.id, values);
|
||||
message.success('商品更新成功');
|
||||
} else {
|
||||
await fetch('/api/v1/products', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(values)
|
||||
await productDataSource.createProduct({
|
||||
sku: values.sku,
|
||||
name: values.name,
|
||||
image: values.image || 'https://via.placeholder.com/80x80?text=Product',
|
||||
category: values.category || '工业自动化',
|
||||
price: values.price,
|
||||
costPrice: values.costPrice || 0,
|
||||
profit: values.profit || 0,
|
||||
roi: values.roi || 0,
|
||||
stock: values.stock || 0,
|
||||
status: 'DRAFT',
|
||||
platformStatus: {},
|
||||
shopId: 'shop-tiktok-1',
|
||||
shopName: 'TikTok旗舰店',
|
||||
platform: 'TikTok',
|
||||
});
|
||||
message.success('商品创建成功');
|
||||
}
|
||||
@@ -215,10 +203,10 @@ const ProductManagement: FC = () => {
|
||||
try {
|
||||
const values = await pricingForm.validateFields();
|
||||
if (pricingProduct) {
|
||||
await fetch(`/api/v1/products/${pricingProduct.id}/pricing`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(values)
|
||||
await productDataSource.updateProduct(pricingProduct.id, {
|
||||
price: values.price,
|
||||
costPrice: values.cost,
|
||||
profit: values.profit
|
||||
});
|
||||
message.success('定价更新成功');
|
||||
setIsPricingModalVisible(false);
|
||||
@@ -241,7 +229,7 @@ const ProductManagement: FC = () => {
|
||||
});
|
||||
|
||||
const sortedProducts = [...filteredProducts].sort((a, b) => {
|
||||
const field = sortField as keyof Product;
|
||||
const field = sortField as keyof ProductType;
|
||||
const aValue = a[field];
|
||||
const bValue = b[field];
|
||||
|
||||
@@ -273,10 +261,10 @@ const ProductManagement: FC = () => {
|
||||
},
|
||||
{
|
||||
title: '成本',
|
||||
dataIndex: 'cost',
|
||||
key: 'cost',
|
||||
dataIndex: 'costPrice',
|
||||
key: 'costPrice',
|
||||
sorter: true,
|
||||
render: (cost: number) => `¥${cost.toFixed(2)}`
|
||||
render: (costPrice: number) => `¥${costPrice.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
title: '库存',
|
||||
@@ -289,32 +277,32 @@ const ProductManagement: FC = () => {
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '草稿', value: 'draft' },
|
||||
{ text: '已定价', value: 'priced' },
|
||||
{ text: '已上架', value: 'listed' },
|
||||
{ text: '同步中', value: 'syncing' },
|
||||
{ text: '已在线', value: 'live' },
|
||||
{ text: '同步失败', value: 'sync_failed' },
|
||||
{ text: '已下架', value: 'offline' }
|
||||
{ text: '草稿', value: 'DRAFT' },
|
||||
{ text: '已定价', value: 'PRICED' },
|
||||
{ text: '已上架', value: 'LISTED' },
|
||||
{ text: '同步中', value: 'SYNCING' },
|
||||
{ text: '已在线', value: 'LIVE' },
|
||||
{ text: '同步失败', value: 'SYNC_FAILED' },
|
||||
{ text: '已下架', value: 'OFFLINE' }
|
||||
],
|
||||
render: (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
draft: 'default',
|
||||
priced: 'processing',
|
||||
listed: 'warning',
|
||||
syncing: 'processing',
|
||||
live: 'success',
|
||||
sync_failed: 'error',
|
||||
offline: 'default'
|
||||
DRAFT: 'default',
|
||||
PRICED: 'processing',
|
||||
LISTED: 'warning',
|
||||
SYNCING: 'processing',
|
||||
LIVE: 'success',
|
||||
SYNC_FAILED: 'error',
|
||||
OFFLINE: 'default'
|
||||
};
|
||||
const textMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
priced: '已定价',
|
||||
listed: '已上架(本地)',
|
||||
syncing: '同步中',
|
||||
live: '已在线',
|
||||
sync_failed: '同步失败',
|
||||
offline: '已下架'
|
||||
DRAFT: '草稿',
|
||||
PRICED: '已定价',
|
||||
LISTED: '已上架(本地)',
|
||||
SYNCING: '同步中',
|
||||
LIVE: '已在线',
|
||||
SYNC_FAILED: '同步失败',
|
||||
OFFLINE: '已下架'
|
||||
};
|
||||
return <Tag color={colorMap[status]}>{textMap[status]}</Tag>;
|
||||
}
|
||||
@@ -327,7 +315,7 @@ const ProductManagement: FC = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: Product) => (
|
||||
render: (_: any, record: ProductType) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
type="link"
|
||||
@@ -349,7 +337,7 @@ const ProductManagement: FC = () => {
|
||||
>
|
||||
定价
|
||||
</Button>
|
||||
{record.status === 'draft' && (
|
||||
{record.status === 'DRAFT' && (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => handlePublish(record)}
|
||||
@@ -417,9 +405,13 @@ const ProductManagement: FC = () => {
|
||||
onChange={handleFilter}
|
||||
allowClear
|
||||
>
|
||||
<Select.Option value="draft">草稿</Select.Option>
|
||||
<Select.Option value="published">已发布</Select.Option>
|
||||
<Select.Option value="synced">已同步</Select.Option>
|
||||
<Select.Option value="DRAFT">草稿</Select.Option>
|
||||
<Select.Option value="PRICED">已定价</Select.Option>
|
||||
<Select.Option value="LISTED">已上架</Select.Option>
|
||||
<Select.Option value="SYNCING">同步中</Select.Option>
|
||||
<Select.Option value="LIVE">已在线</Select.Option>
|
||||
<Select.Option value="SYNC_FAILED">同步失败</Select.Option>
|
||||
<Select.Option value="OFFLINE">已下架</Select.Option>
|
||||
</Select>
|
||||
<Select
|
||||
placeholder="排序字段"
|
||||
@@ -510,7 +502,7 @@ const ProductManagement: FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="cost"
|
||||
name="costPrice"
|
||||
label="成本"
|
||||
rules={[{ required: true, message: '请输入成本' }]}
|
||||
>
|
||||
@@ -538,9 +530,16 @@ const ProductManagement: FC = () => {
|
||||
rules={[{ required: true, message: '请选择平台' }]}
|
||||
>
|
||||
<Select placeholder="请选择平台">
|
||||
<Select.Option value="amazon">Amazon</Select.Option>
|
||||
<Select.Option value="ebay">eBay</Select.Option>
|
||||
<Select.Option value="shopify">Shopify</Select.Option>
|
||||
<Select.Option value="TikTok">TikTok</Select.Option>
|
||||
<Select.Option value="Shopee">Shopee</Select.Option>
|
||||
<Select.Option value="Lazada">Lazada</Select.Option>
|
||||
<Select.Option value="Amazon">Amazon</Select.Option>
|
||||
<Select.Option value="eBay">eBay</Select.Option>
|
||||
<Select.Option value="Shopify">Shopify</Select.Option>
|
||||
<Select.Option value="TemuFull">Temu</Select.Option>
|
||||
<Select.Option value="Shein">Shein</Select.Option>
|
||||
<Select.Option value="AliExpress">AliExpress</Select.Option>
|
||||
<Select.Option value="Walmart">Walmart</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
@@ -576,11 +575,11 @@ const ProductManagement: FC = () => {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Text strong>成本:</Text>
|
||||
<div>¥{viewingProduct.cost.toFixed(2)}</div>
|
||||
<div>¥{viewingProduct.costPrice.toFixed(2)}</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Text strong>利润:</Text>
|
||||
<div>¥{(viewingProduct.price - viewingProduct.cost).toFixed(2)}</div>
|
||||
<div>¥{viewingProduct.profit.toFixed(2)}</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16} style={{ marginTop: 16 }}>
|
||||
@@ -591,9 +590,27 @@ const ProductManagement: FC = () => {
|
||||
<Col span={8}>
|
||||
<Text strong>状态:</Text>
|
||||
<div>
|
||||
<Tag color={viewingProduct.status === 'published' ? 'green' : 'default'}>
|
||||
{viewingProduct.status === 'published' ? '已发布' : viewingProduct.status === 'synced' ? '已同步' : '草稿'}
|
||||
</Tag>
|
||||
{(() => {
|
||||
const colorMap: Record<string, string> = {
|
||||
DRAFT: 'default',
|
||||
PRICED: 'processing',
|
||||
LISTED: 'warning',
|
||||
SYNCING: 'processing',
|
||||
LIVE: 'success',
|
||||
SYNC_FAILED: 'error',
|
||||
OFFLINE: 'default'
|
||||
};
|
||||
const textMap: Record<string, string> = {
|
||||
DRAFT: '草稿',
|
||||
PRICED: '已定价',
|
||||
LISTED: '已上架(本地)',
|
||||
SYNCING: '同步中',
|
||||
LIVE: '已在线',
|
||||
SYNC_FAILED: '同步失败',
|
||||
OFFLINE: '已下架'
|
||||
};
|
||||
return <Tag color={colorMap[viewingProduct.status]}>{textMap[viewingProduct.status]}</Tag>;
|
||||
})()}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
|
||||
Reference in New Issue
Block a user