Warning: `value` prop on `input` should not be null.

本文介绍了一种常见的React警告——关于输入框value属性不应为null的问题,并提供了两种解决方案:一是忽略警告;二是将输入框的初始值设为空字符串。
部署运行你感兴趣的模型镜像

 

// console报错信息如下
Warning: `value` prop on `input` should not be null. Consider using the empty string to clear the component or `undefined` for uncontrolled components.
    in input (created by AddAddress)
    in div (created by AddAddress)
    in div (created by AddAddress)
    in AddAddress (created by RouterContext)
    in div (created by Unknown)
    in div (created by Unknown)
    in Unknown (created by RouterContext)
    in RouterContext (created by Router)
    in Router

 

 

原因解析:

由于input需要设置value,而初始化state里面的value值为null,导致react解析报Waring。

Warning: `value` prop on `input` should not be null. Consider using the empty string to clear the component or `undefined` for uncontrolled components.

 

解决方案:

1、waring忽略不管

2、设置input初始化值位空字符串

<input type="number" maxLength="6" placeholder="选填" value={postcode || ''} />

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

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;修复写出完整的
最新发布
11-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值