提交 26a95c46 作者: 龚洪江

功能:商品新增,商品编辑(联调完成)

上级 679b10ca
......@@ -99,8 +99,12 @@ export type otherServiceType = InterFunction<any, { id: number; saleServiceName:
export type skuUnitType = InterFunction<any, { id: number; unitName: string }[]>;
//商品-批量上下架
export type batchOnShelfOrTakeDownType = InterFunction<{ goodsIds: number[]; status: number }, any>;
//商品-单个上下架(新)
export type upOrDownShelfType = InterFunction<{ id: number; status: number }, any>;
//商品批量删除
export type batchRemoveWareInfoType = InterFunction<number[], any>;
//商品-单个删除
export type removeMallGoodsType = InterFunction<{ id: number }, any>;
//商品-上下移动
export type exchangeGoodsInfoType = InterFunction<{ firstId: number; secondId: number }, any>;
......@@ -116,7 +120,7 @@ export type addMallGoodsType = InterFunction<
chooseType: number;
goodsSpecValuesList: {
channelPrice?: number;
id: number;
id: any;
partNo: string;
salePrice: number;
showPrice: number;
......@@ -124,7 +128,44 @@ export type addMallGoodsType = InterFunction<
specValueName: string;
stock?: number;
}[];
id: any;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
},
any
>;
//商品-编辑(新)
export type editMallGoodsType = InterFunction<
{
categoryPrimaryId: number;
categorySubId: number;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice?: number;
id: any;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock?: number;
}[];
id: any;
mallGoodsId: number;
must: number;
skuUnitId: number;
......@@ -142,3 +183,85 @@ export type addMallGoodsType = InterFunction<
},
any
>;
//商品-列表(新)
export type listPageGoodsInfoType = InterItemFunction<
{ categoryPrimaryId?: number; tradeName?: string; shelfStatus?: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}[];
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
id: number;
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
userAccountId: number;
}[]
>;
//商城-详情(新)
export type mallGoodsDetailsType = InterFunction<
{ id: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}[];
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
id: number;
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
userAccountId: number;
}
>;
......@@ -5,10 +5,15 @@ import {
batchRemoveWareInfoType,
detailGoodsType,
editGoodsType,
editMallGoodsType,
exchangeGoodsInfoType,
listGoodsType,
listPageGoodsInfoType,
mallGoodsDetailsType,
otherServiceType,
removeMallGoodsType,
skuUnitType,
upOrDownShelfType,
} from '~/api/interface/goodsType';
import axios from '../request';
......@@ -41,16 +46,35 @@ class GoodsAPI {
static batchOnShelfOrTakeDown: batchOnShelfOrTakeDownType = (data) => {
return axios.post('/pms/goods/batchOnShelfOrTakeDown', data);
};
//商品-单个上下架(新)
static upOrDownShelf: upOrDownShelfType = (params) =>
axios.get('/pms/mall/goods/upOrDownShelf', { params });
// 商品-批量删除
static batchRemoveWareInfo: batchRemoveWareInfoType = (data) => {
return axios.post('/pms/goods/batchRemoveWareInfo', data);
};
//商品-单个删除
static removeMallGoods: removeMallGoodsType = (params) =>
axios.get('/pms/mall/goods/removeMallGoods', { params });
// 商品-上下移动
static exchangeGoodsInfo: exchangeGoodsInfoType = (params) => {
return axios.get('/pms/goods/exchangeGoodsInfo', { params });
};
//商品-上下移动
static exchange: exchangeGoodsInfoType = (params) =>
axios.get('/pms/mall/goods/exchange', { params });
// 商品-新增(新)
static addMallGoods: addMallGoodsType = (data) =>
axios.post('/pms/mall/goods/addMallGoods', data);
//商品-编辑(新)
static editMallGoods: editMallGoodsType = (data) =>
axios.post('/pms/mall/goods/editMallGoods', data);
// 商品-列表(新)
static getListPageGoodsInfo: listPageGoodsInfoType = (data) =>
axios.post('/pms/mall/goods/listPageGoodsInfo', data);
// 商品-详情(新)
static getMallGoodsDetails: mallGoodsDetailsType = (params) =>
axios.get('/pms/mall/goods/mallGoodsDetails', { params });
}
export default GoodsAPI;
......@@ -39,7 +39,7 @@ const EditableCell: React.FC<
const inputNode = () => {
switch (inputType) {
case 'number':
return <InputNumber />;
return <InputNumber placeholder={`请输入${title}`} maxLength={maxLength} />;
case 'select':
return (
<Select placeholder={`请选择${title}`} style={{ textAlign: 'start' }}>
......
......@@ -118,7 +118,6 @@ const CategoryList = () => {
) : (
''
)}
<Button type='link'>详情</Button>
<Button type='link' onClick={() => addOrEditCategoryModalShowClick(record)}>
编辑
</Button>
......
.goods-video-wrap{
position: relative;
width: 200px;
img{
position: absolute;
right: 0;
top: 0;
transform: translate(50%,-25%);
}
}
......@@ -2,10 +2,12 @@ import { Button, Cascader, Form, Input, Radio, Select } from 'antd';
import { Uploader } from '~/components/uploader';
import type { RadioChangeEvent } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { InterDataType } from '~/api/interface';
import { categoryListRespType } from '~/api/interface/categoryManage';
import { CategoryManageAPI } from '~/api';
import deletePng from '~/assets/image/delete.png';
import './index.scss';
//分类返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
......@@ -76,6 +78,12 @@ const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
useImperativeHandle(ref, () => ({
getForm: () => baseInfoForm,
setLabelShow: (value: boolean) => setLabelShow(value),
mediaData: {
setMainFileList,
setSubFileList,
setVideoFileList,
},
}));
//标签选中监听
......@@ -115,6 +123,11 @@ const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
setCategoryList(result.list || []);
});
};
//视频删除
const deleteVideo = () => {
setVideoFileList([]);
baseInfoForm.setFieldValue('videoList', undefined);
};
useEffect(() => {
getCategoryList();
......@@ -133,10 +146,14 @@ const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
name='tradeName'
rules={[{ required: true, message: '请输入商品名称' }]}
>
<Input placeholder='请输入商品名称' />
<Input placeholder='请输入商品名称' maxLength={50} />
</Form.Item>
<Form.Item label='商品描述' name='description'>
<Input.TextArea rows={4} placeholder='请输入商品描述' />
<Form.Item
label='商品描述'
name='description'
rules={[{ required: true, message: '请输入商品描述' }]}
>
<Input.TextArea rows={4} placeholder='请输入商品描述' maxLength={70} showCount />
</Form.Item>
<Form.Item
label='商品主图'
......@@ -163,14 +180,27 @@ const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
</Uploader>
</Form.Item>
<Form.Item label='商品视频' name='videoList'>
<Uploader
fileUpload
listType='text'
onChange={(fileList) => uploadSuccess(fileList, 'videoList')}
defaultFileList={videoFileList}
>
<Button icon={<UploadOutlined />}>上传视频</Button>
</Uploader>
{videoFileList.length ? (
<div className='goods-video-wrap'>
<video
src={videoFileList[0].url}
style={{ width: '200px', height: '200px' }}
controls
/>
<img src={deletePng} alt='删除' onClick={deleteVideo} />
</div>
) : (
<Uploader
fileUpload
listType='text'
onChange={(fileList) => uploadSuccess(fileList, 'videoList')}
defaultFileList={videoFileList}
fileType={['video/mp4', 'video/avi', 'video/wmv', 'video/rmvb']}
fileSize={50}
>
<Button icon={<UploadOutlined />}>上传视频</Button>
</Uploader>
)}
</Form.Item>
<Form.Item
label='商品分类'
......@@ -205,7 +235,7 @@ const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
name='goodsLabel'
rules={[{ required: true, message: '请输入商品标签' }]}
>
<Input placeholder='请输入商品标签' />
<Input placeholder='请输入商品标签' maxLength={10} />
</Form.Item>
) : (
''
......
import RichText from '~/components/richText';
import { FC } from 'react';
interface selfProps {
onChange: (html: string) => void;
introduceInfo: string;
}
const IntroduceInfo: FC<selfProps> = ({ onChange, introduceInfo }) => {
//富文本修改
const richTextOnChange = (html?: string) => {
onChange(html || '');
};
const IntroduceInfo = () => {
return (
<div className='introduce-info'>
<RichText richTextContent='' height={400} />
<RichText richTextContent={introduceInfo} height={400} onChange={richTextOnChange} />
</div>
);
};
......
import { Button, Form, Input, message, Modal, ModalProps, Radio, Select, Table } from 'antd';
import { Button, Form, Input, Modal, ModalProps, Radio, Select, Table } from 'antd';
import { FC, useEffect, useState } from 'react';
import EditableCell from '~/components/EditableCell';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { InterDataType, InterReqType } from '~/api/interface';
import { addMallGoodsType, skuUnitType } from '~/api/interface/goodsType';
import { filterObjAttr } from '~/utils';
import { isEmptyBol, regPriceNumber } from '~/utils/validateUtils';
import _ from 'lodash';
type EditableTableProps = Parameters<typeof Table>[0];
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
//商品sku规格类型
type goodsSpecType = InterReqType<addMallGoodsType>['goodsSpecList'][0];
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//商品sku规格值类型
type goodsSpecValuesType =
InterReqType<addMallGoodsType>['goodsSpecList'][0]['goodsSpecValuesList'] & { fileList: any };
type goodsSpecValuesType = Exclude<
InterReqType<addMallGoodsType>,
undefined
>['goodsSpecList'][0]['goodsSpecValuesList'][0] & { fileList: any };
//单位返回类型
type unitType = InterDataType<skuUnitType>;
interface selfProps {
onCancel: () => void;
onOk: (value: goodsSpecType) => void;
onHandleOk: (value: goodsSpecType) => void;
skuUnitList: unitType;
currentSku: goodsSpecType | undefined;
}
......@@ -27,12 +31,49 @@ interface selfProps {
const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
open,
onCancel,
onOk,
onHandleOk,
skuUnitList,
currentSku,
}) => {
const [goodsSpecForm] = Form.useForm<Omit<goodsSpecType, 'goodsSpecValuesList'>>();
const [goodsSpecValuesForm] = Form.useForm<goodsSpecValuesType[0]>();
const [goodsSpecValuesForm] = Form.useForm<goodsSpecValuesType>();
//销售正则价格校验
const salePriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (regPriceNumber(value.toString())) {
return Promise.resolve();
} else {
return Promise.reject(new Error('为整数且最多保留两位小数'));
}
} else {
return Promise.reject(new Error('请输入销售价'));
}
};
//渠道正则价格校验
const channelPriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (regPriceNumber(value.toString())) {
return Promise.resolve();
} else {
return Promise.reject(new Error('为整数且最多保留两位小数'));
}
} else {
return Promise.resolve();
}
};
//库存正则校验
const stockPriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (/^[+]{0,1}(\d+)$/.test(value.toString())) {
return Promise.resolve();
} else {
return Promise.reject(new Error('请输入正整数'));
}
} else {
return Promise.resolve();
}
};
const [tableData, setTableData] = useState<goodsSpecValuesType[]>([
{
......@@ -53,6 +94,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
inputType?: string;
radioOption?: { name: string; id: number }[];
rules?: any;
maxLength?: number;
})[] = [
{
title: '序号',
......@@ -74,6 +116,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
editable: true,
dataIndex: 'specValueName',
rules: [{ required: true, message: '请输入选项名称' }],
maxLength: 30,
},
{
title: '料号',
......@@ -81,25 +124,31 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
editable: true,
dataIndex: 'partNo',
rules: [{ required: true, message: '请输入料号' }],
maxLength: 30,
},
{
title: '销售价',
align: 'center',
editable: true,
dataIndex: 'salePrice',
rules: [{ required: true, message: '请输入销售号' }],
rules: [{ required: true, validator: salePriceValidator }],
inputType: 'number',
},
{
title: '渠道价',
editable: true,
align: 'center',
dataIndex: 'channelPrice',
rules: [{ required: false, validator: channelPriceValidator }],
inputType: 'number',
},
{
title: '库存',
editable: true,
align: 'center',
dataIndex: 'stock',
rules: [{ required: false, validator: stockPriceValidator }],
inputType: 'number',
},
{
title: '是否显示',
......@@ -107,7 +156,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
align: 'center',
dataIndex: 'showPrice',
inputType: 'radio',
width: '20%',
width: '18%',
radioOption: [
{ id: 1, name: '显示' },
{ id: 0, name: '不显示' },
......@@ -237,7 +286,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
//提交
const handleOk = () => {
Promise.all([goodsSpecForm.validateFields(), goodsSpecValuesForm.validateFields()]).then(
(values) => {
(values: any) => {
const goodsSpecValuesList: goodsSpecValuesType[] = tableData.map((v) => {
return Object.getOwnPropertyNames(v).reduce((pre: any, cur: string) => {
if (Object.getOwnPropertyNames(values[1]).includes(cur + v.id)) {
......@@ -247,7 +296,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
return pre;
}, {});
});
onOk({ ...values[0], goodsSpecValuesList, id: currentSku?.id || Math.random() });
onHandleOk({ ...values[0], goodsSpecValuesList, id: currentSku?.id || Math.random() });
clearForm();
},
);
......@@ -294,7 +343,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
<Modal
open={open}
title={currentSku ? '编辑规格' : '添加规格'}
width={1000}
width={1100}
onCancel={handleCancel}
onOk={handleOk}
>
......@@ -309,7 +358,7 @@ const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
name='specName'
rules={[{ required: true, message: '请输入规格名称' }]}
>
<Input placeholder='请输入规格名称' />
<Input placeholder='请输入规格名称' maxLength={30} />
</Form.Item>
<Form.Item label='规格值'>
<Form component={false} form={goodsSpecValuesForm}>
......
......@@ -6,7 +6,7 @@ import { InterDataType, InterReqType } from '~/api/interface';
import { addMallGoodsType, skuUnitType } from '~/api/interface/goodsType';
//商品sku规格类型
type goodsSpecType = InterReqType<addMallGoodsType>['goodsSpecList'][0];
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//单位返回类型
type unitType = InterDataType<skuUnitType>;
interface selfProps {
......
......@@ -5,20 +5,24 @@ import SkuInfo from './components/skuInfo';
import IntroduceInfo from './components/introduceInfo';
import SkuAddOrEditModal from './components/skuAddOrEditModal';
import './index.scss';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { baseInfoType } from './components/baseInfo';
import { InterDataType, InterReqType } from '~/api/interface';
import { addMallGoodsType, skuUnitType } from '~/api/interface/goodsType';
import { addMallGoodsType, mallGoodsDetailsType, skuUnitType } from '~/api/interface/goodsType';
import GoodsAPI from '~/api/modules/goodsAPI';
import { filterObjAttr } from '~/utils';
//商品sku规格类型
type goodsSpecType = InterReqType<addMallGoodsType>['goodsSpecList'][0];
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//单位返回类型
type unitType = InterDataType<skuUnitType>;
//商品详情-返回类型
type goodsDetailType = InterDataType<mallGoodsDetailsType>;
const GoodsAddOrEditOrDetail = () => {
const baseInfoRef = useRef<any>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
//单位列表
const [skuUnitList, setSkuUnitList] = useState<unitType>([]);
......@@ -27,10 +31,14 @@ const GoodsAddOrEditOrDetail = () => {
const [addOrEditSkuModalShow, setAddOrEditSkuModalShow] = useState<boolean>(false);
//当前编辑sku
const [currentSku, setCurrentSku] = useState<goodsSpecType>();
//基本信息暂存
const [baseInfo, setBaseInfo] = useState<baseInfoType>();
//skuTable数据
const [skuTable, setSkuTable] = useState<goodsSpecType[]>([]);
//商品介绍详情
const [goodsDetails, setGoodsDetails] = useState<string>('');
//商品id
const [goodsId, setGoodsId] = useState<number>(0);
//商品详情
const [goodsDetailsInfo, setGoodsDetailsInfo] = useState<goodsDetailType>();
//新增、编辑sku弹窗显示
const addOrEditSkuClick = (record?: goodsSpecType) => {
......@@ -56,6 +64,10 @@ const GoodsAddOrEditOrDetail = () => {
skuTable.splice(skuIndex, 1);
setSkuTable([...skuTable]);
};
//商品详情获取
const getIntroduceInfo = (richText: string) => {
setGoodsDetails(richText);
};
const tabItems: TabsProps['items'] = [
{
......@@ -79,7 +91,7 @@ const GoodsAddOrEditOrDetail = () => {
{
key: '3',
label: `商品详情`,
children: <IntroduceInfo />,
children: <IntroduceInfo onChange={getIntroduceInfo} introduceInfo={goodsDetails} />,
},
];
......@@ -94,8 +106,7 @@ const GoodsAddOrEditOrDetail = () => {
baseInfoRef.current
.getForm()
.validateFields()
.then((value: baseInfoType) => {
setBaseInfo(value);
.then(() => {
setTabSelectKeys((Number(tabSelectKeys) + 1).toString());
})
.catch((error: any) => {
......@@ -129,8 +140,95 @@ const GoodsAddOrEditOrDetail = () => {
});
};
//商品保存
const saveGoods = () => {
baseInfoRef.current
.getForm()
.validateFields()
.then((values: baseInfoType) => {
if (skuTable.length) {
const skuList = skuTable.map((v) => ({
...v,
id: goodsDetailsInfo
? goodsDetailsInfo.goodsSpecList.find((i) => i.id === v.id)?.id
: undefined,
goodsSpecValuesList: v.goodsSpecValuesList.map((i) => ({
...i,
id: goodsDetailsInfo
? goodsDetailsInfo.goodsSpecList
.find((j) => j.id === v.id)
?.goodsSpecValuesList.find((k) => k.id === i.id)?.id
: undefined,
})),
}));
const addGoodsEditReq = {
...filterObjAttr(values, ['mainImgList', 'subImgList', 'videoList', 'categoryId']),
resourcesList: [
...values.mainImgList.map((v) => ({ type: 0, url: v.url })),
...(values.subImgList?.map((v) => ({ type: 1, url: v.url })) || []),
...(values.videoList?.map((v) => ({ type: 2, url: v.url })) || []),
],
categoryPrimaryId: values.categoryId[0],
categorySubId: values.categoryId.length === 2 ? values.categoryId[1] : undefined,
goodsDetails,
goodsSpecList: skuList,
id: goodsId || undefined,
};
GoodsAPI[goodsId ? 'editMallGoods' : 'addMallGoods'](addGoodsEditReq).then(({ code }) => {
if (code === '200') {
message.success(goodsId ? '编辑商城成功' : '新增商品成功');
navigate(-1);
}
});
} else {
message.warning('商品规格未添加');
}
})
.catch((error: any) => {
message.error(error.errorFields[0].errors[0]);
});
};
//商品详情
const getMallGoodsDetails = (id: number) => {
GoodsAPI.getMallGoodsDetails({ id }).then(({ result }) => {
const mainImgList = result.resourcesList
.filter((v) => v.type === 0)
.map((v) => ({ id: v.id, name: 'mainImg', uid: v.id, url: v.url }));
const subImgList = result.resourcesList
.filter((v) => v.type === 1)
.map((v) => ({ id: v.id, name: 'subImg', uid: v.id, url: v.url }));
const videoList = result.resourcesList
.filter((v) => v.type === 2)
.map((v) => ({ id: v.id, name: 'video', uid: v.id, url: v.url }));
setGoodsDetailsInfo(result);
setSkuTable(result.goodsSpecList);
setGoodsDetails(result.goodsDetails || '');
baseInfoRef.current.getForm().setFieldsValue({
tradeName: result.tradeName,
description: result.description || undefined,
mainImgList,
subImgList: subImgList.length ? subImgList : undefined,
videoList: videoList.length ? videoList : undefined,
categoryId: result.categorySubId
? [result.categoryPrimaryId, result.categorySubId]
: [result.categoryPrimaryId],
shelfStatus: result.shelfStatus,
labelShow: result.labelShow,
goodsLabel: result.goodsLabel || undefined,
});
baseInfoRef.current.setLabelShow(!!result.labelShow);
baseInfoRef.current.mediaData.setMainFileList(mainImgList);
baseInfoRef.current.mediaData.setSubFileList(subImgList);
baseInfoRef.current.mediaData.setVideoFileList(videoList);
});
};
useEffect(() => {
getSkuUnit();
if (searchParams.get('id')) {
setGoodsId(Number(searchParams.get('id')));
getMallGoodsDetails(Number(searchParams.get('id')));
}
}, []);
return (
......@@ -143,7 +241,9 @@ const GoodsAddOrEditOrDetail = () => {
下一步
</Button>
) : (
<Button type='primary'>保存</Button>
<Button type='primary' onClick={saveGoods}>
保存
</Button>
)}
</div>
<div className='back-btn'>
......@@ -155,7 +255,7 @@ const GoodsAddOrEditOrDetail = () => {
<SkuAddOrEditModal
open={addOrEditSkuModalShow}
onCancel={addOrEditSkuModalCancel}
onOk={addOrEditSkuModalOk}
onHandleOk={addOrEditSkuModalOk}
skuUnitList={skuUnitList}
currentSku={currentSku}
/>
......
const GoodsDetails = () => {
return <div className='goods-detail'></div>;
};
export default GoodsDetails;
......@@ -2,24 +2,22 @@ import SearchBox, { searchColumns } from '~/components/search-box';
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Button, Card, Image, message, Modal, Table } from 'antd';
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons';
import { ArrowDownOutlined, ArrowUpOutlined, PlusOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/es/table';
import GoodsAPI from '~/api/modules/goodsAPI';
import { InterDataType, InterReqType, PaginationProps } from '~/api/interface';
import { listGoodsType } from '~/api/interface/goodsType';
import { CategoryManageAPI } from '~/api';
import { listPageGoodsInfoType } from '~/api/interface/goodsType';
import { filterObjAttr } from '~/utils';
import qs from 'query-string';
import { CategoryManageAPI } from '~/api';
import { categoryListRespType } from '~/api/interface/categoryManage';
//商品返回类型
type goodsType = InterDataType<listGoodsType>['list'];
type goodsType = InterDataType<listPageGoodsInfoType>['list'];
//商品列表筛选类型
type goodsSearchParameters = Omit<InterReqType<listGoodsType>, 'goodsType'>;
type goodsSearchParameters = InterReqType<listPageGoodsInfoType>;
//分类返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
const GoodsList = () => {
//筛选ref
......@@ -41,6 +39,8 @@ const GoodsList = () => {
},
];
const [activeTabKey, setActiveTabKey] = useState<string>('1');
//分类列表
const [categoryList, setCategoryList] = useState<categoryType>([]);
const [searchColumns, setSearchColumns] = useState<searchColumns[]>([
{
label: '商品名称',
......@@ -49,18 +49,12 @@ const GoodsList = () => {
type: 'input',
},
{
label: '所属目录',
placeholder: '请选择所属目录',
name: 'directoryId',
label: '商品类型',
placeholder: '请选择商品类别',
name: 'categoryPrimaryId',
type: 'select',
options: [],
},
{
label: '创建时间',
placeholder: '请输入选择创建时间',
name: 'time',
type: 'rangePicker',
},
]);
const tableColumns: ColumnsType<goodsType[0]> = [
{
......@@ -72,23 +66,29 @@ const GoodsList = () => {
{
title: '图片',
align: 'center',
dataIndex: 'imgUrl',
render: (text: string) => <Image src={text} width={50} height={50} />,
dataIndex: 'resourcesList',
render: (text: goodsType[0]['resourcesList']) => (
<Image src={text.find((v) => v.type === 0)?.url || ''} width={50} height={50} />
),
},
{ title: '商品名称', align: 'center', dataIndex: 'tradeName' },
{
title: '商品类别',
align: 'center',
render: (_text: string, record: goodsType[0]) => getCategoryStr(record),
},
{ title: '商品名称', align: 'center', dataIndex: 'goodsName' },
{ title: '所属目录', align: 'center', dataIndex: 'directoryName' },
{ title: '创建时间', align: 'center', dataIndex: 'createTime' },
{
title: '状态',
align: 'center',
dataIndex: 'status',
dataIndex: 'shelfStatus',
render: (text: number) => (text ? '上架' : '下架'),
},
{
title: '操作',
align: 'center',
dataIndex: 'id',
render: (id: number) => (
render: (id: number, record: goodsType[0]) => (
<>
<Button type='link' onClick={() => toEditGoods(id)}>
编辑
......@@ -96,6 +96,15 @@ const GoodsList = () => {
<Button type='link' onClick={() => toGoodsDetail(id)}>
详情
</Button>
<Button type='link' onClick={() => upOrDownShelf(1, id)} disabled={!!record.shelfStatus}>
上架
</Button>
<Button type='link' onClick={() => upOrDownShelf(0, id)} disabled={!record.shelfStatus}>
下架
</Button>
<Button type='link' danger onClick={() => removeMallGoods(id)}>
删除
</Button>
</>
),
},
......@@ -113,20 +122,20 @@ const GoodsList = () => {
totalPage: 1,
});
//筛选
const [query, setQuery] = useState<goodsSearchParameters>({ status: undefined });
const [query, setQuery] = useState<goodsSearchParameters>({ shelfStatus: undefined });
// 表格多选
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const onTabChange = (key: string) => {
pagination.pageNo = 1;
pagination.pageSize = 10;
query.status = key === '1' ? undefined : key === '2' ? 1 : 0;
query.shelfStatus = key === '1' ? undefined : key === '2' ? 1 : 0;
setSearchParams(
qs.stringify({
pageNo: 1,
pageSize: 10,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
getGoodsList(query);
......@@ -142,7 +151,7 @@ const GoodsList = () => {
pageNo: pageNo,
pageSize: pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
getGoodsList(query);
......@@ -152,28 +161,24 @@ const GoodsList = () => {
const searchSuccess = (data: any) => {
pagination.pageNo = 1;
pagination.pageSize = 10;
setQuery({ ...filterObjAttr(data, ['time']), status: query.status });
getGoodsList({ ...filterObjAttr(data, ['time']), status: query.status });
getGoodsList(
{ ...filterObjAttr(data, ['time']), status: query.status, pageSize: 9999, pageNo: 1 },
true,
);
setQuery({ ...data, shelfStatus: query.shelfStatus });
getGoodsList({ ...data, shelfStatus: query.shelfStatus });
getGoodsList({ ...data, shelfStatus: query.shelfStatus, pageSize: 9999, pageNo: 1 }, true);
setSearchParams(
qs.stringify({
pageNo: 1,
pageSize: 10,
...filterObjAttr(data, ['time']),
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
};
//商品列表
const getGoodsList = (query?: goodsSearchParameters, isAll?: boolean) => {
setLoading(true);
GoodsAPI.getGoodsList({
GoodsAPI.getListPageGoodsInfo({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
goodsType: 0,
...query,
}).then(({ result }) => {
setLoading(false);
......@@ -200,50 +205,38 @@ const GoodsList = () => {
};
//商品详情
const toGoodsDetail = (id: number) => {
navigate({
pathname: '/mallManage/mallGoods/detail',
search: `id=${id}&isDetail=1`,
});
// navigate({
// pathname: '/mallManage/mallGoods/detail',
// search: `id=${id}&isDetail=1`,
// });
};
// 表格多选事件
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
//获取目录列表
const getDirectoryList = () => {
CategoryManageAPI.getDirectoryListClone({ type: 4 }).then(({ result }) => {
if (result) {
const options = result.map((v) => ({ id: v.id, name: v.directoryName }));
searchColumns[1].options = options;
setSearchColumns([...searchColumns]);
}
});
};
//商品-批量上下架
const batchOnShelfOrTakeDown = (status: number) => {
if (selectedRowKeys.length === 0) {
return message.warning('请先选择商品');
}
GoodsAPI.batchOnShelfOrTakeDown({ goodsIds: selectedRowKeys as number[], status }).then(
({ code }) => {
if (code === '200') {
message.success(status ? '上架成功' : '下架成功');
getGoodsList(query);
setSelectedRowKeys([]);
}
//商品-单个上下架
const upOrDownShelf = (status: number, id: number) => {
Modal.confirm({
title: '提示',
content: `确认${status ? '上架' : '下架'}该商品?`,
onOk: () => {
GoodsAPI.upOrDownShelf({ id, status }).then(({ code }) => {
if (code === '200') {
message.success(status ? '上架成功' : '下架成功');
getGoodsList(query);
setSelectedRowKeys([]);
}
});
},
);
});
};
//商品-删除
const deleteGoods = () => {
if (selectedRowKeys.length === 0) {
return message.warning('请先选择商品');
}
const removeMallGoods = (id: number) => {
Modal.confirm({
title: '提示',
content: '删除后数据将会丢失,确定删除吗?',
onOk() {
GoodsAPI.batchRemoveWareInfo(selectedRowKeys as number[]).then(({ code }) => {
GoodsAPI.removeMallGoods({ id }).then(({ code }) => {
if (code === '200') {
if (pagination.pageNo !== 1 && tableData.length == 1) {
pagination.pageNo -= 1;
......@@ -276,7 +269,7 @@ const GoodsList = () => {
: tableData
.filter((_v, i) => index - 1 === i || index === i)
.map((v) => ({ id: v.id }));
GoodsAPI.exchangeGoodsInfo({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
GoodsAPI.exchange({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
({ code }) => {
if (code === '200') {
message.success('上移成功');
......@@ -287,7 +280,7 @@ const GoodsList = () => {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
}
......@@ -319,7 +312,7 @@ const GoodsList = () => {
: tableData
.filter((_v, i) => index + 1 === i || index === i)
.map((v) => ({ id: v.id }));
GoodsAPI.exchangeGoodsInfo({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
GoodsAPI.exchange({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
({ code }) => {
if (code === '200') {
message.success('下移成功');
......@@ -330,7 +323,7 @@ const GoodsList = () => {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
}
......@@ -342,41 +335,55 @@ const GoodsList = () => {
}
}
};
//分类列表
const getCategoryList = () => {
CategoryManageAPI.getCategoryRespList({ pageNo: 1, pageSize: 99999 }).then(({ result }) => {
searchColumns[1].options = (result.list || []).map((v) => ({ id: v.id, name: v.name }));
setSearchColumns([...searchColumns]);
setCategoryList(result.list || []);
});
};
//商品列表,分类组合
const getCategoryStr = (record: goodsType[0]) => {
return categoryList
.reduce((pre: string[], cur) => {
if (cur.id === record.categoryPrimaryId) {
pre.push(cur.name);
if (record.categorySubId) {
pre.push(cur.subDTOList.find((v) => v.id === record.categorySubId)?.name || '');
}
}
return pre;
}, [])
.join('/');
};
useEffect(() => {
getDirectoryList();
getCategoryList();
pagination.pageNo = Number(searchParams.get('pageNo') || 1);
pagination.pageSize = Number(searchParams.get('pageSize') || 10);
const queryObj = {
goodsName: searchParams.get('goodsName') || undefined,
directoryId: searchParams.get('directoryId')
? Number(searchParams.get('directoryId'))
: undefined,
startTime: searchParams.get('startTime') || undefined,
endTime: searchParams.get('endTime') || undefined,
status:
searchParams.get('status') === 'all' || searchParams.get('status') === null
tradeName: searchParams.get('goodsName') || undefined,
shelfStatus:
searchParams.get('shelfStatus') === 'all' || searchParams.get('shelfStatus') === null
? undefined
: Number(searchParams.get('status')),
: Number(searchParams.get('shelfStatus')),
};
getGoodsList(queryObj);
getGoodsList({ ...queryObj, pageNo: 1, pageSize: 9999 }, true);
setActiveTabKey(
searchParams.get('status') === 'all' || searchParams.get('status') === null
searchParams.get('shelfStatus') === 'all' || searchParams.get('shelfStatus') === null
? '1'
: Number(searchParams.get('status')) === 1
: Number(searchParams.get('shelfStatus')) === 1
? '2'
: '3',
);
(searchRef.current as any).getForm().setFieldsValue({
goodsName: searchParams.get('goodsName') || undefined,
directoryId: searchParams.get('directoryId')
? Number(searchParams.get('directoryId'))
: undefined,
time: searchParams.get('startTime')
? [searchParams.get('startTime'), searchParams.get('endTime')]
: undefined,
tradeName: searchParams.get('goodsName') || undefined,
});
}, []);
return (
<div className='goods-list'>
<SearchBox
......@@ -407,30 +414,6 @@ const GoodsList = () => {
>
下移
</Button>
{activeTabKey !== '2' && (
<Button
type='primary'
style={{ marginRight: '10px' }}
icon={<ArrowUpOutlined />}
onClick={() => batchOnShelfOrTakeDown(1)}
>
上架
</Button>
)}
{activeTabKey !== '3' && (
<Button
type='primary'
style={{ marginRight: '10px' }}
icon={<ArrowDownOutlined />}
onClick={() => batchOnShelfOrTakeDown(0)}
>
下架
</Button>
)}
<Button danger icon={<DeleteOutlined />} onClick={deleteGoods}>
删除
</Button>
</div>
<Table
columns={tableColumns}
......@@ -440,6 +423,8 @@ const GoodsList = () => {
rowSelection={{
selectedRowKeys,
onChange: onSelectChange,
type: 'radio',
hideSelectAll: true,
}}
loading={loading}
pagination={{
......
import _ from 'lodash';
// 不能输入数字,其他可以输入
export const exceptNumber = (val: any) => {
val.target.value = val.target.value.replace(/1?(\d|([1-9]\d+))(.\d+)?$/g, '').replace(/\s/g, '');
......@@ -105,3 +107,13 @@ export const maxLength6 = (val: any) => {
val.target.value = val.target.value.replace(/\D/g, '');
}
};
//通用价格,且保留两位小数正则
export const regPriceNumber = (value: string): boolean => {
const reg = /^\d+(\.\d{1,2})?$/;
return reg.test(value);
};
//判断空值
export const isEmptyBol = (value: any): boolean => {
return _.isNull(value) || _.isNaN(value) || _.isNull(value) || _.isUndefined(value);
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论