refactor: 重构项目结构并优化代码

- 删除无用的文件和错误日志
- 创建统一的 imports 模块集中管理依赖
- 重构组件使用新的 imports 方式
- 修复文档路径大小写问题
- 优化类型定义和接口导出
- 更新依赖版本
- 改进错误处理和API配置
- 统一组件导出方式
This commit is contained in:
2026-03-27 16:56:06 +08:00
parent 2748456d8a
commit 22308fe042
337 changed files with 37060 additions and 57483 deletions

View File

@@ -1,14 +1,33 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Card, Typography, Row, Col, Form, Input, Select, Button, Table, Statistic, Spin, message, Alert } from 'antd';
import { CalculatorOutlined, SaveOutlined, HistoryOutlined, LineChartOutlined } from '@ant-design/icons';
import {
useState,
Link,
Card,
Typography,
Row,
Col,
Form,
Input,
Select,
Button,
Table,
Statistic,
Spin,
message,
Alert,
CalculatorOutlined,
SaveOutlined,
HistoryOutlined,
LineChartOutlined,
Title,
Text,
Paragraph,
Option,
Item,
FC,
} from '@/imports';
import { productDataSource } from '@/services/productDataSource';
const { Title, Text, Paragraph } = Typography;
const { Option } = Select;
const { Item } = Form;
interface PricingSuggestion {
id: string;
scenario: string;
@@ -28,7 +47,7 @@ interface PriceHistory {
operator: string;
}
const AIPricing: React.FC = () => {
const AIPricing: FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [pricingSuggestions, setPricingSuggestions] = useState<PricingSuggestion[]>([]);
const [priceHistory, setPriceHistory] = useState<PriceHistory[]>([]);

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -24,8 +25,6 @@ import {
Form,
InputNumber,
Switch,
} from 'antd';
import {
SyncOutlined,
EditOutlined,
EyeOutlined,
@@ -39,13 +38,14 @@ import {
LinkOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
} from '@ant-design/icons';
Text,
Title,
Option,
Search,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
const { Text, Title } = Typography;
const { Option } = Select;
const { Search } = Input;
// ==================== 多商户店铺配置 ====================
// 当前用户拥有的店铺(根据登录用户的权限动态加载)
@@ -302,7 +302,7 @@ const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.R
NOT_LISTED: { color: 'default', text: '未上架', icon: <CloseCircleOutlined /> },
};
const CrossPlatformManage: React.FC = () => {
const CrossPlatformManage: FC = () => {
const [loading, setLoading] = useState(false);
const [products, setProducts] = useState<CrossPlatformProduct[]>([]);
const [filteredProducts, setFilteredProducts] = useState<CrossPlatformProduct[]>([]);
@@ -666,7 +666,7 @@ const CrossPlatformManage: React.FC = () => {
{/* 商品详情弹窗 */}
<Modal
title="商品详情"
visible={detailModalVisible}
open={detailModalVisible}
onCancel={() => setDetailModalVisible(false)}
width={900}
footer={[
@@ -780,7 +780,7 @@ const CrossPlatformManage: React.FC = () => {
{/* 同步弹窗 */}
<Modal
title="同步商品"
visible={syncModalVisible}
open={syncModalVisible}
onCancel={() => setSyncModalVisible(false)}
onOk={handleSync}
width={600}

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import {
useState,
Card,
Upload,
Button,
@@ -18,8 +18,6 @@ import {
Tooltip,
Alert,
Tabs,
} from 'antd';
import {
UploadOutlined,
DeleteOutlined,
EyeOutlined,
@@ -31,13 +29,13 @@ import {
ReloadOutlined,
SearchOutlined,
FilterOutlined,
} from '@ant-design/icons';
Option,
FC,
} from '@/imports';
import type { UploadFile } from 'antd/es/upload/interface';
import { productDataSource } from '@/services/productDataSource';
import { Product } from '@/types/product';
const { Option } = Select;
interface MaterialFile {
id: string;
name: string;
@@ -128,7 +126,7 @@ const formatFileSize = (bytes: number): string => {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
};
export const MaterialUpload: React.FC = () => {
const MaterialUpload: FC = () => {
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [materials, setMaterials] = useState<MaterialFile[]>(MOCK_MATERIALS);
const [uploading, setUploading] = useState(false);

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Descriptions,
Button,
@@ -17,8 +18,6 @@ import {
Typography,
Tooltip,
Badge,
} from 'antd';
import {
ShoppingCartOutlined,
EditOutlined,
DeleteOutlined,
@@ -32,15 +31,15 @@ import {
CheckCircleOutlined,
CloseCircleOutlined,
SyncOutlined,
} from '@ant-design/icons';
Title,
Text,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import { productDataSource } from '@/services/productDataSource';
import { Product } from '@/types/product';
const { Title, Text } = Typography;
interface ProductDetail {
interface ProductDetailData {
id: string;
productId: string;
platform: string;
@@ -155,9 +154,9 @@ const COMPLIANCE_CONFIG: Record<string, { color: string; icon: React.ReactNode }
PENDING_REVIEW: { color: 'warning', icon: <SyncOutlined spin /> },
};
export const ProductDetail: React.FC = () => {
const ProductDetail: FC = () => {
const [loading, setLoading] = useState(true);
const [product, setProduct] = useState<ProductDetail | null>(null);
const [product, setProduct] = useState<ProductDetailData | null>(null);
const [priceHistory, setPriceHistory] = useState<PriceHistory[]>([]);
const [stockHistory, setStockHistory] = useState<StockHistory[]>([]);
const [editModalVisible, setEditModalVisible] = useState(false);

View File

@@ -1,6 +1,9 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import {
useState,
useEffect,
useCallback,
useMemo,
useNavigate,
Card,
Table,
Button,
@@ -26,8 +29,6 @@ import {
Spin,
Tabs,
Statistic,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
@@ -50,66 +51,27 @@ import {
GlobalOutlined,
ShopOutlined,
AppstoreOutlined,
} from '@ant-design/icons';
Title,
Text,
Option,
RangePicker,
Search,
ColumnsType,
TablePaginationConfig,
FilterValue,
SorterResult,
TableCurrentDataSource,
moment,
productDataSource,
Product,
Shop,
ProductFilter,
ProductSort,
PLATFORMS,
} from '@/imports';
import { LoadingState, EmptyState } from '../../components/ui';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
import moment from 'moment';
const { Title, Text } = Typography;
const { Option } = Select;
const { RangePicker } = DatePicker;
const { Search } = Input;
interface Product {
id: string;
sku: string;
name: string;
image: string;
category: string;
price: number;
costPrice: number;
profit: number;
roi: number;
stock: number;
status: 'DRAFT' | 'PRICED' | 'LISTED' | 'SYNCING' | 'LIVE' | 'SYNC_FAILED' | 'OFFLINE';
platformStatus: Record<string, string>;
shopId: string;
shopName: string;
platform: string;
createdAt: string;
updatedAt: string;
}
interface Shop {
id: string;
name: string;
platform: string;
status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';
region: string;
currency: string;
}
interface FilterState {
keyword: string;
status: string[];
platform: string[];
shop: string[];
category: string[];
priceRange: [number, number] | null;
stockRange: [number, number] | null;
roiRange: [number, number] | null;
dateRange: any;
minProfit: number | null;
minStock: number | null;
searchInDescription: boolean;
}
interface SortState {
field: string;
order: 'ascend' | 'descend' | null;
}
const CATEGORIES = ['工业自动化', '电子元器件', '工具设备', '仪器仪表', '安防设备'];
const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
DRAFT: { color: 'default', text: '草稿', icon: <EditOutlined /> },
@@ -121,86 +83,6 @@ const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.R
OFFLINE: { color: 'default', text: '已下架', icon: <ExclamationCircleOutlined /> },
};
const PLATFORMS = [
// TikTok系列
'TikTok', 'TikTokFull',
// Shopee系列
'Shopee', 'ShopeeFull', 'ShopeeLight',
// Lazada系列
'Lazada', 'LazadaFull',
// Temu
'TemuFull',
// SHEIN系列
'Shein', 'SheinHalf',
// 其他平台
'Ozon', 'Yandex', 'AliExpress', 'AliExpressHalf', 'AliExpressPop',
'Coupang', 'Walmart', 'Wildberries', 'Allegro', 'MercadoLibre',
'Jumia', 'Joom', 'Amazon', 'Wish', 'Emag', 'Miravia',
'Daraz', 'Joybuy', 'Alibaba', 'Qoo10', 'Shopify', 'Shoplazza',
'ShopyyV1', 'ShopyyV2', 'Shopline', 'GreatBoss', 'Other',
// 原有平台
'eBay',
];
const CATEGORIES = ['工业自动化', '电子元器件', '工具设备', '仪器仪表', '安防设备'];
// 店铺数据
const SHOPS: Shop[] = [
// TikTok平台 - 多个店铺
{ id: 'shop-tiktok-1', name: 'TikTok旗舰店A', platform: 'TikTok', status: 'ACTIVE', region: '中国', currency: 'CNY' },
{ id: 'shop-tiktok-2', name: 'TikTok旗舰店B', platform: 'TikTok', status: 'ACTIVE', region: '美国', currency: 'USD' },
{ id: 'shop-tiktok-3', name: 'TikTok精品店C', platform: 'TikTok', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-tiktok-4', name: 'TikTok精品店D', platform: 'TikTok', status: 'INACTIVE', region: '东南亚', currency: 'SGD' },
// Shopee平台 - 多个店铺
{ id: 'shop-shopee-1', name: 'Shopee官方店A', platform: 'Shopee', status: 'ACTIVE', region: '东南亚', currency: 'SGD' },
{ id: 'shop-shopee-2', name: 'Shopee官方店B', platform: 'Shopee', status: 'ACTIVE', region: '台湾', currency: 'TWD' },
{ id: 'shop-shopee-3', name: 'Shopee旗舰店C', platform: 'Shopee', status: 'ACTIVE', region: '马来西亚', currency: 'MYR' },
{ id: 'shop-shopee-4', name: 'Shopee旗舰店D', platform: 'Shopee', status: 'SUSPENDED', region: '菲律宾', currency: 'PHP' },
// Lazada平台 - 多个店铺
{ id: 'shop-lazada-1', name: 'Lazada官方店A', platform: 'Lazada', status: 'ACTIVE', region: '东南亚', currency: 'SGD' },
{ id: 'shop-lazada-2', name: 'Lazada官方店B', platform: 'Lazada', status: 'ACTIVE', region: '泰国', currency: 'THB' },
{ id: 'shop-lazada-3', name: 'Lazada旗舰店C', platform: 'Lazada', status: 'ACTIVE', region: '越南', currency: 'VND' },
// Amazon平台 - 多个店铺
{ id: 'shop-amazon-1', name: 'Amazon美国店A', platform: 'Amazon', status: 'ACTIVE', region: '美国', currency: 'USD' },
{ id: 'shop-amazon-2', name: 'Amazon美国店B', platform: 'Amazon', status: 'ACTIVE', region: '美国', currency: 'USD' },
{ id: 'shop-amazon-3', name: 'Amazon欧洲店C', platform: 'Amazon', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-amazon-4', name: 'Amazon欧洲店D', platform: 'Amazon', status: 'ACTIVE', region: '英国', currency: 'GBP' },
{ id: 'shop-amazon-5', name: 'Amazon日本店E', platform: 'Amazon', status: 'ACTIVE', region: '日本', currency: 'JPY' },
// eBay平台 - 多个店铺
{ id: 'shop-ebay-1', name: 'eBay全球店A', platform: 'eBay', status: 'ACTIVE', region: '全球', currency: 'USD' },
{ id: 'shop-ebay-2', name: 'eBay全球店B', platform: 'eBay', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-ebay-3', name: 'eBay美国店C', platform: 'eBay', status: 'ACTIVE', region: '美国', currency: 'USD' },
// Shopify平台 - 多个店铺
{ id: 'shop-shopify-1', name: 'Shopify独立站A', platform: 'Shopify', status: 'ACTIVE', region: '全球', currency: 'USD' },
{ id: 'shop-shopify-2', name: 'Shopify独立站B', platform: 'Shopify', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-shopify-3', name: 'Shopify独立站C', platform: 'Shopify', status: 'ACTIVE', region: '亚洲', currency: 'CNY' },
// Temu平台 - 多个店铺
{ id: 'shop-temu-1', name: 'Temu旗舰店A', platform: 'Temu', status: 'ACTIVE', region: '美国', currency: 'USD' },
{ id: 'shop-temu-2', name: 'Temu旗舰店B', platform: 'Temu', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-temu-3', name: 'Temu旗舰店C', platform: 'Temu', status: 'ACTIVE', region: '澳大利亚', currency: 'AUD' },
// SHEIN平台 - 多个店铺
{ id: 'shop-shein-1', name: 'SHEIN官方店A', platform: 'Shein', status: 'ACTIVE', region: '全球', currency: 'USD' },
{ id: 'shop-shein-2', name: 'SHEIN官方店B', platform: 'Shein', status: 'ACTIVE', region: '欧洲', currency: 'EUR' },
{ id: 'shop-shein-3', name: 'SHEIN官方店C', platform: 'Shein', status: 'ACTIVE', region: '中东', currency: 'AED' },
// 其他平台店铺
{ id: 'shop-ozon-1', name: 'Ozon旗舰店A', platform: 'Ozon', status: 'ACTIVE', region: '俄罗斯', currency: 'RUB' },
{ id: 'shop-ali-1', name: '速卖通旗舰店A', platform: 'AliExpress', status: 'ACTIVE', region: '全球', currency: 'USD' },
{ id: 'shop-walmart-1', name: 'Walmart旗舰店A', platform: 'Walmart', status: 'ACTIVE', region: '美国', currency: 'USD' },
];
const PLATFORM_CONFIG: Record<string, { icon: React.ReactNode; color: string }> = {
// TikTok系列
TikTok: { icon: <GlobalOutlined />, color: '#000000' },
@@ -255,143 +137,7 @@ const PLATFORM_CONFIG: Record<string, { icon: React.ReactNode; color: string }>
eBay: { icon: <GlobalOutlined />, color: '#e53238' },
};
const MOCK_PRODUCTS: Product[] = [
{
id: '1',
sku: 'TP-TEMP-001',
name: '工业温度传感器 Pro',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工业自动化',
price: 89.99,
costPrice: 45.00,
profit: 44.99,
roi: 99.98,
stock: 256,
status: 'LIVE',
platformStatus: { Amazon: 'LIVE', eBay: 'LIVE', Shopee: 'PENDING' },
shopId: 'shop-amazon-1',
shopName: 'Amazon美国店',
platform: 'Amazon',
createdAt: '2025-12-15',
updatedAt: '2026-03-18',
},
{
id: '2',
sku: 'TP-PRES-002',
name: '压力传感器 Digital',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工业自动化',
price: 129.99,
costPrice: 65.00,
profit: 64.99,
roi: 99.98,
stock: 128,
status: 'DRAFT',
platformStatus: {},
shopId: 'shop-tiktok-1',
shopName: 'TikTok旗舰店',
platform: 'TikTok',
createdAt: '2026-03-10',
updatedAt: '2026-03-10',
},
{
id: '3',
sku: 'TP-FLOW-003',
name: '流量计 Ultra',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '仪器仪表',
price: 299.99,
costPrice: 150.00,
profit: 149.99,
roi: 99.99,
stock: 64,
status: 'PRICED',
platformStatus: {},
shopId: 'shop-shopee-1',
shopName: 'Shopee官方店',
platform: 'Shopee',
createdAt: '2026-03-15',
updatedAt: '2026-03-16',
},
{
id: '4',
sku: 'TP-MOTR-004',
name: '步进电机 57型',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工业自动化',
price: 59.99,
costPrice: 30.00,
profit: 29.99,
roi: 99.97,
stock: 512,
status: 'LISTED',
platformStatus: { Amazon: 'LISTED' },
shopId: 'shop-amazon-2',
shopName: 'Amazon欧洲店',
platform: 'Amazon',
createdAt: '2026-03-01',
updatedAt: '2026-03-17',
},
{
id: '5',
sku: 'TP-CTRL-005',
name: 'PLC控制器 Mini',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工业自动化',
price: 199.99,
costPrice: 100.00,
profit: 99.99,
roi: 99.99,
stock: 32,
status: 'SYNCING',
platformStatus: { Amazon: 'SYNCING', eBay: 'SYNCING' },
shopId: 'shop-ebay-1',
shopName: 'eBay全球店',
platform: 'eBay',
createdAt: '2026-03-18',
updatedAt: '2026-03-18',
},
{
id: '6',
sku: 'TP-SENS-006',
name: '光电传感器',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工业自动化',
price: 39.99,
costPrice: 20.00,
profit: 19.99,
roi: 99.95,
stock: 0,
status: 'SYNC_FAILED',
platformStatus: { Amazon: 'FAILED' },
shopId: 'shop-temu-1',
shopName: 'Temu旗舰店',
platform: 'Temu',
createdAt: '2026-03-05',
updatedAt: '2026-03-18',
},
{
id: '7',
sku: 'TP-VALV-007',
name: '电磁阀 24V',
image: 'https://via.placeholder.com/80x80?text=Product',
category: '工具设备',
price: 79.99,
costPrice: 40.00,
profit: 39.99,
roi: 99.98,
stock: 200,
status: 'OFFLINE',
platformStatus: { Amazon: 'OFFLINE', eBay: 'OFFLINE' },
shopId: 'shop-shein-1',
shopName: 'SHEIN官方店',
platform: 'Shein',
createdAt: '2025-11-20',
updatedAt: '2026-02-28',
},
];
export const ProductList: React.FC = () => {
const ProductList: React.FC = () => {
const navigate = useNavigate();
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
@@ -403,23 +149,28 @@ export const ProductList: React.FC = () => {
const [currentProduct, setCurrentProduct] = useState<Product | null>(null);
const [syncLoading, setSyncLoading] = useState<Record<string, boolean>>({});
const [activePlatformTab, setActivePlatformTab] = useState<string>('all');
const [shops, setShops] = useState<Shop[]>([]);
const [platformStats, setPlatformStats] = useState<Record<string, { total: number; live: number; pending: number; failed: number }>>({
all: { total: 0, live: 0, pending: 0, failed: 0 },
unpublished: { total: 0, live: 0, pending: 0, failed: 0 },
});
const [filters, setFilters] = useState<FilterState>({
const [filters, setFilters] = useState<ProductFilter>({
keyword: '',
status: [],
platform: [],
shop: [],
category: [],
priceRange: null,
stockRange: null,
roiRange: null,
dateRange: null,
minProfit: null,
minStock: null,
priceRange: undefined,
stockRange: undefined,
roiRange: undefined,
dateRange: undefined,
minProfit: undefined,
minStock: undefined,
searchInDescription: false,
});
const [sort, setSort] = useState<SortState>({
const [sort, setSort] = useState<ProductSort>({
field: 'updatedAt',
order: 'descend',
});
@@ -432,17 +183,51 @@ export const ProductList: React.FC = () => {
]);
useEffect(() => {
fetchProducts();
fetchInitialData();
}, []);
useEffect(() => {
fetchProducts();
}, [filters, sort, activePlatformTab]);
const fetchInitialData = async () => {
try {
const [shopsData, stats] = await Promise.all([
productDataSource.fetchShops(),
productDataSource.getPlatformStats()
]);
setShops(shopsData);
setPlatformStats(stats);
} catch (error) {
console.error('Failed to fetch initial data:', error);
}
};
const fetchProducts = useCallback(async () => {
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 500));
setProducts(MOCK_PRODUCTS);
setLoading(false);
}, []);
try {
const productFilter: ProductFilter = {
...filters,
platform: activePlatformTab !== 'all' && activePlatformTab !== 'unpublished'
? [activePlatformTab]
: filters.platform
};
const productsData = await productDataSource.fetchProducts(productFilter, sort);
setProducts(productsData);
// 更新平台统计数据
const stats = await productDataSource.getPlatformStats();
setPlatformStats(stats);
} catch (error) {
console.error('Failed to fetch products:', error);
message.error('Failed to load products');
} finally {
setLoading(false);
}
}, [filters, sort, activePlatformTab]);
const handleFilterChange = (key: keyof FilterState, value: any) => {
const handleFilterChange = (key: keyof ProductFilter, value: any) => {
setFilters(prev => ({ ...prev, [key]: value }));
};
@@ -477,12 +262,12 @@ export const ProductList: React.FC = () => {
platform: [],
shop: [],
category: [],
priceRange: null,
stockRange: null,
roiRange: null,
dateRange: null,
minProfit: null,
minStock: null,
priceRange: undefined,
stockRange: undefined,
roiRange: undefined,
dateRange: undefined,
minProfit: undefined,
minStock: undefined,
searchInDescription: false,
});
message.success('筛选条件已重置');
@@ -512,32 +297,36 @@ export const ProductList: React.FC = () => {
navigate(`/dashboard/product/detail/${record.id}`);
};
const handleDeleteProduct = (record: Product) => {
const handleDeleteProduct = async (record: Product) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除商品 "${record.name}" 吗?此操作不可恢复。`,
okText: '删除',
okType: 'danger',
onOk: () => {
setProducts(products.filter(p => p.id !== record.id));
message.success('商品删除成功');
onOk: async () => {
try {
const success = await productDataSource.deleteProduct(record.id);
if (success) {
message.success('商品删除成功');
fetchProducts();
} else {
message.error('删除失败');
}
} catch (error) {
message.error('删除失败');
}
},
});
};
const handleDuplicateProduct = (record: Product) => {
const newProduct: Product = {
...record,
id: `${Date.now()}`,
sku: `${record.sku}-COPY`,
name: `${record.name} (复制)`,
status: 'DRAFT',
platformStatus: {},
createdAt: moment().format('YYYY-MM-DD'),
updatedAt: moment().format('YYYY-MM-DD'),
};
setProducts([newProduct, ...products]);
message.success('商品复制成功');
const handleDuplicateProduct = async (record: Product) => {
try {
await productDataSource.duplicateProduct(record.id);
message.success('商品复制成功');
fetchProducts();
} catch (error) {
message.error('复制失败');
}
};
const handlePricing = (record: Product) => {
@@ -545,13 +334,18 @@ export const ProductList: React.FC = () => {
setPricingModalVisible(true);
};
const handlePublish = (record: Product) => {
const handlePublish = async (record: Product) => {
Modal.confirm({
title: '确认上架',
content: `确定要上架商品 "${record.name}" 吗?`,
onOk: () => {
updateProductStatus(record.id, 'LISTED');
message.success('商品上架成功');
onOk: async () => {
try {
await productDataSource.updateProductStatus(record.id, 'LISTED');
message.success('商品上架成功');
fetchProducts();
} catch (error) {
message.error('上架失败');
}
},
});
};
@@ -559,30 +353,31 @@ export const ProductList: React.FC = () => {
const handleSync = async (record: Product) => {
setSyncLoading(prev => ({ ...prev, [record.id]: true }));
updateProductStatus(record.id, 'SYNCING');
message.loading({ content: '正在同步到平台...', key: record.id });
await new Promise(resolve => setTimeout(resolve, 2000));
const success = Math.random() > 0.3;
if (success) {
updateProductStatus(record.id, 'LIVE');
message.success({ content: '同步成功', key: record.id });
} else {
updateProductStatus(record.id, 'SYNC_FAILED');
try {
await productDataSource.syncProduct(record.id);
message.success({ content: '同步完成', key: record.id });
fetchProducts();
} catch (error) {
message.error({ content: '同步失败,请重试', key: record.id });
} finally {
setSyncLoading(prev => ({ ...prev, [record.id]: false }));
}
setSyncLoading(prev => ({ ...prev, [record.id]: false }));
};
const handleOffline = (record: Product) => {
const handleOffline = async (record: Product) => {
Modal.confirm({
title: '确认下架',
content: `确定要下架商品 "${record.name}" 吗?`,
onOk: () => {
updateProductStatus(record.id, 'OFFLINE');
message.success('商品已下架');
onOk: async () => {
try {
await productDataSource.updateProductStatus(record.id, 'OFFLINE');
message.success('商品已下架');
fetchProducts();
} catch (error) {
message.error('下架失败');
}
},
});
};
@@ -591,12 +386,6 @@ export const ProductList: React.FC = () => {
handleSync(record);
};
const updateProductStatus = (productId: string, status: Product['status']) => {
setProducts(products.map(p =>
p.id === productId ? { ...p, status, updatedAt: moment().format('YYYY-MM-DD') } : p
));
};
const handleBatchDelete = () => {
if (selectedRows.length === 0) {
message.warning('请先选择要删除的商品');
@@ -607,11 +396,16 @@ export const ProductList: React.FC = () => {
content: `确定要删除选中的 ${selectedRows.length} 个商品吗?`,
okText: '删除',
okType: 'danger',
onOk: () => {
const ids = selectedRows.map(r => r.id);
setProducts(products.filter(p => !ids.includes(p.id)));
setSelectedRows([]);
message.success(`成功删除 ${ids.length} 个商品`);
onOk: async () => {
try {
const promises = selectedRows.map(r => productDataSource.deleteProduct(r.id));
await Promise.all(promises);
message.success(`成功删除 ${selectedRows.length} 个商品`);
setSelectedRows([]);
fetchProducts();
} catch (error) {
message.error('批量删除失败');
}
},
});
};
@@ -624,7 +418,7 @@ export const ProductList: React.FC = () => {
message.info(`批量定价功能:已选择 ${selectedRows.length} 个商品`);
};
const handleBatchPublish = () => {
const handleBatchPublish = async () => {
if (selectedRows.length === 0) {
message.warning('请先选择要上架的商品');
return;
@@ -637,15 +431,21 @@ export const ProductList: React.FC = () => {
Modal.confirm({
title: '确认批量上架',
content: `确定要上架选中的 ${pricedProducts.length} 个商品吗?`,
onOk: () => {
pricedProducts.forEach(p => updateProductStatus(p.id, 'LISTED'));
setSelectedRows([]);
message.success(`成功上架 ${pricedProducts.length} 个商品`);
onOk: async () => {
try {
const promises = pricedProducts.map(p => productDataSource.updateProductStatus(p.id, 'LISTED'));
await Promise.all(promises);
message.success(`成功上架 ${pricedProducts.length} 个商品`);
setSelectedRows([]);
fetchProducts();
} catch (error) {
message.error('批量上架失败');
}
},
});
};
const handleBatchSync = () => {
const handleBatchSync = async () => {
if (selectedRows.length === 0) {
message.warning('请先选择要同步的商品');
return;
@@ -656,10 +456,15 @@ export const ProductList: React.FC = () => {
return;
}
message.loading('正在批量同步...');
setTimeout(() => {
listableProducts.forEach(p => handleSync(p));
try {
const promises = listableProducts.map(p => productDataSource.syncProduct(p.id));
await Promise.all(promises);
message.success('批量同步完成');
setSelectedRows([]);
}, 500);
fetchProducts();
} catch (error) {
message.error('批量同步失败');
}
};
const handleBatchUpdate = () => {
@@ -680,109 +485,7 @@ export const ProductList: React.FC = () => {
// 这里可以实现导出商品数据为Excel或CSV文件
};
const filteredProducts = products.filter(product => {
if (filters.keyword) {
const keywordLower = filters.keyword.toLowerCase();
if (!product.name.toLowerCase().includes(keywordLower) &&
!product.sku.toLowerCase().includes(keywordLower)) {
return false;
}
}
if (filters.status.length > 0 && !filters.status.includes(product.status)) {
return false;
}
if (filters.category.length > 0 && !filters.category.includes(product.category)) {
return false;
}
if (filters.shop.length > 0 && !filters.shop.includes(product.shopId)) {
return false;
}
if (filters.priceRange) {
const [min, max] = filters.priceRange;
if (product.price < min || product.price > max) {
return false;
}
}
if (filters.stockRange) {
const [min, max] = filters.stockRange;
if (product.stock < min || product.stock > max) {
return false;
}
}
if (filters.roiRange) {
const [min, max] = filters.roiRange;
if (product.roi < min || product.roi > max) {
return false;
}
}
if (filters.minProfit !== null && product.profit < filters.minProfit) {
return false;
}
if (filters.minStock !== null && product.stock < filters.minStock) {
return false;
}
if (activePlatformTab !== 'all') {
if (activePlatformTab === 'unpublished') {
if (Object.keys(product.platformStatus).length > 0) {
return false;
}
} else {
if (!product.platformStatus[activePlatformTab]) {
return false;
}
}
}
return true;
});
const platformStats = useMemo(() => {
const stats: Record<string, { total: number; live: number; pending: number; failed: number }> = {
all: { total: products.length, live: 0, pending: 0, failed: 0 },
unpublished: { total: 0, live: 0, pending: 0, failed: 0 },
};
PLATFORMS.forEach(platform => {
stats[platform] = { total: 0, live: 0, pending: 0, failed: 0 };
});
products.forEach(product => {
const platforms = Object.keys(product.platformStatus);
if (platforms.length === 0) {
stats.unpublished.total++;
}
platforms.forEach(platform => {
if (!stats[platform]) {
stats[platform] = { total: 0, live: 0, pending: 0, failed: 0 };
}
stats[platform].total++;
const status = product.platformStatus[platform];
if (status === 'LIVE') {
stats[platform].live++;
stats.all.live++;
} else if (status === 'PENDING' || status === 'SYNCING' || status === 'LISTED') {
stats[platform].pending++;
stats.all.pending++;
} else if (status === 'FAILED' || status === 'SYNC_FAILED') {
stats[platform].failed++;
stats.all.failed++;
}
});
});
return stats;
}, [products]);
const sortedProducts = [...filteredProducts].sort((a, b) => {
const field = sort.field as keyof Product;
const aValue = a[field];
const bValue = b[field];
if (sort.order === 'ascend') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
const sortedProducts = products;
const allColumns: ColumnsType<Product> = [
{
@@ -1171,7 +874,7 @@ export const ProductList: React.FC = () => {
title="筛选条件"
placement="right"
onClose={() => setFilterVisible(false)}
visible={filterVisible}
open={filterVisible}
width={400}
>
<Form layout="vertical">
@@ -1209,7 +912,7 @@ export const ProductList: React.FC = () => {
onChange={(value) => handleFilterChange('shop', value)}
style={{ width: '100%' }}
>
{SHOPS.map(shop => (
{shops.map(shop => (
<Option key={shop.id} value={shop.id}>
{shop.name} ({shop.platform})
</Option>
@@ -1362,7 +1065,7 @@ export const ProductList: React.FC = () => {
title="排序设置"
placement="right"
onClose={() => setSortDrawerVisible(false)}
visible={sortDrawerVisible}
open={sortDrawerVisible}
width={300}
>
<Space direction="vertical" style={{ width: '100%' }}>
@@ -1403,7 +1106,7 @@ export const ProductList: React.FC = () => {
title="列设置"
placement="right"
onClose={() => setColumnDrawerVisible(false)}
visible={columnDrawerVisible}
open={columnDrawerVisible}
width={400}
>
<div style={{ marginBottom: 20 }}>
@@ -1509,7 +1212,7 @@ export const ProductList: React.FC = () => {
<Modal
title="商品定价"
visible={pricingModalVisible}
open={pricingModalVisible}
onCancel={() => setPricingModalVisible(false)}
footer={null}
width={600}

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import {
useState,
Card,
Form,
Input,
@@ -22,8 +22,6 @@ import {
Image,
Badge,
Progress,
} from 'antd';
import {
PlusOutlined,
DeleteOutlined,
SaveOutlined,
@@ -37,7 +35,10 @@ import {
TranslationOutlined,
ThunderboltOutlined,
EyeOutlined,
} from '@ant-design/icons';
Option,
TextArea,
FC,
} from '@/imports';
import type { UploadFile } from 'antd/es/upload/interface';
import {
materialProcessingDataSource,
@@ -46,9 +47,7 @@ import {
STYLE_OPTIONS,
TRANSLATION_LANGUAGES,
} from '@/services/materialProcessingDataSource';
const { Option } = Select;
const { TextArea } = Input;
import { useLocale } from '@/contexts/LocaleContext';
interface ProductVariant {
id: string;
@@ -98,7 +97,7 @@ const CURRENCIES = [
{ value: 'CNY', label: 'CNY - Chinese Yuan' },
];
export const ProductPublishForm: React.FC = () => {
const ProductPublishForm: FC = () => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [variants, setVariants] = useState<ProductVariant[]>([]);
@@ -109,6 +108,7 @@ export const ProductPublishForm: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<string>('');
const [processingType, setProcessingType] = useState<'image-to-image' | 'image-to-video'>('image-to-image');
const [processingLoading, setProcessingLoading] = useState(false);
const { t } = useLocale();
const handleAddVariant = () => {
const newVariant: ProductVariant = {
@@ -140,7 +140,7 @@ export const ProductPublishForm: React.FC = () => {
const handleProcessImage = async (params: ImageToImageParams | ImageToVideoParams) => {
if (!selectedImage) {
message.warning('请先选择要处理的图片');
message.warning(t('product.publish.selectImage'));
return;
}
@@ -154,7 +154,7 @@ export const ProductPublishForm: React.FC = () => {
}
setProcessingJobs([...processingJobs, job]);
message.success(`任务已创建: ${job.id}`);
message.success(t('product.publish.taskCreated', { id: job.id }));
setMaterialModalVisible(false);
// 轮询检查任务状态
@@ -162,7 +162,7 @@ export const ProductPublishForm: React.FC = () => {
const updatedJob = await materialProcessingDataSource.getJobStatus(job.id);
if (updatedJob.status === 'completed') {
clearInterval(checkInterval);
message.success('素材处理完成!');
message.success(t('product.publish.processingComplete'));
// 将处理结果添加到文件列表
if (processingType === 'image-to-image') {
setFileList([...fileList, {
@@ -173,13 +173,13 @@ export const ProductPublishForm: React.FC = () => {
thumbUrl: updatedJob.outputUrl,
} as UploadFile]);
} else {
message.info('视频已生成,可在素材库中查看');
message.info(t('product.publish.videoGenerated'));
}
setProcessingJobs(processingJobs.map(j => j.id === job.id ? updatedJob : j));
}
}, 2000);
} catch (error) {
message.error('处理失败');
message.error(t('product.publish.processFailed'));
} finally {
setProcessingLoading(false);
}
@@ -190,9 +190,9 @@ export const ProductPublishForm: React.FC = () => {
await form.validateFields();
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 1000));
message.success('Draft saved successfully');
message.success(t('product.publish.draftSaved'));
} catch (error) {
message.error('Please fill in required fields');
message.error(t('product.publish.fillRequiredFields'));
} finally {
setLoading(false);
}
@@ -389,17 +389,17 @@ export const ProductPublishForm: React.FC = () => {
<TextArea rows={4} placeholder="Enter product description" maxLength={5000} showCount />
</Form.Item>
<Divider>Product Images</Divider>
<Divider>{t('product.publish.productImages')}</Divider>
<Alert
message="AI素材处理"
description="上传图片后可使用AI进行图生图、图生视频等处理。点击已上传图片上的AI按钮开始处理。"
message={t('product.publish.materialProcessing')}
description={t('product.publish.materialProcessingDesc')}
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
<Form.Item label="Images">
<Form.Item label={t('product.publish.images')}>
<Upload
listType="picture-card"
fileList={fileList}
@@ -421,7 +421,7 @@ export const ProductPublishForm: React.FC = () => {
gap: 4,
padding: '2px 0',
}}>
<Tooltip title="图生图">
<Tooltip title={t('product.publish.imageToImage')}>
<Button
size="small"
type="text"
@@ -429,7 +429,7 @@ export const ProductPublishForm: React.FC = () => {
onClick={() => handleOpenMaterialProcessing(file.url || file.thumbUrl || '', 'image-to-image')}
/>
</Tooltip>
<Tooltip title="图生视频">
<Tooltip title={t('product.publish.imageToVideo')}>
<Button
size="small"
type="text"
@@ -445,18 +445,18 @@ export const ProductPublishForm: React.FC = () => {
{fileList.length >= 8 ? null : (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
<div style={{ marginTop: 8 }}>{t('product.publish.upload')}</div>
</div>
)}
</Upload>
</Form.Item>
{processingJobs.length > 0 && (
<Card size="small" title="处理中的任务" style={{ marginTop: 16 }}>
<Card size="small" title={t('product.publish.processingTasks')} style={{ marginTop: 16 }}>
{processingJobs.map(job => (
<div key={job.id} style={{ marginBottom: 8 }}>
<Space>
<Tag color="blue">{job.type === 'image_to_image' ? '图生图' : '图生视频'}</Tag>
<Tag color="blue">{job.type === 'image_to_image' ? t('product.publish.imageToImage') : t('product.publish.imageToVideo')}</Tag>
<Progress percent={job.progress} size="small" style={{ width: 200 }} />
<Tag color={job.status === 'processing' ? 'processing' : 'success'}>
{job.status}
@@ -468,11 +468,11 @@ export const ProductPublishForm: React.FC = () => {
)}
</>
)},
{ key: 'pricing', label: <><DollarOutlined /> Pricing</>, children: (
{ key: 'pricing', label: <><DollarOutlined /> {t('product.publish.pricing')}</>, children: (
<>
<Alert
message="Profit Margin Validation"
description="B2B products must maintain at least 15% profit margin. B2C products require 20% minimum."
message={t('product.publish.profitMarginValidation')}
description={t('product.publish.profitMarginDesc')}
type="info"
showIcon
style={{ marginBottom: 24 }}
@@ -482,8 +482,8 @@ export const ProductPublishForm: React.FC = () => {
<Col span={8}>
<Form.Item
name="price"
label="Selling Price"
rules={[{ required: true, message: 'Please enter price' }]}
label={t('product.publish.sellingPrice')}
rules={[{ required: true, message: t('product.publish.enterPrice') }]}
>
<InputNumber
prefix="$"
@@ -497,8 +497,8 @@ export const ProductPublishForm: React.FC = () => {
<Col span={8}>
<Form.Item
name="costPrice"
label="Cost Price"
rules={[{ required: true, message: 'Please enter cost price' }]}
label={t('product.publish.costPrice')}
rules={[{ required: true, message: t('product.publish.enterCostPrice') }]}
>
<InputNumber
prefix="$"
@@ -510,7 +510,7 @@ export const ProductPublishForm: React.FC = () => {
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="currency" label="Currency">
<Form.Item name="currency" label={t('product.publish.currency')}>
<Select>
{CURRENCIES.map(c => (
<Option key={c.value} value={c.value}>{c.label}</Option>
@@ -632,17 +632,17 @@ export const ProductPublishForm: React.FC = () => {
title={
<Space>
<ThunderboltOutlined style={{ color: '#1890ff' }} />
{processingType === 'image-to-image' ? '图生图' : '图生视频'}
{processingType === 'image-to-image' ? t('product.publish.imageToImage') : t('product.publish.imageToVideo')}
</Space>
}
visible={materialModalVisible}
open={materialModalVisible}
onCancel={() => setMaterialModalVisible(false)}
footer={null}
width={600}
>
{selectedImage && (
<div style={{ marginBottom: 16 }}>
<Text type="secondary"></Text>
<Text type="secondary">{t('product.publish.originalPreview')}</Text>
<Image src={selectedImage} width={200} style={{ marginTop: 8, borderRadius: 8 }} />
</div>
)}
@@ -650,15 +650,15 @@ export const ProductPublishForm: React.FC = () => {
<Form layout="vertical" onFinish={handleProcessImage}>
{processingType === 'image-to-image' ? (
<>
<Form.Item name="prompt" label="提示词">
<Form.Item name="prompt" label={t('product.publish.prompt')}>
<Input.TextArea
rows={2}
placeholder="描述你想要生成的图片效果..."
placeholder={t('product.publish.promptPlaceholder')}
/>
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="style" label="风格" initialValue="realistic">
<Form.Item name="style" label={t('product.publish.style')} initialValue="realistic">
<Select>
{STYLE_OPTIONS.map(opt => (
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
@@ -667,7 +667,7 @@ export const ProductPublishForm: React.FC = () => {
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="strength" label="转换强度" initialValue={0.7}>
<Form.Item name="strength" label={t('product.publish.strength')} initialValue={0.7}>
<InputNumber min={0} max={1} step={0.1} style={{ width: '100%' }} />
</Form.Item>
</Col>
@@ -675,30 +675,30 @@ export const ProductPublishForm: React.FC = () => {
</>
) : (
<>
<Form.Item name="prompt" label="提示词">
<Form.Item name="prompt" label={t('product.publish.prompt')}>
<Input.TextArea
rows={2}
placeholder="描述视频的运动效果..."
placeholder={t('product.publish.promptPlaceholder')}
/>
</Form.Item>
<Row gutter={16}>
<Col span={8}>
<Form.Item name="motion" label="运动类型" initialValue="zoom">
<Form.Item name="motion" label={t('product.publish.motion')} initialValue="zoom">
<Select>
<Option value="zoom"></Option>
<Option value="pan"></Option>
<Option value="rotate"></Option>
<Option value="dynamic"></Option>
<Option value="zoom">{t('product.publish.motionZoom')}</Option>
<Option value="pan">{t('product.publish.motionPan')}</Option>
<Option value="rotate">{t('product.publish.motionRotate')}</Option>
<Option value="dynamic">{t('product.publish.motionDynamic')}</Option>
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="duration" label="时长(秒)" initialValue={5}>
<Form.Item name="duration" label={t('product.publish.duration')} initialValue={5}>
<InputNumber min={1} max={30} style={{ width: '100%' }} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="fps" label="帧率" initialValue={30}>
<Form.Item name="fps" label={t('product.publish.fps')} initialValue={30}>
<Select>
<Option value={24}>24 FPS</Option>
<Option value={30}>30 FPS</Option>
@@ -713,10 +713,10 @@ export const ProductPublishForm: React.FC = () => {
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={processingLoading}>
{t('product.publish.startProcessing')}
</Button>
<Button onClick={() => setMaterialModalVisible(false)}>
{t('product.publish.cancel')}
</Button>
</Space>
</Form.Item>

View File

@@ -1,12 +1,40 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, Select, Button, Table, Statistic, Spin, message, Alert, Badge } from 'antd';
import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { AlertOutlined, UpOutlined, DownOutlined, ReloadOutlined, EyeOutlined } from '@ant-design/icons';
//
import { productDataSource, ProfitMonitor as ProfitMonitorData } from '@/services/productDataSource';
const { Title, Text, Paragraph } = Typography;
const { Option } = Select;
import {
useState,
useEffect,
Card,
Typography,
Row,
Col,
Select,
Button,
Table,
Statistic,
Spin,
message,
Alert,
Badge,
LineChart,
Line,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
AlertOutlined,
UpOutlined,
DownOutlined,
ReloadOutlined,
EyeOutlined,
Title,
Text,
Paragraph,
Option,
FC,
} from '@/imports';
import { productDataSource } from '@/services/productDataSource';
interface ProfitData {
id: string;
@@ -38,7 +66,7 @@ interface ProductProfit {
status: 'up' | 'down' | 'stable';
}
const ProfitMonitor: React.FC = () => {
const ProfitMonitor: FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [profitData, setProfitData] = useState<ProfitData[]>([]);
const [alerts, setAlerts] = useState<AlertItem[]>([]);

View File

@@ -1,27 +1,50 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, DatePicker, Select, Button, Table, Statistic, Spin, message } from 'antd';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, LineChart, Line } from 'recharts';
import { ArrowUpOutlined, ArrowDownOutlined, ReloadOutlined } from '@ant-design/icons';
//
import { productDataSource, ROIAnalysis as ROIAnalysisData } from '@/services/productDataSource';
import {
useState,
useEffect,
Card,
Typography,
Row,
Col,
DatePicker,
Select,
Button,
Table,
Statistic,
Spin,
message,
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
LineChart,
Line,
ArrowUpOutlined,
ArrowDownOutlined,
ReloadOutlined,
Title,
Text,
RangePicker,
Option,
FC,
} from '@/imports';
import { productDataSource } from '@/services/productDataSource';
const { Title, Text } = Typography;
const { RangePicker } = DatePicker;
const { Option } = Select;
interface ROIItem {
id: string;
productId: string;
productName: string;
cost: number;
price: number;
profit: number;
roi: number;
salesVolume: number;
date: string;
}
const ROIAnalysis: React.FC = () => {
const ROIAnalysis: FC = () => {
interface ROIItem {
id: string;
productId: string;
productName: string;
cost: number;
price: number;
profit: number;
roi: number;
salesVolume: number;
date: string;
}
const [loading, setLoading] = useState<boolean>(false);
const [roiData, setRoiData] = useState<ROIItem[]>([]);
const [timeRange, setTimeRange] = useState<any>(null);

View File

@@ -1,11 +1,38 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, Button, Table, Input, Select, Modal, Form, InputNumber, message, Tag, Space, Popconfirm, Tabs } from 'antd';
import { PlusOutlined, UploadOutlined, EditOutlined, SearchOutlined, FilterOutlined, SyncOutlined, DeleteOutlined, EyeOutlined, GlobalOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import {
useState,
useEffect,
Card,
Typography,
Row,
Col,
Button,
Table,
Input,
Select,
Modal,
Form,
InputNumber,
message,
Tag,
Space,
Popconfirm,
Tabs,
PlusOutlined,
UploadOutlined,
EditOutlined,
SearchOutlined,
FilterOutlined,
SyncOutlined,
DeleteOutlined,
EyeOutlined,
GlobalOutlined,
Link,
Title,
Text,
FC,
} from '@/imports';
import CrossPlatformManage from './CrossPlatformManage';
const { Title, Text } = Typography;
interface Product {
id: string;
name: string;
@@ -18,7 +45,7 @@ interface Product {
createdAt: string;
}
const ProductManagement: React.FC = () => {
const ProductManagement: FC = () => {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@@ -450,7 +477,7 @@ const ProductManagement: React.FC = () => {
<Modal
title={editingProduct ? '编辑商品' : '新增商品'}
visible={isModalVisible}
open={isModalVisible}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
width={600}
@@ -521,7 +548,7 @@ const ProductManagement: React.FC = () => {
<Modal
title="商品详情"
visible={isDetailModalVisible}
open={isDetailModalVisible}
onCancel={() => setIsDetailModalVisible(false)}
footer={[
<Button key="close" onClick={() => setIsDetailModalVisible(false)}>
@@ -580,7 +607,7 @@ const ProductManagement: React.FC = () => {
<Modal
title="商品定价"
visible={isPricingModalVisible}
open={isPricingModalVisible}
onOk={handlePricingModalOk}
onCancel={() => setIsPricingModalVisible(false)}
width={600}