import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import {
Card, Typography, Input, Select, Button, Form, Row, Col, Space, Table,
message, Image, Popconfirm, Modal, Switch, Radio, Upload, Checkbox, Flex, Tabs
} from 'antd';
import {
SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
RedoOutlined, CloseOutlined, LeftOutlined, UploadOutlined
} from '@ant-design/icons';
import { goodsListApi, goodsDelApi, goodsUpdownApi, goodsSaveApi } from '@/api/goods/index';
import { goodstypeApi } from '@/api/goodstype/index';
import { dataurl } from '@/api/url';
import ImageUploader from '@/components/ImageUploader';
import UEditor from '@/components/ueditor';
import { handleNumberInput, handleTextOnlyInput } from '@/components/business/index';
import styles from './index.module.css';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { useNavigate } from 'react-router-dom';
// ==================== 常量定义 ====================
const { Title } = Typography;
const { Option } = Select;
// 列表页常量
const PAGE_SIZE_OPTIONS = ['10', '20', '50'];
const RECOMMEND_OPTIONS = [
{ value: '1', label: '热销商品' },
{ value: '2', label: '为你推荐' },
{ value: '3', label: '猜你喜欢' }
] as const;
const SPECIFICATION_OPTIONS = [
{ value: '1', label: '单规格' },
{ value: '0', label: '多规格' }
] as const;
// 编辑页常量
const SPEC_TYPES = {
SINGLE: 'single',
MULTIPLE: 'multiple'
} as const;
const API_SPEC_TYPES = {
SINGLE: '1', // 单规格
MULTIPLE: '0' // 多规格
} as const;
const INITIAL_SPEC_VALUE = {
name: '',
price: '',
originalPrice: '',
stock: '',
imageUrl: ''
};
const SPEC_FIELD_NAMES = ['name', 'price', 'originalPrice', 'stock', 'imageUrl'] as const;
// ==================== 类型定义 ====================
// 列表页类型
interface GoodsItem {
id: number;
title: string;
price: string;
stock: number;
sold: number;
status: number;
sort: number;
img: string;
goods_type_id: number[];
synopsis?: string;
type: number; // 1: 单规格, 0: 多规格
recommend: number[];
keyword?: string;
unit?: string;
imgs?: string;
content?: string;
params?: string;
shipping_address?: string;
courier_fee?: string;
norm_arr?: NormArrItem[];
original_price?: string;
}
interface SearchFormValues {
title?: string;
keyword?: string;
synopsis?: string;
type?: string;
recommend?: string;
goods_type_id?: string;
}
interface ClassOption {
value: number;
label: string;
}
interface ApiResponse<T = any> {
code: number;
data: T;
msg?: string;
}
interface GoodsListResponse {
list: GoodsItem[];
total: number;
type?: { value: number; label: string }[];
}
// 编辑页类型
interface Category {
id: number;
title: string;
status: number;
createTime: string;
}
interface ApiRes<T> {
code: number;
msg: string;
data: T;
}
interface CategoryRes {
list: Category[];
}
interface NormItem {
name: string;
value: string[];
}
interface NormArrItem {
name: string;
price: number;
original_price: number;
stock: number;
img: string;
}
interface StockData {
specType: string;
price?: string;
original_price?: string;
stock?: string;
norm?: NormItem[];
norm_arr?: NormArrItem[];
collectedAt?: string;
}
interface FormData {
category: string;
name: string;
desc: string;
unit: string;
enableVideo: boolean;
video?: string;
mainImg: string;
carouselImgs: string[];
status: 'active' | 'inactive';
price?: string;
originalPrice?: string;
stock?: string;
content: string;
params: string;
address: string;
freight: string;
sort: string;
sold: string;
recommend: string[];
keyword: string;
specType?: 'single' | 'multiple';
specs?: Record<string, any>;
}
interface Spec {
id: number;
name: string;
price: string;
originalPrice: string;
stock: string;
imageUrl: string;
}
// ==================== 工具函数 ====================
// 列表页工具函数
const safeConvertToApiResponse = <T,>(response: any): ApiResponse<T> => {
return response as ApiResponse<T>;
};
const filterGoodsList = (list: GoodsItem[], searchValues: SearchFormValues): GoodsItem[] => {
if (!searchValues || Object.keys(searchValues).length === 0) {
return list;
}
return list.filter(item => {
if (searchValues.title && !item.title?.toLowerCase().includes(searchValues.title.toLowerCase())) {
return false;
}
if (searchValues.keyword) {
const keyword = searchValues.keyword.toLowerCase();
const inTitle = item.title?.toLowerCase().includes(keyword);
const inSynopsis = item.synopsis?.toLowerCase().includes(keyword);
const inKeyword = item.keyword?.toLowerCase().includes(keyword);
if (!inTitle && !inSynopsis && !inKeyword) {
return false;
}
}
if (searchValues.synopsis && !item.synopsis?.toLowerCase().includes(searchValues.synopsis.toLowerCase())) {
return false;
}
if (searchValues.type && item.type.toString() !== searchValues.type) {
return false;
}
if (searchValues.recommend && !item.recommend?.includes(parseInt(searchValues.recommend))) {
return false;
}
if (searchValues.goods_type_id && !item.goods_type_id?.includes(parseInt(searchValues.goods_type_id))) {
return false;
}
return true;
});
};
// 编辑页工具函数
// 验证规则
const createSimpleRules = (fieldName: string) => [
{ required: true, message: `${fieldName}不能为空` }
];
// 富文本编辑器工具函数
const processContent = (text: string, mode: 'save' | 'edit'): string => {
if (!text) return '';
const div = document.createElement('div');
div.innerHTML = text;
div.querySelectorAll('img').forEach(img => {
const src = img.getAttribute('src');
if (!src) return;
if (mode === 'save') {
const index = src.indexOf('/upload');
if (index !== -1) {
img.setAttribute('src', src.substring(index));
}
} else {
if (!src.startsWith('http')) {
const newSrc = src.startsWith('/upload') ? src : `/upload${src.startsWith('/') ? '' : '/'}${src}`;
img.setAttribute('src', `${dataurl}${newSrc}`);
}
}
});
return div.innerHTML;
};
// ==================== 自定义Hook ====================
// 商品列表搜索表单 Hook
const useEditProductStorage = () => {
const setEditProduct = useCallback((product: GoodsItem | null) => {
if (product) {
sessionStorage.setItem('Edittheproduct', JSON.stringify(product));
} else {
sessionStorage.removeItem('Edittheproduct');
}
}, []);
const getEditProduct = useCallback((): GoodsItem | null => {
try {
const data = sessionStorage.getItem('Edittheproduct');
return data ? JSON.parse(data) : null;
} catch {
return null;
}
}, []);
const clearEditProduct = useCallback(() => {
sessionStorage.removeItem('Edittheproduct');
}, []);
return { setEditProduct, getEditProduct, clearEditProduct };
};
// 分类数据 Hook
const useCategories = () => {
const [list, setList] = useState<Category[]>([]);
const [loading, setLoading] = useState(false);
const load = useCallback(async () => {
try {
setLoading(true);
const res = await goodstypeApi().signIn({});
const apiRes = res as ApiRes<CategoryRes>;
if (apiRes?.code === 1) {
setList(apiRes.data?.list || []);
} else {
throw new Error(apiRes?.msg || '获取分类失败');
}
} catch (error) {
message.error(error instanceof Error ? error.message : '加载失败');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
return { list, loading, load };
};
// 规格管理 Hook
// 规格管理
const useSpecs = (form: any) => {
const [specType, setSpecType] = useState<'single' | 'multiple'>(SPEC_TYPES.SINGLE);
const [specs, setSpecs] = useState<Spec[]>([{ id: 1, ...INITIAL_SPEC_VALUE }]);
const [imageUploaderKeys, setImageUploaderKeys] = useState<Record<number, number>>({ 1: Date.now() });
const [isInitialized, setIsInitialized] = useState(false);
// 生成新的规格ID
const getNewId = useCallback(() => specs.length > 0 ? Math.max(...specs.map(item => item.id)) + 1 : 1, [specs]);
// 添加规格
const addSpec = useCallback(() => {
const newId = getNewId();
const newSpec = { id: newId, ...INITIAL_SPEC_VALUE };
setSpecs(prev => [...prev, newSpec]);
setImageUploaderKeys(prev => ({
...prev,
[newId]: Date.now()
}));
}, [getNewId]);
// 删除规格
const deleteSpec = useCallback((id: number) => {
if (specs.length <= 1) {
message.warning('至少保留一个规格');
return;
}
setSpecs(prev => prev.filter(item => item.id !== id));
// 清理表单数据
SPEC_FIELD_NAMES.forEach(field => {
form.setFieldValue([`specs.${id}.${field}`], undefined);
});
// 清理上传器key
setImageUploaderKeys(prev => {
const newKeys = { ...prev };
delete newKeys[id];
return newKeys;
});
}, [specs.length, form]);
// 重置规格
const resetSpec = useCallback((id: number) => {
const resetValues = { ...INITIAL_SPEC_VALUE };
setSpecs(prev => prev.map(item =>
item.id === id ? { ...item, ...resetValues } : item
));
SPEC_FIELD_NAMES.forEach(field => {
form.setFieldValue([`specs.${id}.${field}`], resetValues[field as keyof typeof INITIAL_SPEC_VALUE]);
});
setImageUploaderKeys(prev => ({
...prev,
[id]: Date.now()
}));
}, [form]);
// 图片上传成功处理
const handleImageUploadSuccess = useCallback((id: number, url: string | string[]) => {
const imageUrl = Array.isArray(url) ? url[0] : url;
form.setFieldValue([`specs.${id}.imageUrl`], imageUrl);
setSpecs(prev => prev.map(item =>
item.id === id ? { ...item, imageUrl } : item
));
}, [form]);
// 初始化多规格数据
const initializeSpecs = useCallback((norm_arr: NormArrItem[] = []) => {
if (norm_arr.length === 0) {
setIsInitialized(true);
return;
}
// 根据后端数据创建规格项
const newSpecs: Spec[] = norm_arr.map((spec, index) => ({
id: index + 1,
name: spec.name || '',
price: spec.price?.toString() || '',
originalPrice: spec.original_price?.toString() || '',
stock: spec.stock?.toString() || '',
imageUrl: spec.img || ''
}));
setSpecs(newSpecs);
// 设置图片上传器keys
const newKeys: Record<number, number> = {};
newSpecs.forEach(spec => {
newKeys[spec.id] = Date.now() + spec.id;
});
setImageUploaderKeys(newKeys);
// 设置表单值
const specsFormData: Record<string, any> = {};
newSpecs.forEach(spec => {
specsFormData[`specs.${spec.id}.name`] = spec.name;
specsFormData[`specs.${spec.id}.price`] = spec.price;
specsFormData[`specs.${spec.id}.originalPrice`] = spec.originalPrice;
specsFormData[`specs.${spec.id}.stock`] = spec.stock;
specsFormData[`specs.${spec.id}.imageUrl`] = spec.imageUrl;
});
form.setFieldsValue(specsFormData);
setIsInitialized(true);
}, [form]);
// 设置规格类型
const setSpecTypeWithInit = useCallback((type: 'single' | 'multiple') => {
setSpecType(type);
if (type === SPEC_TYPES.SINGLE) {
setIsInitialized(true);
}
}, []);
return {
specType,
setSpecType: setSpecTypeWithInit,
specs,
imageUploaderKeys,
isInitialized,
addSpec,
deleteSpec,
resetSpec,
handleImageUploadSuccess,
initializeSpecs
};
};
// 库存数据处理 Hook
const useStockData = (specType: 'single' | 'multiple') => {
const getStockData = useCallback((values: any): StockData | null => {
// 单规格处理
if (specType === SPEC_TYPES.SINGLE) {
if (!values.price || !values.originalPrice || !values.stock) {
message.error('请填写完整的单规格信息');
return null;
}
return {
specType: API_SPEC_TYPES.SINGLE,
price: values.price,
original_price: values.originalPrice,
stock: values.stock,
collectedAt: new Date().toISOString()
};
}
// 多规格处理
const allSpecData = Object.keys(values)
.filter(key => key.startsWith('specs.'))
.reduce((acc: any, key) => {
const [_, specId, field] = key.split('.');
if (!acc[specId]) acc[specId] = {};
acc[specId][field] = values[key];
return acc;
}, {});
const validSpecData = Object.values(allSpecData).filter((data: any) =>
data.name?.trim() &&
data.price &&
data.originalPrice &&
data.stock &&
data.imageUrl
);
if (validSpecData.length === 0) {
message.error('请填写完整的规格信息');
return null;
}
// 生成多规格的norm和norm_arr
const norm: NormItem[] = [{
name: "name",
value: validSpecData.map((data: any) => data.name.trim())
}];
const norm_arr: NormArrItem[] = validSpecData.map((data: any) => ({
name: data.name.trim(),
price: Number(data.price || '0'),
original_price: Number(data.originalPrice || '0'),
stock: Number(data.stock || '0'),
img: data.imageUrl || ''
}));
return {
specType: API_SPEC_TYPES.MULTIPLE,
norm: norm,
norm_arr: norm_arr,
collectedAt: new Date().toISOString()
};
}, [specType]);
return { getStockData };
};
// 表单验证 Hook
const useValidation = (form: any, specType: 'single' | 'multiple', specs: Spec[]) => {
const validateStep = useCallback(async (step: number) => {
try {
let fieldsToValidate: string[] = [];
switch (step) {
case 1:
fieldsToValidate = ['category', 'name', 'desc', 'unit', 'mainImg', 'carouselImgs', 'status'];
break;
case 2:
if (specType === SPEC_TYPES.MULTIPLE) {
specs.forEach((spec) => {
SPEC_FIELD_NAMES.forEach(field => {
fieldsToValidate.push(`specs.${spec.id}.${field}`);
});
});
} else {
fieldsToValidate = ['price', 'originalPrice', 'stock'];
}
break;
case 3:
fieldsToValidate = ['content'];
break;
case 4:
fieldsToValidate = ['params'];
break;
case 5:
fieldsToValidate = ['address', 'freight'];
break;
case 6:
fieldsToValidate = ['sort', 'sold', 'recommend', 'keyword'];
break;
default:
fieldsToValidate = [];
}
await form.validateFields(fieldsToValidate);
return true;
} catch (error: any) {
message.error('请完善当前步骤的必填项');
return false;
}
}, [form, specType, specs]);
return { validateStep };
};
// 初始化表单数据 Hook
const useFormInitialization = (form: any, editor1: React.RefObject<any>, editor2: React.RefObject<any>, specHook: any, initialData: GoodsItem | null) => {
const { initializeSpecs, setSpecType } = specHook;
const { getEditProduct } = useEditProductStorage();
useEffect(() => {
const initializeForm = async () => {
try {
// 新增商品时的初始化
// 新增商品时的初始化
if (!editProductData) {
// 设置默认规格类型为单规格
setSpecType(SPEC_TYPES.SINGLE);
return;
}
// 编辑商品的逻辑
const productData = JSON.parse(editProductData);
const isSingleSpec = productData.type === 1;
const specTypeValue = isSingleSpec ? SPEC_TYPES.SINGLE : SPEC_TYPES.MULTIPLE;
setSpecType(specTypeValue);
const initialValues: any = {
category: productData.goods_type_id?.toString() || '', // 假设 goods_type_id 是一个数字
name: productData.title || '',
desc: productData.synopsis || '',
unit: productData.unit || '',
mainImg: productData.img || '',
carouselImgs: productData.imgs ? productData.imgs.split(',') : [],
status: productData.status === 1 ? 'active' : 'inactive',
content: processContent(productData.content || '', 'edit'),
params: processContent(productData.params || '', 'edit'),
address: productData.shipping_address || '',
freight: productData.courier_fee || '',
sort: productData.sort?.toString() || '0',
sold: productData.sold?.toString() || '0',
recommend: productData.recommend?.map((item: number) => item.toString()) || [],
keyword: productData.keyword || '',
specType: specTypeValue,
};
if (isSingleSpec) {
initialValues.price = productData.price || '';
initialValues.originalPrice = productData.original_price || '';
initialValues.stock = productData.stock?.toString() || '';
} else {
// 确保 norm_arr 是一个数组
if (productData.norm_arr && Array.isArray(productData.norm_arr)) {
initializeSpecs(productData.norm_arr);
} else {
initializeSpecs([]); // 传入空数组进行初始化
}
}
form.setFieldsValue(initialValues);
// 设置富文本编辑器内容
const setEditorContent = () => {
if (editor1.current && productData.content) {
try {
editor1.current.setContent(processContent(productData.content, 'edit'));
} catch (error) {
setTimeout(setEditorContent, 100);
}
}
if (editor2.current && productData.params) {
try {
editor2.current.setContent(processContent(productData.params, 'edit'));
} catch (error) {
setTimeout(setEditorContent, 100);
}
}
};
setTimeout(setEditorContent, 500);
} catch (error) {
console.error('初始化表单数据失败:', error);
message.error('加载商品数据失败,使用默认表单');
}
};
initializeForm();
}, [form, editor1, editor2, setSpecType, initializeSpecs]);
};
// ==================== 组件定义 ====================
// 基础信息表单组件
// 基础信息表单组件
const BaseInfoForm: React.FC<{ form: any }> = ({ form }) => {
const { list, loading } = useCategories();
const mainImgValue = Form.useWatch('mainImg', form);
const carouselImgsValue = Form.useWatch('carouselImgs', form);
const onMainImg = useCallback((url: string | string[]) => {
const img = Array.isArray(url) ? url[0] : url;
form.setFieldValue('mainImg', img);
}, [form]);
const onCarouselImgs = useCallback((urls: string | string[]) => {
let newImages: string[] = [];
if (Array.isArray(urls)) {
newImages = urls;
} else {
newImages = [urls];
}
// 获取当前已有的图片
const currentImages = form.getFieldValue('carouselImgs') || [];
// 合并图片并去重
const allImages = [...currentImages, ...newImages];
const uniqueImages = Array.from(new Set(allImages));
form.setFieldValue('carouselImgs', uniqueImages);
}, [form]);
return (
<Form
form={form}
layout="horizontal"
labelCol={{ span: 2 }}
wrapperCol={{ span: 22 }}
initialValues={{ status: 'active', enableVideo: false }}
>
<Form.Item
label="商品分类"
name="category"
rules={createSimpleRules('商品分类')}
>
<Select
placeholder="请选择商品分类"
size="middle"
allowClear
className={styles.productNameInput}
loading={loading}
>
{list.map(item => (
<Select.Option key={item.id} value={item.id.toString()}>
{item.title}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label="商品名称"
name="name"
rules={createSimpleRules('商品名称')}
>
<Input
placeholder="请输入商品名称"
size="middle"
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'name', e, { maxLength: 50 })
}
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'name', e, { maxLength: 50 })
}
/>
</Form.Item>
<Form.Item
label="商品简介"
name="desc"
rules={createSimpleRules('商品简介')}
>
<Input
placeholder="请输入商品简介"
size="middle"
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'desc', e, { maxLength: 100 })
}
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'desc', e, { maxLength: 100 })
}
/>
</Form.Item>
<Form.Item
label="单位"
name="unit"
rules={createSimpleRules('单位')}
>
<Input
placeholder="请输入单位"
size="middle"
maxLength={10}
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'unit', e, { maxLength: 10 })
}
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(form, 'unit', e, { maxLength: 10 })
}
/>
</Form.Item>
<Form.Item label="是否添加视频" name="enableVideo" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prev, curr) => prev.enableVideo !== curr.enableVideo}
>
{({ getFieldValue }) =>
getFieldValue('enableVideo') ? (
<Form.Item
label="商品视频"
name="video"
rules={createSimpleRules('商品视频')}
>
<div className={styles.videoUploadContainer}>
<Upload maxCount={1} accept="video/mp4,video/avi">
<Button icon={<UploadOutlined />} size="middle" style={{ width: '100%' }}>
点击上传视频
</Button>
</Upload>
<Button danger icon={<DeleteOutlined />}>删除</Button>
</div>
</Form.Item>
) : null
}
</Form.Item>
<Row className="imageUploadRow">
<Col span={6}>
<Form.Item
label="商品图片"
name="mainImg"
rules={createSimpleRules('商品图片')}
labelCol={{ span: 8 }}
style={{ margin: 0 }}
wrapperCol={{ span: 12 }}
className={styles.photoGraph}
>
<ImageUploader
key={`main-img-${mainImgValue || 'empty'}`}
maxCount={1}
onUploadSuccess={onMainImg}
imageUrl={mainImgValue}
/>
</Form.Item>
</Col>
<Col span={16}>
<Form.Item
label="轮播图"
name="carouselImgs"
className={styles.photoGraph}
rules={[{
validator: (_, value) =>
!value || value.length < 2
? Promise.reject(new Error('请上传至少两张轮播图'))
: Promise.resolve()
}]}
>
<ImageUploader
key={`carousel-${carouselImgsValue?.length || 0}`}
maxCount={4}
onUploadSuccess={onCarouselImgs}
imageUrl={carouselImgsValue}
/>
</Form.Item>
</Col>
</Row>
<Form.Item
label="状态"
name="status"
rules={createSimpleRules('商品状态')}
style={{ marginTop: -20 }}
>
<Radio.Group size="large">
<Radio value="active">上架</Radio>
<Radio value="inactive">下架</Radio>
</Radio.Group>
</Form.Item>
</Form>
);
};
// 规格配置组件
interface SpecConfigProps {
form: any;
specHook: any;
}
const SpecConfig: React.FC<SpecConfigProps> = ({ form, specHook }) => {
const {
specType,
setSpecType,
specs,
imageUploaderKeys,
isInitialized,
addSpec,
deleteSpec,
resetSpec,
handleImageUploadSuccess
} = specHook;
// 规格类型切换时的数据清理
useEffect(() => {
if (!isInitialized) return;
const currentValues = form.getFieldsValue(true);
if (specType === SPEC_TYPES.SINGLE) {
// 切换到单规格时清理多规格数据
const updatedValues = { ...currentValues };
specs.forEach(spec => {
SPEC_FIELD_NAMES.forEach(field => {
const fieldName = `specs.${spec.id}.${field}`;
delete updatedValues[fieldName];
});
});
delete updatedValues.specs;
form.setFieldsValue(updatedValues);
} else {
// 切换到多规格时清理单规格数据
const updatedValues = { ...currentValues };
delete updatedValues.price;
delete updatedValues.originalPrice;
delete updatedValues.stock;
form.setFieldsValue(updatedValues);
}
}, [specType, form, specs, isInitialized]);
const multiSpecs = useMemo(() => (
<>
<Form.Item
label="规格名称"
required
style={{ marginBottom: 16 }}
>
<div className={styles.specNameInputLayout}>
<Button type="default" icon={<PlusOutlined />} onClick={addSpec}>
添加新规格
</Button>
<div className={styles.specNameGrid}>
{specs.map((spec) => (
<Form.Item
key={`name-${spec.id}`}
name={[`specs.${spec.id}.name`]}
rules={createSimpleRules('规格名称')}
required
style={{ margin: 0 }}
validateTrigger={['onChange', 'onBlur']}
>
<Input
placeholder="请输入规格名称"
suffix={specs.length > 1 && (
<DeleteOutlined
onClick={() => deleteSpec(spec.id)}
style={{ cursor: 'pointer', color: '#ff4d4f' }}
/>
)}
/>
</Form.Item>
))}
</div>
</div>
</Form.Item>
<Form.Item
label="批量设置"
required
style={{ marginBottom: 16 }}
>
<table className={styles.batchTable}>
<thead>
<tr className={styles.tableHeader}>
<th className={styles.tableHeaderCell}>图片</th>
<th className={styles.tableHeaderCell}>售价</th>
<th className={styles.tableHeaderCell}>原价</th>
<th className={styles.tableHeaderCell}>库存</th>
<th className={styles.tableHeaderCell}>操作</th>
</tr>
</thead>
<tbody>
{specs.map((spec) => (
<tr key={`row-${spec.id}`}>
<td className={styles.pictureIncorrect}>
<Form.Item
name={[`specs.${spec.id}.imageUrl`]}
rules={createSimpleRules('商品图片')}
style={{ margin: 0 }}
required
>
<div className={styles.specImageUploadContainer}>
<ImageUploader
key={`uploader-${spec.id}-${imageUploaderKeys[spec.id] || Date.now()}`}
imageUrl={spec.imageUrl}
onUploadSuccess={(url) => handleImageUploadSuccess(spec.id, url)}
maxCount={1}
/>
</div>
</Form.Item>
</td>
<td className={styles.tableBodyCell}>
<Form.Item
name={[`specs.${spec.id}.price`]}
rules={createSimpleRules('售价')}
style={{ margin: 0 }}
required
>
<Input
placeholder="请输入价格"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, `specs.${spec.id}.price`, e, { supportDecimal: true })
}
/>
</Form.Item>
</td>
<td className={styles.tableBodyCell}>
<Form.Item
name={[`specs.${spec.id}.originalPrice`]}
rules={createSimpleRules('原价')}
style={{ margin: 0 }}
required
>
<Input
placeholder="请输入原价"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, `specs.${spec.id}.originalPrice`, e, { supportDecimal: true })
}
/>
</Form.Item>
</td>
<td className={styles.tableBodyCell}>
<Form.Item
name={[`specs.${spec.id}.stock`]}
rules={createSimpleRules('库存')}
style={{ margin: 0 }}
required
>
<Input
placeholder="请输入库存"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, `specs.${spec.id}.stock`, e, { supportDecimal: false })
}
/>
</Form.Item>
</td>
<td className={styles.tableBodyCell}>
<div className={styles.operationBtnLayout}>
<Button
className={styles.deleteButton}
danger
icon={<DeleteOutlined />}
onClick={() => deleteSpec(spec.id)}
disabled={specs.length <= 1}
>
删除
</Button>
<Button
className={styles.resetButton}
icon={<ReloadOutlined />}
onClick={() => resetSpec(spec.id)}
>
重置
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</Form.Item>
</>
), [specs, imageUploaderKeys, addSpec, deleteSpec, resetSpec, handleImageUploadSuccess, form]);
const singleSpec = useMemo(() => (
<>
<Form.Item
label="售价"
name="price"
rules={createSimpleRules('售价')}
required
style={{ marginBottom: 16 }}
>
<Input
placeholder="请输入价格"
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, 'price', e, { supportDecimal: true })
}
/>
</Form.Item>
<Form.Item
label="原价"
name="originalPrice"
rules={createSimpleRules('原价')}
required
style={{ marginBottom: 16 }}
>
<Input
placeholder="请输入原价"
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, 'originalPrice', e, { supportDecimal: true })
}
/>
</Form.Item>
<Form.Item
label="库存"
name="stock"
rules={createSimpleRules('库存')}
required
style={{ marginBottom: 16 }}
>
<Input
placeholder="请输入库存"
className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(form, 'stock', e, { supportDecimal: false })
}
/>
</Form.Item>
</>
), [form]);
if (!isInitialized) {
return <div>加载规格数据中...</div>;
}
return (
<Form
form={form}
layout="horizontal"
labelCol={{ span: 2 }}
wrapperCol={{ span: 22 }}
validateMessages={{ required: '${label}不能为空' }}
>
<Form.Item label="商品规格" required style={{ marginBottom: 16 }}>
<div className={styles.specTypeLayout}>
<Radio.Group
value={specType}
onChange={(e) => setSpecType(e.target.value)}
>
<Radio value={SPEC_TYPES.SINGLE}>单规格</Radio>
<Radio value={SPEC_TYPES.MULTIPLE}>多规格</Radio>
</Radio.Group>
</div>
</Form.Item>
{specType === SPEC_TYPES.MULTIPLE ? multiSpecs : singleSpec}
</Form>
);
};
// 主组件
const GoodsManagement: React.FC = () => {
// ==================== 状态管理 ====================
const [pageState, setPageState] = useState<'list' | 'edit'>('list');
const [currentGoods, setCurrentGoods] = useState<GoodsItem | null>(null);
// 列表页状态
const [searchForm] = Form.useForm<SearchFormValues>();
const [allGoodsList, setAllGoodsList] = useState<GoodsItem[]>([]);
const [filteredGoodsList, setFilteredGoodsList] = useState<GoodsItem[]>([]);
const [displayGoodsList, setDisplayGoodsList] = useState<GoodsItem[]>([]);
const [loading, setLoading] = useState(false);
const [goodsTypeList, setGoodsTypeList] = useState<{ value: string; label: string }[]>([]);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loadingStatus, setLoadingStatus] = useState<{ [key: number]: boolean }>({});
// 分页状态
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条数据`,
pageSizeOptions: PAGE_SIZE_OPTIONS,
});
// 编辑页状态
const [editForm] = Form.useForm<FormData>();
const [activeTab, setActiveTab] = useState('1');
const editor1 = useRef<any>(null);
const editor2 = useRef<any>(null);
// 编辑页自定义Hook
const specHook = useSpecs(editForm);
const { specType, specs, isInitialized } = specHook;
const { getStockData } = useStockData(specType);
const { validateStep } = useValidation(editForm, specType, specs);
// sessionStorage管理
const { setEditProduct, getEditProduct, clearEditProduct } = useEditProductStorage();
// 初始化编辑表单数据
useFormInitialization(editForm, editor1, editor2, specHook, currentGoods);
const navigate = useNavigate();
// ==================== 列表页方法 ====================
// 获取商品列表数据
const getGoodsList = useCallback(async () => {
setLoading(true);
try {
const response = await goodsListApi({ current: 1, pageSize: 1000 });
const apiResponse = safeConvertToApiResponse<GoodsListResponse>(response);
if (apiResponse.code === 1) {
const data = apiResponse.data || {};
const goodsData = data.list || [];
setAllGoodsList(goodsData);
setFilteredGoodsList(goodsData);
setPagination(prev => ({
...prev,
total: goodsData.length,
current: 1,
}));
if (data.type) {
setGoodsTypeList(data.type.map(item => ({
value: item.value.toString(),
label: item.label
})));
}
} else {
message.error(apiResponse.msg || '获取数据失败');
}
} catch (error) {
console.error('获取数据失败:', error);
message.error('网络错误,获取数据失败');
} finally {
setLoading(false);
}
}, []);
// 上下架状态切换函数
const handleStatusChange = useCallback(async (ids: number | number[], checked: boolean) => {
try {
const newStatus = checked ? 1 : 0;
const isBatch = Array.isArray(ids);
if (isBatch) {
setLoading(true);
} else {
setLoadingStatus(prev => ({ ...prev, [ids]: true }));
}
const requestData = {
ids: Array.isArray(ids) ? ids.join(',') : ids.toString(),
status: newStatus.toString()
};
const response = await goodsUpdownApi(requestData);
const apiResponse = safeConvertToApiResponse(response);
if (apiResponse.code === 1) {
message.success(isBatch ? `批量${checked ? '上架' : '下架'}成功` : `商品已${checked ? '上架' : '下架'}`);
} else {
message.error(apiResponse.msg || `${isBatch ? '批量' : ''}${checked ? '上架' : '下架'}失败`);
}
// 无论成功失败都重新获取数据
getGoodsList();
} catch (error) {
console.error('上下架操作失败:', error);
message.error('操作失败,请重试');
// 出错也重新获取数据
getGoodsList();
} finally {
if (Array.isArray(ids)) {
setLoading(false);
} else {
setLoadingStatus(prev => ({ ...prev, [ids]: false }));
}
}
}, [getGoodsList]);
// 处理商品状态变化
const handleBatchStatusChange = useCallback((checked: boolean) => {
if (selectedRowKeys.length === 0) {
message.warning('请选择要操作的商品');
return;
}
const actionText = checked ? '上架' : '下架';
Modal.confirm({
title: `是否确认批量${actionText}?`,
content: `即将${actionText} ${selectedRowKeys.length} 个商品`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
centered: true,
onOk: () => {
const ids = selectedRowKeys.map(key => Number(key));
handleStatusChange(ids, checked);
},
});
}, [selectedRowKeys, handleStatusChange]);
// 删除商品功能
const handleDelete = useCallback(async (ids: React.Key[]) => {
try {
setAllGoodsList(prev => prev.filter(item => !ids.includes(item.id)));
setFilteredGoodsList(prev => prev.filter(item => !ids.includes(item.id)));
const response = await goodsDelApi({ ids });
const apiResponse = safeConvertToApiResponse(response);
if (apiResponse.code === 1) {
message.success(ids.length === 1 ? '删除成功' : '批量删除成功');
setSelectedRowKeys(prev => prev.filter(key => !ids.includes(key))); // 清除已选中的行
getGoodsList(); // 重新获取数据
} else {
message.error('删除失败:' + (apiResponse.msg || '未知错误'));
getGoodsList();
}
} catch (error) {
console.error('删除商品失败:', error);
message.error('删除失败');
getGoodsList();
}
}, [getGoodsList]);
// 批量删除功能
const handleBatchDelete = useCallback(() => {
if (selectedRowKeys.length === 0) {
message.warning('请选择要删除的商品');
return;
}
Modal.confirm({
title: '是否确认批量删除?',
content: `即将删除 ${selectedRowKeys.length} 条数据`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
centered: true,
onOk: () => handleDelete(selectedRowKeys)
});
}, [selectedRowKeys, handleDelete]);
// 处理本地搜索
const handleLocalSearch = useCallback((searchValues: SearchFormValues) => {
const filteredList = filterGoodsList(allGoodsList, searchValues);
setFilteredGoodsList(filteredList);
const newPagination = { ...pagination, current: 1, total: filteredList.length };
setPagination(newPagination);
setDisplayGoodsList(filteredList.slice(0, newPagination.pageSize || 10));
}, [allGoodsList, pagination]);
// 处理分页变化
const handlePaginationChange = useCallback((current: number, pageSize: number) => {
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
setDisplayGoodsList(filteredGoodsList.slice(startIndex, endIndex));
setPagination(prev => ({ ...prev, current, pageSize, total: filteredGoodsList.length }));
}, [filteredGoodsList]);
// 处理表格的分页、排序和筛选变化
const handleTableChange = useCallback((newPagination: TablePaginationConfig) => {
const current = newPagination.current || 1;
const pageSize = newPagination.pageSize || 10;
handlePaginationChange(current, pageSize);
setSelectedRowKeys([]);
}, [handlePaginationChange]);
// 搜索功能
const handleSearch = useCallback(() => {
searchForm.validateFields().then(values => {
handleLocalSearch(values);
});
}, [searchForm, handleLocalSearch]);
// 重置功能
const handleReset = useCallback(() => {
searchForm.resetFields();
setFilteredGoodsList(allGoodsList);
handlePaginationChange(1, pagination.pageSize || 10);
setSelectedRowKeys([]);
}, [searchForm, allGoodsList, pagination.pageSize, handlePaginationChange]);
// 刷新功能
const handleRefresh = useCallback(() => {
setSelectedRowKeys([]);
getGoodsList();
}, [getGoodsList]);
const handleInputKeyPress = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSearch();
}
}, [handleSearch]);
// 编辑商品
const handleEdit = useCallback((goods: GoodsItem) => {
try {
// 保存到sessionStorage
setEditProduct(goods);
setCurrentGoods(goods);
setPageState('edit');
setActiveTab('1');
} catch (error) {
console.error('准备编辑商品数据失败:', error);
message.error('准备编辑商品失败');
}
}, [setEditProduct]);
// 新增商品
const handleAdd = useCallback(() => {
// 清除编辑状态
clearEditProduct();
setCurrentGoods(null);
setPageState('edit');
setActiveTab('1');
}, [clearEditProduct]);
// ==================== 编辑页方法 ====================
// 同步富文本编辑器内容到表单
const syncEditorContent = useCallback(() => {
if (editor1.current) {
editForm.setFieldValue('content', editor1.current.getContent());
}
if (editor2.current) {
editForm.setFieldValue('params', editor2.current.getContent());
}
}, [editForm]);
// 切换标签页
const handleTabChange = useCallback(async (key: string) => {
const currentStep = parseInt(activeTab, 10);
const newStep = parseInt(key, 10);
if (newStep > currentStep) {
const isValid = await validateStep(currentStep);
if (!isValid) return;
}
setActiveTab(key);
}, [activeTab, validateStep]);
// 切换步骤(上一步/下一步)
const handleStepChange = useCallback(async (direction: 'prev' | 'next') => {
const currentStep = parseInt(activeTab, 10);
if (direction === 'next') {
const isValid = await validateStep(currentStep);
if (!isValid) return;
}
const newStep = direction === 'prev'
? Math.max(1, currentStep - 1)
: Math.min(6, currentStep + 1);
setActiveTab(newStep.toString());
}, [activeTab, validateStep]);
// 保存商品 - 修复后的版本
const handleSave = useCallback(async () => {
try {
syncEditorContent();
// 验证所有步骤
for (let step = 1; step <= 6; step++) {
const isValid = await validateStep(step);
if (!isValid) {
setActiveTab(step.toString());
return;
}
}
const values = form.getFieldsValue(true);
const stockData = getStockData(values);
if (!stockData) return;
const content = processContent(values.content || '', 'save');
const params = processContent(values.params || '', 'save');
// 构建提交数据
const submitData: any = {
type: stockData.specType === API_SPEC_TYPES.SINGLE ? 1 : 0,
goods_type_id: values.category ? Number(values.category) : '',
title: values.name || "",
synopsis: values.desc || "",
unit: values.unit || "",
img: values.mainImg || "",
imgs: values.carouselImgs?.join(',') || "",
status: values.status === 'active' ? 1 : 0,
content: content,
params: params,
shipping_address: values.address || "",
courier_fee: values.freight || "0.00",
sort: Number(values.sort) || 0,
sold: Number(values.sold) || 0,
keyword: values.keyword || "",
recommend: values.recommend?.join(',') || "",
// 单规格数据
...(stockData.specType === API_SPEC_TYPES.SINGLE ? {
price: stockData.price || "0.00",
original_price: stockData.original_price || "0.00",
stock: Number(stockData.stock) || 0
} : {
// 多规格数据 - 修复关键问题
norm: JSON.stringify(stockData.norm || []),
norm_arr: JSON.stringify(stockData.norm_arr || [])
})
};
// **核心改动:编辑时添加商品ID**
const editProductData = sessionStorage.getItem('Edittheproduct');
if (editProductData) {
const productData = JSON.parse(editProductData);
if (productData.id !== undefined && productData.id !== null) {
submitData.id = productData.id; // 将商品ID添加到提交数据中
}
}
console.log('提交数据:', submitData);
// 调用保存接口
const response = await goodsSaveApi(submitData);
if (response && response.code === 1) {
message.success('商品保存成功!');
// 清除编辑状态
sessionStorage.removeItem('Edittheproduct');
// 延迟1.5秒后返回上一页
setTimeout(() => {
window.history.back();
}, 1500);
} else {
const errorMsg = response?.msg || '商品保存失败,请稍后重试。';
message.error(errorMsg);
}
} catch (error: any) {
console.error('保存失败详情:', error);
message.error('保存失败,请检查网络或联系管理员。');
}
}, [form, syncEditorContent, validateStep, getStockData]);
// 返回列表页
const handleCancel = useCallback(() => {
// 清除编辑状态
clearEditProduct();
setPageState('list');
setCurrentGoods(null);
}, [clearEditProduct]);
// 渲染操作按钮
const renderActionButtons = useCallback((tabKey: string) => {
const isFirstStep = tabKey === '1';
const isLastStep = tabKey === '6';
return (
<Flex gap="small" style={{ marginTop: 24, padding: '0 20px' }}>
{!isFirstStep && (
<Button onClick={() => handleStepChange('prev')}>
上一步
</Button>
)}
{!isLastStep ? (
<Button type="primary" onClick={() => handleStepChange('next')}>
下一步
</Button>
) : (
<Button type="primary" onClick={handleSave}>
保存
</Button>
)}
</Flex>
);
}, [handleStepChange, handleSave]);
// ==================== 渲染 ====================
// 列表页列定义
const columns: ColumnsType<GoodsItem> = useMemo(() => [
{
title: '序号',
key: 'index',
align: 'center',
width: 80,
render: (_, __, index) => {
const current = pagination.current || 1;
const pageSize = pagination.pageSize || 10;
return (current - 1) * pageSize + index + 1;
},
},
{
title: '商品图片',
dataIndex: 'img',
align: 'center',
key: 'img',
render: (img: string) =>
img ? (
<Image
width={40}
height={40}
src={img}
style={{ objectFit: 'cover', borderRadius: '4px' }}
preview={{ minScale: 0.5, maxScale: 1.2, scaleStep: 0.1 }}
/>
) : '无',
},
{
title: '商品名称',
dataIndex: 'title',
align: 'center',
key: 'title',
},
{
title: '售价',
dataIndex: 'price',
align: 'center',
key: 'price',
render: (price: string) => `${price}`
},
{
title: '库存',
dataIndex: 'stock',
align: 'center',
key: 'stock',
},
{
title: '销量',
dataIndex: 'sold',
align: 'center',
key: 'sold',
},
{
title: '上下架',
dataIndex: 'status',
align: 'center',
key: 'status',
render: (status: number, record: GoodsItem) => (
<Switch
checked={status === 1}
checkedChildren="上架"
unCheckedChildren="下架"
loading={loadingStatus[record.id]}
onChange={(checked) => handleStatusChange(record.id, checked)}
/>
),
},
{
title: '排序',
dataIndex: 'sort',
align: 'center',
key: 'sort',
},
{
title: '操作',
key: 'action',
width: 180,
align: 'center',
render: (_, record: GoodsItem) => (
<Space size="small">
<Button type="primary" icon={<EditOutlined />} className={styles.deleteButton}
onClick={() => handleEdit(record)}>
编辑
</Button>
<Popconfirm
title="是否确认删除?"
description="删除后不可恢复"
onConfirm={() => handleDelete([record.id])}
okText="确定"
cancelText="取消"
okType="danger"
>
<Button type="primary" danger className={styles.deleteButton} icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
</Space>
),
},
], [pagination.current, pagination.pageSize, loadingStatus, handleStatusChange, handleDelete, handleEdit]);
// 列表选择项
const rowSelection = useMemo(() => ({
selectedRowKeys,
onChange: setSelectedRowKeys,
columnWidth: 30,
}), [selectedRowKeys]);
// 编辑页标签项
const tabItems = useMemo(() => [
{
key: '1',
label: '基础信息',
children: (
<>
<BaseInfoForm form={editForm} />
{renderActionButtons('1')}
</>
),
},
{
key: '2',
label: '规格库存',
children: (
<>
<SpecConfig form={editForm} specHook={specHook} />
{renderActionButtons('2')}
</>
),
},
{
key: '3',
label: '商品详情',
children: (
<>
<Form form={editForm} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }}>
<Form.Item
name="content"
label="商品详情"
rules={createSimpleRules('商品详情')}
>
<UEditor ref={editor1} />
</Form.Item>
</Form>
{renderActionButtons('3')}
</>
),
},
{
key: '4',
label: '产品参数',
children: (
<>
<Form form={editForm} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }}>
<Form.Item
name="params"
label="产品参数"
rules={createSimpleRules('产品参数')}
>
<UEditor ref={editor2} />
</Form.Item>
</Form>
{renderActionButtons('4')}
</>
),
},
{
key: '5',
label: '物流设置',
children: (
<>
<Form form={editForm} colon={false} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }}>
<Form.Item
name="address"
label="发货地址"
rules={createSimpleRules('发货地址')}
>
<Input placeholder="请输入发货地址" className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(editForm, 'address', e, { maxLength: 100 })
}
/>
</Form.Item>
<Form.Item
name="freight"
label="运费"
rules={createSimpleRules('运费')}
>
<Input placeholder="请输入运费" className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(editForm, 'freight', e, { supportDecimal: true })
}
/>
</Form.Item>
</Form>
{renderActionButtons('5')}
</>
),
},
{
key: '6',
label: '营销设置',
children: (
<>
<Form form={editForm} colon={false} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }}>
<Form.Item
name="sort"
label="排序"
rules={createSimpleRules('排序')}
>
<Input placeholder="请输入排序" className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(editForm, 'sort', e, { supportDecimal: false })
}
/>
</Form.Item>
<Form.Item
name="sold"
label="已售数量"
rules={createSimpleRules('已售数量')}
>
<Input placeholder="请输入已售数量" className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleNumberInput(editForm, 'sold', e, { supportDecimal: false })
}
/>
</Form.Item>
<Form.Item
name="recommend"
label="商品推荐"
rules={createSimpleRules('商品推荐')}
>
<Checkbox.Group>
<Checkbox value="1">热销商品</Checkbox>
<Checkbox value="2">为您推荐</Checkbox>
<Checkbox value="3">猜你喜欢</Checkbox>
</Checkbox.Group>
</Form.Item>
<Form.Item
name="keyword"
label="关键字"
rules={createSimpleRules('关键字')}
>
<Input placeholder="请输入关键字" className={styles.productNameInput}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTextOnlyInput(editForm, 'keyword', e, { maxLength: 50 })
}
/>
</Form.Item>
</Form>
{renderActionButtons('6')}
</>
),
},
], [editForm, renderActionButtons, specHook]);
// 初始化获取商品列表
useEffect(() => {
if (pageState === 'list') {
getGoodsList();
}
}, [pageState, getGoodsList]);
// 分页变化时更新显示列表
useEffect(() => {
handlePaginationChange(pagination.current || 1, pagination.pageSize || 10);
}, [filteredGoodsList, pagination.current, pagination.pageSize, handlePaginationChange]);
// 渲染页面
if (pageState === 'list') {
return (
<div className={styles.pageWrapper}>
<Title level={4} className={styles.pageTitle}>商品列表</Title>
<div className={styles.recycleSearchContainer}>
<Form form={searchForm} layout="inline" colon={false} labelAlign="left" className={styles.recycleSearchLeft}>
<Row gutter={[24, 16]} style={{ width: '100%', marginBottom: 16 }}>
<Col span={8}>
<Form.Item name="title" label="商品名称" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Input placeholder="请输入商品名称" onPressEnter={handleInputKeyPress} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="keyword" label="关键字" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Input placeholder="请输入关键字" onPressEnter={handleInputKeyPress} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="goods_type_id" label="商品分类" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Select placeholder="全部" allowClear onClick={handleSearch} onClear={handleSearch}>
{goodsTypeList.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={[24, 16]} style={{ width: '100%' }}>
<Col span={8}>
<Form.Item name="synopsis" label="商品简介" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Input placeholder="请输入商品简介" onPressEnter={handleInputKeyPress} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="type" label="规格" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Select placeholder="全部" allowClear onClick={handleSearch} onClear={handleSearch}>
{SPECIFICATION_OPTIONS.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="recommend" label="商品推荐" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Select placeholder="全部" allowClear onClick={handleSearch} onClear={handleSearch}>
{RECOMMEND_OPTIONS.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
</Form>
<div className={styles.recycleSearchRight}>
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch} className={styles.recycleSearchButton}>
查询
</Button>
<Button icon={<ReloadOutlined />} onClick={handleReset} className={styles.recycleResetButton}>
重置
</Button>
</div>
</div>
<Card variant="borderless" styles={{ body: { padding: 0 } }}>
<div className={styles.tabContent}>
<div className={styles.actionBar}>
<div className={styles.actionButtons}>
<Space>
<Button type="primary" className={styles.recycleDeleteButton} icon={<PlusOutlined />} onClick={handleAdd} >
新建
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={handleBatchDelete}
className={styles.recycleDeleteButton}
disabled={selectedRowKeys.length === 0}
>
批量删除
</Button>
<Button
type="primary"
danger
icon={<CloseOutlined />}
className={styles.recycleDeleteButton}
onClick={() => handleBatchStatusChange(true)}
disabled={selectedRowKeys.length === 0}
>
批量上架
</Button>
</Space>
</div>
<div className={styles.refreshIcon}>
<RedoOutlined
style={{ fontSize: '18px', cursor: 'pointer' }}
className={loading ? styles.refreshing : ''}
title="刷新页面"
onClick={handleRefresh}
/>
</div>
</div>
<Table
columns={columns}
dataSource={displayGoodsList}
rowKey="id"
loading={loading}
className={styles.customTable}
pagination={pagination}
onChange={handleTableChange}
scroll={{ x: 'max-content' }}
rowSelection={rowSelection}
/>
</div>
</Card>
</div>
);
} else {
return (
<Card className={styles.cardContainer}>
<div className={styles.headerBar}>
<Button
type="text"
icon={<LeftOutlined className={styles.backIcon} />}
onClick={handleCancel}
className={styles.backButton}
>
返回列表
</Button>
<span className={styles.pageTitle}>
{currentGoods ? '编辑商品' : '新建商品'}
</span>
</div>
<Tabs
activeKey={activeTab}
items={tabItems}
onChange={handleTabChange}
size="large"
className={styles.tabsContainer}
/>
</Card>
);
}
};
export default GoodsManagement;修复写出完整的
最新发布