Commit 1aa6a80d authored by 武广's avatar 武广

Merge branch 'master' of git.quantgroup.cn:ui/merchant-manage-ui into feature/ue-upgrade

parents ed8dc42b a0ac8049
...@@ -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'],
}, },
......
...@@ -206,11 +206,23 @@ export default { ...@@ -206,11 +206,23 @@ export default {
icon: 'smile', icon: 'smile',
component: './password', component: './password',
}, },
{
path: '/chainStoreManage',
name: 'chainStoreManage',
icon: 'smile',
component: './chainStoreManage',
},
// { // {
// path: '/GoodsManage-new', // path: '/GoodsManage-new',
// name: 'GoodsManageNew', // name: 'GoodsManageNew',
// component: './GoodsManage-new', // component: './GoodsManage-new',
// }, // },
{
title: '服务商品改造-商品模块',
path: '/ServiceGoods/:id',
name: 'ServiceGoods',
component: './ServiceGoods/index',
},
{ {
component: './404', component: './404',
}, },
......
...@@ -11,7 +11,7 @@ const envAPi = { ...@@ -11,7 +11,7 @@ const envAPi = {
prologueDomain: 'https://mall-yxm.liangkebang.net', prologueDomain: 'https://mall-yxm.liangkebang.net',
// qiniuHost: 'https://appsync.lkbang.net', // qiniuHost: 'https://appsync.lkbang.net',
qiniuHost: 'https://kdspstatic.q-gp.com/', qiniuHost: 'https://kdspstatic.q-gp.com/',
opapiHost: 'https://opapi-yxm.liangkebang.net', opapiHost: 'https://opapi-xyqb.liangkebang.net',
}; };
const prodApi = { const prodApi = {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -67,9 +67,11 @@ ...@@ -67,9 +67,11 @@
"path-to-regexp": "^3.1.0", "path-to-regexp": "^3.1.0",
"qs": "^6.9.0", "qs": "^6.9.0",
"react": "^16.8.6", "react": "^16.8.6",
"react-bmapgl": "^0.2.7",
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-helmet": "^5.2.1", "react-helmet": "^5.2.1",
"react-router-dom": "^5.1.2",
"react-sortablejs": "^6.0.0", "react-sortablejs": "^6.0.0",
"slash2": "^2.0.0", "slash2": "^2.0.0",
"sortablejs": "^1.13.0", "sortablejs": "^1.13.0",
...@@ -125,4 +127,4 @@ ...@@ -125,4 +127,4 @@
"scripts/**/*.js" "scripts/**/*.js"
], ],
"author": "congmin.hao" "author": "congmin.hao"
} }
\ No newline at end of file
import React, { useState } from 'react';
import { Map, Marker, ZoomControl, CityListControl } from 'react-bmapgl';
import { Modal, Input } from 'antd';
export default props => {
const { visible, onSetPoint, onCancel, lngLat } = props;
let defaultLnglat = { lng: 116.404449, lat: 39.914889 };
if (lngLat) {
defaultLnglat = lngLat;
}
const [lnglat, setLnglat] = useState(defaultLnglat);
const [lnglatText, setLnglatText] = useState(`${defaultLnglat.lng},${defaultLnglat.lat}`);
const handleOk = () => {
onSetPoint(lnglatText);
onCancel(true);
};
const handleCancle = () => onCancel(true);
const onGetPoint = e => {
setLnglat({
lng: e.latlng.lng,
lat: e.latlng.lat,
});
setLnglatText(`${e.latlng.lng},${e.latlng.lat}`);
};
return (
<Modal
title="门店信息"
visible={visible}
width="800px"
onOk={() => handleOk()}
onCancel={() => handleCancle()}
>
<div style={{ marginBottom: '20px' }}>
<Input value={lnglatText} placeholder="点击地图选择经纬度" />
</div>
<div style={{ width: '100%', height: '360px' }}>
<Map
center={lnglat}
enableScrollWheelZoom
enableDoubleClickZoom
coordType="gcj02"
onClick={e => onGetPoint(e)}
zoom={15}
>
<Marker
position={lnglat}
Icon
coordType="gcj02"
autoViewport
viewportOptions={{
zoomFactor: -12,
}}
/>
<CityListControl />
<ZoomControl />
</Map>
</div>
</Modal>
);
};
...@@ -195,6 +195,13 @@ class goodsManage extends Component { ...@@ -195,6 +195,13 @@ class goodsManage extends Component {
<InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} /> <InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} />
</FormItem> </FormItem>
</FormItem> </FormItem>
<FormItem label="商品类型" name="productType">
<Select style={selectW} placeholder="请选择商品类型">
<Option value={1}>实体商品</Option>
<Option value={2}>虚拟商品</Option>
<Option value={4}>服务类商品</Option>
</Select>
</FormItem>
<FormItem name="thirdSkuNo" label="第三方SKU编码"> <FormItem name="thirdSkuNo" label="第三方SKU编码">
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} /> <Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />
</FormItem> </FormItem>
......
...@@ -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],
......
...@@ -22,9 +22,10 @@ import { ...@@ -22,9 +22,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,
...@@ -50,6 +51,9 @@ class goodsManage extends Component { ...@@ -50,6 +51,9 @@ class goodsManage extends Component {
templateList: [], templateList: [],
stockSkuIds: [], stockSkuIds: [],
isType: '', isType: '',
serviceVisble: false,
serviceData: {},
}; };
currentLog = null; currentLog = null;
...@@ -117,7 +121,7 @@ class goodsManage extends Component { ...@@ -117,7 +121,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;
...@@ -320,6 +324,43 @@ class goodsManage extends Component { ...@@ -320,6 +324,43 @@ class goodsManage extends Component {
}); });
}; };
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 = () => {
console.log('============>open');
this.serviceVisbleClose(true);
};
serviceVisbleClose = (flag, refresh) => {
this.setState({
serviceVisble: flag,
serviceData: {},
});
if (refresh) {
this.handleSearch();
}
};
render() { render() {
const { const {
goodsManage: { tableData = {} }, goodsManage: { tableData = {} },
...@@ -353,6 +394,7 @@ class goodsManage extends Component { ...@@ -353,6 +394,7 @@ class goodsManage extends Component {
// addSpu={() => this.setState({ createVisible: true, initData: {} })} // addSpu={() => this.setState({ createVisible: true, initData: {} })}
selectNum={selectedRowKeys.length} selectNum={selectedRowKeys.length}
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}>
...@@ -438,6 +480,16 @@ class goodsManage extends Component { ...@@ -438,6 +480,16 @@ 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}
onChange={this.serviceVisbleClose}
SourceData={this.state.serviceData}
shopList={this.shopList}
categoryList={this.state.treeData}
virtualCategoryList={this.state.virtualTreeData}
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 = [
{ {
...@@ -37,7 +38,7 @@ export function column() { ...@@ -37,7 +38,7 @@ export function column() {
width: 125, width: 125,
align: 'center', align: 'center',
render: (_, row) => { render: (_, row) => {
if (row.type !== 1) { if (row.type === 2) {
return ( return (
<Badge <Badge
count={ count={
...@@ -160,7 +161,13 @@ export function column() { ...@@ -160,7 +161,13 @@ export function column() {
type="primary" type="primary"
size="small" size="small"
className={styles.button} className={styles.button}
onClick={() => this.onUpdateInfo(row)} onClick={() => {
if (row.type === 4) {
this.serviceVisbleChange(row);
} else {
this.onUpdateInfo(row);
}
}}
> >
修改 修改
</Button> </Button>
...@@ -203,3 +210,138 @@ export const productTypeList = [ ...@@ -203,3 +210,138 @@ 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, id } = 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);
}
// origin.skuItemResultList.push(item);
return orgin;
},
{
firstSpecValue: [],
secondSpecValue: [],
firstDuplicate: [],
secondDuplicate: [],
skuItemResultList: [],
},
);
const filterCarouseList = (carouseList = []) =>
carouseList.reduce((origin, itemImg) => {
if (itemImg.skuSpecImageList.length) {
origin[itemImg.specValue || 'NOT'] = itemImg.skuSpecImageList || [];
}
return origin;
}, {});
const filterServiceItem = (type, serviceItem) => {
if (type !== 4) {
return {};
}
console.log(serviceItem);
return {
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, // 接待数量
},
};
};
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 = filterServiceItem(data.productType, serviceItem);
const specsParams = data.specs.reduce((origin, item) => {
origin[item.specId] = item.specValues.map(child => child.value);
return origin;
}, {});
let commonImageList = data.commonImageList || [];
let cardImageList = [];
if (data.productType === 4) {
const [firstImg, ...towAfterImgList] = oneItem.imageList || [];
cardImageList = towAfterImgList || [];
commonImageList = [firstImg];
}
const SourceData = {
state: row.state,
id: data.id,
productType: data.productType,
pageProductType: row.productType,
infoMation: {
...specsParams,
brandId: data.brandId,
supplierId: data.supplierId,
character: data.character,
name: data.name,
categoryId: [data.firstCategoryId, data.secondCategoryId, data.thirdCategoryId],
description: serviceItem.description || null,
afterAddressId: data.afterAddressId || null,
},
infoSpecData: {
firstSpec: oneItem.firstSpec,
firstSpecId: oneItem.firstSpecId,
firstSpecValue: orginSpecItem.firstSpecValue,
secondSpec: oneItem.secondSpec,
secondSpecId: oneItem.secondSpecId,
secondSpecValue: orginSpecItem.secondSpecValue,
},
infoImageData: {
imageList,
cardImageList,
commonImageList,
detailImageList: data.detailImageList,
},
skuList: data.skuList,
...servesItemParams,
};
console.log('SourceData===============>', SourceData);
return SourceData;
};
/** ********************************************************************************* */
/** ********************************************************************************* */
/** ********************************************************************************* */
.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;
}
}
}
.pictureWrapper {
position: relative;
}
.pullImage {
position: absolute;
top: 30px;
right: 40px;
}
import React, { useState } 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 {
batchRole,
rowIndex,
dataIndex,
onBlurEvent,
onClickEvent,
record,
role,
roleProps,
title,
type,
batchProps,
disabeldRender,
...options
} = props;
if (type === 'input') {
return (
<Input
{...roleProps}
{...options}
style={{ width: '100%' }}
placeholder={`请输入${title}`}
onBlur={e => onBlurEvent(e.target.value, dataIndex, rowIndex)}
/>
);
}
if (type === 'btnText') {
return (
<Popover content={record.name} trigger="hover">
<Button type="text">查看名称</Button>
</Popover>
);
}
if (type === 'text') {
return <span className="ant-form-text">{record[dataIndex]}</span>;
}
if (type === 'option') {
return (
<>
{record.skuLink && (
<Button type="primary" size="small" onClick={() => onClickEvent('cloneImg', record)}>
拉图片
</Button>
)}
{roleProps.isJDGoods && (
<Button
type="primary"
size="small"
onClick={() => onClickEvent('updateName', { ...record, dataIndex, rowIndex })}
disabled={props.disabled}
>
修改sku名称
</Button>
)}
</>
);
}
return (
<InputNumber
{...roleProps}
{...options}
style={{ width: '100%' }}
placeholder={`请输入${title}`}
onBlur={e => {
onBlurEvent(e.target.value, dataIndex, rowIndex);
}}
/>
);
};
import { Input, Modal, Button, Form, Table, InputNumber } 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 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(() => {
form.setFieldsValue({
tableList: initData,
});
setDataSource([...initData]);
}, [initData]);
// const handleSave = row => {
// form.setFieldsValue({
// tableList: [...row],
// });
// const dataList = [...dataSource];
// dataList[rowIndex][dataIndex] = value;
// setDataSource([...dataList]);
// };
const handleSave = (value, dataIndex, rowIndex) => {
// form.setFieldsValue({
// [`tableList[${rowIndex}][${dataIndex}]`]: value,
// });
const dataList = [...dataSource];
dataList[rowIndex][dataIndex] = value;
setDataSource([...dataList]);
};
const onCheck = async () => {
try {
await form.validateFields();
const newTableList = [...dataSource].map(item => {
const { rowSpanCount, option, ...childItem } = item;
return childItem;
});
console.log('===============>newTableList', newTableList);
return newTableList;
} catch (errorInfo) {
console.log(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,
});
};
const onClickEvent = () => {};
useImperativeHandle(ref, () => ({
onCheck,
form,
}));
// 根据这里做判断渲染表格
const columns = defaultColumns
.filter(item => !item.role || item.role.includes(customer.productType))
.map((col, colIndex) => ({
...col,
align: 'center',
render(text, record, rowIndex) {
const {
dataIndex,
title,
editable,
roleRules = {},
roleProps = {},
inputType,
...otherParams
} = col;
const disabled = Boolean(col.disabeldRender ? col.disabeldRender(record) : false);
const childrenElement = (
<Form.Item
style={{ margin: 0 }}
key={`${dataIndex}_${rowIndex}`}
name={['tableList', rowIndex, dataIndex]}
initialValue={record[dataIndex] || null}
rules={[{ required: roleRules.required, message: `请输入${title}.` }]}
>
<CreateFormInput
{...otherParams}
title={title}
type={inputType}
record={record}
rowIndex={rowIndex}
roleProps={roleProps}
dataIndex={dataIndex}
disabled={disabled}
onBlurEvent={handleSave}
onClickEvent={onClickEvent}
/>
</Form.Item>
);
const rowProps = { children: childrenElement, props: {} };
if (dataIndex === 'firstSpecValue' && mergeTable) {
const rowSpan = record.rowSpanCount;
rowProps.props.rowSpan = rowSpan || 0;
}
return rowProps;
},
}));
return (
<>
<Form form={form} scrollToFirstError component={false}>
{/* <Button onClick={onCheck}>测试</Button> */}
<EditableContext.Provider value={form}>
<Table
scroll={{ y: 300, x: 1000 }}
height={300}
pagination={false}
size="small"
bordered
dataSource={dataSource}
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,
newCategoryList,
categoryList,
virtualCategoryList,
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();
const specs = [];
// 非服务类商品过滤specs数据
if (!customer.isCard) {
specListData.forEach(item => {
const specValues = values[item.specId];
// 判断是否包含改参数
if (specValues) {
specs.push({ specId: item.specId, specValues });
}
delete values[item.specId];
});
}
values.specs = specs;
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]);
return (
<Form
{...formItemLayout}
form={form}
name="register"
initialValues={{
brandId: null,
name: '',
categoryId: [],
description: '',
}}
scrollToFirstError
>
{/* <Button type="primary" onClick={onCheck}>
测试
</Button> */}
<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={newCategoryList[customer.productType]}
/>
</Form.Item>
{!customer.isCard && (
<Form.Item
name="brandId"
label="商品品牌"
rules={[{ required: true, message: '请选择商品品牌!' }]}
extra="若需新增品牌请联系业务员"
>
<Select
disabled={customer.isService}
showSearch
placeholder="请选择商品品牌"
filterOption={fileterBrandOptions}
>
{customer.productType === 2
? CreateSelectOption(noreBrandList)
: CreateSelectOption(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="商品卖点"
rules={[{ required: true, message: '请输入商品卖点!', whitespace: true }]}
>
<Input
maxLength={50}
placeholder="卖点最优可填写3个词,12个字。前后用空格加竖杠分隔,例: 莹莹剔透 | 粒粒优选 | 易煮易熟"
/>
</Form.Item>
)}
{!customer.isCard && (
<Form.Item
name="afterAddressId"
label="售后地址"
rules={[{ required: true, message: '请选择售后地址!' }]}
>
<Select showSearch placeholder="请选择售后地址" filterOption={fileterBrandOptions}>
{(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 name={item.specId} key={item.specId} label={item.specName}>
<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: '请输入描述,100字以内!' }]}
>
<Input.TextArea showCount maxLength={100} placeholder="请输入描述, 100字以内!" />
</Form.Item>
) : null}
</Form>
);
});
export default FormInformationBasic;
import { ConsoleSqlOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Select, Space, Modal, InputNumber, notification } from 'antd';
import React, {
useState,
forwardRef,
useImperativeHandle,
useRef,
useEffect,
useContext,
} from 'react';
import { formItemLayout, StaticColumns } from '../config';
import EditFormTable from './EditFormTable';
import {
createProductData,
cleanArray,
filterCoverItemList,
batchTableSourceData,
fliterSkuListSortData,
filterSkuNotIdList,
} from '../utils';
import { ServiceContext } from '../context';
import { SelectTemplate } from './CommonTemplate';
const initSpecReced = () => ({
firstSpec: '',
firstSpecId: null,
firstSpecValue: [],
secondSpec: '',
secondSpecId: null,
secondSpecValue: [],
});
const validatorSpecValue = (value, list, index, specName) => {
const checkList = list.filter((item, ind) => {
if (ind === index) {
return false;
}
return item[specName] === value;
});
return checkList.length;
};
const SpecificationTemplate = (props, _) => {
const {
form,
formName,
label,
specName,
specList,
selectName,
onChange = () => {},
specDataList,
} = props;
const customer = useContext(ServiceContext);
const handleChange = (val, option) => {
const optionSpecName = option ? option.specName : null;
form.setFieldsValue({ [selectName]: optionSpecName });
// onChange();
};
const inputOnblurEvent = event => {
if (event.target.value) {
onChange();
}
};
const bundlePlusAddSpecEvent = addCallback => {
const specId = form.getFieldValue(formName);
if (!specId) {
Modal.warning({
maskClosable: true,
title: `请先选择${label}!`,
});
return;
}
addCallback();
onChange();
};
const bundlePlusRemoveSpecEvent = (removeCallback, fieldName) => {
removeCallback(fieldName);
const timer = setTimeout(() => {
onChange();
clearTimeout(timer);
}, 500);
};
return (
<>
<Form.Item name={formName} label={label}>
<Select
disabled={customer.isEdit}
allowClear
options={specList}
style={{ width: 200 }}
fieldNames={{ label: 'specName', value: 'specId' }}
placeholder={`请选择${label}`}
showSearch
filterOption={(input, option) =>
option.specName.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
onChange={handleChange}
/>
</Form.Item>
<Form.Item name={selectName} hidden>
<Input hidden />
</Form.Item>
<Form.List name={specName}>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Form.Item key={field.key} noStyle shouldUpdate={(prevValues, curValues) => false}>
{() => (
<Space key={field.key} align="baseline">
<Form.Item
style={{ marginLeft: 10 }}
name={[field.name, specName]}
rules={[
{ required: true, message: '请输入规格名称' },
{
message: '规格名不能重复!',
validator(rule, value, callback) {
const checkList = form.getFieldValue(specName);
const check = validatorSpecValue(
value,
cleanArray(checkList),
index,
specName,
);
return check ? callback('规格名不能重复!') : callback();
},
},
]}
>
<Input
disabled={specDataList[index] && specDataList[index].id}
onBlur={inputOnblurEvent}
placeholder="请输入规格名称"
/>
</Form.Item>
{!(specDataList[index] && specDataList[index].id) && (
<MinusCircleOutlined
onClick={() => bundlePlusRemoveSpecEvent(remove, field.name)}
/>
)}
</Space>
)}
</Form.Item>
))}
{fields.length < 3 && (
<Form.Item noStyle>
<Button
style={{ marginLeft: 10, marginBottom: 24 }}
type="dashed"
onClick={() => bundlePlusAddSpecEvent(add)}
icon={<PlusOutlined />}
/>
</Form.Item>
)}
</>
)}
</Form.List>
</>
);
};
const CreateBatchFormItems = ({ specInitValue, batchChange, editRef, defaultColumns }) => {
const customer = useContext(ServiceContext);
const formItems = defaultColumns
.filter(item => item.batchRole && item?.batchRole.includes(customer.productType))
.map((item, index) => (
<Form.Item
noStyle
key={`${item.dataIndex}`}
name={['batchItem', item.dataIndex]}
initialValue={null}
>
<InputNumber {...(item.batchProps || {})} placeholder={item.title} />
</Form.Item>
));
return (
<>
{formItems.length ? (
<Space style={{ marginBottom: 20 }}>
<SelectTemplate
width={150}
noSty
formName="bacthFirst"
placeholder={specInitValue.firstSpec}
dataList={specInitValue.firstSpecValue}
fieldNames={{ label: 'firstSpecValue', value: 'firstSpecValue' }}
/>
<SelectTemplate
noSty
width={150}
formName="bacthSecon"
placeholder={specInitValue.secondSpec}
dataList={specInitValue.secondSpecValue}
fieldNames={{ label: 'secondSpecValue', value: 'secondSpecValue' }}
/>
{formItems.concat(
<Button key="batch" type="primary" disabled={customer.isService} onClick={batchChange}>
批量设置
</Button>,
)}
</Space>
) : null}
</>
);
};
const FormPriceOrStock = forwardRef((props, ref) => {
const { specList, editData, skuList = [], onSpecChange } = props;
const editRef = useRef(null);
const customer = useContext(ServiceContext);
const [form] = Form.useForm();
const [specInitValue, setSpecInitValue] = useState(initSpecReced());
const [defaultColumns, setDefaultColumns] = useState([]);
const [tableData, setTableData] = useState([]);
const [mergeTable, setMergeTable] = useState(false);
const onCheck = async () => {
try {
const values = await form.validateFields();
const items = await editRef.current.onCheck();
if (items) {
return { ...values, items, temp: 'infoSpecData' };
}
notification.open({
message: '提示',
description: '请生成商品信息!',
});
return null;
} catch (errorInfo) {
return null;
}
};
const CreateColumnsEvent = specData => {
const columsData = [];
if (specData.firstSpec && specData.firstSpecValue.length) {
columsData.push({
title: specData.firstSpec,
dataIndex: 'firstSpecValue',
inputType: 'text',
});
}
if (specData.secondSpec && specData.secondSpecValue.length) {
columsData.push({
title: specData.secondSpec,
dataIndex: 'secondSpecValue',
inputType: 'text',
});
}
const dynamicColumns = [...columsData, ...StaticColumns(customer)];
setDefaultColumns(dynamicColumns);
};
const onFinish = async () => {
try {
const values = await form.validateFields();
const cleanValues = {
firstValues: cleanArray(values.firstSpecValue || []),
secondValues: cleanArray(values.secondSpecValue || []),
firstSpecId: values.firstSpecId,
secondSpecId: values.secondSpecId,
};
const { inIdList: fisrtInIdList, noIdList: fisrtNoIdList } = filterSkuNotIdList(
cleanValues.firstValues,
);
const { inIdList: secndInIdList, noIdList: secndNoIdList } = filterSkuNotIdList(
cleanValues.secondValues,
);
const createSkuList = createProductData(
{
firstSpecId: cleanValues.firstSpecId,
secondSpecId: cleanValues.secondSpecId,
fisrtInIdList,
secndInIdList,
fisrtNoIdList,
secndNoIdList,
},
customer.isEdit,
);
CreateColumnsEvent(values);
if (!cleanValues.firstSpecId && !createSkuList.secondSpecId) {
setTableData([...createSkuList]);
return;
}
setMergeTable(Boolean(cleanValues.secondValues.length));
setTableData(fliterSkuListSortData([...skuList, ...createSkuList]));
} catch (error) {
console.log(error);
}
};
const batchChange = () => {
const batchItem = form.getFieldValue('batchItem');
const bacthFirst = form.getFieldValue('bacthFirst');
const bacthSecon = form.getFieldValue('bacthSecon');
const resetObject = batchTableSourceData({ batchItem, tableData, bacthSecon, bacthFirst });
setTableData(resetObject);
};
const onSpecificationEvent = async () => {
try {
const values = await form.validateFields();
const cleanValues = {
firstSpec: values.firstSpec,
firstSpecId: values.firstSpecId,
firstSpecValue: cleanArray(values.firstSpecValue),
secondSpec: values.secondSpec,
secondSpecId: values.secondSpecId,
secondSpecValue: cleanArray(values.secondSpecValue),
};
setSpecInitValue(cleanValues);
CreateColumnsEvent(cleanValues);
return cleanValues;
} catch (error) {
console.log(error);
}
return null;
};
const firstOnChangeEvent = async () => {
const cleanValues = await onSpecificationEvent();
if (cleanValues) {
const firstSpecValueList = cleanArray(cleanValues.firstSpecValue).map(
item => item.firstSpecValue,
);
onSpecChange(firstSpecValueList);
}
};
const seconOnChangeEvent = async () => {
await onSpecificationEvent();
};
useImperativeHandle(ref, () => ({
onCheck,
reset: () => {
form.resetFields();
setDefaultColumns([]);
setTableData([]);
setMergeTable(false);
setSpecInitValue(initSpecReced());
},
}));
useEffect(() => {
if (customer.isEdit) {
if (!editData) return;
form.setFieldsValue(editData); // 设置规格数据
firstOnChangeEvent(); // 触发成底部动态表格数据
setSpecInitValue(editData); // 缓存规格数据
CreateColumnsEvent(editData);
setMergeTable(Boolean(editData.secondSpecValue.length));
setTableData(fliterSkuListSortData(skuList));
}
}, [customer.isEdit, editData]);
return (
<>
<Form form={form} autoComplete="off" initialValues={initSpecReced()}>
<SpecificationTemplate
form={form}
label="一级规格"
selectName="firstSpec"
formName="firstSpecId"
specName="firstSpecValue"
onChange={firstOnChangeEvent}
specList={specList}
specDataList={specInitValue.firstSpecValue}
/>
<SpecificationTemplate
form={form}
label="二级规格"
selectName="secondSpec"
formName="secondSpecId"
specName="secondSpecValue"
onChange={seconOnChangeEvent}
specList={specList}
specDataList={specInitValue.secondSpecValue}
/>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 20 }}>
<Button type="primary" disabled={customer.isService} onClick={onFinish}>
生成商品信息
</Button>
</div>
<CreateBatchFormItems
batchChange={batchChange}
specInitValue={specInitValue}
defaultColumns={defaultColumns}
/>
</Form>
<EditFormTable
ref={editRef}
mergeTable={mergeTable}
setTableData={setTableData}
defaultColumns={defaultColumns}
initData={tableData}
/>
</>
);
});
export default FormPriceOrStock;
import { Form, Input, Select, Checkbox, DatePicker } from 'antd';
import React, { useEffect, forwardRef, useImperativeHandle, useContext } from 'react';
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="purchaseTime" label="购买时间" {...rangeConfig}>
<RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />
</Form.Item>
<Form.Item name="useTime" 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: false, 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 FormRuleVPictures = forwardRef((props, ref) => {
const { editData, specKeyItem } = props;
const [imageList, setImageList] = useState({});
const [commonImageList, setCommonImageList] = useState([]);
const [detailImageList, setDetailImageList] = useState([]);
const [cardImageList, setCardImageList] = useState([]);
const [form] = Form.useForm();
const customer = useContext(ServiceContext);
useEffect(() => {
if (customer.isEdit) {
if (editData) {
setImageList(editData.imageList);
setCardImageList(editData.cardImageList);
setCommonImageList(editData.commonImageList); // 编辑状态下设置公共图
setDetailImageList(editData.detailImageList); // 编辑状态下设置详情图
const editParams = {
commonImageList: editData.commonImageList,
detailImageList: editData.detailImageList,
};
if (customer.isCard) {
editParams.cardImageList = editData.cardImageList;
} else {
editParams.imageList = editData.imageList;
}
const timer = setTimeout(() => {
form.setFieldsValue(editParams);
clearTimeout(timer);
});
}
}
}, [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) {
console.log(errorInfo);
return null;
}
};
useImperativeHandle(ref, () => ({
onCheck,
setFieldsValue: form.setFieldsValue,
getFieldsValue: form.getFieldsValue,
reset: () => {
form.resetFields();
setImageList({});
setCardImageList([]);
setCommonImageList([]);
setDetailImageList([]);
},
}));
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 onCardSuccessImageList = imgList => {
setCardImageList(imgList);
form.setFieldsValue({
cardImageList: imgList,
});
};
const onDetailSuccessImageList = imgList => {
setDetailImageList(imgList);
form.setFieldsValue({
detailImageList: imgList,
});
};
const [{ imgConfig }] = TaskList.filter(item => item.type === customer.productType);
return (
<Form
form={form}
{...formItemLayout}
initialValues={{
commonImageList: [],
cardImageList: [],
imageList: {},
detailImageList: [],
}}
>
<Form.Item
name="commonImageList"
label={imgConfig.commonImageList.title}
extra={imgConfig.commonImageList.renderExtra(commonImageList.length)}
rules={[
{
required: imgConfig.commonImageList.rule,
type: 'array',
message: '请上传图片!',
},
{
validateTrigger: 'submit',
validator(rule, value, callback) {
if (customer.productType !== 1) {
return callback();
}
// 规格图不为必传,如未上传规格图时,公共图为必传图
const checkImageList = form.getFieldValue('imageList');
const check = Object.keys(checkImageList || {}).length;
return check || value.length ? callback() : callback('请上传封面图片');
},
},
]}
>
<UploadImage
disabled={customer.isService}
name="commonImageList"
limit={imgConfig.commonImageList.limit}
pictures={commonImageList}
setPictureList={list => onCommonSuccessEvent(list)}
/>
</Form.Item>
{customer.isCard && (
<Form.Item
name="cardImageList"
label={imgConfig.cardImageList.title}
extra={imgConfig.cardImageList.renderExtra(cardImageList.length)}
rules={[
{
required: imgConfig.cardImageList.rule,
type: 'array',
message: `请上传${imgConfig.cardImageList.title}!`,
},
]}
>
<UploadImage
disabled={customer.isService}
name="cardImageList"
limit={imgConfig.cardImageList.limit}
pictures={cardImageList}
setPictureList={list => onCardSuccessImageList(list)}
/>
</Form.Item>
)}
{!customer.isCard &&
Object.keys(imageList).map(key => (
<div key={key} className={commonStyle.pictureWrapper}>
<Form.Item
name={['imageList', key]}
key={key}
label={`商品图片(${key})`}
rules={[
{ required: imgConfig.imageList.rule, type: 'array', message: '请上传图片!' },
]}
extra={imgConfig.imageList.renderExtra()}
>
<UploadImage
disabled={customer.isService}
name={key}
limit={11}
pictures={imageList[key]}
setPictureList={list => onPictureSuccessEvent(list, key)}
/>
</Form.Item>
<Button className={commonStyle.pullImage} type="primary">
拉取公共图
</Button>
</div>
))}
<Form.Item
name="detailImageList"
label={imgConfig.detailImageList.title}
rules={[
{
type: 'array',
required: imgConfig.detailImageList.rule,
message: `请上传${imgConfig.detailImageList.title}!`,
},
]}
extra={imgConfig.detailImageList.renderExtra()}
>
<UploadImage
disabled={customer.isService}
limit={imgConfig.detailImageList.limit}
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: '请输入套餐内容, 1000字以内!!' }]}
>
<Input.TextArea showCount maxLength={1000} placeholder="请输入套餐内容, 1000字以内!!" />
</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, Button } from 'antd';
import React, { useState, useEffect, useRef, forwardRef } 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 }}>上传图片</div>
</div>
);
const UploadImage = forwardRef((props, ref) => {
const {
name = `${Date.now()}`,
limit = null,
multiple = true,
disabled,
uploadParams,
pictures = [],
setPictureList = () => {},
...imgOptions
} = 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 => {
// setFileList(imgFile);
const imgList = imgFile.map(item => item.url);
setPictureList(imgList);
props.onChange(imgList);
};
const handleRemove = file => {
const freshFiles = fileList?.filter(ele => ele.uid !== file.uid);
bundleChange(freshFiles);
};
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);
if (res.data) {
const proFiles = (res.data || []).map((urlItem, urlIndex) =>
imageLoading(checkFiles[urlIndex], urlItem),
);
const imagList = await Promise.all(proFiles);
const newFiles = [...fileListRef.current, ...imagList];
bundleChange(newFiles);
} else {
notification.warning({
message: '警告',
description: res.msg,
});
}
setUploadLoading(false);
}
} catch (error) {
console.log(error);
setUploadLoading(false);
Modal.warning({
maskClosable: true,
title: '上传失败,请重新尝试!',
});
}
return null;
}),
);
return (
<Spin tip="正在上传..." spinning={uploadLoading} delay={100}>
<Upload
{...uploadParams}
disabled={Boolean(disabled)}
multiple={multiple}
name={name}
customRequest={() => {}}
listType="picture-card"
beforeUpload={defaultBeforeUpload}
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
>
{limit !== null && fileList.length >= 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: {
commonImageList: {
title: '公共滑动图',
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
imageList: {
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
detailImageList: {
title: '详情图',
rule: true,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
},
},
{
name: '虚拟商品',
type: 2,
desc: '无需物流',
hide: true,
imgConfig: {
commonImageList: {
title: '公共滑动图',
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
imageList: {
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
detailImageList: {
title: '详情图',
rule: true,
limit: null,
renderExtra: () => '(图片最大上传2M)',
},
},
},
// {
// name: '电子卡卷',
// type: 3,
// desc: '无需物流',
// hide: true,
// imgConfig: {
// commonImageList: {
// title: '封面图片',
// rule: true,
// limit: 11,
// renderExtra(leng) {
// return `建议尺寸: ##宽##高 (${leng} / 1) `;
// },
// },
// imageList: {
// rule: true,
// limit: 1,
// },
// detailImageList: {
// title: '商品图片',
// rule: true,
// limit: null,
// renderExtra() {
// return '请上传商品图!';
// },
// },
// },
// },
{
name: '电子卡卷',
type: 4,
desc: '无需物流',
imgConfig: {
commonImageList: {
title: '封面图片',
rule: true,
limit: 1,
renderExtra(leng) {
return `建议尺寸: ##宽##高 (${leng} / 1) 封面图第一张 `;
},
},
cardImageList: {
title: '商品图片',
rule: true,
limit: 11,
renderExtra(leng) {
return `建议尺寸: ##宽##高,sku商品轮播图(${leng} / 11)`;
},
},
detailImageList: {
title: '商品详情图',
rule: true,
limit: 30,
renderExtra() {
return '最多上传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 },
disabeldRender: () => customer.isJDGoods || customer.isService,
},
{
title: '佣金费率',
dataIndex: 'commissionRate',
editable: true,
role: [4],
roleRules: { required: false },
roleProps: {
min: 0,
// max: 100,
},
},
{
title: '市场价',
dataIndex: 'marketPrice',
editable: true,
batchRole: [1, 2, 3, 4],
roleProps: {
precision: 2,
min: 0,
},
roleRules: { required: true },
disabeldRender: () => customer.isService,
},
{
title: '销售价',
dataIndex: 'salePrice',
editable: true,
batchRole: [4],
role: [4],
roleRules: { required: true },
roleProps: {
precision: 2,
min: 0,
},
},
{
title: '重量(kg)',
dataIndex: 'weight',
editable: true,
batchRole: [1],
batchProps: {
min: 0,
precision: 3,
max: 999999.999,
},
role: [1],
roleRules: { required: true },
roleProps: {
min: 0,
precision: 3,
max: 999999.999,
},
disabeldRender: () => customer.isService,
},
{
title: '库存',
dataIndex: 'productStock',
editable: true,
role: [1, 2, 4],
batchRole: [1, 2, 4],
batchProps: {
precision: 0,
step: 1,
min: 0,
},
roleProps: {
min: 0,
},
roleRules: { required: true },
disabeldRender: () => customer.isService,
},
{
title: '库存预警',
dataIndex: 'productStockWarning',
editable: true,
batchRole: [1],
role: [1, 4],
roleRules: { required: true },
roleProps: {
min: 0,
precision: 0,
maxLength: 5,
},
disabeldRender: () => customer.isService,
},
{
title: '商品自编码',
dataIndex: 'thirdSkuNo',
editable: true,
role: [1, 2],
inputType: 'input',
roleRules: { required: true },
disabeldRender: () => customer.isService,
},
{
title: '京东链接',
dataIndex: 'skuLink',
editable: true,
role: [1, 2],
inputType: 'input',
roleRules: { required: false },
disabeldRender: () => customer.isService,
},
{
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 },
},
];
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 {
merchantBrandList,
merchantSpecList,
afterSalesAddrsPage,
merchantgetJdPicList,
supplierListQuery,
shopGetBySupplierId,
merchantProductAdd,
merchantProductEdit,
getByProductType,
} from './service';
import { isUrl, filterSendData, clearCurrent } from './utils';
import { ServiceContext } from './context';
/**
* 服务商品改造-商品模块
* @param {*} router options
* @returns ReactDOM
*/
const ServiceGoods = options => {
const { SourceData, categoryList, virtualCategoryList, 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 [afterAddressList, setAfterAddressList] = useState([]); // 售后地址
const [supplierIdList, setSupplierIdList] = useState([]); // 适用们店列表
const [brandList, setBrandList] = useState([]); // 获取商品牌
const [specList, setSpecList] = useState([]); // 规格列表
const [editData, setEditData] = useState({}); // 编辑保存数据
const [newCategoryList, setNewCategoryList] = useState({});
// const [shopList, setShopList] = useState([]); // 供应商列表
const [checkFormList] = useState([basicRef, stockRef, settingRef, settleOtrRef, picturesRef]);
const [specKeyList, setSpecKeyList] = useState([]); // 记录一级规格key字段
const resetForm = () => clearCurrent(checkFormList).forEach(({ current }) => current.reset());
const productChange = task => {
setProductType(task.type);
const timer = setTimeout(() => {
resetForm();
clearTimeout(timer);
}, 1000);
};
const handleCancel = refresh => {
setPageId(null);
setIsEdit(false);
setProductType(4); // 默认写死服务类商品
setEditData({});
setSpecKeyList([]);
resetForm();
options.onChange(false, refresh);
};
// 获取商品牌数据
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 sendMerchantProductHttpRequest = async sendData => {
try {
setPageLoading(true);
const sendAsyncHttpRequest = isEdit ? merchantProductEdit : merchantProductAdd;
const addResponse = await sendAsyncHttpRequest(sendData);
if (addResponse.data) {
message.success(`${isEdit ? '修改' : '添加'}成功!`);
handleCancel(true);
}
setPageLoading(false);
} catch (error) {
console.log(error);
setPageLoading(false);
}
};
const shopGetBySupplierIdResponse = async () => {
if (!supplierIdList.length) {
const result = await shopGetBySupplierId();
setSupplierIdList(result.data);
}
};
const shopGetByProductType = async type => {
console.log(newCategoryList[type]);
if (!newCategoryList[type]?.length) {
const result = await getByProductType(type);
setNewCategoryList({
...newCategoryList,
[type]: result.data || [],
});
}
// console.log(result);
};
const submitEvent = async () => {
const checkPromiseList = clearCurrent(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);
if (isEdit) {
sendData.id = pageId;
}
sendMerchantProductHttpRequest(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) {
setEditData(SourceData);
setPageId(SourceData.id);
setProductType(SourceData.productType);
// changeCheckList(SourceData.productType);
setIsEdit(true);
}
setPageLoading(false);
})();
}, [SourceData]);
useEffect(() => {
if (options.visible) {
shopGetByProductType(productType);
}
}, [productType, options.visible]);
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,
});
}
};
const providerValue = {
pageId,
isEdit,
productType,
isCard: productType === 4,
isService: SourceData.state && SourceData.state !== 4,
isJDGoods: isEdit && SourceData.pageProductType && +SourceData.pageProductType !== 1,
onEventBus,
};
return (
<Modal
title={isEdit ? '修改商品' : '新增商品'}
visible={options.visible}
onCancel={() => handleCancel()}
width={1000}
maskClosable={false}
keyboard={false}
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}
newCategoryList={newCategoryList}
categoryList={categoryList}
virtualCategoryList={virtualCategoryList}
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 merchantProductEdit = data =>
request.post('/product/api/merchant/edit', {
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,
});
export const getByProductType = type =>
request.get(`/product/category/getByProductType/${type}`, {
prefix: goodsApi,
headers,
});
import { sortBy } from 'lodash';
import UUID from '../../utils/uuid';
export const clearCurrent = currentList => currentList.filter(item => item.current);
export const filterSpecId = (input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0;
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;
};
export const filterCoverItemList = (list, key) => (list || []).map(item => ({ [key]: item[key] }));
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,
uuid: UUID.createUUID(),
};
};
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;
const { imageList = {}, commonImageList, cardImageList } = infoImageData;
return infoSpecData.items.map(item => {
if (type === 4) {
// 如果为服务类商品,默认取cardImageList,cardImageList为必选项目
item.imageList = [...commonImageList, ...cardImageList];
} else {
// 如果为虚拟或实体商品,默认取自己{}中对应的的,没有则取公共图
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);
const commonImageList = type === 4 ? [] : infoImageData.commonImageList;
return {
type,
items,
name: infoMation.name,
specs: infoMation.specs || [],
supplierId: infoMation.supplierId,
brandId: infoMation.brandId || null,
character: infoMation.character,
categoryId: infoMation.categoryId[2],
afterAddressId: infoMation.afterAddressId,
detailImageList: infoImageData.detailImageList,
commonImageList,
};
};
export const fliterSkuListSortData = list => {
if (!list || !list.length) return;
const fristSkuList = sortBy(list, item => item.firstSpecValue).reduce((origin, item) => {
if (!origin[item.firstSpecValue]) {
origin[item.firstSpecValue] = [item];
} else {
origin[item.firstSpecValue].push(item);
}
return origin;
}, {});
let finialList = [];
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const skuKey in fristSkuList) {
const skuFirst = fristSkuList[skuKey];
skuFirst.forEach((sku, ind) => {
if (!ind) {
sku.rowSpanCount = skuFirst.length;
}
});
finialList = [...finialList, ...skuFirst];
}
// eslint-disable-next-line consistent-return
return finialList;
};
export const filterSkuNotIdList = list =>
(list || []).reduce(
(origin, item) => {
if (item.id) {
origin.inIdList.push(item);
} else {
origin.noIdList.push(item);
}
return origin;
},
{
inIdList: [],
noIdList: [],
},
);
const createInitSkuItems = () => ({
weight: null,
productStockWarning: null,
marketPrice: null,
supplyPrice: null,
stock: null,
thirdSkuNo: null,
skuLink: null,
name: null,
});
export const createSkuListData = (first, second, firstSpecId, secondSpecId) => {
const list = [];
const skuItem = createInitSkuItems();
console.log(first, second, firstSpecId, secondSpecId);
if (first && first.length) {
// 一级规格有值时,生成的编辑表格
first.forEach(fir => {
const copy = { ...skuItem };
copy.firstSpecId = firstSpecId;
copy.firstSpecValue = fir.firstSpecValue;
// console.log(copy);
if (second.length) {
second.forEach(sec => {
const copySec = { ...copy };
copySec.secondSpecId = secondSpecId;
copySec.secondSpecValue = sec.secondSpecValue;
list.push(copySec);
});
return;
}
list.push(copy);
});
} else if (second && second.length) {
// 缺少一级规格值,只有二级规格值时,生成的编辑表格
second.forEach(sec => {
const copy = { ...skuItem };
copy.secondSpecId = secondSpecId;
copy.secondSpecValue = sec.secondSpecValue;
list.push(copy);
});
} else {
// 缺少一级和二级规格时生成的编辑表格
list.push(skuItem);
}
return list;
};
export const createProductData = (props, isEdit) => {
const {
firstSpecId,
secondSpecId,
fisrtNoIdList,
secndNoIdList,
fisrtInIdList,
secndInIdList,
} = props;
const newFirstList = fisrtNoIdList.concat(fisrtInIdList || []);
let list = [];
// if (!isFirstSame && !isSecondSame) {
if (!isEdit) {
list = createSkuListData(fisrtNoIdList, secndNoIdList, firstSpecId, secondSpecId);
} else {
const list1 = fisrtNoIdList.length
? createSkuListData(fisrtNoIdList, secndInIdList, firstSpecId, secondSpecId)
: [];
const list2 = secndNoIdList.length
? createSkuListData(newFirstList, secndNoIdList, firstSpecId, secondSpecId)
: [];
list = [...list1, ...list2];
}
// }
return list;
};
import React, { useState, useEffect, useRef } from 'react';
import { Form } from '@ant-design/compatible';
import moment from 'moment';
import { PlusSquareFilled, MinusSquareFilled } from '@ant-design/icons';
import {
Modal,
Input,
DatePicker,
TimePicker,
Checkbox,
Cascader,
Radio,
notification,
} from 'antd';
import { apiAddrArea, apiCreatStore, apiEditStore } from '../services';
import { weekOptions, weekDefault, layout } from '../data';
import MapModal from '@/components/BaiduMap';
import style from './style.less';
import { isCheckNumberLine } from '@/utils/validator';
const FormItem = Form.Item;
const StoreModal = props => {
const {
visible,
onCancel,
form: { getFieldDecorator, setFieldsValue, validateFields, resetFields },
formInfo,
} = props;
const [areaAddr, setAreaAddr] = useState([]);
const [visibleMap, setVisibleMap] = useState(false);
const [times, setTimes] = useState([{ name: 'time0' }]);
const [formData, setFormData] = useState({});
const divDom = useRef();
const handleCancel = isSuccess => {
resetFields();
onCancel(isSuccess);
};
const onSubmit = () => {
validateFields(async (error, fieldsValue) => {
if (!error) {
const params = Object.assign({}, formInfo, fieldsValue);
const areaArr = ['provinceId', 'cityId', 'countyId', 'townId'];
if (params.addr && params.addr.forEach) {
params.addr.forEach((item, i) => {
params[areaArr[i]] = item;
});
}
if (params.lnglat) {
const arr = params.lnglat.split(',');
if (arr.length === 2) {
params.longitude = arr[0] || '';
params.latitude = arr[1] || '';
}
}
const hoursItems = [];
if (times && times.length && times.forEach) {
times.forEach(item => {
hoursItems.push({
begin: moment(params[item.name][0]).format('HH:mm'),
end: moment(params[item.name][1]).format('HH:mm'),
});
});
}
params.businessHours = {
weeks: params.week,
hoursItems,
};
let api = apiCreatStore;
if (params.id) {
api = apiEditStore;
}
const res = await api(params);
if (res === '0000') {
notification.success({ message: '保存成功' });
handleCancel(true);
}
}
});
};
// 显示地图
const openMap = v => setVisibleMap(v);
// 获取地址省
const getAreaAddr = async id => {
const params = {};
if (id) {
params.parentId = id;
}
const res = await apiAddrArea(params);
if (res) {
const arr = res.map(item => ({
isLeaf: false,
loading: false,
label: item.addrName,
value: item.addrId,
}));
setAreaAddr(arr);
return arr;
}
return [];
};
// 获取市区街道
const loadData = async selectedOptions => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const res = await apiAddrArea({
parentId: targetOption.value,
});
if (res) {
const children = res.map(item => ({
isLeaf: +item.addrLevel === 4,
loading: false,
label: item.addrName,
value: item.addrId,
}));
targetOption.loading = false;
if (children.length > 0) {
targetOption.children = children;
} else {
targetOption.isLeaf = true;
if (selectedOptions.length === 3) {
divDom.current.blur();
}
}
setAreaAddr([...areaAddr]);
}
};
const onTimePlus = () => {
times.push({
name: `time${times.length}`,
});
setTimes([...times]);
};
const onTimeMinus = i => {
times.splice(i, 1);
setTimes([...times]);
};
const onSetPoint = e => {
setFieldsValue({
lnglat: e,
});
};
const getLazyAddr = async info => {
const arr = await getAreaAddr();
const parr = arr.filter(item => +item.value === +info.provinceId);
parr[0].children = await getAreaAddr(parr[0].value);
const carr = parr[0].children.filter(item => +item.value === +info.cityId);
carr[0].children = await getAreaAddr(carr[0].value);
if (info.townId) {
const aarr = carr[0].children.filter(item => +item.value === +info.countyId);
aarr[0].children = await getAreaAddr(aarr[0].value);
}
setAreaAddr([...arr]);
};
useEffect(() => {
if (props.visible) {
resetFields();
const info = Object.assign({}, formInfo);
if (info && info.id) {
info.week = info.businessHours.weeks;
const hours = info.businessHours.hoursItems;
const harr = [];
if (hours && hours.length) {
hours.forEach((item, i) => {
info[`time${i}`] = [moment(item.begin, 'HH:mm'), moment(item.end, 'HH:mm')];
harr.push({
name: `time${i}`,
});
});
}
info.lnglat = `${info.longitude},${info.latitude}`;
info.addr = [info.provinceId, info.cityId, info.countyId];
if (info.townId) {
info.addr.push(info.townId);
}
setTimes(harr);
setFormData(info);
getLazyAddr(info);
} else {
getAreaAddr(0);
setFormData({});
}
}
}, [visible]);
return (
<Modal
title="门店信息"
visible={visible}
width="800px"
onOk={() => onSubmit()}
onCancel={() => handleCancel()}
>
<Form {...layout} name="formData">
<FormItem label="门店名称" name="name">
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入门店名称!' }],
initialValue: formData.name,
})(<Input placeholder="请输入门店名称" allowClear maxLength={20} />)}
</FormItem>
<FormItem label="门店电话" name="phone">
{getFieldDecorator('phone', {
rules: [
{ required: true, message: '请输入门店电话!' },
{ validator: isCheckNumberLine, message: '请输入正确的门店电话!' },
],
initialValue: formData.phone,
})(<Input placeholder="请输入门店电话" allowClear maxLength={20} />)}
</FormItem>
<FormItem label="营业时间" required>
{times &&
times.map((item, i) => (
<div className={style.timerWrapper} key={item.name}>
<div>
<FormItem name={item.name}>
{getFieldDecorator(item.name, {
rules: [{ required: true, message: '请选择营业时间!' }],
initialValue: formData[item.name],
})(<TimePicker.RangePicker format="HH:mm" />)}
</FormItem>
</div>
{i > 0 ? (
<div className={style.timerWrapperRight} onClick={() => onTimeMinus(i)}>
<MinusSquareFilled style={{ color: '#ff4d4f', fontSize: '30px' }} />
</div>
) : (
<div className={style.timerWrapperRight} onClick={() => onTimePlus()}>
<PlusSquareFilled style={{ color: '#1890ff', fontSize: '30px' }} />
</div>
)}
</div>
))}
</FormItem>
<FormItem label="营业日">
{getFieldDecorator('week', {
rules: [{ required: true, message: '请选择营业日!' }],
initialValue: formData.week || weekDefault,
})(<Checkbox.Group options={weekOptions} />)}
</FormItem>
<FormItem label="店铺区域">
{getFieldDecorator('addr', {
rules: [{ required: true, type: 'array', message: '请选择店铺区域!' }],
initialValue: formData.addr,
})(
<Cascader
ref={divDom}
options={areaAddr}
placeholder="请选择店铺区域"
loadData={e => loadData(e)}
changeOnSelect
/>,
)}
</FormItem>
<FormItem label="详细地址">
{getFieldDecorator('address', {
rules: [{ required: true, message: '请输入详细地址!' }],
initialValue: formData.address,
})(<Input placeholder="请输入详细地址" allowClear maxLength={100} />)}
</FormItem>
<FormItem label="经纬度">
{getFieldDecorator('lnglat', {
rules: [{ required: true, message: '请选择经纬度!' }],
initialValue: formData.lnglat,
})(
<Input
placeholder="请选择经纬度"
readOnly
onClick={() => openMap(true)}
maxLength={100}
/>,
)}
</FormItem>
<FormItem label="是否启用">
{getFieldDecorator('state', {
rules: [{ required: true, message: '请选择是否启用!' }],
initialValue: formData.state,
})(
<Radio.Group>
<Radio value={1}></Radio>
<Radio value={0}></Radio>
</Radio.Group>,
)}
</FormItem>
</Form>
<MapModal
visible={visibleMap}
onCancel={() => openMap(false)}
onSetPoint={e => onSetPoint(e)}
></MapModal>
</Modal>
);
};
export default Form.create()(StoreModal);
.timerWrapper {
display: flex;
}
.timerWrapperRight {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
padding-left: 10px;
cursor: pointer;
}
export const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六', '周日'];
export const stateDesc = { 1: '启用', 0: '禁用' };
export const weekOptions = [
{ label: '周一', value: 1 },
{ label: '周二', value: 2 },
{ label: '周三', value: 3 },
{ label: '周四', value: 4 },
{ label: '周五', value: 5 },
{ label: '周六', value: 6 },
{ label: '周日', value: 7 },
];
export const weekDefault = [1, 2, 3, 4, 5, 6, 7];
export const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
};
import React, { useState, useRef, useEffect } from 'react';
import { notification, Button, Form, Input, Cascader, Col, Row, Table, Pagination } from 'antd';
import _ from 'lodash';
import { searchList, apiEnableStore, apiAddrArea } from './services';
import { stateDesc, weeks, layout } from './data';
import StoreModal from './components/storeModal';
import style from './style.less';
export default () => {
const [visible, setVisible] = useState(false);
const [storeInfo, setStoreInfo] = useState({});
const [areaAddr, setAreaAddr] = useState([]);
const [dataList, setDataList] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [totalNum, setTotalNum] = useState(0);
const [pageSize, setPageSize] = useState(20);
const table = useRef();
const refSearch = useRef();
const divDom = useRef();
const onEnableState = async ({ id, state }) => {
const enable = +state === 1 ? 0 : 1;
const res = await apiEnableStore({ id, state: enable });
if (res === '0000') {
notification.success({ message: `已${state ? '禁用' : '启用'}` });
// eslint-disable-next-line no-unused-expressions
table.current?.reload?.();
}
};
const onCreate = () => {
setStoreInfo({});
setVisible(true);
};
const onShowInfo = info => {
setStoreInfo(info);
setVisible(true);
};
const closeModal = isReload => {
if (isReload === true) {
// eslint-disable-next-line no-unused-expressions
table.current?.reload?.();
}
setVisible(false);
};
// 获取市区街道
const loadData = async selectedOptions => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const res = await apiAddrArea({
parentId: targetOption.value,
});
if (res) {
const children = res.map(item => ({
isLeaf: +item.addrLevel === 4,
loading: false,
label: item.addrName,
value: item.addrId,
}));
targetOption.loading = false;
if (children.length > 0) {
targetOption.children = children;
} else {
targetOption.isLeaf = true;
if (selectedOptions.length === 3) {
divDom.current.blur();
}
}
setAreaAddr([...areaAddr]);
}
};
const getAreaAddr = async id => {
const params = {};
if (id) {
params.parentId = id;
}
const res = await apiAddrArea(params);
if (res) {
const arr = res.map(item => ({
isLeaf: false,
loading: false,
label: item.addrName,
value: item.addrId,
}));
setAreaAddr(arr);
return arr;
}
return [];
};
const getList = async (params = {}) => {
const res = await searchList(Object.assign({ current: pageNo, pageSize }, params));
if (res && res.data && res.data.length) {
setDataList(res.data);
} else {
setDataList([]);
}
setTotalNum(res.total);
};
// 搜索
const onSearch = async val => {
const params = {};
if (val.name) {
params.name = val.name;
}
if (val.addr && val.addr.length) {
const ids = ['provinceId', 'cityId', 'countyId', 'townId'];
val.addr.forEach((item, i) => {
params[ids[i]] = item;
});
}
getList(params);
};
const onReset = () => {
if (refSearch.current && refSearch.current.resetFields) {
refSearch.current.resetFields();
}
};
const onPageChange = (e, size) => {
setPageNo(e);
setPageSize(size);
};
useEffect(() => {
getList();
getAreaAddr();
}, []);
const getWeekSlots = weekArr => {
const htmlArr = [];
if (weekArr && weekArr.length) {
const arr = [];
let brr = [];
weekArr.reduce((prev, cur) => {
if (prev + 1 === cur) {
brr.push(cur);
} else {
arr.push(brr);
brr = [cur];
}
return cur;
}, 0);
arr.push(brr);
if (arr.length) {
arr.forEach(item => {
if (htmlArr.length) {
htmlArr.push(',');
}
if (item.length > 1) {
htmlArr.push(
<span key={item[0]}>
{weeks[item[0]]} ~ {weeks[item[item.length - 1]]}
</span>,
);
} else {
htmlArr.push(<span key={item[0]}>{weeks[item[0]]}</span>);
}
});
}
}
return htmlArr;
};
const columns = [
{
title: '门店名称',
dataIndex: 'name',
width: 120,
},
{
title: '门店电话',
dataIndex: 'phone',
hideInSearch: true,
width: 120,
},
{
title: '营业时间',
dataIndex: 'businessHours',
hideInSearch: true,
width: 150,
render: businessHours => (
<div>
<div>{getWeekSlots(businessHours.weeks)}</div>
<div>
{businessHours.hoursItems.map(item => (
<div key={item.begin}>
<span>{item.begin}</span>-<span>{item.end}</span>
</div>
))}
</div>
</div>
),
},
{
title: '地区',
dataIndex: 'addr',
width: 200,
hideInSearch: true,
render: (addr, r) => (
<span>{`${r.provinceName}${r.cityName}${r.countyName}${r.townName || ''}`}</span>
),
},
{
title: '详细地址',
dataIndex: 'address',
hideInSearch: true,
width: 150,
},
{
title: '经纬度',
dataIndex: 'latlng',
hideInSearch: true,
width: 120,
render: (latlng, r) => <span>{`${r.longitude},${r.latitude}`}</span>,
},
{
title: '状态',
dataIndex: 'state',
hideInSearch: true,
width: 120,
render: v => <span>{`${stateDesc[v]}`}</span>,
},
{
title: '创建时间',
dataIndex: 'createdAt',
hideInSearch: true,
width: 120,
},
{
title: '操作',
hideInSearch: true,
dataIndex: 'action',
width: 170,
fixed: 'right',
render: (val, r) => [
<Button key="enable1" onClick={() => onEnableState(r)} type="primary">
{+r.state ? '禁用' : '启用'}
</Button>,
<Button key="seek" style={{ marginLeft: '10px' }} onClick={() => onShowInfo(r)}>
查看
</Button>,
],
},
];
return (
<div>
<div className={style.serachForm}>
<Form {...layout} ref={refSearch} onFinish={e => onSearch(e)} name="formData">
<Row gutter={24}>
<Col span={8}>
<Form.Item label="门店名称" name="name">
<Input placeholder="请输入门店名称" allowClear maxLength={20} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="地区" name="addr">
<Cascader
ref={divDom}
options={areaAddr}
loadData={e => loadData(e)}
changeOnSelect
/>
</Form.Item>
</Col>
<Col span={8}>
<Button type="primary" htmlType="submit" size="middle">
搜索
</Button>
<Button size="middle" className={style.searchBtn} onClick={() => onReset()}>
重置
</Button>
<Button type="primary" size="middle" onClick={() => onCreate()}>
新建
</Button>
</Col>
</Row>
</Form>
</div>
<Table
dataSource={dataList}
bordered
columns={columns}
rowKey={record => record.id}
pagination={false}
// className={styles.tabletop}
scroll={{ x: '100%' }}
// rowSelection={rowSelection}
/>
{dataList && dataList.length && (
<div className={style.pageBox}>
<Pagination
style={{ marginBottom: 10 }}
onChange={onPageChange}
total={totalNum}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={onPageChange}
/>
</div>
)}
<StoreModal visible={visible} onCancel={closeModal} formInfo={storeInfo} />
</div>
);
};
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import _ from 'lodash';
const { kdspApi } = config;
// 分页查询所有数据
export async function searchList(params) {
const param = {
...params,
pageNo: params.current,
pageSize: params.pageSize || 20,
};
const data = await request.post('/shop/pageQuery', {
prefix: kdspApi,
data: _.omitBy(param, v => !v),
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
});
if (data.data) {
return {
total: data.data.total,
data: data.data.records,
};
}
return {
total: 0,
data: [],
};
}
// 停启用门店
export async function apiEnableStore({ id, state }) {
const data = await request.get(`/shop/updateState/${id}/${state}`, {
prefix: kdspApi,
});
return data.businessCode;
}
// 获取地址
export async function apiAddrArea(params) {
const data = await request.post('/api/kdsp/area/addr/query', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return data.data || [];
}
// 创建门店
export async function apiCreatStore(params) {
const data = await request.post('/shop/create', {
prefix: kdspApi,
data: params,
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
});
return data.businessCode;
}
// 更新门店
export async function apiEditStore(params) {
const data = await request.post('/shop/update', {
prefix: kdspApi,
data: params,
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
});
return data.businessCode;
}
.serachForm {
margin-bottom: 20px;
padding: 20px 50px 0 10px;
background-color: #fff;
}
.searchBtn {
margin: 0 10px;
}
.pageBox {
padding: 20px 20px 15px;
text-align: right;
background-color: #fff;
border: 1px solid #f0f0f0;
border-top: 0;
}
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();
/* 验证必填项 */
export const validateRequired = (rule, value, callback) => {
const reg = /^\s*$/;
if (!value || reg.test(value)) {
callback(new Error('必填项'));
} else {
callback();
}
};
/* 是否合法IP地址 */
export function validateIP(rule, value, callback) {
if (value === '' || value === 'undefined' || value === null) {
callback();
} else {
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
if (!reg.test(value) && value !== '') {
callback(new Error('请输入正确的IP地址'));
} else {
callback();
}
}
}
/* 是否手机号码或者固话 */
export function validatePhoneTwo(rule, value, callback) {
const reg = /^((0\d{2,3}-\d{7,8})|(1[34578]\d{9}))$/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value) && value !== '') {
callback(new Error('请输入正确的电话号码或者固话号码'));
} else {
callback();
}
}
/* 是否固话 */
export function validateTelphone(rule, value, callback) {
const reg = /0\d{2}-\d{7,8}/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value) && value !== '') {
callback(new Error('请输入正确的固话(格式:区号+号码,如010-1234567)'));
} else {
callback();
}
}
/* 是否手机号码 */
export function validatePhone(rule, value, callback) {
const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value) && value !== '') {
callback(new Error('请输入正确的电话号码'));
} else {
callback();
}
}
/* 是否身份证号码 */
export function validateIdNo(rule, value, callback) {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value) && value !== '') {
callback(new Error('请输入正确的身份证号码'));
} else {
callback();
}
}
/* 是否邮箱 */
export function validateEMail(rule, value, callback) {
const reg = /^([a-zA-Z0-9]+[-_.]?)+@[a-zA-Z0-9]+.[a-z]+$/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value)) {
callback(new Error('请输入正确的邮箱地址'));
} else {
callback();
}
}
/* 验证内容是否英文数字以及下划线 */
export function isPassword(rule, value, callback) {
const reg = /^[_a-zA-Z0-9]+$/;
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (!reg.test(value)) {
callback(new Error('密码仅由英文字母,数字以及下划线组成'));
} else {
callback();
}
}
/* 自动检验数值的范围 */
export function checkMaxNumber(rule, value, callback) {
const re = /^[1-9][0-9]{0,1}$/;
const rsCheck = re.test(value);
if (value === '' || value === 'undefined' || value === null) {
callback();
} else if (rule.isInteger && !rsCheck) {
callback(new Error(`请输入[${rule.min}, ${rule.max}]之间的正整数`));
} else if (!Number(value)) {
callback(new Error(`请输入[${rule.min}, ${rule.max}]之间的数字`));
} else if (value < rule.min || value > rule.max) {
callback(new Error(`请输入[${rule.min}, ${rule.max}]之间的数字`));
} else {
callback();
}
}
// 验证数字输入框最大数值,32767
export function checkMaxVal(rule, value, callback) {
if (value < 0 || value > 32767) {
callback(new Error('请输入[0,32767]之间的数字'));
} else {
callback();
}
}
// 验证是否1-99之间
export function isOneToNinetyNine(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入正整数'));
} else {
const re = /^[1-9][0-9]{0,1}$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入正整数,值为【1,99】'));
} else {
callback();
}
}
}, 0);
}
// 验证是否整数
export function isInteger(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入正整数'));
} else {
const re = /^[0-9]*[1-9][0-9]*$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入正整数'));
} else {
callback();
}
}
}, 0);
}
// 验证是否大于0的整数
export const isIntegerNotZero = (rule, value, callback) => {
if (!value) {
callback(new Error('输入不可以为空'));
}
if (!Number(value)) {
callback(new Error('请输入大于0的正整数'));
} else {
const re = /^[1-9][0-9]{0,}$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入大于0的正整数'));
} else {
callback();
}
}
};
// 验证是否整数,非必填
export function isIntegerNotMust(rule, value, callback) {
if (!value) {
callback();
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入正整数'));
} else {
const re = /^[0-9]*[1-9][0-9]*$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入正整数'));
} else {
callback();
}
}
}, 1000);
}
// 验证是否是[0-1]的小数
export function isDecimal(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入[0,1]之间的数字'));
} else if (value < 0 || value > 1) {
callback(new Error('请输入[0,1]之间的数字'));
} else {
callback();
}
}, 100);
}
// 验证是否是[1-10]的小数,即不可以等于0
export function isBtnOneToTen(rule, value, callback) {
if (typeof value === 'undefined') {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入正整数,值为[1,10]'));
} else if (
!(
value === '1' ||
value === '2' ||
value === '3' ||
value === '4' ||
value === '5' ||
value === '6' ||
value === '7' ||
value === '8' ||
value === '9' ||
value === '10'
)
) {
callback(new Error('请输入正整数,值为[1,10]'));
} else {
callback();
}
}, 100);
}
// 验证是否是[1-100]的小数,即不可以等于0
export function isBtnOneToHundred(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入整数,值为[1,100]'));
} else if (value < 1 || value > 100) {
callback(new Error('请输入整数,值为[1,100]'));
} else {
callback();
}
}, 100);
}
// 验证是否是[0-100]的小数
export function isBtnZeroToHundred(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (!Number(value)) {
callback(new Error('请输入[1,100]之间的数字'));
} else if (value < 0 || value > 100) {
callback(new Error('请输入[1,100]之间的数字'));
} else {
callback();
}
}, 100);
}
// 验证端口是否在[0,65535]之间
export function isPort(rule, value, callback) {
if (!value) {
callback(new Error('输入不可以为空'));
}
setTimeout(() => {
if (value === '' || typeof value === 'undefined') {
callback(new Error('请输入端口值'));
} else {
const re = /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入在[0-65535]之间的端口值'));
} else {
callback();
}
}
}, 100);
}
// 验证端口是否在[0,65535]之间,非必填,isMust表示是否必填
export function isCheckPort(rule, value, callback) {
if (!value) {
callback();
}
setTimeout(() => {
if (value === '' || typeof value === 'undefined') {
// callback(new Error('请输入端口值'));
} else {
const re = /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入在[0-65535]之间的端口值'));
} else {
callback();
}
}
}, 100);
}
/* 小写字母 */
export function validateLowerCase(str) {
const reg = /^[a-z]+$/;
return reg.test(str);
}
/* 保留2为小数 */
export function validatetoFixedNew(str) {
return str;
}
/* 验证key */
// export function validateKey (str) {
// var reg = /^[a-z_\-:]+$/;
// return reg.test(str);
// }
/* 大写字母 */
export function validateUpperCase(str) {
const reg = /^[A-Z]+$/;
return reg.test(str);
}
/* 大小写字母 */
export function validatAlphabets(str) {
const reg = /^[A-Za-z]+$/;
return reg.test(str);
}
/* 大小写字母数字 */
export function validatAlphabetsNumber(rule, value, callback) {
const reg = /^[A-Za-z0-9]+$/;
if (value && !reg.test(value)) {
callback(new Error('请输入字母或数字'));
} else {
callback();
}
}
// 验证数字和英文逗号
export function isCheckNumberComma(rule, value, callback) {
if (!value) {
callback();
} else {
const re = /^\d+(,+\d+)*$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入数字或数字+英文逗号,以数字结尾'));
} else {
callback();
}
}
}
// 验证数字和-
export function isCheckNumberLine(rule, value, callback) {
if (!value) {
callback();
} else {
const re = /^\d+(-+\d+)*$/;
const rsCheck = re.test(value);
if (!rsCheck) {
callback(new Error('请输入数字或数字和-,以数字结尾'));
} else {
callback();
}
}
}
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