提交 2411238c 作者: 龚洪江

联调:商品联调

上级 7075ed8a
...@@ -32,11 +32,11 @@ ...@@ -32,11 +32,11 @@
"query-string": "^8.1.0", "query-string": "^8.1.0",
"react": "^18.1.0", "react": "^18.1.0",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-quill": "^2.0.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-viewer": "^3.2.2", "react-viewer": "^3.2.2",
"sort-by": "^1.2.0", "sort-by": "^1.2.0",
"wangeditor": "^4.7.15",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -48,6 +48,15 @@ export type categoryListType = InterItemFunction< ...@@ -48,6 +48,15 @@ export type categoryListType = InterItemFunction<
{ directoryId?: number; type?: number }, { directoryId?: number; type?: number },
categoryReposeType[] categoryReposeType[]
>; >;
//目录列表类型-分页
export type directoryPageListType = InterItemFunction<
{ type?: number },
{
id: number;
directoryName: string;
type: number;
}[]
>;
//目录列表类型-不分页 //目录列表类型-不分页
export type directoryListType = InterFunction< export type directoryListType = InterFunction<
{ type: number }, { type: number },
......
...@@ -14,4 +14,4 @@ export type BackEndLoginType = InterFunction< ...@@ -14,4 +14,4 @@ export type BackEndLoginType = InterFunction<
} }
>; >;
// 上传图片 // 上传图片
export type uploadOssType = InterFunction<any, NonNullable<unknown>>; export type uploadOssType = InterFunction<any, { filePath: string }>;
import { UploadFile } from 'antd/es/upload/interface';
// 商城-新增商品
import { InterFunction } from '~/api/interface';
//商品-新增
export type BaseInfoType = {
goodsName: string;
goodsDesc: string;
directoryId: number;
tag: string;
shelfStatus: number;
categoryByOne: number;
categoryByTwo: number;
images: { id: number; imgType: number; imgUrl: string }[];
goodsVideo: string;
};
// 库存规格
export interface specEntity {
id: number;
categoryId: number | string;
goodsSpecName: string;
skuId: number;
specIds: any;
chooseType: number;
must: number;
skuUnitId: number;
customizeInfo?: customizeEntity[];
productSpecList?: customizeEntity[];
industrySpecList?: any;
isCustomProd: boolean;
skuName?: string;
flag: number;
productName?: string;
}
// 自定义规格
export interface customizeEntity {
id: number;
partNo: string;
specName: string;
specImage?: string;
fileList?: UploadFile[];
productSpecCPQVO?: any;
productSpec?: number;
}
export type addGoodsType = InterFunction<
BaseInfoType & {
productSpec: specEntity[];
goodsDetailVO: { goodsDesc: string; productDesc?: string };
otherService: number[];
},
any
>;
//商品-编辑
export type editGoodsType = InterFunction<
BaseInfoType & {
productSpec: specEntity[];
goodsDetailVO: { goodsDesc: string; productDesc?: string };
otherService: number[];
id: number;
},
any
>;
//商品-详情
export type detailGoodsType = InterFunction<
{ goodsInfoId: number },
BaseInfoType & {
goodsSpec: specEntity[];
goodsDetail: { goodsDesc: string; productDesc: string };
otherService: number[];
goodsVideoId: number;
id: number;
}
>;
//商品-其它服务列表
export type otherServiceType = InterFunction<any, { id: number; saleServiceName: string }[]>;
//商品-规格单位
export type skuUnitType = InterFunction<any, { id: number; unitName: string }[]>;
import axios from '../request'; import axios from '../request';
import { categoryListType, directoryListType, GoodsInfo } from '~/api/interface/categoryManage'; import {
categoryListType,
directoryListType,
directoryPageListType,
GoodsInfo,
} from '~/api/interface/categoryManage';
export class CategoryManageAPI { export class CategoryManageAPI {
// 分类目录 // 分类目录
...@@ -9,17 +14,15 @@ export class CategoryManageAPI { ...@@ -9,17 +14,15 @@ export class CategoryManageAPI {
}); });
}; };
// 分类目录(类型) // 分类目录(类型)
static getDirectoryListClone: directoryListType = (params) => { static directoryListClone: directoryPageListType = (params) => {
return axios.get('/pms/classify/getDirectoryList', { params }); return axios.get('pms/classify/directoryList', {
};
//目录列表不含分页
static getDirectoryList = (params: { type: number }) => {
return axios.get('pms/classify/getDirectoryList', {
params, params,
}); });
}; };
// 分类目录-不分页(类型)
static getDirectoryListClone: directoryListType = (params) => {
return axios.get('/pms/classify/getDirectoryList', { params });
};
// 新增或编辑目录 // 新增或编辑目录
static addOrEditDirectory = ( static addOrEditDirectory = (
data: { id?: number; directoryName: string; type: number; relevance: number }[], data: { id?: number; directoryName: string; type: number; relevance: number }[],
......
import {
addGoodsType,
detailGoodsType,
editGoodsType,
otherServiceType,
skuUnitType,
} from '~/api/interface/goodsType';
import axios from '../request';
class GoodsAPI {
//商品-新增
static addGoods: addGoodsType = (data) => {
return axios.post('/pms/goods/addGoodsInfo', data);
};
//商品-编辑
static editGoods: editGoodsType = (data) => {
return axios.post('/pms/goods/editGoodsInfo', data);
};
//商品-详情
static getGoodsDetail: detailGoodsType = (params) => {
return axios.get('/pms/goods/getGoodsInfoDetail', { params });
};
// 商品-单位
static getSkuUnit: skuUnitType = () => {
return axios.get('/pms/goods/getSkuUnit');
};
// 商品-其它服务列表
static getOtherServiceList: otherServiceType = () => {
return axios.get('/pms/goods/listOtherService');
};
}
export default GoodsAPI;
...@@ -14,7 +14,6 @@ import { ...@@ -14,7 +14,6 @@ import {
ProductSpecListType, ProductSpecListType,
productSpecPriceType, productSpecPriceType,
} from '~/api/interface/produceManageType'; } from '~/api/interface/produceManageType';
import dayjs from 'dayjs';
export class ProduceManageAPI { export class ProduceManageAPI {
// 产品管理-分页列表 // 产品管理-分页列表
......
import { Form, InputNumber, Input } from 'antd';
import React from 'react';
// 表格可编辑单元格
interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
editing: boolean;
dataIndex: string;
title: any;
inputType: 'number' | 'text';
record: any;
index: number;
children: React.ReactNode;
}
const EditableCell: React.FC<EditableCellProps> = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode =
inputType === 'number' ? <InputNumber /> : <Input placeholder={`请输入${title}`} />;
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex + record.id}
style={{ margin: 0 }}
rules={[
{
required: true,
message: `请输入${title}`,
},
]}
>
{inputNode}
</Form.Item>
) : (
children
)}
</td>
);
};
export default EditableCell;
import ReactQuill from 'react-quill'; import React, { useEffect } from 'react';
import 'react-quill/dist/quill.snow.css'; import E from 'wangeditor';
import { FC, useEffect, useRef } from 'react'; import { message } from 'antd';
// import events from '@/events';
import { CommonAPI } from '~/api';
interface selfProps { let editor: any = null;
richTextHeight: number;
interface PropsType {
onChange: (html?: string) => void;
value: string;
// eslint-disable-next-line react/require-default-props
isDetail?: boolean;
} }
const RichText: FC<selfProps> = ({ richTextHeight }) => { const RichText: React.FC<PropsType> = ({ onChange, value, isDetail }) => {
const quillRef = useRef<any>(); useEffect(() => {
const modules = { // 注:class写法需要在componentDidMount 创建编辑器
toolbar: { editor = new E('.edit');
container: [ editor.config.uploadImgShowBase64 = false;
[{ size: ['small', false, 'large', 'huge'] }], //字体设置 editor.config.zIndex = 1;
// [{ 'header': [1, 2, 3, 4, 5, 6, false] }], //标题字号,不能设置单个字大小 editor.config.height = 550;
['bold', 'italic', 'underline', 'strike'], editor.config.uploadImgMaxLength = 5;
[{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }], editor.config.uploadImgMaxSize = 1024 * 1024 * 3; // 2M
['link', 'image'], // a链接和图片的显示 editor.config.customUploadImg = async (resultFiles: any, insertImgFn: any) => {
[{ align: [] }], // resultFiles 是 input 中选中的文件列表
[ // insertImgFn 是获取图片 url 后,插入到编辑器的方法
{ const formData = new FormData();
background: [ resultFiles.map(async (item: any) => {
'rgb( 0, 0, 0)', formData.append('uploadFile', item);
'rgb(230, 0, 0)', });
'rgb(255, 153, 0)', const { code, result } = await CommonAPI.uploadOss(formData);
'rgb(255, 255, 0)', if (code === '200') {
'rgb( 0, 138, 0)', insertImgFn(result.filePath);
'rgb( 0, 102, 204)', message.success('上传成功');
'rgb(153, 51, 255)', } else {
'rgb(255, 255, 255)', message.error('上传失败');
'rgb(250, 204, 204)', }
'rgb(255, 235, 204)', };
'rgb(255, 255, 204)', editor.config.onchange = (newHtml: string) => {
'rgb(204, 232, 204)', if (newHtml) {
'rgb(204, 224, 245)', onChange(newHtml);
'rgb(235, 214, 255)', } else {
'rgb(187, 187, 187)', onChange(undefined);
'rgb(240, 102, 102)', }
'rgb(255, 194, 102)', };
'rgb(255, 255, 102)',
'rgb(102, 185, 102)', // 需要展示的菜单
'rgb(102, 163, 224)', editor.config.menus = [
'rgb(194, 133, 255)', 'head',
'rgb(136, 136, 136)', 'bold',
'rgb(161, 0, 0)', 'fontSize',
'rgb(178, 107, 0)', 'fontName',
'rgb(178, 178, 0)', 'italic',
'rgb( 0, 97, 0)', 'underline',
'rgb( 0, 71, 178)', 'strikeThrough',
'rgb(107, 36, 178)', 'indent',
'rgb( 68, 68, 68)', 'lineHeight',
'rgb( 92, 0, 0)', 'foreColor',
'rgb(102, 61, 0)', 'backColor',
'rgb(102, 102, 0)', 'link',
'rgb( 0, 55, 0)', 'list',
'rgb( 0, 41, 102)', 'todo',
'rgb( 61, 20, 10)', 'justify',
], 'quote',
}, 'table',
], 'splitLine',
[ 'undo',
{ 'redo',
color: [ 'image',
'rgb( 0, 0, 0)', ];
'rgb(230, 0, 0)',
'rgb(255, 153, 0)', /** 一定要创建 */
'rgb(255, 255, 0)', editor.create();
'rgb( 0, 138, 0)', // events.addListener('clearEdit', () => {
'rgb( 0, 102, 204)', // editor.txt.html('');
'rgb(153, 51, 255)', // });
'rgb(255, 255, 255)', return () => {
'rgb(250, 204, 204)', // 组件销毁时销毁编辑器 注:class写法需要在componentWillUnmount中调用
'rgb(255, 235, 204)', editor.destroy();
'rgb(255, 255, 204)', };
'rgb(204, 232, 204)',
'rgb(204, 224, 245)', // 这里一定要加上下面的这个注释
'rgb(235, 214, 255)', // eslint-disable-next-line react-hooks/exhaustive-deps
'rgb(187, 187, 187)', }, []);
'rgb(240, 102, 102)',
'rgb(255, 194, 102)',
'rgb(255, 255, 102)',
'rgb(102, 185, 102)',
'rgb(102, 163, 224)',
'rgb(194, 133, 255)',
'rgb(136, 136, 136)',
'rgb(161, 0, 0)',
'rgb(178, 107, 0)',
'rgb(178, 178, 0)',
'rgb( 0, 97, 0)',
'rgb( 0, 71, 178)',
'rgb(107, 36, 178)',
'rgb( 68, 68, 68)',
'rgb( 92, 0, 0)',
'rgb(102, 61, 0)',
'rgb(102, 102, 0)',
'rgb( 0, 55, 0)',
'rgb( 0, 41, 102)',
'rgb( 61, 20, 10)',
],
},
],
['clean'], //清空
['emoji'], //emoji表情,设置了才能显示
['video2'], //我自定义的视频图标,和插件提供的不一样,所以设置为video2
],
// handlers: {
// image: this.imageHandler.bind(this), //点击图片标志会调用的方法
// video2: this.showVideoModal.bind(this),
// },
},
// ImageExtend: {
// loading: true,
// name: 'img',
// action: RES_URL + "connector?isRelativePath=true",
// response: res => FILE_URL + res.info.url
// },
// ImageDrop: true,
// 'emoji-toolbar': true, //是否展示出来
// 'emoji-textarea': false, //我不需要emoji展示在文本框所以设置为false
// 'emoji-shortname': true,
};
useEffect(() => { useEffect(() => {
quillRef.current.editor.container.style.height = richTextHeight + 'px'; if (editor) {
}); editor.txt.html(value);
return <ReactQuill modules={modules} ref={quillRef} />; }
if (isDetail) {
editor.disable();
}
}, [value]);
return <div className='edit' />;
}; };
export default RichText; export default RichText;
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { message, Upload, UploadProps } from 'antd'; import { message, Upload, UploadProps } from 'antd';
// import { UploadFile } from "antd/es/upload/interface";
import { CommonAPI } from '~/api'; import { CommonAPI } from '~/api';
import './index.scss'; import './index.scss';
...@@ -26,7 +25,17 @@ export const Uploader: React.FC<PropsType> = (props) => { ...@@ -26,7 +25,17 @@ export const Uploader: React.FC<PropsType> = (props) => {
listType: 'text', listType: 'text',
fileSize: 2, fileSize: 2,
fileLength: 1, fileLength: 1,
fileType: ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'], fileType: [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'image/bmp',
'video/mp4',
'video/avi',
'video/mov',
'video/mkv',
],
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: () => {}, onChange: () => {},
defaultFileList: [], defaultFileList: [],
......
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Select, Radio, Button, Table, message, ModalProps } from 'antd';
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
import type { RadioChangeEvent } from 'antd';
import EditableCell from '~/components/EditableCell';
import { InterDataType } from '~/api/interface';
import { categoryListType } from '~/api/interface/categoryManage';
import { ProduceManageAPI } from '~/api';
import {
cooperationTagType,
productListType,
ProductSpecListType,
} from '~/api/interface/produceManageType';
import { customizeEntity, skuUnitType, specEntity } from '~/api/interface/goodsType';
import { Uploader } from '~/components/uploader';
import ConfigurePriceModal from '../configurePriceModal';
//分类返回类型
type categoryType = InterDataType<categoryListType>['list'];
//产品返回类型
type productType = InterDataType<productListType>['list'];
//产品-规格返回类型
type productSpecType = InterDataType<ProductSpecListType>['list'];
//产品-规格单位返回类型
type unitType = InterDataType<skuUnitType>;
//加盟标签返回类型
type cooperationTagResponseType = InterDataType<cooperationTagType>;
interface selfProps {
handleCancel: () => void;
handleOk: (data: specEntity) => void;
currentDesc: number;
categoryList: categoryType;
curtRowData: Partial<specEntity>;
skuUnitList: unitType;
}
// interface tableRowEntity {
// goodsTypeId: number;
// prodOrScheme: any;
// selectSource: any;
// skuUnit: any;
// }
const AddOrEditSkuModal: React.FC<ModalProps & selfProps> = ({
open,
handleCancel,
handleOk,
currentDesc,
categoryList,
curtRowData,
skuUnitList,
}) => {
// 弹窗标题
const [dialogTitle, setDialogTitle] = useState<string>('添加');
// 规格表单
const [skuForm] = Form.useForm<any>();
// 自定义产品表单
const [selfProduct] = Form.useForm<any>();
// 是否自定义产品类型
const [isCustomProd, setIsCustomProd] = useState<boolean>(false);
//产品列表
const [productList, setProductList] = useState<productType>([]);
//产品规格列表
const [productSpecList, setProductSpecList] = useState<productSpecType>([]);
// 配置价格弹窗
const [configurePriceModalShow, setConfigurePriceModalShow] = useState<boolean>(false);
const [customRowData, setCustomRowData] = useState<Partial<customizeEntity>>({
id: -1,
productSpecCPQVO: {},
});
const [selfProductData, setSelfProductData] = useState<customizeEntity[]>([
{
id: 1,
specName: '',
partNo: '',
specImage: '',
fileList: [],
productSpecCPQVO: {},
},
]);
// 等级标签列表
const [tagInfoList, setTagInfoList] = useState<cooperationTagResponseType>([]);
// 自定义id数组
const [selfIds, setSelfIds] = useState<number[]>([]);
// 自定义选项表格
const selfSelectColumns = [
{
title: '选项名称',
align: 'center',
dataIndex: 'specName',
editable: true,
},
{
title: '料号',
align: 'center',
dataIndex: 'partNo',
editable: true,
},
{
title: '操作',
align: 'center',
width: '220px',
ellipsis: true,
render: (_text: string, record: any, i: number) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Uploader
fileUpload
listType='picture-card'
onChange={(fileList) => selfUploadSuccess(fileList, record, i)}
defaultFileList={record.fileList}
>
<UploadOutlined />
</Uploader>
<Button size='small' type='link' onClick={() => configurePriceEvent(record)}>
配置价格
</Button>
{i != 0 ? (
<Button
className='delete'
size='small'
type='link'
onClick={() => deleteSelfProduct(record)}
>
删除
</Button>
) : (
''
)}
</div>
);
},
},
];
const mergedColumns = selfSelectColumns.map((col: any) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any) => ({
record,
inputType: col.dataIndex === 'age' ? 'number' : 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: true,
}),
};
});
// // 根据产品类型获取产品列表
const handleProdTypeSelect = (id: number) => {
getProductList(id);
// skuForm.setFieldsValue({
// skuId: undefined,
// specIds: undefined,
// });
// getProduct(id);
};
//
// // 根据所属行业获取方案列表
// const handleIndeTypeSelect = async (id: number) => {
// getIndustrySku(id);
// skuForm.setFieldsValue({
// skuId: undefined,
// specIds: undefined,
// });
// };
//
// 根据选择产品或行业获取选项来源列表
const handleProdOrInduSelected = (id: number) => {
getProductSpecList(id);
};
// // 选项来源选中
// const handleSourceSelect = (id: number | string) => {
// const values: any[] = skuForm.getFieldValue('specIds');
// const obj = optionSource.find((v: sourceEntity) => v.id === id);
// if (!obj?.priceList) {
// message.warning('所选规格来源未配置好价格,请配置好SKU价格后重新选择!');
// const index: number = values.findIndex((i: number) => i === id);
// values.splice(index, 1);
// }
// skuForm.setFieldValue('specIds', values);
// };
// // 规格来源切换
const skuSourceRadioChange = (e: RadioChangeEvent) => {
setIsCustomProd(e.target.value === 1);
};
// // 清除表单
// const onCancel = () => {
// // 关闭前恢复初始值
// skuForm.resetFields();
// selfProduct.resetFields();
// setSelfProductData([
// {
// id: 1,
// specName: '',
// partNo: '',
// specImage: '',
// fileList: [],
// productSpecCPQVO: {},
// },
// ]);
// setCustomRowData({
// id: -1,
// productSpecCPQVO: {},
// });
// setIsCustomProd(false);
// handleCancel();
// };
//获取产品列表
const getProductList = (categoryId: number) => {
ProduceManageAPI.listPageProductSku({ pageNo: 1, pageSize: 9999, categoryId }).then(
({ result }) => {
setProductList(result.list || []);
},
);
};
//产品-规格
const getProductSpecList = (productSkuId: number) => {
ProduceManageAPI.listPageProductSpec({ pageNo: 1, pageSize: 9999, productSkuId }).then(
({ result }) => {
setProductSpecList(result.list || []);
if (result.list) {
const ids: number[] = result.list.reduce((pre: number[], cur) => {
// return cur.priceList ? [...pre, cur.id] : pre;
return [...pre, cur.id];
}, []);
skuForm.setFieldValue('specIds', ids);
}
},
);
};
// 添加自定义产品项
const addSelfSelectItem = () => {
setSelfProductData([
...selfProductData,
{
id: selfProductData.length + 1,
specName: '',
partNo: '',
specImage: '',
fileList: [],
productSpecCPQVO: {},
},
]);
};
// 删除自定义产品
const deleteSelfProduct = (record: customizeEntity) => {
const cusObj: customizeEntity | undefined =
curtRowData.customizeInfo &&
curtRowData.customizeInfo.find((i: customizeEntity) => i.id === record.id);
if (cusObj) {
setSelfIds([...selfIds, cusObj.id]);
}
const index: number = selfProductData.findIndex((item: any) => item.id === record.id);
selfProductData.splice(index, 1);
setSelfProductData([...selfProductData]);
};
//自定义商品上传成功
const selfUploadSuccess = (
fileList: {
id: number;
name: string;
uid: number;
url: string;
}[],
record: any,
index: number,
) => {
record.fileList = [
{
uid: `${Math.random()}`,
status: 'done',
url: fileList[0].url,
name: fileList[0].name,
},
];
record.specImage = fileList[0].url;
selfProductData.splice(index, 1, record);
setSelfProductData([...selfProductData]);
};
// 配置价格操作
const configurePriceEvent = (record: customizeEntity) => {
getTagNameList();
setConfigurePriceModalShow(true);
setCustomRowData({ ...record });
};
const configurePriceHandleOk = (specPrice: any) => {
setConfigurePriceModalShow(false);
const index: number = selfProductData.findIndex(
(i: customizeEntity) => i.id === customRowData.id,
);
selfProductData[index].productSpecCPQVO = {
productSpecId: selfProductData[index].productSpecCPQVO.productSpecId || undefined,
specPrice,
type: 0,
};
selfProductData.splice(index, 1, selfProductData[index]);
setSelfProductData([...selfProductData]);
};
const configurePriceHandleCancel = () => {
setConfigurePriceModalShow(false);
};
// 获取等级标签
const getTagNameList = () => {
ProduceManageAPI.getCooperationListTag().then(({ result }) => {
setTagInfoList(result || []);
});
};
// 点击确定,触发表单验证
const submitSku = () => {
skuForm
.validateFields()
.then(async (values) => {
if (!isCustomProd) {
let productObj: productType[0] | undefined = undefined;
switch (currentDesc) {
case 2:
// const schemeObj = schemeList.find((i) => i.id === values.skuId);
// values.skuName = schemeObj?.solutionName;
break;
default:
productObj = productList.find((i) => i.id === values.skuId);
values.skuName = productObj?.productName;
}
values.specIds = values.specIds.reduce((pre: any, cur: number | string) => {
const sourceObj = productSpecList.find((i) => i.id === cur);
const specObj: any =
curtRowData?.specIds && curtRowData?.specIds.find((i: any) => i.mallSpecId === cur);
pre.push({
mallSpecId: cur,
specName: sourceObj?.specName,
partNo: sourceObj?.partNo,
id: specObj ? specObj.id : undefined,
});
return pre;
}, []);
handleOk({ ...values, id: curtRowData?.id || undefined });
} else {
const res: any = await Promise.all([validateCustomForm()]);
handleOk({
...values,
customizeInfo: res[0],
id: curtRowData?.id || Math.random(),
skuId: curtRowData?.skuId,
skuName: values.productName,
delProductSpecId: selfIds,
});
}
// onCancel();
})
.catch(() => {});
// 验证自定义表单
const validateCustomForm: any = () => {
return new Promise((resolve, reject) => {
selfProduct
.validateFields()
.then(async (values) => {
for (let i = 0; i < selfProductData.length; i++) {
const v = selfProductData[i];
if (!v.specImage) {
return message.warning(`自定义选项第${i + 1}行未上传图片`);
}
}
for (let i = 0; i < selfProductData.length; i++) {
const v = selfProductData[i];
if (!(Object.keys(v.productSpecCPQVO).length != 0 && v.productSpecCPQVO.specPrice)) {
return message.warning(`自定义选项第${i + 1}行未配置价格`);
}
}
const arr: customizeEntity[] = selfProductData.map((i: customizeEntity) => {
return {
specName: values[`specName${i.id}`],
partNo: values[`partNo${i.id}`],
specImage: i.specImage,
productSpecCPQVO: i.productSpecCPQVO,
fileList: i.fileList,
id: i.id,
productSkuId: curtRowData && curtRowData.skuId,
};
});
resolve([...arr]);
})
.catch((err) => {
message.warning(err.errorFields[0].errors[0]).then();
reject([]);
});
});
};
};
useEffect(() => {
if (Object.keys(curtRowData).length !== 0) {
setDialogTitle('编辑');
const specIds: number[] =
curtRowData.specIds && curtRowData.specIds.map((i: any) => i.mallSpecId);
skuForm.setFieldsValue({
goodsSpecName: curtRowData.goodsSpecName,
chooseType: curtRowData.chooseType,
skuUnitId: curtRowData.skuUnitId,
categoryId: curtRowData.categoryId, // 产品类型或所属行业ID
skuId: curtRowData.skuId, // 产品或方案ID
must: curtRowData.must,
specIds, // 选项来源ID数组
productName: curtRowData.flag === 1 ? curtRowData.skuName : undefined,
flag: curtRowData.flag,
});
setIsCustomProd(curtRowData.flag === 1);
if (curtRowData.flag === 1) {
setSelfProductData([...(curtRowData.customizeInfo as customizeEntity[])]);
const objForm: any = curtRowData.customizeInfo?.reduce((pre: any, cur: customizeEntity) => {
pre[`partNo${cur.id}`] = cur.partNo;
pre[`specName${cur.id}`] = cur.specName;
return pre;
}, {});
selfProduct.setFieldsValue(objForm);
}
} else {
setDialogTitle('添加');
}
}, [curtRowData]);
return (
<div>
<Modal
open={open}
title={`${dialogTitle}规格`}
onOk={() => submitSku()}
onCancel={() => handleCancel()}
width={820}
destroyOnClose
>
<Form
preserve={false}
labelCol={{ span: 5 }}
wrapperCol={{ span: 16 }}
form={skuForm}
initialValues={{ chooseType: 0, skuUnitId: 1, must: 0, flag: 0 }}
>
<Form.Item
label='规格名称'
name='goodsSpecName'
rules={[{ required: true, message: '请输入规格名称' }]}
>
<Input placeholder='请输入规格名称' />
</Form.Item>
{currentDesc != 2 && (
<Form.Item label='规格来源' name='flag'>
<Radio.Group onChange={skuSourceRadioChange}>
<Radio value={0}>获取</Radio>
<Radio value={1}>自定义</Radio>
</Radio.Group>
</Form.Item>
)}
{currentDesc != 2 ? (
<>
<Form.Item
name='categoryId'
label='产品类型'
rules={[{ required: true, message: '请选择产品类型' }]}
>
<Select
placeholder='请选择产品类型'
onSelect={handleProdTypeSelect}
showSearch
filterOption={(input, option: any) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
>
{categoryList.map((item) => (
<Select.Option value={item.id} key={item.id}>
{item.classifyName}
</Select.Option>
))}
</Select>
</Form.Item>
{!isCustomProd ? (
<Form.Item
name='skuId'
label='产品'
rules={[{ required: true, message: '请选择产品' }]}
>
<Select
placeholder='请选择产品'
onSelect={handleProdOrInduSelected}
filterOption={(input, option) =>
(option!.children as unknown as string)
.toLowerCase()
.includes(input.toLowerCase())
}
showSearch
>
{productList.map((item: any) => (
<Select.Option value={item.id} key={item.id}>
{item.productName}
</Select.Option>
))}
</Select>
</Form.Item>
) : (
<Form.Item
name='productName'
label='产品'
rules={[{ required: true, message: '请选择产品' }]}
>
<Input placeholder='请输入产品名称' maxLength={50} />
</Form.Item>
)}
</>
) : (
<>
<Form.Item
label='所属行业'
name='goodsTypeId'
rules={[{ required: true, message: '请选择所属行业' }]}
>
<Select
placeholder='请选择所属行业'
// onSelect={handleIndeTypeSelect}
filterOption={(input, option) =>
(option!.children as unknown as string)
.toLowerCase()
.includes(input.toLowerCase())
}
showSearch
>
{/*{categoryList.map((item: categoryEntity) => (*/}
{/* <Select.Option value={item.goodsMasterTypeId} key={item.goodsMasterTypeId}>*/}
{/* {item.goodsMasterType}*/}
{/* </Select.Option>*/}
{/*))}*/}
</Select>
</Form.Item>
<Form.Item
label='方案'
name='skuId'
rules={[{ required: true, message: '请选择方案' }]}
>
<Select
showSearch
placeholder='请选择方案'
// onSelect={handleProdOrInduSelected}
filterOption={(input, option) =>
(option!.children as unknown as string)
.toLowerCase()
.includes(input.toLowerCase())
}
>
{/*{schemeList.map((item: any) => (*/}
{/* <Select.Option value={item.id} key={item.id}>*/}
{/* {item.solutionName}*/}
{/* </Select.Option>*/}
{/*))}*/}
</Select>
</Form.Item>
</>
)}
{isCustomProd ? (
<Form.Item label='自定义选项'>
<div style={{ marginBottom: '10px' }}>
<Button icon={<PlusOutlined />} type='primary' onClick={addSelfSelectItem} />
</div>
<Form form={selfProduct} component={false}>
<Table
size='small'
rowKey='id'
bordered
columns={mergedColumns}
dataSource={selfProductData}
pagination={false}
components={{
body: {
cell: EditableCell,
},
}}
/>
</Form>
</Form.Item>
) : (
<Form.Item
name='specIds'
label='选项来源'
rules={[{ required: true, message: '请选择选项来源' }]}
>
<Select
placeholder='请选择选项来源'
allowClear
mode='multiple'
// onSelect={handleSourceSelect}
>
{productSpecList.map((item: any) => (
<Select.Option value={item.id} key={item.id} disabled={item.disabled}>
{item.specName}&nbsp;{item.partNo && `(${item.partNo})`}
</Select.Option>
))}
</Select>
</Form.Item>
)}
<Form.Item label='选择方式' name='chooseType'>
<Radio.Group>
<Radio value={0}>单选</Radio>
<Radio value={1}>多选</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label='是否必选' name='must'>
<Radio.Group>
<Radio value={0}>非必选</Radio>
<Radio value={1}>必选</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label='规格单位'
name='skuUnitId'
rules={[{ required: true, message: '请选择规格单位' }]}
>
<Select placeholder='请选择规格单位'>
{skuUnitList.map((item: any) => {
return (
<Select.Option value={item.id} key={item.id}>
{item.unitName}
</Select.Option>
);
})}
</Select>
</Form.Item>
</Form>
</Modal>
{/*配置价格*/}
<ConfigurePriceModal
open={configurePriceModalShow}
handleOk={configurePriceHandleOk}
handleCancel={configurePriceHandleCancel}
customRowData={customRowData}
tagInfoList={tagInfoList}
/>
</div>
);
};
export default AddOrEditSkuModal;
import React, { forwardRef } from 'react'; import React, { forwardRef, useEffect, useState, useImperativeHandle } from 'react';
import { Button, Form, Upload, Input, Radio, Select, Cascader } from 'antd'; import { Button, Form, Input, Radio, Select, Cascader } from 'antd';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { Uploader } from '~/components/uploader'; import { Uploader } from '~/components/uploader';
import './index.scss'; import './index.scss';
import { BaseInfoType, detailGoodsType } from '~/api/interface/goodsType';
import { CategoryManageAPI } from '~/api';
import { InterDataType } from '~/api/interface';
import { categoryListType, directoryPageListType } from '~/api/interface/categoryManage';
const BaseInfo: React.FC<any> = forwardRef(() => { //目录返回类型
return ( type directoryType = InterDataType<directoryPageListType>['list'];
<div className='base-info'> //分类返回类型
<div className='base-info-title'>基本信息</div> type categoryType = InterDataType<categoryListType>['list'];
<div className='base-info-form'> //商品返回类型
<Form labelCol={{ span: 2 }} wrapperCol={{ span: 16 }} initialValues={{ shelfStatus: 1 }}> type goodsDetailType = InterDataType<detailGoodsType>;
<Form.Item
name='mainFileList' interface selfProps {
label='商品主图' getCategoryList: (id: number) => void;
rules={[{ required: true, message: '请上传商品主图' }]} categoryList: categoryType;
> ref: any;
<Uploader listType='picture-card'> goodsDetail: goodsDetailType | undefined;
<div>上传</div> }
</Uploader>
</Form.Item> const BaseInfo: React.FC<selfProps> = forwardRef(
<Form.Item label='商品副图' name='subFileList'> ({ getCategoryList, categoryList, goodsDetail }, ref) => {
<Uploader listType='picture-card'> const [form] = Form.useForm<
<div>上传</div> BaseInfoType & {
</Uploader> mainImg: {
</Form.Item> id: number;
<Form.Item label='商品视频'> name: string;
<Upload> uid: number;
<Button icon={<UploadOutlined />}>上传视频</Button> url: string;
</Upload> };
</Form.Item> subImg: {
<Form.Item id: number;
name='goodsName' name: string;
label='商品名称' uid: number;
rules={[{ required: true, message: '请输入商品名称' }]} url: string;
> }[];
<Input placeholder='请输入商品名称' maxLength={50} style={{ width: '400px' }} /> masterTypeId: number[];
</Form.Item> }
<Form.Item >();
name='goodsDesc' //目录
label='商品描述' const [directoryList, setDirectoryList] = useState<directoryType>([]);
rules={[{ required: true, message: '请输入商品描述' }]} //是否添加标签
> const [isAddTag, setIsAddTag] = useState<boolean>(false);
<Input.TextArea //主图文件列表
placeholder='请输入商品描述' const [mainImgList, setMainImgList] = useState<
maxLength={70} {
style={{ width: '400px' }} id: number;
rows={4} name: string;
showCount uid: number;
/> url: string;
</Form.Item> }[]
<Form.Item >([]);
name='sortTypeId' //副图文件列表
label='所属目录' const [subImgList, setSubImgList] = useState<
rules={[{ required: true, message: '请选择所属目录' }]} {
> id: number;
<Select placeholder='请选择所属目录' style={{ width: '400px' }}> name: string;
<Select.Option>1</Select.Option> uid: number;
</Select> url: string;
</Form.Item> }[]
<Form.Item >([]);
name='masterTypeId' //视频文件列表
label='商品分类' const [videoList, setVideoList] = useState<
rules={[{ required: true, message: '请选择商品分类' }]} {
> id: number;
<Cascader name: string;
style={{ width: '400px' }} uid: number;
fieldNames={{ url: string;
label: 'goodsMasterType', }[]
value: 'goodsMasterTypeId', >([]);
children: 'goodsSlaveTypeDTO',
}} useImperativeHandle(ref, () => ({
placeholder='请选择商品分类' baseInform: form,
allowClear }));
/>
</Form.Item> //获取目录列表
<Form.Item label='商品标签'> const getDirectoryList = () => {
<Radio.Group> CategoryManageAPI.getDirectoryListClone({ type: 4 }).then(({ result }) => {
<Radio value={false}>不加</Radio> setDirectoryList(result || []);
<Radio value></Radio> });
</Radio.Group> };
</Form.Item> const directorySelectChange = (value: number) => {
<Form.Item getCategoryList(value);
name='tag' };
label='标签名称' //商品标签
rules={[{ required: true, message: '请输入标签名称' }]} const radioChangeEvent = (e: any) => {
> setIsAddTag(e.target.value);
<Input placeholder='请输入标签名称' style={{ width: '400px' }} maxLength={5} /> };
</Form.Item> //主图上传
<Form.Item const mainImgUploadSuccess = (
label='商品状态' fileList: {
name='shelfStatus' id: number;
rules={[{ required: true, message: '请选择商品状态' }]} name: string;
uid: number;
url: string;
}[],
) => {
setMainImgList(fileList);
form.setFieldsValue({
mainImg: fileList,
});
};
//副图上传
const subImgUploadSuccess = (
fileList: {
id: number;
name: string;
uid: number;
url: string;
}[],
) => {
setSubImgList(fileList);
form.setFieldsValue({
subImg: fileList,
});
};
//视频上传
const videoUploadSuccess = (
fileList: {
id: number;
name: string;
uid: number;
url: string;
}[],
) => {
setVideoList(fileList);
form.setFieldsValue({
goodsVideo: fileList[0].url,
});
};
useEffect(() => {
getDirectoryList();
}, []);
useEffect(() => {
if (goodsDetail) {
const goodsMainImg = goodsDetail.images
.filter((v) => v.imgType === 0)
.map((v) => ({
id: v.id,
name: 'mainImg',
uid: v.id,
url: v.imgUrl,
}));
setMainImgList(goodsMainImg);
const goodsSubImg = goodsDetail.images
.filter((v) => v.imgType === 1)
.map((v) => ({
id: v.id,
name: 'sunImg',
uid: v.id,
url: v.imgUrl,
}));
setSubImgList(goodsSubImg);
if (goodsDetail.goodsVideo) {
setVideoList([
{
id: goodsDetail.goodsVideoId || Math.random(),
name: 'video',
uid: goodsDetail.goodsVideoId || Math.random(),
url: goodsDetail.goodsVideo,
},
]);
}
form.setFieldsValue({
mainImg: goodsMainImg,
subImg: goodsSubImg.length ? goodsSubImg : undefined,
goodsVideo: goodsDetail.goodsVideo || undefined,
goodsName: goodsDetail.goodsName,
goodsDesc: goodsDetail.goodsDetail.goodsDesc,
masterTypeId: [goodsDetail.categoryByOne, goodsDetail.categoryByTwo],
directoryId: goodsDetail.directoryId,
shelfStatus: goodsDetail.shelfStatus,
tag: goodsDetail.tag || undefined,
});
setIsAddTag(!!goodsDetail.tag);
}
}, [goodsDetail]);
return (
<div className='base-info'>
<div className='base-info-title'>基本信息</div>
<div className='base-info-form'>
<Form
labelCol={{ span: 2 }}
wrapperCol={{ span: 16 }}
initialValues={{ shelfStatus: 1 }}
form={form}
> >
<Select placeholder='请选择商品状态' style={{ width: '400px' }}> <Form.Item
<Select.Option value={1}>上架</Select.Option> name='mainImg'
<Select.Option value={0}>下架</Select.Option> label='商品主图'
</Select> rules={[{ required: true, message: '请上传商品主图' }]}
</Form.Item> >
</Form> <Uploader
listType='picture-card'
fileUpload
onChange={mainImgUploadSuccess}
defaultFileList={mainImgList}
>
<UploadOutlined />
</Uploader>
</Form.Item>
<Form.Item label='商品副图' name='subImg'>
<Uploader
listType='picture-card'
fileUpload
onChange={subImgUploadSuccess}
fileLength={3}
defaultFileList={subImgList}
>
<UploadOutlined />
</Uploader>
</Form.Item>
<Form.Item label='商品视频' name='goodsVideo'>
{videoList.length ? (
<video
src={videoList[0].url}
style={{ width: '200px', height: '200px' }}
controls
/>
) : (
<Uploader
listType='text'
fileUpload
onChange={videoUploadSuccess}
defaultFileList={videoList}
>
<Button icon={<UploadOutlined />}>上传视频</Button>
</Uploader>
)}
</Form.Item>
<Form.Item
name='goodsName'
label='商品名称'
rules={[{ required: true, message: '请输入商品名称' }]}
>
<Input placeholder='请输入商品名称' maxLength={50} style={{ width: '400px' }} />
</Form.Item>
<Form.Item
name='goodsDesc'
label='商品描述'
rules={[{ required: true, message: '请输入商品描述' }]}
>
<Input.TextArea
placeholder='请输入商品描述'
maxLength={70}
style={{ width: '400px' }}
rows={4}
showCount
/>
</Form.Item>
<Form.Item
name='directoryId'
label='所属目录'
rules={[{ required: true, message: '请选择所属目录' }]}
>
<Select
placeholder='请选择所属目录'
style={{ width: '400px' }}
onChange={directorySelectChange}
>
{directoryList.map((v) => (
<Select.Option value={v.id} key={v.id}>
{v.directoryName}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name='masterTypeId'
label='商品分类'
rules={[{ required: true, message: '请选择商品分类' }]}
>
<Cascader
style={{ width: '400px' }}
options={categoryList}
fieldNames={{
label: 'classifyName',
value: 'id',
children: 'children',
}}
placeholder='请选择商品分类'
allowClear
/>
</Form.Item>
<Form.Item label='商品标签'>
<Radio.Group onChange={radioChangeEvent} value={isAddTag}>
<Radio value={false}>不加</Radio>
<Radio value={true}></Radio>
</Radio.Group>
</Form.Item>
{isAddTag && (
<Form.Item
name='tag'
label='标签名称'
rules={[{ required: true, message: '请输入标签名称' }]}
>
<Input placeholder='请输入标签名称' style={{ width: '400px' }} maxLength={5} />
</Form.Item>
)}
<Form.Item
label='商品状态'
name='shelfStatus'
rules={[{ required: true, message: '请选择商品状态' }]}
>
<Select placeholder='请选择商品状态' style={{ width: '400px' }}>
<Select.Option value={1}>上架</Select.Option>
<Select.Option value={0}>下架</Select.Option>
</Select>
</Form.Item>
</Form>
</div>
</div> </div>
</div> );
); },
}); );
export default BaseInfo; export default BaseInfo;
import React, { useEffect, useState } from 'react';
import { Modal, Form, Select, Input, message, Button, ModalProps } from 'antd';
import { InterDataType } from '~/api/interface';
import { cooperationTagType } from '~/api/interface/produceManageType';
import { customizeEntity } from '~/api/interface/goodsType';
//加盟标签返回类型
type cooperationTagResponseType = InterDataType<cooperationTagType>;
interface selfProps {
handleOk: (specPrice: any) => void;
handleCancel: () => void;
customRowData: Partial<customizeEntity>;
tagInfoList: cooperationTagResponseType;
}
const ConfigurePriceModal: React.FC<ModalProps & selfProps> = ({
open,
handleOk,
handleCancel,
customRowData,
tagInfoList,
}) => {
// 选择的列表
const [selectList, setSelectList] = useState<number[]>([]);
// 配置价格Form
const [cfgPriceForm] = Form.useForm<any>();
const deselectEvent = (id: number) => {
const obj: any = {};
obj[id] = undefined;
cfgPriceForm.setFieldsValue(obj);
const numArr: number[] = selectList.filter((i: number) => i !== id);
setSelectList([...numArr]);
};
const levelSelectEvent = (id: number) => {
selectList.push(id);
setSelectList([...selectList]);
};
// 将val转换为label
const transValtoLabel = (id: number) => {
const item = tagInfoList.find((i) => i.id === id);
return item ? item.tagName : id;
};
// 表单验证
const handleSubmit = async () => {
cfgPriceForm
.validateFields()
.then(async (values) => {
const specPrice = Object.keys(values).reduce((pre: any, cur: string) => {
if (Object.keys(customRowData.productSpecCPQVO).length != 0) {
const priceItem: any = customRowData.productSpecCPQVO.specPrice.find(
(i: any) => i.cooperationTag === Number(cur),
);
pre.push({
id: priceItem?.id,
price: values[cur],
cooperationTag: cur,
});
} else {
pre.push({ price: values[cur], cooperationTag: cur });
}
return pre;
}, []);
handleOk([...specPrice]);
})
.catch((err) => {
message.warning(err.errorFields[0].errors[0]).then();
});
};
const handleCancelEvent = () => {
cfgPriceForm.resetFields();
handleCancel();
};
// 价格正则
const priceValidator = (_rule: any, value: any) => {
const regExp = /^[1-9]\d{0,7}(\.\d{1,2})?$|^0(\.\d{1,2})?$/;
const bol: boolean = regExp.test(value);
if (!value) {
return Promise.reject(new Error('请输入定价金额'));
}
if (!bol) {
return Promise.reject(
new Error('金额应为数字,小数最多两位,整数最多八位,不能输入0开头的整数'),
);
}
return Promise.resolve();
};
useEffect(() => {
// 新增规格则清空表单数据
if (Object.keys(customRowData.productSpecCPQVO).length === 0) {
cfgPriceForm.resetFields();
setSelectList([]);
} else {
const ids: number[] = [];
customRowData.productSpecCPQVO.specPrice.map((item: any) => {
cfgPriceForm.setFieldValue(Number(item.tagInfoId), item.price);
if (item.tagInfoId != '0') {
ids.push(Number(item.tagInfoId));
}
setSelectList(ids);
});
}
}, [customRowData]);
return (
<Modal
title='配置价格'
open={open}
onCancel={handleCancelEvent}
zIndex={1009}
footer={[
<Button key={1} type='default' onClick={handleCancelEvent}>
取消
</Button>,
<Button key={2} type='primary' onClick={handleSubmit}>
确认
</Button>,
]}
>
<Form labelCol={{ span: 7 }} wrapperCol={{ span: 14 }} form={cfgPriceForm}>
<Form.Item label='渠道等级'>
<Select
placeholder='请选择渠道等级'
mode='multiple'
filterOption={(input, option) =>
(option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
}
onDeselect={deselectEvent}
onSelect={levelSelectEvent}
value={selectList}
>
{tagInfoList.map((item) => (
<Select.Option key={item.id} value={item.id}>
{item.tagName}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label='市场单价'
name={0}
rules={[{ required: true, validator: priceValidator }]}
>
<Input placeholder='请输入市场单价' maxLength={11} />
</Form.Item>
{selectList.map((item: number) => (
<Form.Item
label={transValtoLabel(item)}
key={item}
name={item}
rules={[{ required: true, validator: priceValidator }]}
>
<Input placeholder='请输入定价金额' maxLength={11} />
</Form.Item>
))}
</Form>
</Modal>
);
};
export default ConfigurePriceModal;
import './index.scss'; import './index.scss';
import RichText from '~/components/richText'; import RichText from '~/components/richText';
const GoodsIntroduce = () => { import { FC } from 'react';
interface selfProps {
getRichText: (html?: string) => void;
}
const GoodsIntroduce: FC<selfProps> = ({ getRichText }) => {
const richTextChange = (html?: string) => {
getRichText(html);
};
return ( return (
<div className='goods-introduce'> <div className='goods-introduce'>
<div className='goods-introduce-title'>产品介绍图</div> <div className='goods-introduce-title'>产品介绍图</div>
<div className='goods-introduce-content'> <div className='goods-introduce-content'>
<RichText richTextHeight={300} /> <RichText value='' onChange={richTextChange} />
</div> </div>
</div> </div>
); );
......
import React, { forwardRef } from 'react'; import React, { useEffect, useState } from 'react';
import { Checkbox, Form, Select, Row, Col } from 'antd'; import { Checkbox, Form } from 'antd';
import './index.scss'; import './index.scss';
import GoodsAPI from '~/api/modules/goodsAPI';
import { detailGoodsType, otherServiceType } from '~/api/interface/goodsType';
import { InterDataType } from '~/api/interface';
//商品返回类型
type goodsDetailType = InterDataType<detailGoodsType>;
const OtherInfo: React.FC<any> = forwardRef(() => { interface selfProps {
otherServiceSelect: (id: number[]) => void;
goodsDetail: goodsDetailType | undefined;
}
//其它服务返回类型
type otherServiceListType = InterDataType<otherServiceType>;
const OtherInfo: React.FC<selfProps> = ({ otherServiceSelect, goodsDetail }) => {
//其它服务
const [otherServiceList, setOtherServiceList] = useState<otherServiceListType>([]);
const otherServiceRadioChange = (e: any) => {
otherServiceSelect(e);
};
//获取其它服务
const getOtherServiceList = () => {
GoodsAPI.getOtherServiceList().then(({ result }) => {
setOtherServiceList(result);
});
};
useEffect(() => {
getOtherServiceList();
});
return ( return (
<div className='other-info'> <div className='other-info'>
<div className='other-info-title'>其它信息</div> <div className='other-info-title'>其它信息</div>
<div className='other-info-form'> <div className='other-info-form'>
<Form> <Form>
<Form.Item label='搭配服务' name='otherService'> <Form.Item label='搭配服务' name='otherService'>
<Checkbox.Group> <Checkbox.Group
<Checkbox>1</Checkbox> onChange={otherServiceRadioChange}
value={goodsDetail?.otherService || []}
>
{otherServiceList.map((item: any, index: number) => (
<Checkbox value={item.id} key={index}>
{item.saleServiceName}
</Checkbox>
))}
</Checkbox.Group> </Checkbox.Group>
</Form.Item> </Form.Item>
<Row>
<Col>
<span>用服务&gt;云享飞:</span>
</Col>
<Col span={8}>
<Form.Item name='shareFlyServiceId'>
<Select
allowClear
placeholder='请选择服务'
showSearch
filterOption={(input, option) =>
(option!.children as unknown as string)
.toLowerCase()
.includes(input.toLowerCase())
}
>
<Select.Option>1</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row>
<Col>
<span>&nbsp;&nbsp;&nbsp;&nbsp;我要租&gt;云仓:</span>
</Col>
<Col span={8}>
<Form.Item name='repoId'>
<Select
allowClear
placeholder='请选择商品'
showSearch
filterOption={(input, option) =>
(option!.children as unknown as string)
.toLowerCase()
.includes(input.toLowerCase())
}
>
<Select.Option>1</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
</Form> </Form>
</div> </div>
</div> </div>
); );
}); };
export default OtherInfo; export default OtherInfo;
...@@ -3,81 +3,141 @@ import { Table, Button } from 'antd'; ...@@ -3,81 +3,141 @@ import { Table, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import './index.scss'; import './index.scss';
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
const columns: ColumnsType<any> = [ import { customizeEntity, skuUnitType, specEntity } from '~/api/interface/goodsType';
{ import { InterDataType } from '~/api/interface';
title: '序号', import { categoryListType } from '~/api/interface/categoryManage';
align: 'center',
render: () => <div>--</div>, //分类返回类型
}, type categoryType = InterDataType<categoryListType>['list'];
{
title: `产品类型`, //产品-规格单位返回类型
align: 'center', type unitType = InterDataType<skuUnitType>;
// dataIndex: "goodsTypeId",
render: () => <div>--</div>, interface selfProps {
}, addOrEditSku: (record?: specEntity) => void;
{ specData: specEntity[];
title: '规格名称', categoryList: categoryType;
align: 'center', skuUnitList: unitType;
dataIndex: 'goodsSpecName', }
},
{ const StockSku: React.FC<selfProps> = ({ addOrEditSku, specData, categoryList, skuUnitList }) => {
title: `方案`, const columns: ColumnsType<specEntity> = [
align: 'center', {
dataIndex: 'skuName', title: '序号',
}, align: 'center',
{ render: (_text: string, _record: specEntity, index: number) => index + 1,
title: '选项来源',
align: 'center',
dataIndex: 'specIds',
render: () => <div>--</div>,
},
{
title: '选择方式',
align: 'center',
dataIndex: 'chooseType',
render: () => <div>--</div>,
},
{
title: '是否必选',
align: 'center',
dataIndex: 'must',
render: () => <div>--</div>,
},
{
title: '规格单位',
align: 'center',
dataIndex: 'skuUnitId',
render: () => {
return <div>--</div>;
}, },
}, {
{ title: `产品类型`,
title: '操作', align: 'center',
align: 'center', dataIndex: 'categoryId',
width: '20%', render: (text: string) => <div>{getGoodsTypeName(text)}</div>,
render: () => { },
return ( {
title: '规格名称',
align: 'center',
dataIndex: 'goodsSpecName',
},
{
title: `方案`,
align: 'center',
dataIndex: 'skuName',
},
{
title: '选项来源',
align: 'center',
dataIndex: 'specIds',
render: (_text: string, record) => (
<div> <div>
<Button type='link' style={{ marginRight: '10px' }}> {record.flag !== 1
编辑 ? record.specIds.map((i: any, j: number) => (
</Button> <div key={j}>
<Button type='link'>删除</Button> {i.specName}
{i.partNo && `(${i.partNo})`}
</div>
))
: record.customizeInfo &&
record.customizeInfo.map((i, index: number) => (
<div key={index}>{getSelfSpecName(i)}</div>
))}
</div> </div>
); ),
},
{
title: '选择方式',
align: 'center',
dataIndex: 'chooseType',
render: (text: number) => <div>{text === 0 ? '单选' : '多选'}</div>,
},
{
title: '是否必选',
align: 'center',
dataIndex: 'must',
render: (text: number) => <div>{text ? '必选' : '非必选'}</div>,
},
{
title: '规格单位',
align: 'center',
dataIndex: 'skuUnitId',
render: (text: number) => {
return <div>{getSkuUnitName(text)}</div>;
},
},
{
title: '操作',
align: 'center',
width: '20%',
render: (_text: any, record) => {
return (
<div>
<Button
type='link'
style={{ marginRight: '10px' }}
onClick={() => addOrEditSkuClick(record)}
>
编辑
</Button>
<Button type='link'>删除</Button>
</div>
);
},
}, },
}, ];
]; //添加、编辑规格操作
const StockSku: React.FC<any> = () => { const addOrEditSkuClick = (record?: specEntity) => {
addOrEditSku(record);
};
// 自定义选项来源名称
const getSelfSpecName = (obj: customizeEntity) => {
return `${obj.specName}(${obj.partNo || ''})`;
};
// 行业或产品名称
const getGoodsTypeName = (id: number | string) => {
const obj = categoryList.find((i) => i.id === id);
return obj?.classifyName;
};
// 单位名称
const getSkuUnitName = (id: number) => {
const unitObj = skuUnitList.find((i) => i.id === id);
return unitObj?.unitName;
};
return ( return (
<div className='stock-sku'> <div className='stock-sku'>
<div className='stock-sku-title'>库存规格</div> <div className='stock-sku-title'>库存规格</div>
<div className='stock-sku-content'> <div className='stock-sku-content'>
<div className='stock-sku-operate'> <div className='stock-sku-operate'>
<Button icon={<PlusOutlined />} type='primary'> <Button icon={<PlusOutlined />} type='primary' onClick={() => addOrEditSkuClick()}>
添加规格 添加规格
</Button> </Button>
</div> </div>
<Table size='small' bordered rowKey='id' pagination={false} columns={columns} /> <Table
size='small'
bordered
dataSource={specData}
rowKey='id'
pagination={false}
columns={columns}
/>
</div> </div>
</div> </div>
); );
......
...@@ -2,23 +2,277 @@ import BaseInfo from './components/baseInfo'; ...@@ -2,23 +2,277 @@ import BaseInfo from './components/baseInfo';
import StockSku from './components/stockSku'; import StockSku from './components/stockSku';
import OtherInfo from './components/otherInfo'; import OtherInfo from './components/otherInfo';
import GoodsIntroduce from './components/goodsIntroduce'; import GoodsIntroduce from './components/goodsIntroduce';
import { Button } from 'antd'; import AddOrEditSkuModal from './components/addOrEditSkuModal';
import { useNavigate } from 'react-router-dom'; import { Button, message } from 'antd';
import { useNavigate, useSearchParams } from 'react-router-dom';
import './index.scss'; import './index.scss';
import { useEffect, useRef, useState } from 'react';
import { CategoryManageAPI } from '~/api';
import { InterDataType } from '~/api/interface';
import { categoryListType } from '~/api/interface/categoryManage';
import {
customizeEntity,
detailGoodsType,
skuUnitType,
specEntity,
} from '~/api/interface/goodsType';
import goodsAPI from '~/api/modules/goodsAPI';
import { filterObjAttr } from '~/utils';
import GoodsAPI from '~/api/modules/goodsAPI';
import { UploadFile } from 'antd/es/upload/interface';
//分类返回类型
type categoryType = InterDataType<categoryListType>['list'];
//产品-规格单位返回类型
type unitType = InterDataType<skuUnitType>;
//商品返回类型
type goodsDetailType = InterDataType<detailGoodsType>;
const GoodsAddOrEditOrDetail = () => { const GoodsAddOrEditOrDetail = () => {
const [searchParams] = useSearchParams();
//基本信息ref
const baseInfoRef = useRef<any>();
const navigate = useNavigate(); const navigate = useNavigate();
//当前目录
const [currentDesc, setCurrentDesc] = useState<number>(-1);
//分类
const [categoryList, setCategoryList] = useState<categoryType>([]);
//添加、编辑库存规格弹窗
const [addOrEditSkuModalShow, setAddOrEditSkuModalShow] = useState(false);
//库存规格数据
const [specData, setSpecData] = useState<specEntity[]>([]);
const [goodsSpecCopy, setGoodsSpecCopy] = useState<specEntity[]>([]);
//产品规格-单位
const [skuUnitList, setSkuUnitList] = useState<unitType>([]);
//其它服务
const [otherService, setOtherService] = useState<number[]>([]);
// 当前操作行数据
const [curtRowData, setCurtRowData] = useState<Partial<specEntity>>({});
//商品详情
const [goodsDetail, setGoodsDetail] = useState<goodsDetailType>();
//产品介绍
const [productIntroduce, setProductIntroduce] = useState<string>('');
//添加、编辑规格
const addOrEditSkuShowEvent = (record?: specEntity) => {
const baseInfoForm = baseInfoRef.current.baseInform;
setCurrentDesc(baseInfoForm.getFieldValue('directoryId') || -1);
if (!baseInfoForm.getFieldValue('directoryId')) {
return message.warning('请先选择目录');
}
if (record) {
setCurtRowData({ ...record });
}
setAddOrEditSkuModalShow(true);
};
const addOrEditSkuModalCancel = () => {
setAddOrEditSkuModalShow(false);
};
const addOrEditSkuModalOk = (data: specEntity) => {
if (Object.keys(curtRowData).length != 0) {
const index: number = specData.findIndex((i) => i.id === data.id);
specData.splice(index, 1, data);
setSpecData([...specData]);
} else {
console.log('执行了--->', curtRowData);
setSpecData([...specData, data]);
}
setAddOrEditSkuModalShow(false);
};
//根据目录获取分类列表
const getCategoryList = (directoryId: number) => {
CategoryManageAPI.getCategoryList({ directoryId, type: 4, pageSize: 9999, pageNo: 1 }).then(
({ result }) => {
setCategoryList(result.list || []);
},
);
};
//产品-单位
const getSkuUnit = () => {
goodsAPI.getSkuUnit().then(({ result }) => {
setSkuUnitList(result || []);
});
};
//其它服务选择
const otherServiceSelect = (ids: number[]) => {
setOtherService(ids);
};
//获取产品详情
const getRichText = (html?: string) => {
setProductIntroduce(html || '');
};
//商品详情
const getGoodsDetail = (goodsInfoId: number) => {
GoodsAPI.getGoodsDetail({ goodsInfoId }).then(({ result }) => {
setGoodsDetail(result);
getCategoryList(result.directoryId);
const specList: specEntity[] = result.goodsSpec.reduce((pre: any, cur: specEntity) => {
// 自定义
if (cur.flag === 1) {
const cusList: customizeEntity[] =
cur.productSpecList &&
cur.productSpecList.reduce((preProd: any, curProd: customizeEntity, index: number) => {
const obj: UploadFile = {
uid: `img${index}`,
status: 'done',
url: curProd.specImage,
name: 'image',
};
preProd.push({ ...curProd, fileList: [obj] });
return preProd;
}, []);
cur.customizeInfo = cusList;
} else {
const specId: number[] =
result.directoryId === 2
? cur.industrySpecList &&
cur.industrySpecList.map((curIndu: any) => {
return {
mallSpecId: curIndu.industrySpecId,
specName: curIndu.specName,
partNo: curIndu.partNo,
id: curIndu.id,
};
})
: cur.productSpecList &&
cur.productSpecList.map((item: any) => {
return {
mallSpecId: item.productSpec,
specName: item.specName,
partNo: item.partNo,
id: item.id,
};
});
cur.specIds = specId;
}
pre.push({ ...cur, productName: cur.skuName });
return pre;
}, []);
setGoodsSpecCopy(result.goodsSpec);
setSpecData(specList);
});
};
//保存
const saveSubmit = () => {
const baseInfoForm = baseInfoRef.current.baseInform;
baseInfoForm.validateFields().then((values: any) => {
if (specData.length === 0) {
return message.warning('清添加库存规格');
}
//主图
values.images = [
{
imgType: 0,
imgUrl: values.mainImg[0].url,
id: goodsDetail ? values.mainImg[0].id : undefined,
},
];
//副图
if (values.subImg) {
values.images.push(
...values.subImg.map((v: any) => ({
imgType: 1,
imgUrl: v.url,
id: goodsDetail ? v.id : undefined,
})),
);
}
//分类
values.categoryByOne = values.masterTypeId[0];
values.categoryByTne = values.masterTypeId[1] || undefined;
// 过滤对象属性
const goodsSpecVO: specEntity[] = specData.reduce((pre: any, cur: specEntity) => {
cur.customizeInfo = cur.customizeInfo?.reduce((cusPre: any, cusCur: customizeEntity) => {
const bol: boolean = goodsSpecCopy.some((i: specEntity) => {
return i.customizeInfo?.some((i: customizeEntity) => i.id === cusCur.id);
});
cusPre = [
...cusPre,
bol ? filterObjAttr(cusCur, ['fileList']) : filterObjAttr(cusCur, ['id', 'fileList']),
];
return cusPre;
}, []);
// 存在对象属性改变!
cur.specIds = cur.specIds?.reduce((preSpec: any, curSpec: any) => {
preSpec = [...preSpec, filterObjAttr(curSpec, ['specName', 'partNo'])];
return preSpec;
}, []);
// 是否新增
const isAdd: boolean = goodsSpecCopy.every((i: specEntity) => i.id != cur.id);
// 是否修改了某一条
const isEdit: boolean = goodsSpecCopy.every(
(i: specEntity) => i.categoryId != cur.categoryId,
);
pre = [
...pre,
!isAdd
? isEdit
? filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName', 'id'])
: filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName'])
: filterObjAttr(cur, ['id', 'skuName']),
];
return pre;
}, []);
goodsAPI[goodsDetail ? 'editGoods' : 'addGoods']({
...filterObjAttr(values, ['mainImg', 'subImg', 'video', 'masterTypeId', 'id', 'goodsDesc']),
productSpec: goodsSpecVO,
goodsType: 0,
goodsDetailVO: { goodsDesc: values.goodsDesc, productDesc: productIntroduce },
otherService: otherService,
id: goodsDetail ? goodsDetail.id : undefined,
}).then(({ code }) => {
if (code === '200') {
message.success(goodsDetail ? '编辑成功' : '新增成功');
navigate(-1);
}
});
});
};
//返回 //返回
const backRoute = () => { const backRoute = () => {
navigate(-1); navigate(-1);
}; };
useEffect(() => {
if (searchParams.get('id')) {
getGoodsDetail(Number(searchParams.get('id')));
}
getSkuUnit();
}, []);
return ( return (
<div className='goods-info'> <div className='goods-info'>
<BaseInfo /> {/* 基本信息*/}
<StockSku /> <BaseInfo
<OtherInfo /> ref={baseInfoRef}
<GoodsIntroduce /> categoryList={categoryList}
getCategoryList={getCategoryList}
goodsDetail={goodsDetail}
/>
{/* 库存规格*/}
<StockSku
addOrEditSku={addOrEditSkuShowEvent}
specData={specData}
categoryList={categoryList}
skuUnitList={skuUnitList}
/>
{/*其它信息*/}
<OtherInfo otherServiceSelect={otherServiceSelect} goodsDetail={goodsDetail} />
{/*产品介绍图*/}
<GoodsIntroduce getRichText={getRichText} />
{/*库存规格,添加、编辑弹窗*/}
<AddOrEditSkuModal
currentDesc={currentDesc}
open={addOrEditSkuModalShow}
handleCancel={addOrEditSkuModalCancel}
handleOk={addOrEditSkuModalOk}
categoryList={categoryList}
skuUnitList={skuUnitList}
curtRowData={curtRowData}
/>
<div className='goods-info-operate'> <div className='goods-info-operate'>
<Button type='primary'>保存</Button> <Button type='primary' onClick={saveSubmit}>
保存
</Button>
<Button onClick={backRoute}>返回</Button> <Button onClick={backRoute}>返回</Button>
</div> </div>
</div> </div>
......
...@@ -59,7 +59,9 @@ const GoodsList = () => { ...@@ -59,7 +59,9 @@ const GoodsList = () => {
align: 'center', align: 'center',
render: () => ( render: () => (
<> <>
<Button type='link'>编辑</Button> <Button type='link' onClick={toEditGoods}>
编辑
</Button>
<Button type='link'>详情</Button> <Button type='link'>详情</Button>
</> </>
), ),
...@@ -74,6 +76,13 @@ const GoodsList = () => { ...@@ -74,6 +76,13 @@ const GoodsList = () => {
const toAddMallGoods = () => { const toAddMallGoods = () => {
navigate({ pathname: '/mallManage/mallGoods/add' }); navigate({ pathname: '/mallManage/mallGoods/add' });
}; };
//编辑商品
const toEditGoods = () => {
navigate({
pathname: '/mallManage/mallGoods/add',
search: `id=${43}`,
});
};
return ( return (
<div className='goods-list'> <div className='goods-list'>
<SearchBox <SearchBox
......
import { Button } from 'antd'; import { Button } from 'antd';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import './index.scss'; import './index.scss';
import RichText from '~/components/richText';
const ServiceIntroduce = () => { const ServiceIntroduce = () => {
const navigate = useNavigate(); const navigate = useNavigate();
...@@ -19,9 +18,7 @@ const ServiceIntroduce = () => { ...@@ -19,9 +18,7 @@ const ServiceIntroduce = () => {
</Button> </Button>
</div> </div>
<div className='service-introduce-title'></div> <div className='service-introduce-title'></div>
<div className='service-introduce-rich-text'> <div className='service-introduce-rich-text'></div>
<RichText richTextHeight={500} />
</div>
</div> </div>
); );
}; };
......
...@@ -16,9 +16,6 @@ import { ...@@ -16,9 +16,6 @@ import {
ReconciliationOutlined, ReconciliationOutlined,
SolutionOutlined, SolutionOutlined,
RedEnvelopeOutlined, RedEnvelopeOutlined,
SettingOutlined,
UserOutlined,
SisternodeOutlined,
SendOutlined, SendOutlined,
RocketOutlined, RocketOutlined,
AppstoreAddOutlined, AppstoreAddOutlined,
...@@ -44,7 +41,6 @@ import DivideRules from '~/pages/pointManage/divideRules'; ...@@ -44,7 +41,6 @@ import DivideRules from '~/pages/pointManage/divideRules';
import CustomListView from '~/pages/customManage/customList'; import CustomListView from '~/pages/customManage/customList';
import CustomMoneyView from '~/pages/customManage/customMoney'; import CustomMoneyView from '~/pages/customManage/customMoney';
import CustomMoneyDetail from '~/pages/customManage/customMoney/detail'; import CustomMoneyDetail from '~/pages/customManage/customMoney/detail';
import AccountManageView from '~/pages/systemManage/accountManage';
// 活动 // 活动
const ActivityList = React.lazy(() => import('src/pages/activityManage/activityList')); //活动管理 const ActivityList = React.lazy(() => import('src/pages/activityManage/activityList')); //活动管理
...@@ -294,6 +290,16 @@ export const routerList: Array<RouteObjectType> = [ ...@@ -294,6 +290,16 @@ export const routerList: Array<RouteObjectType> = [
}, },
}, },
{ {
path: '/mallManage/mallGoods/edit',
element: withLoadingComponent(<MallAddOrEditOrDetailView />),
meta: {
id: 10146,
icon: <SmileOutlined />,
title: '商城商品编辑',
hidden: true,
},
},
{
path: '/mallManage/produceList', path: '/mallManage/produceList',
element: withLoadingComponent(<ProduceListView />), element: withLoadingComponent(<ProduceListView />),
meta: { meta: {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论