Commit d827006c authored by 武广's avatar 武广

Merge branch 'feature/chainStore' of git.quantgroup.cn:ui/merchant-manage-ui...

Merge branch 'feature/chainStore' of git.quantgroup.cn:ui/merchant-manage-ui into feature/service-product
parents 10d1de3c ec420187
...@@ -9,6 +9,7 @@ module.exports = { ...@@ -9,6 +9,7 @@ module.exports = {
'max-len': ['error', { code: 200 }], 'max-len': ['error', { code: 200 }],
'no-param-reassign': 0, 'no-param-reassign': 0,
'no-console': 0, 'no-console': 0,
'no-plusplus': ['off'],
'@typescript-eslint/camelcase': ['off'], '@typescript-eslint/camelcase': ['off'],
'@typescript-eslint/no-unused-vars': ['off'], '@typescript-eslint/no-unused-vars': ['off'],
}, },
......
...@@ -217,6 +217,12 @@ export default { ...@@ -217,6 +217,12 @@ export default {
// name: 'GoodsManageNew', // name: 'GoodsManageNew',
// component: './GoodsManage-new', // component: './GoodsManage-new',
// }, // },
{
title: '服务商品改造-商品模块',
path: '/ServiceGoods/:id',
name: 'ServiceGoods',
component: './ServiceGoods/index',
},
{ {
component: './404', component: './404',
}, },
......
This diff is collapsed.
...@@ -176,6 +176,9 @@ class goodsManage extends Component { ...@@ -176,6 +176,9 @@ class goodsManage extends Component {
<Button type="primary" className={styles.button} onClick={this.addSpu}> <Button type="primary" className={styles.button} onClick={this.addSpu}>
新增商品 新增商品
</Button> </Button>
<Button type="primary" className={styles.button} onClick={this.props.serviceVisbleOpen}>
新增服务类商品
</Button>
{/* <Button {/* <Button
className={styles.button} className={styles.button}
type="primary" type="primary"
......
...@@ -710,7 +710,7 @@ class goodsManage extends Component { ...@@ -710,7 +710,7 @@ class goodsManage extends Component {
</Col> </Col>
{specListData.length {specListData.length
? specListData.map((item, index) => ( ? specListData.map((item, index) => (
<Col span={24}> <Col key={item.specId} span={24}>
<FormItem label={item.specName} labelCol={{ span: 2 }}> <FormItem label={item.specName} labelCol={{ span: 2 }}>
{getFieldDecorator(`${item.specId}`, { {getFieldDecorator(`${item.specId}`, {
initialValue: initData[item.specId], initialValue: initData[item.specId],
......
import { Form } from '@ant-design/compatible'; import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css'; import '@ant-design/compatible/assets/index.css';
import { Card, Pagination, Table, notification, Drawer, Spin } from 'antd'; import { Card, Pagination, Table, notification, Drawer, Spin, Button } from 'antd';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout'; import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { connect } from 'dva'; import { connect } from 'dva';
...@@ -21,9 +21,10 @@ import { ...@@ -21,9 +21,10 @@ import {
} from './service'; } from './service';
import LogModal from './LogModal'; import LogModal from './LogModal';
import CreateModal from './createModal'; import CreateModal from './createModal';
import { column, JDSHOPID } from './staticdata'; import { column, JDSHOPID, ProcessEditData } from './staticdata';
import SearchForm from './SearchForm'; import SearchForm from './SearchForm';
import TempleatModal from './TempleatModal'; import TempleatModal from './TempleatModal';
import ServiceGoods from '../ServiceGoods';
@connect(({ goodsManage }) => ({ @connect(({ goodsManage }) => ({
goodsManage, goodsManage,
...@@ -48,6 +49,9 @@ class goodsManage extends Component { ...@@ -48,6 +49,9 @@ class goodsManage extends Component {
isAll: 0, isAll: 0,
templateList: [], templateList: [],
isType: '', isType: '',
serviceVisble: false,
serviceData: {},
}; };
currentLog = null; currentLog = null;
...@@ -115,7 +119,7 @@ class goodsManage extends Component { ...@@ -115,7 +119,7 @@ class goodsManage extends Component {
this.setState({ this.setState({
createloading: true, createloading: true,
}); });
const { data, msg } = await spuDetail({ id: spuId }); const { data, msg } = await spuDetail({ id: spuId }); // spuId
if (data) { if (data) {
data.state = state; data.state = state;
data.pageProductType = productType; data.pageProductType = productType;
...@@ -290,6 +294,45 @@ class goodsManage extends Component { ...@@ -290,6 +294,45 @@ class goodsManage extends Component {
}); });
}; };
// serviceVisbleChange = row => {
// this.setState({
// serviceVisble: true,
// });
// };
serviceVisbleChange = async row => {
const { state, spuId, productType } = row;
this.setState({ createloading: true });
const { data, msg } = await spuDetail({ id: spuId }); // spuId
if (data) {
const SourceData = ProcessEditData(data, row);
console.log('index.js=============>', SourceData);
this.setState({
serviceData: SourceData,
serviceVisble: true,
createloading: false,
});
} else {
this.setState({
createloading: false,
});
notification.warning({
message: msg,
});
}
};
serviceVisbleOpen = () => {
this.serviceVisbleClose(true);
};
serviceVisbleClose = flag => {
this.setState({
serviceVisble: flag,
serviceData: {},
});
};
render() { render() {
const { const {
goodsManage: { tableData = {} }, goodsManage: { tableData = {} },
...@@ -314,6 +357,7 @@ class goodsManage extends Component { ...@@ -314,6 +357,7 @@ class goodsManage extends Component {
shopList={this.shopList} shopList={this.shopList}
addSpu={() => this.setState({ createVisible: true, initData: {} })} addSpu={() => this.setState({ createVisible: true, initData: {} })}
setArea={(isALL, type) => this.setArea(isALL, type)} setArea={(isALL, type) => this.setArea(isALL, type)}
serviceVisbleOpen={this.serviceVisbleOpen}
/> />
</Card> </Card>
<Spin spinning={this.state.loading}> <Spin spinning={this.state.loading}>
...@@ -398,6 +442,20 @@ class goodsManage extends Component { ...@@ -398,6 +442,20 @@ class goodsManage extends Component {
isType={this.state.isType} isType={this.state.isType}
templateList={this.state.templateList} templateList={this.state.templateList}
/> />
{/* '894048258062' */}
<ServiceGoods
visible={this.state.serviceVisble}
id={894048258062}
onChange={this.serviceVisbleClose}
SourceData={this.state.serviceData}
shopList={this.shopList}
categoryList={
this.state.serviceData.productType === 2
? this.state.virtualTreeData
: this.state.treeData
}
specListData={this.state.specListData}
/>
</Spin> </Spin>
</PageHeaderWrapper> </PageHeaderWrapper>
); );
......
import React from 'react'; import React from 'react';
import { Button, Badge } from 'antd'; import { Button, Badge } from 'antd';
import styles from './style.less'; import styles from './style.less';
import { resetTime } from '../../utils/utils';
export const productType = [ export const productType = [
{ {
...@@ -152,15 +153,26 @@ export function column() { ...@@ -152,15 +153,26 @@ export function column() {
render: (_, row) => ( render: (_, row) => (
<div className={styles.actionBtn}> <div className={styles.actionBtn}>
{(row.state === 4 || (row.state >= 5 && row.updateState !== 1)) && ( {(row.state === 4 || (row.state >= 5 && row.updateState !== 1)) && (
<Button <>
key="edit" <Button
type="primary" key="edit"
size="small" type="primary"
className={styles.button} size="small"
onClick={() => this.onUpdateInfo(row)} className={styles.button}
> onClick={() => this.onUpdateInfo(row)}
修改 >
</Button> 修改
</Button>
<Button
key="222"
type="primary"
size="small"
className={styles.button}
onClick={() => this.serviceVisbleChange(row)}
>
修改
</Button>
</>
)} )}
<Button <Button
key="viewP" key="viewP"
...@@ -200,3 +212,153 @@ export const productTypeList = [ ...@@ -200,3 +212,153 @@ export const productTypeList = [
]; ];
export const JDSHOPID = [3, 5, 6]; export const JDSHOPID = [3, 5, 6];
// 编辑回显示时只获取用到的数据
const filterItem = skuItem => {
const { serviceItem, ...argsItem } = skuItem;
argsItem.disabled = true;
return argsItem;
};
const filterSpecData = skuList =>
skuList.reduce(
(orgin, skuItem) => {
const item = filterItem(skuItem);
const { firstSpecValue, secondSpecValue } = item;
if (firstSpecValue && !orgin.firstDuplicate.includes(firstSpecValue)) {
orgin.firstSpecValue.push(item);
orgin.firstDuplicate.push(firstSpecValue);
}
if (secondSpecValue && !orgin.secondDuplicate.includes(secondSpecValue)) {
orgin.secondSpecValue.push(item);
orgin.secondDuplicate.push(secondSpecValue);
}
return orgin;
},
{
firstSpecValue: [],
secondSpecValue: [],
dataSource: [],
firstDuplicate: [],
secondDuplicate: [],
},
);
const filterCarouseList = (carouseList = []) =>
carouseList.reduce((origin, itemImg) => {
if (itemImg.skuSpecImageList.length) {
origin[itemImg.specValue || 'NOT'] = itemImg.skuSpecImageList || [];
}
return origin;
}, {});
export const ProcessEditData = (data, row) => {
const [oneItem = {}] = data.skuList;
const serviceItem = oneItem.serviceItem || {};
const orginSpecItem = filterSpecData(data.skuList);
const imageList = filterCarouseList(data.carouseList);
const servesItemParams =
data.productType === 4
? {
serviceItem: {
useTime: [resetTime(serviceItem.useStartTime), resetTime(serviceItem.useEndTime)],
purchaseTime: [
resetTime(serviceItem.purchaseStartTime),
resetTime(serviceItem.purchaseEndTime),
], // 购买开始时间
shopIds: serviceItem.shopIds || [], // 适用门店列表
unavailableDate: serviceItem.unavailableDate, // 不可用日期
useTimeDescription: serviceItem.useTimeDescription, // 使用时间
useMethod: serviceItem.useMethod, // 使用方法
ruleDescription: serviceItem.ruleDescription, // 规则说明
applyScope: serviceItem.applyScope, // 适用范围
tips: serviceItem.tips, // 温馨提示
},
settlementItem: {
settlementMethod: 1,
limitPurchase: Boolean(serviceItem.limitPurchase), // 是否限购1:限购/0:不限购
limitPurchaseType: serviceItem.limitPurchaseType, // 限购类型,如果限购必填1:长期限购/2:周期限购
limitPurchaseCycle: serviceItem.limitPurchaseCycle, // 限购周期1:每天/7:7天/30:30天
limitPurchaseQuantity: serviceItem.limitPurchaseQuantity, // 限购数量
packageContent: serviceItem.packageContent,
appointment: serviceItem.appointment, // 预约
receptionVolume: serviceItem.receptionVolume, // 接待数量
},
}
: {};
const SourceData = {
productType: data.productType,
pageProductType: row.productType,
state: row.state,
infoMation: {
brandId: data.brandId,
supplierId: data.supplierId,
character: data.character,
name: data.name,
categoryId: [data.firstCategoryId, data.secondCategoryId, data.thirdCategoryId],
description: serviceItem.description || null,
},
infoSpecData: {
firstSpec: oneItem.firstSpec,
firstSpecId: oneItem.firstSpecId,
firstSpecValue: orginSpecItem.firstSpecValue,
secondSpec: oneItem.secondSpec,
secondSpecId: oneItem.secondSpecId,
secondSpecValue: orginSpecItem.secondSpecValue,
},
infoImageData: {
imageList,
commonImageList: data.commonImageList,
detailImageList: data.detailImageList,
},
skuList: data.skuList,
...servesItemParams,
};
return SourceData;
// data.state = state;
// data.pageProductType = productType;
// data.categoryId = data.thirdCategoryId;
// data.firstSpecId = data.skuList[0].firstSpecId;
// data.secondSpecId = data.skuList[0].secondSpecId;
// data.firstSpecName = data.skuList[0].firstSpec;
// data.secondSpecName = data.skuList[0].secondSpec;
// data.firstSpecList = [];
// data.secondSpecList = [];
// data.colorKeys = [];
// data.carouseList.forEach(i => {
// if (i.specValue) {
// data.colorKeys.push(i.specValue);
// }
// });
// data.skuList.forEach(i => {
// if (data.firstSpecList.indexOf(i.firstSpecValue) === -1) {
// data.firstSpecList.push(`${i.firstSpecValue}`);
// }
// if (i.secondSpecValue && data.secondSpecList.indexOf(i.secondSpecValue) === -1) {
// data.secondSpecList.push(i.secondSpecValue);
// }
// });
// data.specs.forEach(item => {
// const arr = [];
// if (item.specValues.length) {
// item.specValues.forEach(childItem => {
// arr.push(childItem.value);
// });
// }
// data[item.specId] = arr;
// });
// data.imageList = [];
// data.carouseList.forEach(i => {
// data.imageList[`${i.specValue}`] = i.skuSpecImageList || [];
// });
// data.editData = sortBy(data.skuList, item => item.firstSpecValue);
// this.setState({
// initData: data,
// createVisible: true,
// createloading: false,
// });
};
.header {
:global {
.ant-pro-page-header-wrap-children-content {
margin: 0;
}
}
}
.container {
padding: 0 10px;
background-color: #fff;
}
.title {
position: relative;
height: 44px;
margin-bottom: 10px;
padding-left: 30px;
line-height: 44px;
background-color: #f8f8f8;
&::before {
position: absolute;
top: 12px;
left: 15px;
width: 4px;
height: 20px;
background-color: #319bfe;
content: '';
}
}
.prodcutContent {
display: flex;
padding-bottom: 10px;
}
.productCard {
margin-bottom: 0;
margin-left: 20px;
padding: 10px 20px 5px 20px;
text-align: center;
border: solid #ccc 1px;
border-radius: 5px;
cursor: pointer;
:global {
.prodcut-name {
font-weight: bold;
}
.prodcut-desc {
color: #bbb;
}
}
}
.activeCard {
position: relative;
overflow: hidden;
border-color: #165cd3;
&::after {
position: absolute;
right: -15px;
bottom: -15px;
width: 30px;
height: 30px;
background-color: #165cd3;
transform: rotate(45deg);
content: '';
}
:global {
.prodcut-name {
color: #165cd3;
font-weight: bold;
}
}
}
.pullImage {
position: absolute;
top: 30px;
right: -80px;
}
import React from 'react';
import { Select, Form, InputNumber, Input, Button, Popover } from 'antd';
import commonStyle from '../common.less';
export const WrapperContainer = props => (
<div className={commonStyle.container}>{props.children}</div>
);
/**
* title 组件
* value 可以传入多种类型的值
* onChange只会回调 number|undefined 类型
* 当isNaN(Number(value)) 为true的时候,代表选择默认类型
* 当选择默认类型的时候,onChange会回调undefined
* @param props
*/
export const Title = props => (
<div className={commonStyle.title}>
<h3>{props.title}</h3>
</div>
);
export const SelectTemplate = props => {
const {
value,
onChange,
formName,
noSty,
defaultOptionName,
dataList,
fieldNames,
...resetProps
} = props;
if (!dataList.length) {
return null;
}
const selectTemp = (
<Select allowClear {...resetProps}>
{defaultOptionName ? (
<Select.Option key="default" value={0}>
{defaultOptionName}
</Select.Option>
) : null}
{dataList.map((item, index) => {
const val = fieldNames ? item[fieldNames.value] : item;
const lab = fieldNames ? item[fieldNames.label] : item;
return (
<Select.Option key={val} value={val}>
{lab}
</Select.Option>
);
})}
</Select>
);
return formName ? (
<Form.Item noStyle={noSty} name={formName} key={formName}>
{selectTemp}
</Form.Item>
) : (
selectTemp
);
};
export const CreateFormInput = props => {
const { title, record, dataIndex, rowIndex, onClick, type, ...options } = props;
if (type === 'input') {
return <Input placeholder={title} {...options} />;
}
if (type === 'btnText') {
return (
<Popover content={record.name} trigger="hover">
<Button type="text">查看名称</Button>
</Popover>
);
}
if (type === 'option') {
return (
<>
{record.skuLink && (
<Button type="primary" size="small" onClick={() => onClick('cloneImg', record)}>
拉图片
</Button>
)}
{props.isJDGoods && (
<Button
type="primary"
size="small"
onClick={() => onClick('updateName', { ...record, dataIndex, rowIndex })}
disabled={props.disabled}
>
修改sku名称
</Button>
)}
</>
);
}
return <InputNumber placeholder={title} {...options} />;
};
import { InputNumber, Input, Modal, Button, Form, Table } from 'antd';
import React, {
useContext,
createContext,
useEffect,
useState,
forwardRef,
useImperativeHandle,
} from 'react';
import { ServiceContext } from '../context';
import { CreateFormInput } from './CommonTemplate';
import UUID from '../../../utils/uuid';
const UpdateSkuName = ({ skuVisble, value, confirmEvent, cancelEvent }) => {
const [skuForm] = Form.useForm();
const handleCancel = () => {
skuForm.resetFields();
cancelEvent();
};
const handleSaveEvent = async () => {
try {
const result = await skuForm.validateFields();
confirmEvent({ skuName: result.skuName, rowIndex: value.rowIndex });
handleCancel();
} catch (error) {
console.log(error);
}
};
return (
<Modal title="修改SKU名称" visible={skuVisble} onOk={handleSaveEvent} onCancel={handleCancel}>
<Form form={skuForm} name="skuRegister">
<Form.Item
label="sku名称"
key="skuName"
name="skuName"
initialValue={value.skuName}
rules={[{ required: true, message: '请输入sku名称!' }]}
>
<Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} allowClear />
</Form.Item>
</Form>
</Modal>
);
};
const EditableContext = createContext(null);
const EditableRow = ({ index, ...props }) => (
// console.log('=============>EditableRow', props);
<tr {...props} />
);
const EditableCell = props => {
const {
rowIndex,
title,
editable,
children,
dataIndex,
record,
roleProps,
handleSave,
roleHidden,
rowOnClickEvent,
roleRules = {},
...restProps
} = props;
// console.log('==============>', props);
const form = useContext(EditableContext);
const customer = useContext(ServiceContext);
const save = async () => {
try {
const tableList = form.getFieldValue('tableList');
handleSave(tableList);
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
const onClickEvent = (type, row) => {
// 点击拉取京东图片功能
if (type === 'cloneImg') {
customer.onEventBus(type, row);
return;
}
// 修改sku名称
if (type === 'updateName') {
rowOnClickEvent(row);
}
};
const childNode = editable ? (
<Form.Item
style={{ margin: 0 }}
hidden={roleHidden}
name={['tableList', rowIndex, dataIndex]}
rules={[{ required: roleRules.required, message: `请输入${title}.` }]}
>
<CreateFormInput
{...roleProps}
title={title}
rowIndex={rowIndex}
dataIndex={dataIndex}
record={record}
type={props.type}
onBlur={save}
onClick={onClickEvent}
disabled={props.disabled}
/>
</Form.Item>
) : (
children
); // <Button type="text">{children[1]}</Button>
return <td {...restProps}>{childNode}</td>;
};
const EditFormTable = forwardRef((props, ref) => {
const { initData, defaultColumns, setTableData, mergeTable } = props;
const customer = useContext(ServiceContext);
const [dataSource, setDataSource] = useState([]);
const [skuVisble, setSkuVisble] = useState(false);
const [skuNameItem, setSkuNameItem] = useState({
skuName: '',
rowIndex: null,
});
const [form] = Form.useForm();
useEffect(() => {
console.log('==============>坚听initData', initData);
form.setFieldsValue({
tableList: initData,
});
setDataSource(initData);
}, [initData]);
const handleAdd = async () => {
try {
const { tableList } = await form.validateFields();
console.log(tableList);
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
const handleSave = row => {
form.setFieldsValue({
tableList: [...row],
});
// setTableData([...row]);
};
const onCheck = async () => {
try {
const values = await form.validateFields();
return values;
} catch (errorInfo) {
return null;
}
};
const rowOnClickEvent = row => {
setSkuVisble(true);
setSkuNameItem({
skuName: row.name,
rowIndex: row.rowIndex,
});
};
const cancelEvent = () => {
setSkuVisble(false);
};
const confirmEvent = skuItem => {
const newDataSource = [...dataSource];
newDataSource[skuItem.rowIndex].name = skuItem.skuName;
setDataSource(newDataSource);
form.setFieldsValue({
tableList: newDataSource,
});
};
useImperativeHandle(ref, () => ({
onCheck,
form,
}));
// 根据这里做判断渲染表格
const columns = defaultColumns
.filter(item => !item.role || item.role.includes(customer.productType))
.map((col, colIndex) => ({
...col,
onCell: (record, rowIndex) => {
let rowSpan = null;
if (col.dataIndex === 'firstSpecValue' && mergeTable) {
rowSpan = record.rowSpanCount || 0;
}
return {
rowSpan,
record,
rowIndex,
roleRules: col.roleRules || {},
editable: col.editable,
dataIndex: col.dataIndex,
disabled: col.disabled,
type: col.inputType,
roleProps: col.roleProps || {},
title: col.title,
handleSave,
rowOnClickEvent,
};
},
}));
return (
<>
<Form form={form} scrollToFirstError component={false}>
<EditableContext.Provider value={form}>
{/* scroll={{ y: 300, x: 1200 }} */}
<Table
height={300}
pagination={false}
size="small"
components={{
body: {
row: EditableRow,
cell: EditableCell,
},
}}
bordered
dataSource={dataSource}
rowKey={row => UUID.createUUID()}
columns={columns}
/>
</EditableContext.Provider>
</Form>
<Button onClick={() => {}}></Button>
<UpdateSkuName
skuVisble={skuVisble}
form={form}
value={skuNameItem}
confirmEvent={confirmEvent}
cancelEvent={cancelEvent}
/>
</>
);
});
export default EditFormTable;
import React, { useState, useContext, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Cascader, Form, Input, Select, Popover, Button, Checkbox } from 'antd';
import { formItemLayout } from '../config';
import { ServiceContext } from '../context';
const CreateSelectOption = optionList =>
optionList.map(brandItem => (
<Select.Option key={brandItem.id} value={brandItem.id}>
{brandItem.name}
</Select.Option>
));
const fileterBrandOptions = (input, options) => options.children.includes(input);
const filterCategoryOptions = (inputValue, path) =>
path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
const FormInformationBasic = forwardRef((props, ref) => {
const { editData, categoryList, brandList, shopList, afterAddressList, specListData } = props;
const [form] = Form.useForm();
const [noreBrandList, setNoreBrandList] = useState([]);
const customer = useContext(ServiceContext);
const onCheck = async () => {
try {
const values = await form.validateFields();
values.temp = 'infoMation';
return values;
} catch (errorInfo) {
return null;
}
};
useImperativeHandle(ref, () => ({
onCheck,
reset: form.resetFields,
}));
useEffect(() => {
const noreList = (brandList || []).filter(item => item.name === '虚拟商品');
setNoreBrandList(noreList);
}, [brandList]);
useEffect(() => {
if (customer.isEdit) {
if (!editData) return;
form.setFieldsValue(editData);
}
}, [customer.isEdit, editData]);
const onFinish = values => {
console.log('Received values of form: ', values);
};
return (
<Form
{...formItemLayout}
form={form}
name="register"
onFinish={onFinish}
initialValues={{
brandId: null,
supplierId: '',
name: '',
categoryId: [],
description: '',
}}
scrollToFirstError
>
{/* <Form.Item
label="供应商名称"
name="supplierId"
rules={[{ required: true, message: '请选择供应商名称' }]}
>
<Select showSearch placeholder="请选择供应商名称" filterOption={fileterBrandOptions}>
{CreateSelectOption(shopList)}
</Select>
</Form.Item> */}
<Form.Item
name="categoryId"
label="商品类目"
rules={[{ type: 'array', required: true, message: '请输入商品类目!' }]}
>
<Cascader
disabled={customer.isService}
placeholder="请选择商品类目!"
showSearch={{ filter: filterCategoryOptions }}
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={categoryList}
/>
</Form.Item>
{!customer.isCard && (
<Form.Item
name="brandId"
label="商品品牌"
rules={[{ required: true, message: '请选择商品品牌!' }]}
extra="若需新增品牌请联系业务员"
>
<Select
disabled={customer.isService}
showSearch
placeholder="请选择商品品牌"
filterOption={fileterBrandOptions}
>
{CreateSelectOption(customer.productType === 2 ? noreBrandList : brandList)}
</Select>
</Form.Item>
)}
<Popover content={form.getFieldValue('name')} trigger="hover">
<Form.Item
key="name"
name="name"
label="商品名称"
rules={[
{ required: true, min: 2, message: '请输入最少两个字符的商品名称!', whitespace: true },
]}
>
<Input disabled={customer.isService} placeholder="请输入商品名称" />
</Form.Item>
</Popover>
{customer.isJDGoods && (
<Button key="jdMsg" danger type="text">
*本列表的商品名称仅供搜索使用,不在前端作展示。若要修改APP端展示的商品名称,请在商品信息中修改。
</Button>
)}
{!customer.isCard && (
<Form.Item
name="character"
label="商品卖点"
maxLength={50}
placeholder="卖点最优可填写3个词,12个字。前后用空格加竖杠分隔,例: 莹莹剔透 | 粒粒优选 | 易煮易熟"
rules={[{ required: true, message: '请输入商品卖点!', whitespace: true }]}
>
<Input placeholder="请输入商品名称" />
</Form.Item>
)}
{!customer.isCard && (
<Form.Item
name="afterAddressId"
label="售后地址"
rules={[{ required: true, message: '请选择售后地址!' }]}
>
<Select showSearch placeholder="请选择商品品牌" filterOption={fileterBrandOptions}>
{([{ id: 9527, addressName: '测试地址' }] || afterAddressList).map(item => (
<Select.Option key={item.id} value={item.id}>
{item.addressName}
</Select.Option>
))}
</Select>
</Form.Item>
)}
{!customer.isCard &&
specListData.map((item, index) => (
<Form.Item
key={item.specId}
label={item.specName}
name={item.specId}
labelCol={{ span: 2 }}
>
<Checkbox.Group options={item.specValues} />
</Form.Item>
))}
{/* <Form.Item
name="brandId"
label="供应商名称"
rules={[{ required: true, message: '请选择供应商名称!' }]}
>
<Select showSearch placeholder="请选择供应商名称" filterOption={fileterBrandOptions}>
{CreateSelectOption(brandList)}
</Select>
</Form.Item> */}
{customer.isCard ? (
<Form.Item
key="description"
name="description"
label="描述"
rules={[{ required: true, message: '请输入描述!' }]}
>
<Input.TextArea showCount maxLength={100} placeholder="请输入描述!" />
</Form.Item>
) : null}
</Form>
);
});
export default FormInformationBasic;
This diff is collapsed.
import { Form, Input, Select, Checkbox, DatePicker } from 'antd';
import React, { useState, useEffect, forwardRef, useImperativeHandle, useContext } from 'react';
import moment from 'moment';
import { WeeksList, formItemLayout } from '../config';
import { ServiceContext } from '../context';
import { formatTime } from '../../../utils/utils';
const { Option } = Select;
const { RangePicker } = DatePicker;
const WeekCheckBox = () =>
WeeksList.map(item => (
<Checkbox key={item.value} value={item.value} style={{ lineHeight: '32px' }}>
{item.name}
</Checkbox>
));
const rangeConfig = {
rules: [{ type: 'array', required: true, message: '请选择时间!' }],
};
const FormRuleSetting = forwardRef((props, ref) => {
const { editData, supplierIdList } = props;
const [form] = Form.useForm();
const customer = useContext(ServiceContext);
useEffect(() => {
if (customer.isEdit) {
if (!editData) return;
form.setFieldsValue(editData);
}
}, [customer.isEdit, editData]);
const onCheck = async () => {
try {
const { useTime, purchaseTime, ...values } = await form.validateFields();
return {
useStartTime: formatTime(useTime[0]),
useEndTime: formatTime(useTime[1]),
purchaseStartTime: formatTime(purchaseTime[0]),
purchaseEndTime: formatTime(purchaseTime[1]),
temp: 'serviceItem',
...values,
};
} catch (errorInfo) {
return null;
}
};
useImperativeHandle(ref, () => ({
onCheck,
reset: form.resetFields,
}));
return (
<>
<Form
{...formItemLayout}
form={form}
name="register"
initialValues={{
useTime: [], // 使用开始时间
// useEndTime: '', // 使用结束时间
purchaseTime: [], // 购买开始时间
// purchaseEndTime: '2022-07-27 06', // 购买结束时间
shopIds: [], // 适用门店列表
unavailableDate: [], // 不可用日期
useTimeDescription: '', // 使用时间
useMethod: '', // 使用方法
ruleDescription: '', // 规则说明
applyScope: '', // 适用范围
tips: '', // 温馨提示
}}
scrollToFirstError
>
<Form.Item name="useTime" label="购买时间" {...rangeConfig}>
<RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />
</Form.Item>
<Form.Item name="purchaseTime" label="有效期" {...rangeConfig}>
<RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />
</Form.Item>
<Form.Item
name="shopIds"
label="适用门店"
rules={[{ required: true, message: '请选择适用门店!', type: 'array' }]}
>
<Select mode="multiple" placeholder="请选择适用门店">
{(supplierIdList || []).map(item => (
<Option value={item.id} key={item.id}>
{item.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="unavailableDate"
label="不可用日期"
rules={[{ required: true, message: '请选择不可用日期!', type: 'array' }]}
>
<Checkbox.Group>{WeekCheckBox()}</Checkbox.Group>
</Form.Item>
<Form.Item
name="useTimeDescription"
label="使用时间"
rules={[{ required: false, message: '请输入描述!' }]}
>
<Input.TextArea
showCount
maxLength={100}
placeholder="例如:11:00-12:00;14:00-17:00可用,其他时间不可用"
/>
</Form.Item>
<Form.Item
name="useMethod"
label="使用方法"
rules={[{ required: true, message: '请输使用方法,200字以内!' }]}
>
<Input.TextArea showCount maxLength={200} placeholder="请输使用方法,200字以内!" />
</Form.Item>
<Form.Item
name="ruleDescription"
label="规则说明"
rules={[{ required: true, message: '请输规则说明,200字以内!' }]}
>
<Input.TextArea showCount maxLength={200} placeholder="请输规则说明,200字以内!" />
</Form.Item>
<Form.Item
name="applyScope"
label="适用范围"
rules={[{ required: false, message: '请输入适用范围' }]}
>
<Input.TextArea
showCount
maxLength={50}
placeholder="请输入适用范围,50字以内 例如:全场通用"
/>
</Form.Item>
<Form.Item
name="tips"
label="温馨提示"
rules={[{ required: false, message: '请输入温馨提示' }]}
>
<Input.TextArea
showCount
maxLength={200}
placeholder="请输入温馨提示,200字以内 例如:全场通用例如:\n不兑零"
/>
</Form.Item>
</Form>
</>
);
});
export default FormRuleSetting;
import React, { useState, useEffect, useContext, forwardRef, useImperativeHandle } from 'react';
import { Form, Button } from 'antd';
import { ServiceContext } from '../context';
import { TaskList, formItemLayout } from '../config';
import UploadImage from './UploadImage';
import commonStyle from '../common.less';
const { imgConfig: defaultImgConfig } = TaskList[0];
const FormRuleVPictures = forwardRef((props, ref) => {
const { editData, specKeyItem } = props;
const [form] = Form.useForm();
const [imageList, setImageList] = useState({});
const [commonImageList, setCommonImageList] = useState([]);
const [detailImageList, setDetailImageList] = useState([]);
const customer = useContext(ServiceContext);
useEffect(() => {
if (customer.isEdit) {
if (editData) {
setImageList(editData.imageList);
setCommonImageList(editData.commonImageList); // 编辑状态下设置公共图
setDetailImageList(editData.detailImageList); // 编辑状态下设置详情图
form.setFieldsValue({
imageList: editData.imageList,
commonImageList: editData.commonImageList,
detailImageList: editData.detailImageList,
});
}
}
}, [customer.isEdit, editData]);
useEffect(() => {
if (customer.isCard) return;
if (specKeyItem.length) {
const newImageList = specKeyItem.reduce((origin, item) => {
const showItem = imageList[item] || [];
origin[item] = showItem;
return origin;
}, {});
setImageList(newImageList);
}
}, [specKeyItem]);
const onCheck = async () => {
try {
const values = await form.validateFields();
values.temp = 'infoImageData';
return values;
} catch (errorInfo) {
return null;
}
};
useImperativeHandle(ref, () => ({
onCheck,
setFieldsValue: form.setFieldsValue,
getFieldsValue: form.getFieldsValue,
reset: () => {
form.resetFields();
setImageList({});
setCommonImageList([]);
setDetailImageList([]);
},
}));
const onFinish = values => {
console.log('Received values of form: ', values);
};
const onPictureSuccessEvent = (imgList, key) => {
const newImgList = { ...imageList, [key]: imgList };
setImageList(newImgList);
form.setFieldsValue({
imageList: newImgList,
});
};
const onCommonSuccessEvent = imgList => {
setCommonImageList(imgList);
form.setFieldsValue({
commonImageList: imgList,
});
};
const onDetailSuccessImageList = imgList => {
setDetailImageList(imgList);
form.setFieldsValue({
detailImageList: imgList,
});
};
const [{ imgConfig }] = TaskList.filter(item => item.type === customer.productType);
return (
<>
<Form
{...formItemLayout}
form={form}
name="register"
onFinish={onFinish}
initialValues={{
imageList: {},
commonImageList: [],
detailImageList: [],
}}
scrollToFirstError
>
<Form.Item
label="封面图片"
name="commonImageList"
extra={`建议尺寸: ##宽##高 (${commonImageList.length} / 1) `}
rules={[
{
required: imgConfig.commonImageList.rule,
type: 'array',
message: '请上传图片!',
validateTrigger: 'submit',
},
{
validator(rule, value, callback) {
if (customer.productType !== 1) callback();
const checkImageList = form.getFieldValue('imageList');
const check = Object.keys(checkImageList).length;
return check > 0 ? callback() : callback('请上传封面图片');
},
validateTrigger: 'submit',
},
]}
>
<UploadImage
disabled={customer.isService}
name="commonImageList"
limit={1}
pictures={commonImageList}
setPictureList={list => onCommonSuccessEvent(list)}
/>
</Form.Item>
{!customer.isCard &&
Object.keys(imageList).map(key => (
<Form.Item
key={key}
label={`商品图片(${key})`}
name={['imageList', key]}
extra={`建议尺寸: ##宽##高 (${imageList[key].length} / 11) `}
rules={[
{
required: imgConfig.imageList.rule,
type: 'array',
message: '请上传图片!',
validateTrigger: 'submit',
},
]}
>
<UploadImage
disabled={customer.isService}
name={key}
limit={11}
pictures={imageList[key]}
setPictureList={list => onPictureSuccessEvent(list, key)}
/>
<Button className={commonStyle.pullImage} type="primary">
拉取公共图
</Button>
</Form.Item>
))}
<Form.Item
label="商品详情图"
name="detailImageList"
extra={`最多上传30张,${detailImageList.length} / 30`}
rules={[
{
type: 'array',
required: imgConfig.detailImageList.rule,
message: '请上传商品详情图!',
validateTrigger: 'submit',
},
]}
>
<UploadImage
disabled={customer.isService}
name="detailImageList"
pictures={detailImageList}
setPictureList={list => onDetailSuccessImageList(list)}
/>
</Form.Item>
</Form>
</>
);
});
export default FormRuleVPictures;
import React, { useContext, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Form, Input, Select, Checkbox, Radio, Space, InputNumber } from 'antd';
import { Title } from './CommonTemplate';
import { formItemLayout } from '../config';
import { ServiceContext } from '../context';
const createInitValues = () => ({
settlementMethod: 1,
limitPurchase: null, // 是否限购1:限购/0:不限购
limitPurchaseType: null, // 限购类型,如果限购必填1:长期限购/2:周期限购
limitPurchaseCycle: null, // 限购周期1:每天/7:7天/30:30天
limitPurchaseQuantity: null, // 限购数量
packageContent: '',
appointment: null, // 预约
receptionVolume: null,
});
const FormSettlementOthers = forwardRef((props, ref) => {
const { editData } = props;
const [form] = Form.useForm();
const [initValue, setInitValue] = useState(createInitValues());
const customer = useContext(ServiceContext);
const onCheck = async () => {
try {
const values = await form.validateFields();
return {
...values,
temp: 'settlementItem',
limitPurchase: values.limitPurchase ? 1 : 0,
};
} catch (errorInfo) {
return null;
}
};
useEffect(() => {
if (customer.isEdit) {
if (!editData) return;
form.setFieldsValue(editData);
setInitValue({ ...editData });
}
}, [customer.isEdit, editData]);
useImperativeHandle(ref, () => ({
onCheck,
reset: () => {
setInitValue(createInitValues());
form.resetFields();
},
}));
const radioChangeEvent = key => {
const value = form.getFieldValue(key);
setInitValue({
...initValue,
[key]: value,
});
};
const AuditLimitPurchaseType = () => {
const limitPurchaseType = form.getFieldValue('limitPurchaseType');
if (limitPurchaseType === 1) {
return null;
}
return (
<Form.Item name="limitPurchaseCycle" rules={[{ required: true, message: '请选择限购周期' }]}>
<Select placeholder="请选择限购周期" style={{ width: 150 }}>
<Select.Option value={1}>每天</Select.Option>
<Select.Option value={7}>7天</Select.Option>
<Select.Option value={30}>30天</Select.Option>
</Select>
</Form.Item>
);
};
const AuditLimitPurchaseTemplate = () => {
if (!initValue.limitPurchase) {
return null;
}
const PurchaseTemplate =
initValue.limitPurchaseType !== null ? (
<Form.Item wrapperCol={{ offset: 3, span: 16 }}>
<Space>
{AuditLimitPurchaseType()}
<Form.Item
name="limitPurchaseQuantity"
rules={[{ required: initValue.limitPurchase, message: '请输入限购数量' }]}
>
<InputNumber placeholder="请输入限购数量" style={{ width: 150 }} />
</Form.Item>
</Space>
</Form.Item>
) : null;
return (
<>
<Form.Item
name="limitPurchaseType"
wrapperCol={{ offset: 3, span: 16 }}
rules={[{ required: true, message: '请选择限购类型' }]}
>
<Radio.Group onChange={() => radioChangeEvent('limitPurchaseType')}>
<Space direction="vertical">
<Radio value={1}>每ID限购</Radio>
<Radio value={2}>按周期限购</Radio>
</Space>
</Radio.Group>
</Form.Item>
{PurchaseTemplate}
</>
);
};
return (
<Form
{...formItemLayout}
form={form}
name="register"
initialValues={initValue}
scrollToFirstError
>
<Form.Item
name="appointment"
label="预约"
rules={[{ required: true, message: '请选择是否预约!' }]}
>
<Radio.Group>
<Radio value={1}></Radio>
<Radio value={0}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="receptionVolume"
label="每日最低接待量"
rules={[{ required: true, message: '每日最低接待量' }]}
>
<InputNumber min={0} style={{ width: 200 }} placeholder="请输入每日最低接待量" />
</Form.Item>
<Title title="结算信息" />
<Form.Item
name="packageContent"
label="套餐内容"
rules={[{ required: true, message: '请输入套餐内容!' }]}
>
<Input.TextArea showCount maxLength={100} placeholder="请输入套餐内容!" />
</Form.Item>
<Form.Item
name="settlementMethod"
label="结算方式"
rules={[{ required: true, message: '请输入套餐内容!' }]}
extra="自动分账: 合同期内订单结算款实时分账到甲方指定账号。"
>
<span style={{ color: 'rgba(0, 0, 0, 0.45)' }}>(默认)</span>
</Form.Item>
<Title title="其他设置" />
<Form.Item label="限购" name="limitPurchase" valuePropName="checked">
<Checkbox onChange={() => radioChangeEvent('limitPurchase')}>
<b style={{ marginLeft: 10 }}>启用限购</b>
<span style={{ marginLeft: 10 }} className="ant-form-text">
限制每人可购买数量
</span>
</Checkbox>
</Form.Item>
{AuditLimitPurchaseTemplate()}
</Form>
);
});
export default FormSettlementOthers;
import React, { useContext } from 'react';
import { TaskList } from '../config';
import { ServiceContext } from '../context';
import commonStyle from '../common.less';
export const TaskTypeSelect = props => {
const customer = useContext(ServiceContext);
const selectTabs = task => {
if (!customer.isEdit) {
props.onChange(task);
}
};
return (
<div className={commonStyle.prodcutContent}>
{TaskList.map(task => {
const activeClassName = props.productType === task.type ? commonStyle.activeCard : '';
if (task.hide) return null;
return (
<dl
key={task.type}
onClick={() => selectTabs(task)}
className={`${commonStyle.productCard} ${activeClassName}`}
>
<dd className="prodcut-name">{task.name}</dd>
<dd className="prodcut-desc">({task.desc})</dd>
</dl>
);
})}
</div>
);
};
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload, notification, Spin } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import lodash from 'lodash';
import { merchantUpload } from '../service';
const MAX_FILE_SIZE = 5;
const UNIT = 1024 * 1024;
// const getBase64 = (file: RcFile): Promise<string> =>
// new Promise((resolve, reject) => {
// const reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = () => resolve(reader.result.toString());
// reader.onerror = error => reject(error);
// });
const uploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
const UploadImage = props => {
const {
name = `${Date.now()}`,
limit = null,
multiple = true,
disabled,
uploadParams,
pictures = [],
onChange = () => {},
setPictureList = () => {},
} = props;
const [uploadLoading, setUploadLoading] = useState(false);
const [previewVisible, setPreviewVisible] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState([]);
const fileListRef = useRef([]);
useEffect(() => {
const newPictures = pictures.map((url, ind) => ({
url,
name: url,
uid: `${ind}`,
}));
fileListRef.current = [...newPictures];
setFileList([...newPictures]);
}, [pictures]);
const handleCancel = () => setPreviewVisible(false);
const handlePreview = async file => {
setPreviewImage(file.url);
setPreviewVisible(true);
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
};
const bundleChange = imgFile => {
const imgList = imgFile.map(item => item.url);
setPictureList(imgList);
};
const handleRemove = file => {
const freshFiles = fileList?.filter(ele => ele.uid !== file.uid);
bundleChange(freshFiles);
};
// const saveFiles = (file, ret) => {
// return new Promise((resolve) => {
// const reader = new FileReader();
// // 监听图片转换完成
// reader.addEventListener('load',
// () => {
// // ...接入antd upload 的 filelist 中
// // if (typeof setPictureList === 'function') {
// const temFile = { uid: file.uid, status: 'done', name: file.name, url: ret };
// let newFiles = [...fileListRef.current, temFile];
// if (!uploadParams.multiple) {
// // 只保留最后一个文件
// newFiles = newFiles.slice(-1);
// }
// bundleChange(newFiles);
// // }
// },
// false,
// );
// reader.readAsDataURL(file);
// })
// // 图片转换成base64编码作为缩略图
// };
// const customRequestObject = async params => {
// console.log(fileList.length);
// const { file, onSuccess, onError } = params;
// try {
// const res = await merchantUpload([file]);
// const [url] = res.data;
// onSuccess();
// saveFiles(file, url);
// setFileList(preFileList => preFileList.map(item => id === item.uid ? { ...item, status: 'done', url, percent: 100,} : item));
// } catch(error) {
// onError();
// console.log(error);
// }
// };
const checkFile = file =>
new Promise(resolve => {
const curType = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
const fileType = ['jpg', 'jpeg', 'png'];
if (!fileType.includes(curType)) {
notification.open({
message: file.name,
description: '图片格式须为jpg、jpeg、png!',
});
return resolve(null);
}
if (file.size > MAX_FILE_SIZE * UNIT) {
notification.open({
message: file.name,
description: `单个图片大小不能超过${MAX_FILE_SIZE}M!`,
});
return resolve(null);
}
return resolve(file);
});
const imageLoading = (file, ret) =>
new Promise(resolve => {
const reader = new FileReader();
// 监听图片转换完成
reader.addEventListener(
'load',
() => {
const temFile = { uid: file.uid, status: 'done', name: file.name, url: ret };
resolve(temFile);
},
false,
);
reader.readAsDataURL(file);
});
const defaultBeforeUpload = lodash.debounce(
(file, fileArray) =>
// 文件显示
new Promise(async () => {
if (limit && fileListRef.current.length + fileArray.length > limit) {
Modal.warning({
maskClosable: true,
title: '超出上传个数',
});
return Upload.LIST_IGNORE;
}
const fileAll = fileArray.map(item => checkFile(item));
const checkFiles = (await Promise.all(fileAll)).filter(item => item !== null);
try {
if (checkFiles.length) {
setUploadLoading(true);
const res = await merchantUpload(checkFiles);
const proFiles = (res.data || []).map((urlItem, urlIndex) =>
imageLoading(checkFiles[urlIndex], urlItem),
);
const imagList = await Promise.all(proFiles);
const newFiles = [...fileListRef.current, ...imagList];
bundleChange(newFiles);
setUploadLoading(false);
}
} catch (error) {
setUploadLoading(false);
Modal.warning({
maskClosable: true,
title: '上传失败,请重新尝试!',
});
}
return null;
}),
);
return (
<Spin tip="正在上传..." spinning={uploadLoading} delay={100}>
<Upload
disabled={disabled}
{...uploadParams}
multiple={multiple}
name={name}
customRequest={() => {}}
listType="picture-card"
beforeUpload={defaultBeforeUpload}
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
>
{fileList.length >= props.limit ? null : uploadButton}
</Upload>
<Modal visible={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</Spin>
);
};
export default UploadImage;
export const formItemLayout = {
labelCol: {
sm: { span: 3 },
},
wrapperCol: {
sm: { span: 16 },
},
};
export const TaskList = [
{
name: '实体商品',
type: 1,
desc: '物流发货',
hide: true,
imgConfig: {
imageList: {
tip: '(图片最大上传2M)',
rule: false,
limit: null,
},
commonImageList: {
tip: '(图片最大上传2M)',
rule: false,
limit: null,
},
detailImageList: {
tip: '(图片最大上传2M)',
rule: true,
limit: null,
},
},
},
{
name: '虚拟商品',
type: 2,
desc: '无需物流',
hide: true,
imgConfig: {
imageList: {
rule: false,
limit: null,
},
commonImageList: {
rule: false,
limit: false,
},
detailImageList: {
rule: false,
limit: false,
},
},
},
{
name: '电子卡卷',
type: 3,
desc: '无需物流',
hide: true,
imgConfig: {
imageList: {
rule: true,
limit: 1,
},
commonImageList: {
rule: true,
limit: 11,
},
detailImageList: {
rule: true,
limit: 30,
},
},
},
{
name: '服务类商品',
type: 4,
desc: '无需物流',
imgConfig: {
imageList: {
rule: true,
limit: 1,
},
commonImageList: {
rule: true,
limit: 11,
},
detailImageList: {
rule: true,
limit: 30,
},
},
},
];
export const WeeksList = [
{
name: '法定节假日',
value: 8,
},
{
name: '周一',
value: 1,
},
{
name: '周二',
value: 2,
},
{
name: '周三',
value: 3,
},
{
name: '周四',
value: 4,
},
{
name: '周五',
value: 5,
},
{
name: '周六',
value: 6,
},
{
name: '周日',
value: 7,
},
];
export const StaticColumns = customer => [
{
title: '供货价',
dataIndex: 'supplyPrice',
editable: true,
batchRole: [1, 2, 3, 4],
roleProps: {
precision: 2,
min: 0,
},
roleRules: { required: true },
disabled: customer.isJDGoods || customer.isService,
},
{
title: '佣金费率',
dataIndex: 'commissionRate',
editable: true,
role: [4],
roleRules: { required: true },
},
{
title: '市场价',
dataIndex: 'marketPrice',
editable: true,
batchRole: [1, 2, 3, 4],
roleProps: {
precision: 2,
min: 0,
},
roleRules: { required: true },
disabled: customer.isService,
},
{
title: '销售价',
dataIndex: 'salePrice',
editable: true,
batchRole: [4],
role: [4],
roleRules: { required: true },
},
{
title: '重量(kg)',
dataIndex: 'weight',
editable: true,
batchRole: [1],
batchProps: {
precision: 3,
max: 999999.999,
},
role: [1, 2],
roleRules: { required: true },
disabled: customer.isService,
},
{
title: '库存',
dataIndex: 'productStock',
editable: true,
role: [4],
batchRole: [1, 2, 4],
batchProps: {
precision: 0,
step: 1,
// eslint-disable-next-line radix
formatter: val => parseInt(val, '10') || '',
},
roleProps: {
precision: 2,
min: 0,
},
roleRules: { required: true },
},
// {
// title: '库存',
// dataIndex: 'productStock',
// editable: true,
// role: [1, 2],
// batchRole: [1, 2],
// batchProps: {
// precision: 0,
// step: 1,
// // eslint-disable-next-line radix
// formatter: val => parseInt(val, '10') || '',
// },
// roleProps: {
// precision: 2,
// min: 0,
// },
// roleRules: { required: true },
// disabled: customer.isService,
// },
{
title: '库存预警阈值',
dataIndex: 'productStockWarning',
editable: true,
batchRole: [1],
role: [1, 4],
roleRules: { required: true },
disabled: customer.isService,
},
{
title: '商品自编码',
dataIndex: 'thirdSkuNo',
editable: true,
role: [1, 2],
inputType: 'input',
disabled: customer.isService,
roleRules: { required: true },
},
{
title: '京东链接',
dataIndex: 'skuLink',
editable: true,
role: [1, 2],
inputType: 'input',
disabled: customer.isService,
roleRules: { required: false },
},
{
title: 'sku名称',
dataIndex: 'name',
editable: true,
role: customer.isEdit && customer.isJDGoods ? [1, 2] : [],
inputType: 'btnText',
roleRules: { required: false },
},
{
title: '操作',
editable: true,
dataIndex: 'option',
role: [1, 2],
inputType: 'option',
roleProps: {
isJDGoods: customer.isJDGoods,
min: 0,
},
roleRules: { required: false },
disabled: customer.isService,
},
];
import React from 'react';
export const ServiceContext = React.createContext(null);
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { Spin, Button, Modal, message, notification } from 'antd';
import { ConsoleSqlOutlined } from '@ant-design/icons';
import { Title, WrapperContainer } from './components/CommonTemplate';
import { TaskTypeSelect } from './components/TaskTypeSelect';
import FormInformationBasic from './components/FormInformationBasic';
import FormPriceOrStock from './components/FormPriceOrStock';
import FormRuleSetting from './components/FormRuleSetting';
import FormRuleVPictures from './components/FormRuleVPictures';
import FormSettlementOthers from './components/FormSettlementOthers';
import {
getProductDetail,
merchantCategoryGetAll,
merchantBrandList,
merchantSpecList,
afterSalesAddrsPage,
merchantgetJdPicList,
merchantProductAdd,
// getSupplierList,
supplierListQuery,
shopGetBySupplierId,
} from './service';
import { isUrl, filterSendData } from './utils';
import { ServiceContext } from './context';
/**
* 服务商品改造-商品模块
* @param {*} router options
* @returns ReactDOM
*/
const ServiceGoods = options => {
const { SourceData, categoryList, specListData } = options;
const basicRef = useRef(null);
const stockRef = useRef(null);
const settingRef = useRef(null);
const picturesRef = useRef(null);
const settleOtrRef = useRef(null);
const [pageId, setPageId] = useState(null);
const [isEdit, setIsEdit] = useState(false); // 是否是编辑状态
const [productType, setProductType] = useState(4); // 商品状态
const [pageLoading, setPageLoading] = useState(false); // 页面加载状态
// const [categoryList, setCategoryList] = useState([]); // 获取三级类目
const [afterAddressList, setAfterAddressList] = useState([]);
const [supplierIdList, setSupplierIdList] = useState([]);
// const [shopList, setShopList] = useState([]);
const [brandList, setBrandList] = useState([]); // 获取商品牌
const [specList, setSpecList] = useState([]); // 规格列表
const [editData, setEditData] = useState({}); // 编辑保存数据
const baseCheckList = [basicRef, stockRef, settingRef, settleOtrRef, picturesRef]; // 卡卷默认5个,到时改版为实体对应3个
const [checkFormList, setCheckFormList] = useState(baseCheckList);
const [specKeyList, setSpecKeyList] = useState([]); // 记录一级规格key字段
const changeCheckList = proType => {
const newBaseCheckList =
proType === 4 ? [...baseCheckList, settingRef, settleOtrRef] : baseCheckList;
setCheckFormList(newBaseCheckList);
};
const resetForm = () => {
checkFormList.forEach(({ current }) => {
if (current) {
current.reset();
}
});
};
const productChange = task => {
setProductType(task.type);
changeCheckList(task.type);
const timer = setTimeout(() => {
resetForm();
clearTimeout(timer);
}, 1000);
};
const handleCancel = () => {
setPageId(null);
setIsEdit(false);
setProductType(4); // 默认写死服务类商品
setEditData({});
setSpecKeyList([]);
resetForm();
options.onChange(false);
};
// 编辑回显详情数据
const getProductDetailResponse = async id => {
try {
const res = await getProductDetail(id);
if (res && res.data) {
setProductType(res.data.type);
setEditData(res.data);
}
} catch (err) {
console.log('接口调用失败!');
}
};
// 获取三级类目分类数据
// const getMerchantCategory = async () => {
// const result = await merchantCategoryGetAll();
// // setCategoryList(result.data || []);
// };
// 获取商品牌数据
const getMerchantBrandList = async () => {
if (!brandList.length) {
const result = await merchantBrandList();
setBrandList(result.data || []);
}
};
// 获取规格列表
const getMerchantSpecList = async () => {
if (!specList.length) {
const result = await merchantSpecList();
setSpecList(result.data || []);
}
};
const getAfterSalesAddrsPage = async () => {
if (!afterAddressList.length) {
const result = await afterSalesAddrsPage();
setAfterAddressList(result.data.records);
}
};
// const getSupplierListResponse = async () => {
// if (!shopList.length) {
// const result = await supplierListQuery();
// console.log('=================>result', result);
// setShopList(result.data);
// }
// };
const sendMerchantProductAdd = async sendData => {
setPageLoading(true);
const addResponse = await merchantProductAdd(sendData);
if (addResponse.data) {
message.success('添加成功!');
handleCancel();
}
setPageLoading(false);
};
const shopGetBySupplierIdResponse = async () => {
if (!supplierIdList.length) {
const result = await shopGetBySupplierId();
setSupplierIdList(result.data);
}
};
const submitEvent = async () => {
const checkPromiseList = checkFormList.map(({ current }) => current.onCheck());
const resuslt = await Promise.all(checkPromiseList);
if (!resuslt.includes(null)) {
const params = resuslt.reduce((origin, item) => {
const { temp, ...other } = item;
origin[temp] = other;
return origin;
}, {});
const sendData = filterSendData(productType, params);
sendMerchantProductAdd(sendData);
}
};
const getMerchantgetJdPicList = async params => {
const result = await merchantgetJdPicList(params);
if (result) {
const { detailImageList, imageList } = picturesRef.current.getFieldsValue();
const detailList = result.detailList || [];
const newImageList = imageList[result.firstSpecValue];
const carouseList = result.carouseList || [];
imageList[result.firstSpecValue] = newImageList
? [...newImageList, ...carouseList]
: carouseList;
picturesRef.current.setFieldsValue({
// [`imageList[${data.firstSpecValue}]`]: this.state.colorImg[data.firstSpecValue],
imageList,
detailImageList: [...detailImageList, ...detailList],
});
}
};
useEffect(() => {
(async () => {
if (!options.visible) {
return;
}
setPageLoading(true);
await shopGetBySupplierIdResponse();
// await getSupplierListResponse();
await getMerchantBrandList();
await getAfterSalesAddrsPage();
await getMerchantSpecList();
if (Object.keys(SourceData).length) {
// const isService = initData.state && initData.state !== 4;
setEditData(SourceData);
setPageId(options.id);
setProductType(SourceData.productType);
changeCheckList(SourceData.productType);
setIsEdit(true);
}
setPageLoading(false);
})();
}, [SourceData]);
const onSpecCommonImgEvent = useCallback(
keys => {
setSpecKeyList(keys);
},
[specKeyList],
);
const onEventBus = (event, params) => {
if (event === 'cloneImg') {
if (!isUrl(params.skuLink)) {
notification.open({
message: '提示',
description: '请输入正确的URL!',
});
return;
}
getMerchantgetJdPicList({
firstSpecId: params.firstSpecId,
firstSpecValue: params.firstSpecValue,
secondSpecId: params.secondSpecId,
secondSpecValue: params.secondSpecValue,
skuLink: params.skuLink,
jdSkuInfoUrl: params.skuLink,
});
}
console.log(event, params);
};
const providerValue = {
pageId,
isEdit,
productType,
isCard: productType === 4,
isService: SourceData.state && SourceData.state !== 4,
isJDGoods: isEdit && SourceData.pageProductType && +SourceData.pageProductType !== 1,
onEventBus,
};
return (
<Modal
visible={options.visible}
width={1366}
footer={[
<Button key="submit" type="primary" loading={pageLoading} onClick={submitEvent}>
提交
</Button>,
<Button key="back" onClick={handleCancel}>
返回
</Button>,
]}
>
<Spin tip="正在加载..." spinning={pageLoading} delay={100}>
<WrapperContainer>
<ServiceContext.Provider value={providerValue}>
<Title title="商品类型" />
<TaskTypeSelect productType={productType} onChange={productChange} />
<Title title="商品基本信息编辑" />
<FormInformationBasic
ref={basicRef}
editData={editData.infoMation}
categoryList={categoryList}
brandList={brandList}
afterAddressList={afterAddressList}
specListData={specListData}
/>
<Title title="价格与库存" />
<FormPriceOrStock
ref={stockRef}
specList={specList}
onSpecChange={onSpecCommonImgEvent}
editData={editData.infoSpecData}
skuList={editData.skuList}
/>
<Title title="规则设置" />
{productType === 4 && (
<FormRuleSetting
ref={settingRef}
editData={editData.serviceItem}
supplierIdList={supplierIdList}
/>
)}
<FormRuleVPictures
ref={picturesRef}
specKeyItem={specKeyList}
editData={editData.infoImageData}
/>
{productType === 4 && (
<FormSettlementOthers ref={settleOtrRef} editData={editData.settlementItem} />
)}
</ServiceContext.Provider>
</WrapperContainer>
</Spin>
</Modal>
);
};
export default ServiceGoods;
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import _ from 'lodash';
const { goodsApi, kdspApi } = config;
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
export const merchantUpload = async files => {
const params = new FormData();
files.forEach(file => params.append('file', file));
const data = await request.post('/image/api/merchant/upload', {
prefix: goodsApi,
data: params,
});
return data;
};
export const getProductDetail = id =>
request.post('/product/api/merchant/detail', {
prefix: goodsApi,
params: { id },
headers,
});
export const merchantCategoryGetAll = () =>
request.post('/product/category/api/merchant/getAll', {
prefix: goodsApi,
});
// 获取商品品牌
export const merchantBrandList = () =>
request.post('/product/brand/api/merchant/list', {
prefix: goodsApi,
});
// 获取规格列表
export const merchantSpecList = () =>
request.post('/product/spec/api/merchant/list', {
prefix: goodsApi,
});
// 查询供应商售后地址
export const afterSalesAddrsPage = () => {
const params = {
pageSize: 100,
pageNo: 1,
};
const data = request.post('/api/kdsp/supplier/after-sales-addrs-page', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return data;
};
export const merchantgetJdPicList = async params => {
const { data } = await request.post('/product/api/merchant/item/getJdPicList', {
data: stringify(params),
prefix: goodsApi,
headers,
});
return data;
};
export const merchantProductAdd = data =>
request.post('/product/api/merchant/add', {
prefix: goodsApi,
data,
});
export const supplierListQuery = () =>
request.get('/api/kdsp/supplier/supplier-list-query', {
prefix: config.kdspApi,
headers,
});
// supplierId: 供应商id(优先使用token中的supplierId,这里可以传任意值,建议传0)
// state: 状态:-1-全部,1-启用,0-禁用
export const shopGetBySupplierId = (state = 1, supplierId = 0) =>
request.get(`/shop/getBySupplierId/${supplierId}/${state}`, {
prefix: goodsApi,
headers,
});
import { Button, Form, Input, Popconfirm, Table } from 'antd';
export interface Task {
name: string;
type: number;
desc: string;
}
interface EditableRowProps {
index: number;
}
export type EditableTableProps = Parameters<typeof Table>[0];
export type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
import { ConsoleSqlOutlined } from '@ant-design/icons';
export const cleanArray = actual => {
const newArray = [];
// eslint-disable-next-line no-plusplus
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i]);
}
}
return newArray;
};
const createInitProduct = (skuItem, isCreate) => {
if (isCreate && Object.keys(skuItem).length > 5) {
return skuItem;
}
return {
weight: null,
productStockWarning: null,
marketPrice: null,
supplyPrice: null,
stock: null,
thirdSkuNo: null,
skuLink: null,
name: null,
};
};
const initData = {
weight: null,
productStockWarning: null,
marketPrice: null,
supplyPrice: null,
stock: null,
thirdSkuNo: null,
skuLink: null,
name: null,
};
const createSecondProduct = (secondSpecList, initItem, secondSpec, dataSource, callback) => {
secondSpecList.forEach(secondItem => {
const specSecond =
Object.keys(secondItem).length < 2
? {
firstSpecId: initItem.firstSpecId,
firstSpecValue: initItem.firstSpecValue,
...initData,
}
: Object.assign({}, initItem); // 继承fist参数
if (callback) {
callback(specSecond);
}
specSecond.secondSpecId = secondSpec;
specSecond.secondSpecValue = secondItem.secondSpecValue;
// specSecond.uuid = UUID.createUUID();
dataSource.push(specSecond);
});
};
export const createProductData = ({ firstValues, secondValues, firstSpecId, secondSpecId }) => {
console.log('=============>firstValues, secondValues', firstValues, secondValues);
const countRowSpan = {};
const dataSource = [];
if (firstValues.length) {
firstValues.forEach((fisrtItem, index) => {
const specFirst = createInitProduct(fisrtItem, true);
specFirst.firstSpecId = firstSpecId;
specFirst.firstSpecValue = fisrtItem.firstSpecValue;
// specFirst.uuid = UUID.createUUID();
if (secondSpecId && secondValues.length) {
createSecondProduct(secondValues, specFirst, secondSpecId, dataSource, specSecond => {
if (!countRowSpan[specFirst.firstSpecValue]) {
countRowSpan[specFirst.firstSpecValue] = true;
specSecond.rowSpanCount = secondValues.length;
}
});
return;
}
dataSource.push(specFirst);
});
} else if (secondValues.length) {
createSecondProduct(secondValues, initData, secondSpecId, dataSource);
} else {
const specFirst = createInitProduct();
dataSource.push(specFirst);
}
console.log('dataSource===========>', dataSource);
return dataSource;
};
export const isUrl = path => {
// eslint-disable-next-line no-useless-escape
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
return reg.test(path);
};
export const batchTableSourceData = ({ batchItem, tableData, bacthSecon, bacthFirst }) => {
const batchItemKey = Object.keys(batchItem);
return tableData.map((item, index) => {
if (!bacthFirst && !bacthSecon) {
batchItemKey.forEach(key => {
item[key] = batchItem[key] || null;
});
}
if (bacthFirst && !bacthSecon) {
batchItemKey.forEach(key => {
if (item.firstSpecValue === bacthFirst) {
item[key] = batchItem[key] || null;
}
});
}
if (!bacthFirst && bacthSecon) {
batchItemKey.forEach(key => {
if (item.secondSpecValue === bacthSecon) {
item[key] = batchItem[key] || null;
}
});
}
if (bacthFirst && bacthSecon) {
batchItemKey.forEach(key => {
if (item.firstSpecValue === bacthFirst && item.secondSpecValue === bacthSecon) {
item[key] = batchItem[key] || null;
}
});
}
return item;
});
};
const filterItems = (type, props) => {
const { infoSpecData, serviceItem, infoImageData, infoMation, settlementItem } = props;
console.log('===========>serviceItem', serviceItem);
console.log('===========>settlementItem', settlementItem);
const { imageList = {}, commonImageList } = infoImageData;
return infoSpecData.items.map(item => {
const imgList = imageList[item.firstSpecValue] || [];
item.imageList = imgList.length ? imgList : commonImageList;
item.firstSpecId = infoSpecData.firstSpecId;
item.secondSpecId = infoSpecData.secondSpecId || null;
if (type === 4) {
item.serviceItem = {
...serviceItem,
...settlementItem,
description: infoMation.description || '',
};
}
return item;
});
};
export const filterSendData = (type, params) => {
console.log('===============>生成数据', params);
const { infoMation, infoImageData } = params;
const items = filterItems(type, params);
return {
type,
items,
name: infoMation.name,
brandId: infoMation.brandId || null,
character: infoMation.character,
categoryId: infoMation.categoryId[2],
afterAddressId: infoMation.afterAddressId,
detailImageList: infoImageData.detailImageList,
commonImageList: infoImageData.commonImageList,
};
};
import { parse } from 'querystring'; import { parse } from 'querystring';
import pathRegexp from 'path-to-regexp'; import pathRegexp from 'path-to-regexp';
import moment from 'moment';
/* eslint no-useless-escape:0 import/prefer-default-export:0 */ /* eslint no-useless-escape:0 import/prefer-default-export:0 */
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
...@@ -52,3 +53,6 @@ export function toThousands(data, num) { ...@@ -52,3 +53,6 @@ export function toThousands(data, num) {
} }
return result; return result;
} }
export const formatTime = (time, crm = 'YYYY-MM-DD HH:mm:ss') => time.format(crm);
export const resetTime = (time, crm = 'YYYY-MM-DD HH:mm:ss') => moment(time, crm);
/**
* [UUID 生成UUID]
*/
class UUID {
constructor() {
this.id = this.createUUID();
}
/**
* [valueOf 重写 valueOf 方法]
* @return {String} [description]
*/
valueOf() {
return this.id;
}
/**
* [valueOf 重写 toString 方法]
* @return {String} [description]
*/
toString() {
return this.id;
}
/**
* [createUUID 创建UUID]
* @return {String} [description]
*/
createUUID() {
const dg = new Date(1582, 10, 15, 0, 0, 0, 0);
const dc = new Date();
const t = dc.getTime() - dg.getTime();
const h = '';
const tl = this.getIntegerBits(t, 0, 31);
const tm = this.getIntegerBits(t, 32, 47);
const thv = `${this.getIntegerBits(t, 48, 59)}1`; // version 1, security version is 2
const csar = this.getIntegerBits(this.rand(4095), 0, 7);
const csl = this.getIntegerBits(this.rand(4095), 0, 7);
const n =
this.getIntegerBits(this.rand(8191), 0, 7) +
this.getIntegerBits(this.rand(8191), 8, 15) +
this.getIntegerBits(this.rand(8191), 0, 7) +
this.getIntegerBits(this.rand(8191), 8, 15) +
this.getIntegerBits(this.rand(8191), 0, 15); // this last number is two octets long
return tl + h + tm + h + thv + h + csar + csl + h + n;
}
/**
* [getIntegerBits description]
* @param {[type]} val [description]
* @param {[type]} start [description]
* @param {[type]} end [description]
* @return {[type]} [description]
*/
getIntegerBits(val, start, end) {
const base16 = this.returnBase(val, 16);
const quadArray = [];
let quadString = '';
let i = 0;
for (i = 0; i < base16.length; i++) {
quadArray.push(base16.substring(i, i + 1));
}
for (i = Math.floor(start / 4); i <= Math.floor(end / 4); i++) {
if (!quadArray[i] || quadArray[i] === '') {
quadString += '0';
} else {
quadString += quadArray[i];
}
}
return quadString;
}
/**
* [returnBase description]
* @param {[type]} number [description]
* @param {[type]} base [description]
* @return {[type]} [description]
*/
returnBase(number, base) {
const convert = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
];
let output;
if (number < base) {
output = convert[number];
} else {
const MSD = `${Math.floor(number / base)}`;
const LSD = number - MSD * base;
if (MSD >= base) {
output = this.returnBase(MSD, base) + convert[LSD];
} else {
output = convert[MSD] + convert[LSD];
}
}
return output;
}
/**
* [rand description]
* @param {[type]} max [description]
* @return {[type]} [description]
*/
// eslint-disable-next-line class-methods-use-this
rand(max) {
return Math.floor(Math.random() * max);
}
}
export default new UUID();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment