Commit d72841d4 authored by 郭志伟's avatar 郭志伟

Merge branch 'feature/20230327_public_takeaway' into feat/buildForTest_tmp

parents bbd6d125 d55b139b
......@@ -15,5 +15,6 @@ module.exports = {
'import/no-unresolved': 0,
'import/extensions': 0,
'no-unused-expressions': ['error', { allowShortCircuit: true }],
'template-curly-spacing': 'off',
},
};
......@@ -273,6 +273,18 @@ export default {
name: 'systemManageLog',
component: './systemManage/Log',
},
{
title: '商户管理后台-合同查看',
path: '/contractView',
name: 'contractView',
component: './contractView',
},
{
title: '商户管理后台-商家资料',
path: '/businessInfo',
name: 'businessInfo',
component: './businessManage/info',
},
{
component: './404',
},
......
const isProduction = process.env.NODE_ENV === 'production';
const isPre = process.env.PRE_ENV === 'pre';
const environment = 'xyqb';
const environment = 'yxm2';
const envAPi = {
api: `https://security-${environment}.liangkebang.net`, //'https://security-xyqb.liangkebang.net',
kdspOpApi: `https://sc-merchant-api-${environment}.liangkebang.net`,
......
......@@ -5,6 +5,7 @@ export const GOOD_MANAGE = {
EDITABLE: '020102', // 新增/修改
ADD_SERVICE_GOODS: '020103', // 新增服务商品
ADD_NORMAL_GOODS: '020104', // 新增实物商品
ADD_TAKEAWAY_GOODS: '020105', // 新增外卖商品
};
// 配送区域
......
......@@ -6960,6 +6960,39 @@
}
}
},
"antd-img-crop": {
"version": "4.11.0",
"resolved": "http://npmprivate.quantgroups.com/antd-img-crop/-/antd-img-crop-4.11.0.tgz",
"integrity": "sha512-DWf72AsFc8r2BKRfNMhUrMDh3xg2PvYB6b0gCYPrBEkVbQE1VP7Qt3HnthdeWDapSGdnmUPKFTcMgzML/FyuTA==",
"requires": {
"compare-versions": "6.0.0-rc.1",
"react-easy-crop": "^4.7.4",
"tslib": "^2.5.0"
},
"dependencies": {
"react-easy-crop": {
"version": "4.7.4",
"resolved": "http://npmprivate.quantgroups.com/react-easy-crop/-/react-easy-crop-4.7.4.tgz",
"integrity": "sha512-oDi1375Jo/zuPUvo3oauxnNbfy8L4wsbmHD1KB2vT55fdgu+q8/K0w/rDWzy9jz4jfQ94Q9+3Yu366sDDFVmiA==",
"requires": {
"normalize-wheel": "^1.0.1",
"tslib": "2.0.1"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}
}
},
"tslib": {
"version": "2.5.0",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
}
}
},
"antd-mobile": {
"version": "2.3.4",
"resolved": "http://npmprivate.quantgroups.com/antd-mobile/-/antd-mobile-2.3.4.tgz",
......@@ -7170,6 +7203,11 @@
"integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
"dev": true
},
"array-move": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
"integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ=="
},
"array-reduce": {
"version": "0.0.0",
"resolved": "http://npmprivate.quantgroups.com/array-reduce/-/array-reduce-0.0.0.tgz",
......@@ -8730,6 +8768,11 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"compare-versions": {
"version": "6.0.0-rc.1",
"resolved": "http://npmprivate.quantgroups.com/compare-versions/-/compare-versions-6.0.0-rc.1.tgz",
"integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ=="
},
"component-classes": {
"version": "1.2.6",
"resolved": "http://npmprivate.quantgroups.com/component-classes/-/component-classes-1.2.6.tgz",
......@@ -18381,6 +18424,11 @@
"object-visit": "^1.0.0"
}
},
"mapvgl": {
"version": "1.0.0-beta.175",
"resolved": "https://registry.npmjs.org/mapvgl/-/mapvgl-1.0.0-beta.175.tgz",
"integrity": "sha512-BaUPH4EAhgIjcTwqoOEBB6Vk93K7itLdaES7Qz8RnrwJeGBzoQKypRSZtszeueuFjRW/M5GntjxCC8vm5PD+OA=="
},
"markdown-escapes": {
"version": "1.0.4",
"resolved": "http://npmprivate.quantgroups.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
......@@ -19334,6 +19382,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "http://npmprivate.quantgroups.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"normalize.css": {
"version": "7.0.0",
"resolved": "http://npmprivate.quantgroups.com/normalize.css/-/normalize.css-7.0.0.tgz",
......@@ -20916,6 +20969,11 @@
}
}
},
"pubsub-js": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.4.tgz",
"integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A=="
},
"pump": {
"version": "3.0.0",
"resolved": "http://npmprivate.quantgroups.com/pump/-/pump-3.0.0.tgz",
......@@ -22115,6 +22173,15 @@
"resolved": "http://npmprivate.quantgroups.com/react-amap/-/react-amap-1.2.8.tgz",
"integrity": "sha512-uHPEUXti+CcwFyCeqGGqR0ACnXJA9D8S/lQYal9AG3XEOrwkaOFbWUavrvXxjcfAclROIWg8uKxzlRpMQnkHFg=="
},
"react-bmapgl": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/react-bmapgl/-/react-bmapgl-0.2.17.tgz",
"integrity": "sha512-pcpPOaUeHY3eMmP3Wfz3qdkcrkmtBcbLy8Ih14exYwCe0XJ3IZpmkkarnMvayLymzhH2iyXAyZv42tGnkmv2LA==",
"requires": {
"mapvgl": "^1.0.0-beta.174",
"shallowequal": "^1.1.0"
}
},
"react-copy-to-clipboard": {
"version": "5.0.2",
"resolved": "http://npmprivate.quantgroups.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
......@@ -22420,7 +22487,7 @@
},
"react-router-dom": {
"version": "5.1.2",
"resolved": "http://npmprivate.quantgroups.com/react-router-dom/-/react-router-dom-5.1.2.tgz",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz",
"integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==",
"requires": {
"@babel/runtime": "^7.1.2",
......@@ -22479,6 +22546,16 @@
"shallowequal": "^1.0.1"
}
},
"react-sortable-hoc": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz",
"integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==",
"requires": {
"@babel/runtime": "^7.2.0",
"invariant": "^2.2.4",
"prop-types": "^15.5.7"
}
},
"react-sortablejs": {
"version": "6.0.0",
"resolved": "http://npmprivate.quantgroups.com/react-sortablejs/-/react-sortablejs-6.0.0.tgz",
......@@ -22498,6 +22575,15 @@
"tween-functions": "^1.0.1"
}
},
"react-window": {
"version": "1.8.8",
"resolved": "http://npmprivate.quantgroups.com/react-window/-/react-window-1.8.8.tgz",
"integrity": "sha512-D4IiBeRtGXziZ1n0XklnFGu7h9gU684zepqyKzgPNzrsrk7xOCxni+TCckjg2Nr/DiaEEGVVmnhYSlT2rB47dQ==",
"requires": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
}
},
"read-pkg": {
"version": "2.0.0",
"resolved": "http://npmprivate.quantgroups.com/read-pkg/-/read-pkg-2.0.0.tgz",
/* eslint-disable no-console */
import * as Sentry from '@sentry/react';
import localStorage from '@/utils/localStorage';
// process.env.SENTRY_ENV !== 'test' 加上测试环境不会报错
if (process.env.NODE_ENV === 'production' && process.env.SENTRY_ENV !== 'test') {
if (process.env.NODE_ENV === 'production') {
try {
Sentry.init({
dsn: 'https://b3f60c62e1234e26a5b851b9f26fba07@sentry.q-gp.com/34',
......
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;
const defaultLnglat = { lng: 116.404449, lat: 39.914889 };
if (lngLat) {
defaultLnglat.lng = lngLat.lng;
defaultLnglat.lat = lngLat.lat;
}
const [lnglatPoint, setLnglatPoint] = useState(defaultLnglat);
const [lnglatText, setLnglatText] = useState(`${defaultLnglat.lng},${defaultLnglat.lat}`);
const handleOk = () => {
onSetPoint(lnglatPoint);
onCancel(true);
};
const handleCancle = () => onCancel(true);
const onGetPoint = e => {
setLnglatPoint({
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={lnglatPoint}
enableScrollWheelZoom
enableDoubleClickZoom
coordType="gcj02"
onClick={e => onGetPoint(e)}
zoom={15}
>
<Marker
position={lnglatPoint}
Icon
coordType="gcj02"
autoViewport
viewportOptions={{
zoomFactor: -12,
}}
/>
<CityListControl />
<ZoomControl />
</Map>
</div>
</Modal>
);
};
......@@ -12,6 +12,7 @@ import {
Divider,
} from 'antd';
import React, { Component, useState } from 'react';
import { SwapRightOutlined } from '@ant-design/icons';
import { connect } from 'dva';
import { saveAs } from 'file-saver';
import { format } from 'date-fns';
......@@ -31,6 +32,7 @@ class goodsManage extends Component {
state = {
loading: false,
productType: null,
};
componentDidMount() {
......@@ -59,6 +61,10 @@ class goodsManage extends Component {
const form = this.formRef.current;
form.resetFields();
this.props.onReset();
this.props.changeProductType(1);
this.setState({
productType: 1,
});
};
addSpu = () => {
......@@ -78,6 +84,24 @@ class goodsManage extends Component {
}
};
onChangeProductType = (v = null) => {
const form = this.formRef.current;
form.setFieldsValue({
skuId: '',
skuName: '',
thirdSkuNo: '',
productCategoryId: null,
state: null,
supplyPriceMin: null,
supplyPriceMax: null,
productType: v,
});
this.props.changeProductType(v);
this.setState({
productType: v,
});
};
// 导出明细
onExportGoodsInfo = async () => {
this.setState({
......@@ -105,7 +129,6 @@ class goodsManage extends Component {
const { treeData, permissions } = this.props;
const selectW = { width: 250 };
const iptNumWidth = { width: 118 };
const that = this;
const canEditable = permissions[GOOD_MANAGE.EDITABLE];
const content = (
<div>
......@@ -130,46 +153,29 @@ class goodsManage extends Component {
</Button>
</div>
);
// const uploadProps = {
// name: 'file',
// async customRequest(info) {
// const result = await uploadFile(info.file);
// if (result && result.businessCode === '0000') {
// that.handleSearch();
// notification.success({
// message: '操作成功',
// });
// } else {
// notification.warning({
// message: result.msg,
// description: (
// <div>
// {result.data?.length &&
// result.data.map(item => <p>{item.skuNo + item.errSkuMessage}</p>)}
// </div>
// ),
// duration: 6,
// });
// }
// },
// accept: '.xlsx',
// showUploadList: false,
// };
const filterOption = (input, op) => op.props.children.includes(input);
return (
<Form
ref={this.formRef}
name="horizontal_login"
initialValues={{ productType: 1 }}
layout="inline"
className={styles.searchForm}
>
<FormItem label="SKU编码" name="skuId">
<Input placeholder="请输入SKU编码" allowClear style={selectW} />
<InputNumber placeholder="请输入SKU编码" max={99999999999999999} style={selectW} />
</FormItem>
<FormItem label="商品名称" name="skuName">
<Input placeholder="请输入商品名称" allowClear style={selectW} />
</FormItem>
<FormItem label="商品类型" name="productType">
<Select style={selectW} placeholder="请选择商品类型" onChange={this.onChangeProductType}>
<Option value={1}>实体商品</Option>
<Option value={4}>服务类商品</Option>
<Option value={5}>外卖商品</Option>
</Select>
</FormItem>
<FormItem label="类目" name="productCategoryId">
<Cascader
placeholder="请选择类目"
......@@ -180,82 +186,79 @@ class goodsManage extends Component {
options={treeData}
/>
</FormItem>
<FormItem label="审核状态" name="state">
<Select
style={selectW}
placeholder="请选择审核状态"
allowClear
filterOption={filterOption}
>
{stateList?.map(item => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>
</FormItem>
<FormItem label="供货价区间">
<FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle>
<InputNumber placeholder="请输入" style={iptNumWidth} />
</FormItem>
<span>--</span>
<FormItem name="supplyPriceMax" className={styles.iptNumRight} noStyle>
<InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} />
</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编码">
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />
</FormItem>
{this.state.productType !== 5 && (
<>
<FormItem label="审核状态" name="state">
<Select
style={selectW}
placeholder="请选择审核状态"
allowClear
filterOption={filterOption}
>
{stateList?.map(item => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>
</FormItem>
<FormItem label="供货价区间">
<FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle>
<InputNumber placeholder="请输入" min={0} max={999999999} style={iptNumWidth} />
</FormItem>
<span>
<SwapRightOutlined />
</span>
<FormItem name="supplyPriceMax" className={styles.iptNumRight} noStyle>
<InputNumber
style={iptNumWidth}
min={0}
max={999999999}
placeholder="请输入"
onChange={this.valueMin}
/>
</FormItem>
</FormItem>
<FormItem name="thirdSkuNo" label="第三方SKU编码">
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />
</FormItem>
</>
)}
<FormItem className={styles.queryBtn}>
<Button onClick={() => this.handleSearch()} type="primary" className={styles.button}>
查询
</Button>
<Button onClick={() => this.onReset()} type="primary" className={styles.button}>
<Button onClick={() => this.onReset()} className={styles.button}>
重置
</Button>
<Button
loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()}
className={styles.button}
>
导出
</Button>
</FormItem>
{canEditable ? (
<FormItem style={{ float: 'right' }}>
<Popover content={content} onVisibleChange={this.handleVisibleChange}>
<Button type="primary" className={styles.button}>
批量设置
{this.state.productType !== 5 && (
<>
<Button
loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()}
type="primary"
ghost
className={styles.button}
>
导出
</Button>
</Popover>
{this.props.selectNum > 0 && <Tag color="green">已选商品 {this.props.selectNum}</Tag>}
{/* <Button
className={styles.button}
type="primary"
icon="download"
ghost
onClick={() => {
window.location.href = 'https://kdspstatic.q-gp.com/批量修改库存模板.xlsx';
}}
>
模版
</Button>
<Upload {...uploadProps}>
<Button type="primary" className={styles.button}>
批量库存修改
</Button>
</Upload> */}
</FormItem>
) : (
''
)}
{canEditable ? (
<FormItem style={{ float: 'right' }}>
<Popover content={content} onVisibleChange={this.handleVisibleChange}>
<Button type="primary" className={styles.button}>
批量设置
</Button>
</Popover>
{this.props.selectNum > 0 && (
<Tag color="green">已选商品 {this.props.selectNum}</Tag>
)}
</FormItem>
) : (
''
)}
</>
)}
</FormItem>
</Form>
);
}
......
import React from 'react';
import { Button, Dropdown, Menu, message, Modal } from 'antd';
import { PlusOutlined, DownOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { batchAction } from '../../staticdata';
import styles from '../../style.less';
import { apiGoodsActionBatch } from '../../service';
const ActionBar = options => {
// 上下架
const changeStatus = async state => {
console.log('options.shopId :>> ', options.shopId);
Modal.confirm({
icon: <ExclamationCircleOutlined />,
content: `确认${+state === 6 ? '下架' : '上架'}商品?`,
onOk: async () => {
const res = await apiGoodsActionBatch({
skuIds: options.selectedRowKeys,
type: 2,
shopId: options.shopId,
productStatus: +state === 6 ? 0 : 1, // 6:上架,7:下架
});
if (res.businessCode === '0000' && res.code === '0000') {
options.handleSearch();
message.success('处理成功!');
}
},
});
};
/**
* 批量操作
* up 上架
* down 下架
* stock 修改库存
* time 修改可售时间
* group 修改分组
* send 设置单点不送
* buy 修改最少购买数量
*/
const onChangeState = type => {
if (options.selectedRowKeys && options.selectedRowKeys.length) {
if (['up', 'down'].includes(type)) {
changeStatus(type === 'up' ? 7 : 6);
} else {
options.openModal(type);
}
} else {
message.warning('请选择商品!');
}
};
const eventObj = {
onChangeState,
};
const actions = batchAction(eventObj);
const menus = (
<Menu>
{actions.map(item => (
<Menu.Item key={item.key}>{item.label}</Menu.Item>
))}
</Menu>
);
return (
<div className={styles['action-bar-box']}>
{(options.canAddTakeaway && (
<Button type="primary" icon={<PlusOutlined />} onClick={options.newGoods}>
该分组下新增商品
</Button>
)) ||
''}
<Dropdown overlay={menus} className={styles['action-bar-box--down']} placement="bottomLeft">
<Button type="primary">
批量操作 <DownOutlined />
</Button>
</Dropdown>
</div>
);
};
export default ActionBar;
import React, { useRef, useState } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { Tag, Input, Popconfirm } from 'antd';
import { HolderOutlined, FormOutlined, CloseCircleOutlined } from '@ant-design/icons';
import styles from '../../style.less';
const ItemTypes = {
CARD: 'card',
};
const DragTag = ({ text, id, index, changePosition, endChangePosition, edit, del, selected }) => {
const [isEdit, setIsEdit] = useState(false);
const [inputValue, setInputValue] = useState('');
const refInput = useRef();
const handleEdit = () => {
edit(id);
};
const handleInputChange = e => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
setIsEdit(false);
setInputValue('');
};
const handleClose = () => {
del(id);
};
const ref = useRef(null);
// 因为没有定义收集函数,所以返回值数组第一项不要
const [, drop] = useDrop({
accept: ItemTypes.CARD,
hover: (item, monitor) => {
if (!ref.current) return;
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) return; // 如果回到自己的坑,那就什么都不做
changePosition(dragIndex, hoverIndex); // 调用传入的方法完成交换
item.index = hoverIndex; // 将当前当前移动到Box的index赋值给当前拖动的box,不然会出现两个盒子疯狂抖动!
},
drop: (item, monitor) => {
endChangePosition(); // 调用传入的方法完成交换
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.CARD,
item: { id, index, type: ItemTypes.CARD },
end: () => {},
isDragging: monitor => index === monitor.getItem().index,
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
const inputRender = () => (
<Input
type="text"
size="small"
ref={refInput}
className={styles['groupBox-body--tag-input']}
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
);
const groupEditRender = () => (
<Tag
className={[styles['groupBox-body--tag']]}
ref={drag(drop(ref))}
style={{
opacity: isDragging ? 0.3 : 1,
display: isEdit ? 'none' : 'inline-block',
}}
>
<HolderOutlined className={styles['groupBox-body--tag__move']} />
<span className={styles['groupBox-body--tag__text']}>{text}</span>
<span>
<FormOutlined className={styles['groupBox-body--tag__edit']} onClick={handleEdit} />
</span>
<Popconfirm title="确定删除该分组吗?" onConfirm={handleClose} okText="确定" cancelText="取消">
<CloseCircleOutlined className={styles['groupBox-body--tag__close']} />
</Popconfirm>
</Tag>
);
return (
<>
{isEdit && inputRender()}
{groupEditRender()}
</>
);
};
export default DragTag;
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Button, Select, Tag } from 'antd';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import styles from '../../style.less';
import DragTag from './DragTag';
import InsertTag from './InsertTag';
import GroupInfo from './GroupInfo';
import { apiDelStorage, apiSortStorage, apiStorageList, apiSupplierShopList } from '../../service';
const GoodsGroup = forwardRef((options, ref) => {
const [groupEdit, setGroupEdit] = useState(false);
const [selected, setSelected] = useState(0);
const [storageId, setStorageId] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
const [shops, setShops] = useState([]);
const [tags, setTags] = useState([]);
const getShopList = async () => {
const res = await apiSupplierShopList({
state: 1,
productBusiness: 1,
});
if (res && res.data && res.data.length > 0) {
setShops(
res.data.map(item => ({
label: item.name,
value: +item.id,
})),
);
options.changeShop(+res.data[0].id);
} else {
options.changeShop(0);
}
};
const getGroupList = async () => {
if (options.shopId) {
const res = await apiStorageList({
shopId: options.shopId,
});
if (res && res.data && res.data.length > 0) {
const arr = res.data
.sort((x, y) => x.priority - y.priority)
.map(item => ({
text: item.name,
id: item.rackId,
}));
setTags(arr);
setSelected(res.data[0].rackId);
} else {
setTags([]);
setSelected(0);
}
} else {
setTags([]);
setSelected(0);
}
};
const handleEdit = async id => {
setStorageId(id || 0);
setIsModalOpen(true);
};
const handleDelete = async id => {
const res = await apiDelStorage({
shopId: options.shopId,
id,
});
if (res.businessCode === '0000' && res.code === '0000') {
getGroupList();
}
};
// 更换位置
const changePosition = async (dIndex, hIndex) => {
const data = tags.slice();
const temp = data[dIndex];
// 交换位置
data[dIndex] = data[hIndex];
data[hIndex] = temp;
setTags(data);
};
const endChangePosition = async () => {
const data = tags.slice();
const storageRankList = data.map((item, i) => ({
id: item.id,
priority: i + 1,
}));
const params = {
shopId: options.shopId,
storageRankList,
};
await apiSortStorage(params);
getGroupList();
};
const onSelect = i => {
setSelected(i);
};
useEffect(() => {
if (options.shopId) {
getGroupList();
}
}, [options.shopId]);
useEffect(() => {
getShopList();
}, []);
useEffect(() => {
options.changeGroup(selected);
}, [selected]);
useImperativeHandle(ref, () => ({
setSelected,
}));
return (
<div className={styles.groupBox}>
{(shops && shops.length && (
<>
<div className={styles['groupBox-title']}>
<div className={styles['groupBox-title--name']}>所属门店</div>
<Select
showSearch
value={options.shopId}
placeholder="请选择所属门店"
onChange={options.changeShop}
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={shops}
/>
</div>
<div className={styles['groupBox-title']}>
<div className={styles['groupBox-title--name']}>商品分组</div>
<Button onClick={() => setGroupEdit(!groupEdit)}>
{groupEdit ? '完成' : '编辑分组'}
</Button>
</div>
<div className={styles['groupBox-body']}>
{groupEdit ? (
<DndProvider backend={HTML5Backend}>
<div className={styles['groupBox-body--dragbox']}>
{tags.map((item, index) => (
<DragTag
changePosition={changePosition}
endChangePosition={endChangePosition}
index={index}
{...item}
selected={selected}
edit={handleEdit}
del={handleDelete}
key={item.id}
/>
))}
<InsertTag handleOpen={handleEdit} />
</div>
</DndProvider>
) : (
<div className={styles['groupBox-body--dragbox']}>
{tags.map(item => (
<Tag
key={item.id}
onClick={() => onSelect(item.id)}
className={[
styles['groupBox-body--tag-normal'],
selected === item.id ? styles['groupBox-body--tag__cur'] : '',
]}
>
<span className={styles['groupBox-body--tag__text']}>{item.text}</span>
</Tag>
))}
<InsertTag key="insert" handleOpen={handleEdit} />
</div>
)}
</div>
</>
)) ||
''}
<GroupInfo
isModalOpen={isModalOpen}
id={storageId}
shopId={options.shopId}
search={getGroupList}
handleClose={setIsModalOpen}
/>
</div>
);
});
export default GoodsGroup;
import React, { useEffect, useState } from 'react';
import { Form, Modal, Input, Switch, Alert, message } from 'antd';
import { apiCreateStorage, apiEditStorage, apiStorageInfo } from '../../service';
import { stringOrObjectTrim } from '@/utils/utils';
const GroupInfo = options => {
const [form] = Form.useForm();
const [isChecked, setIsChecked] = useState(false);
// 关闭分组信息弹窗
const handleCancel = () => {
options.handleClose(false);
};
// 添加/保存分组
const handleConfirm = async () => {
const { name, necessary } = await form.validateFields();
const api = options.id ? apiEditStorage : apiCreateStorage;
const res = await api({
name: stringOrObjectTrim(name),
necessary: necessary ? 1 : 0,
shopId: options.shopId,
id: options.id,
});
if (res.code === '0000' && res.businessCode === '0000') {
message.success('保存成功!');
handleCancel();
options.search();
}
};
const getInfo = async id => {
const res = await apiStorageInfo({
shopId: options.shopId,
id,
});
if (res && res.data && res.data.id) {
const { name, necessary } = res.data;
setIsChecked(+necessary === 1);
form.setFieldsValue({
name,
necessary: +necessary === 1,
});
}
};
useEffect(() => {
if (options.id && options.isModalOpen) {
getInfo(options.id);
}
}, [options.id, options.isModalOpen]);
const extra = <Alert message="选中后,顾客下单需至少选择1个“下单必选分组”" type="error" />;
return (
<Modal
title="分组信息"
visible={options.isModalOpen}
destroyOnClose
maskClosable={false}
width="600px"
onOk={handleConfirm}
onCancel={handleCancel}
>
<Form name="basic" form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }}>
<Form.Item
label="分组名称"
name="name"
rules={[{ required: true, message: '请输入分组名称!' }]}
>
<Input />
</Form.Item>
<Form.Item label="下单必选分组" name="necessary" extra={extra}>
<Switch
checkedChildren="开启"
checked={isChecked}
unCheckedChildren="关闭"
onChange={setIsChecked}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default GroupInfo;
import React from 'react';
import { Tag } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import styles from '../../style.less';
const InsertTag = options => {
const showInput = () => {
options.handleOpen();
};
return (
<Tag
className={[styles['groupBox-body--tag'], styles['groupBox-body--new']]}
color="blue"
onClick={showInput}
>
<PlusOutlined /> 添加
</Tag>
);
};
export default InsertTag;
import React from 'react';
import { Modal, Form, InputNumber } from 'antd';
import styles from '../../style.less';
const MinimumPurchase = options => {
const [form] = Form.useForm();
const handleCancel = () => {
options.cancel(false);
};
const handleOk = async () => {
const values = await form.validateFields();
console.log('values :>> ', values);
options.confirm({
type: 5,
...values,
});
};
return (
<Modal
visible={options.visible}
title="修改最少购买数量"
onOk={handleOk}
maskClosable={false}
keyboard={false}
confirmLoading={options.loading}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={{ minPurchaseNum: 1 }}
autoComplete="off"
>
<Form.Item
label="最少购买/份"
name="minPurchaseNum"
rules={[{ required: true, message: '请输入最少购买数量!' }]}
>
<InputNumber min={1} max={999999} className={styles.inputWdith} />
</Form.Item>
</Form>
</Modal>
);
};
export default MinimumPurchase;
import React from 'react';
import { Modal, Form, Radio } from 'antd';
const SendModal = options => {
const [form] = Form.useForm();
const handleCancel = () => {
options.cancel(false);
};
const handleOk = async () => {
const values = await form.validateFields();
console.log('values :>> ', values);
options.confirm({
type: 6,
...values,
});
};
const radioOptions = [{ label: '', value: 1 }, { label: '', value: 0 }];
const initialValues = Object.assign({}, options.initialValues);
return (
<Modal
visible={options.visible}
title="设置单点不送"
onOk={handleOk}
maskClosable={false}
keyboard={false}
confirmLoading={options.loading}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={initialValues}
autoComplete="off"
>
<Form.Item
label="单点不送"
name="isSingleDelivery"
rules={[{ required: true, message: '请选择!' }]}
>
<Radio.Group options={radioOptions} />
</Form.Item>
</Form>
<div>开启后顾客单点这些商品不可下单</div>
</Modal>
);
};
export default SendModal;
import React, { useState, useEffect } from 'react';
import { Modal, Form, InputNumber, Checkbox, Switch } from 'antd';
import { deepClone } from '@/utils/utils';
import styles from '../../style.less';
import { apiProductStock } from '../../service';
import { isIntegerNotZero } from '@/utils/validator';
const StockModal = options => {
const [stockType, setStockType] = useState(0);
const [maxStock, setMaxStock] = useState(0);
const [isChecked, setIsChecked] = useState(false);
const [form] = Form.useForm();
const onChangeType = v => {
setStockType(v === stockType ? 0 : v);
if (v === 1) {
form.setFieldsValue({
productStock: 0,
});
}
};
const onChangeMaxStock = value => {
setMaxStock(value);
};
const handleCancel = () => {
options.cancel(false);
};
const handleOk = async () => {
const values = await form.validateFields();
const params = deepClone(values);
params.autoStock = values.autoStock ? 1 : 0;
options.confirm({
type: 7,
...params,
});
};
const getStockInfo = async () => {
const res = await apiProductStock({
skuId: options.skuIds[0],
shopId: options.shopId,
});
if (res && res.code === '0000' && res.businessCode === '0000') {
const info = res.data;
form.setFieldsValue({
autoStockStep: info.autoStockStep,
productStock: info.stock,
autoStock: info.autoStock === 1,
});
setMaxStock(info.autoStockStep);
setIsChecked(info.autoStock === 1);
}
};
const initialValues = Object.assign(
{
productStock: '',
autoStockStep: '',
autoStock: false,
},
options.initialValues,
);
useEffect(() => {
if (stockType === 2) {
form.setFieldsValue({
productStock: maxStock,
});
}
}, [maxStock, stockType]);
useEffect(() => {
if (options.visible) {
setStockType(0);
setMaxStock(0);
setIsChecked(false);
form.resetFields();
if (options.skuIds && options.skuIds.length === 1) {
getStockInfo();
}
}
}, [options.visible]);
const maxStockRule = [{ validator: isIntegerNotZero, message: '请输入大于0的整数' }];
if (isChecked || stockType === 2) {
maxStockRule.push({ required: true, message: '请输入最大库存!' });
}
return (
<Modal
visible={options.visible}
title="修改库存"
onOk={handleOk}
maskClosable={false}
keyboard={false}
confirmLoading={options.loading}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={initialValues}
autoComplete="off"
className={styles['stock-box']}
>
<Form.Item
label="剩余库存"
name="productStock"
labelCol={{ span: 6 }}
wrapperCol={{ span: 8 }}
rules={[
{ required: true, message: '请输入剩余库存!' },
{ validator: isIntegerNotZero, message: '请输入大于0的整数' },
]}
>
<InputNumber
min={0}
max={999999999}
className={styles['stock-box--inputnum']}
disabled={stockType > 0}
/>
</Form.Item>
<div className={styles['stock-box--btns']}>
<Checkbox checked={stockType === 1} onChange={() => onChangeType(1)}>
清零
</Checkbox>
<Checkbox checked={stockType === 2} onChange={() => onChangeType(2)}>
最大
</Checkbox>
</div>
<Form.Item label="最大库存" name="autoStockStep" rules={maxStockRule}>
<InputNumber
min={0}
max={999999999}
className={styles['stock-box--inputnum']}
onChange={onChangeMaxStock}
/>
</Form.Item>
<Form.Item label="自动补足" name="autoStock">
<Switch
checkedChildren="开启"
checked={isChecked}
unCheckedChildren="关闭"
onChange={setIsChecked}
/>
</Form.Item>
</Form>
<div className={styles['stock-box--red']}>修改成功后,原库存将被替换,请谨慎操作</div>
</Modal>
);
};
export default StockModal;
import React from 'react';
import { Modal, Form, Select } from 'antd';
const SwitchGroupModal = options => {
const [form] = Form.useForm();
const handleCancel = () => {
options.cancel(false);
};
const handleOk = async () => {
const values = await form.validateFields();
console.log('values :>> ', values);
options.confirm({
type: 3,
...values,
});
};
const radioOptions = [{ label: '', value: 1 }, { label: '', value: 0 }];
const initialValues = Object.assign({}, options.initialValues);
return (
<Modal
visible={options.visible}
title="更改分组"
onOk={handleOk}
maskClosable={false}
keyboard={false}
confirmLoading={options.loading}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={initialValues}
autoComplete="off"
>
<Form.Item
label="分组"
name="storageRackId"
rules={[{ required: true, message: '请选择!' }]}
>
<Select options={radioOptions} />
</Form.Item>
</Form>
</Modal>
);
};
export default SwitchGroupModal;
import React, { useState, useEffect } from 'react';
import { Modal, Radio, Form, TimePicker, Checkbox } from 'antd';
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
import moment from 'moment';
import { deepClone } from '@/utils/utils';
import { saleWeeks } from '../../staticdata';
import styles from '../../style.less';
const WeekTime = options => {
const [form] = Form.useForm();
const [type, setType] = useState(0);
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 22, offset: 0 },
sm: { span: 16, offset: 6 },
},
};
const onChangeType = ({ target: { value } }) => {
setType(value);
};
const radioOptions = [{ label: '全时段', value: 0 }, { label: '自定义售卖时间', value: 1 }];
const handleCancel = () => {
options.cancel(false);
};
const handleOk = async () => {
const params = await form.validateFields();
// const params = values;
if (params.saleTimes && params.saleTimes.length) {
params.saleTimes = params.saleTimes.map(item => ({
startTime: moment(item[0]).format('HH:mm'),
endTime: moment(item[1]).format('HH:mm'),
}));
}
options.confirm({
type: 4,
...params,
});
};
const onTimeValidator = (rule, value, callback) => {
if (value && value.length === 2) {
if (moment(value[0]).format('HH:mm') === moment(value[1]).format('HH:mm')) {
callback(new Error('请输入大于0的数字'));
} else {
callback();
}
} else {
callback();
}
};
const initialValues = Object.assign(
{
saleTimeType: 0,
saleDates: [],
saleTimes: [[]],
},
options.initialValues,
);
useEffect(() => {
options.visible && setType(0);
}, [options.visible]);
return (
<Modal
visible={options.visible}
title="售卖时间"
onOk={handleOk}
confirmLoading={options.loading}
maskClosable={false}
keyboard={false}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={initialValues}
autoComplete="off"
>
<Form.Item
label="售卖时间段类型"
name="saleTimeType"
rules={[{ required: true, message: '请选择售卖时间段类型!' }]}
>
<Radio.Group
options={radioOptions}
onChange={onChangeType}
value={type}
optionType="button"
buttonStyle="solid"
/>
</Form.Item>
{type === 1 ? (
<>
<Form.Item
label="售卖日期"
name="saleDates"
rules={[{ required: true, message: '请选择售卖日期!' }]}
>
<Checkbox.Group options={saleWeeks} />
</Form.Item>
<Form.List
label="售卖时间"
name="saleTimes"
rules={[
{
validator: async (_, saleTimes) => {
if (!saleTimes || saleTimes.length < 1) {
return Promise.reject(new Error('请选择售卖时间!'));
}
return Promise.resolve();
},
},
]}
>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Form.Item
label={index === 0 ? '售卖日期' : ''}
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
required
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
message: '请选择售卖时间',
},
{
validator: onTimeValidator,
message: '结束时间不能和开始时间相同',
},
]}
noStyle
>
<TimePicker.RangePicker format="HH:mm" minuteStep={30} />
</Form.Item>
{index > 0 ? (
<MinusSquareOutlined
className={[styles['week-time-box--icon'], styles.error]}
onClick={() => remove(field.name)}
/>
) : (
<PlusSquareOutlined
className={[styles['week-time-box--icon'], styles.primary]}
onClick={() => add()}
/>
)}
</Form.Item>
))}
</>
)}
</Form.List>
</>
) : (
''
)}
</Form>
</Modal>
);
};
export default WeekTime;
import React, { useState, useEffect, useRef } from 'react';
import { Spin, Table, Pagination, message, notification } from 'antd';
import { unstable_batchedUpdates } from 'react-dom';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { arrayMoveImmutable } from 'array-move';
import { GOOD_MANAGE } from '@/../config/permission.config';
import PubSub from 'pubsub-js';
import GoodsGroup from './components/GoodsGroup';
import {
apiTakeawayGoods,
apiGoodsActionBatch,
apiSortTakeawayGoods,
apiTopTakeawayGoods,
} from '../service';
import styles from '../style.less';
import { takeawayColumn } from '../staticdata';
// import VirtualTable from './components/VirtualTable';
import ActionBar from './components/ActionBar';
import WeekTime from './components/WeekTime';
import StockModal from './components/StockModal';
import SendModal from './components/SendModal';
import MinimumPurchaseModal from './components/MinimumPurchase';
import SwitchGroupModal from './components/SwitchGroupModal';
const Takeaway = options => {
const [tableData, setTableData] = useState([]);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [shopId, setShopId] = useState(0);
const [groupId, setGroupId] = useState(0);
const [pageNo, setPageNo] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [actionLoading, setActionLoading] = useState(false);
const [visibleWeekTime, setVisibleWeekTime] = useState(false);
const [visibleStock, setVisibleStock] = useState(false);
const [visibleBuy, setVisibleBuy] = useState(false);
const [visibleSend, setVisibleSend] = useState(false);
const [visibleSwitchGroup, setVisibleSwitchGroup] = useState(false);
const [scribeToken, setScribeToken] = useState('');
const groupRef = useRef(null);
const rowSelection = {
selectedRowKeys,
onChange: setSelectedRowKeys,
};
const getDataList = async (page = pageNo, size = pageSize, storageRackId = groupId) => {
setLoading(true);
const params = Object.assign({}, options.searchValue, {
pageNo: page || pageNo,
productType: 5,
pageSize: size || pageSize,
storageRackId,
});
const productCategoryId = options.searchValue?.productCategoryId || [];
params.productCategoryId =
(productCategoryId.length && productCategoryId[productCategoryId.length - 1]) || '';
const res = await apiTakeawayGoods(params);
setLoading(false);
if (res && res.data) {
setTableData(res.data.records);
setTotal(res.data.total);
}
};
const onPageChange = (page, size) => {
unstable_batchedUpdates(() => {
setPageNo(page);
setPageSize(size);
});
getDataList(page, size);
};
const onSortEnd = async ({ oldIndex, newIndex }) => {
if (oldIndex !== newIndex) {
const newData = arrayMoveImmutable(tableData.slice(), oldIndex, newIndex).filter(el => !!el);
const skuSorts = newData.map((item, index) => ({
skuId: item.skuId,
sort: pageSize * (pageNo - 1) + index + 1,
}));
const params = {
storageRackId: groupId,
type: 1,
shopId,
skuSorts,
};
await apiSortTakeawayGoods(params);
getDataList(pageNo, pageSize);
// setTableData(newData);
}
};
const SortableItem = SortableElement(props => <tr {...props} />);
const SortableBody = SortableContainer(props => <tbody {...props} />);
const DraggableContainer = props => (
<SortableBody
useDragHandle
disableAutoscroll
helperClass={styles['row-dragging']}
onSortEnd={onSortEnd}
{...props}
/>
);
const DraggableBodyRow = ({ className, style, ...restProps }) => {
// function findIndex base on Table rowKey props and should always be a right array index
const index = tableData.findIndex(x => x.skuId === restProps['data-row-key']);
return <SortableItem index={index} {...restProps} />;
};
// 批量操作 type 1-是否列出 2-修改上下架 3-改货架 4-售卖时间更新 5-调整商品起购数量 6-调整商品是否单点不送 7-修改库存
const handleBatchAction = async params => {
const json = {
skuIds: selectedRowKeys,
shopId,
};
setActionLoading(true);
const res = await apiGoodsActionBatch(Object.assign({}, json, params));
setActionLoading(false);
if (res.businessCode === '0000' && res.code === '0000') {
message.success('处理成功!');
unstable_batchedUpdates(() => {
setActionLoading(false);
setVisibleWeekTime(false);
setVisibleStock(false);
setVisibleSwitchGroup(false);
setVisibleBuy(false);
setVisibleSend(false);
});
getDataList(pageNo, pageSize);
}
};
// 显示弹窗
const openModal = type => {
type === 'time' && setVisibleWeekTime(true);
type === 'stock' && setVisibleStock(true);
type === 'group' && setVisibleSwitchGroup(true);
type === 'buy' && setVisibleBuy(true);
type === 'send' && setVisibleSend(true);
};
// 单商品修改库存
const onShowStockModal = ({ skuId }) => {
setSelectedRowKeys([skuId]);
openModal('stock');
};
// 编辑
const onEdit = ({ spuId, skuId }) => {
options.handleEdit({
shopId,
spuId,
skuId,
});
};
// 新建商品
const onNew = () => {
options.handleEdit({
shopId,
groupId,
});
};
// 置顶
const toTop = async ({ skuId }) => {
// onSortEnd({ oldIndex, newIndex: 0 });
const res = await apiTopTakeawayGoods({
productItemId: skuId,
shopId,
storageRackId: groupId,
});
if (res.businessCode === '0000' && res.code === '0000') {
getDataList(pageNo, pageSize);
message.success('处理成功!');
}
};
useEffect(() => {
if (groupId) {
setPageNo(1);
getDataList(1, pageSize);
} else {
setTableData([]);
}
}, [groupId, options.refresh]);
useEffect(() => {
const stoken = PubSub.subscribe('refreshTakeAway', (_, data) => {
console.log('refreshTakeAway :>> ', data);
if (data.groupId && groupId !== data.groupId) {
setGroupId(data.groupId);
if (groupRef.current) {
groupRef.current.setSelected(`${data.groupId}`);
}
}
});
setScribeToken(stoken);
return () => {
PubSub.unsubscribe(scribeToken);
};
}, []);
const actions = {
onShowStockModal,
toTop,
onEdit,
getDataList,
shopId,
pageNo,
};
const canAddTakeaway = options.permissions[GOOD_MANAGE.ADD_TAKEAWAY_GOODS];
return (
<div className={styles.takeawayBox}>
<Spin spinning={loading}>
<GoodsGroup
ref={groupRef}
shopId={shopId}
changeShop={setShopId}
changeGroup={setGroupId}
/>
{(shopId && (
<ActionBar
selectedRowKeys={selectedRowKeys}
shopId={shopId}
canAddTakeaway={canAddTakeaway}
handleSearch={getDataList}
openModal={openModal}
newGoods={onNew}
/>
)) ||
''}
<Table
dataSource={tableData}
bordered
columns={takeawayColumn(actions)}
rowKey={record => record.skuId}
pagination={false}
scroll={{ x: '100%', y: 500 }}
rowSelection={rowSelection}
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
},
}}
/>
<br />
{(tableData && (
<Pagination
className={styles['takeawayBox--page']}
onChange={onPageChange}
total={total}
showTotal={o => `共${o}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={onPageChange}
/>
)) ||
''}
</Spin>
<WeekTime
visible={visibleWeekTime}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleWeekTime}
/>
<StockModal
visible={visibleStock}
loading={actionLoading}
skuIds={selectedRowKeys}
shopId={shopId}
confirm={handleBatchAction}
cancel={setVisibleStock}
/>
<SendModal
visible={visibleSend}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleSend}
/>
<MinimumPurchaseModal
visible={visibleBuy}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleBuy}
/>
<SwitchGroupModal
visible={visibleSwitchGroup}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleSwitchGroup}
/>
</div>
);
};
export default Takeaway;
......@@ -12,6 +12,7 @@ import UpdateStock from './UpdateStock';
import {
spuDetail,
categoryList,
apiCategoryListType,
getVirtualCategory,
getTemplateList,
specList,
......@@ -26,6 +27,7 @@ import TempleatModal from './TempleatModal';
import ServiceGoods from '../ServiceGoods';
import InfoAudit from './infoAudit';
import DraftModal from './DraftModal';
import Takeaway from './Takeaway';
import { GOOD_MANAGE } from '@/../config/permission.config';
......@@ -38,6 +40,7 @@ class goodsManage extends Component {
pageNo: 1,
loading: false,
treeData: [],
categoryTree: [],
virtualTreeData: [],
pageSize: 20,
priceInfo: {},
......@@ -59,6 +62,10 @@ class goodsManage extends Component {
auditRow: {}, // 查看审核信息使用
isVisibleDraft: false, // 显示隐藏草稿箱
isEditDraft: false, // 是否编辑草稿
productType: 1, // 商品类型
takeAway: {}, // 弹窗外卖商品参数
searchValue: {}, // 搜索条件
refresh: '', // 外卖刷新
};
currentLog = null;
......@@ -71,36 +78,46 @@ class goodsManage extends Component {
componentDidMount() {
this.props.goodsManage.tableData = {};
this.categoryList();
this.categoryList(this.state.productType);
this.categoryListByType(this.state.productType);
this.getVirtualCategory();
this.specList();
}
handleSearch = page => {
this.onSelectChange([]);
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
loading: true,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'goodsManage/getList',
payload: {
pageNo,
pageSize,
...this.searchForm.getFieldsValue(),
},
}).finally(() => {
this.setState({
loading: false,
const searchValue = this.searchForm.getFieldsValue() || {};
this.setState({ searchValue });
if (searchValue.productType !== 5) {
this.onSelectChange([]);
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
loading: true,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'goodsManage/getList',
payload: {
pageNo,
pageSize,
...searchValue,
},
}).finally(() => {
this.setState({
loading: false,
});
});
});
},
);
},
);
} else {
this.setState({
refresh: new Date().getTime(),
searchValue,
});
}
};
onPageChange = page => {
......@@ -267,7 +284,7 @@ class goodsManage extends Component {
categoryList = async () => {
try {
const { data: treeData } = await categoryList();
const { data: treeData } = await categoryList({});
if (!treeData) return;
this.setState({ treeData });
} catch (e) {
......@@ -275,6 +292,26 @@ class goodsManage extends Component {
}
};
categoryListByType = async type => {
try {
const { data: categoryTree } = await apiCategoryListType(type);
if (!categoryTree) return;
this.setState({ categoryTree });
} catch (e) {
console.log(e);
}
};
changeProductType = e => {
this.setState({
productType: e || 1,
});
this.categoryListByType(e);
if (e !== 5) {
this.handleSearch(1);
}
};
getVirtualCategory = async () => {
try {
const { data: virtualTreeData } = await getVirtualCategory();
......@@ -336,6 +373,7 @@ class goodsManage extends Component {
serviceVisble: flag,
isEditDraft: false,
serviceData: {},
takeAway: {},
});
if (refresh) {
this.handleSearch();
......@@ -355,6 +393,14 @@ class goodsManage extends Component {
this.serviceVisbleChange(this.state.auditRow);
};
// 显示外卖商品弹窗
handleTakeawayEdit = params => {
this.setState({
takeAway: params,
serviceVisble: true,
});
};
render() {
const {
goodsManage: { tableData = {} },
......@@ -368,10 +414,18 @@ class goodsManage extends Component {
const canAddService = permissions[GOOD_MANAGE.ADD_SERVICE_GOODS];
const canAddNormal = permissions[GOOD_MANAGE.ADD_NORMAL_GOODS];
const canAddTakeaway = permissions[GOOD_MANAGE.ADD_TAKEAWAY_GOODS];
this.canEditable = permissions[GOOD_MANAGE.EDITABLE];
// console.log('serviceData', this.state.serviceData);
// console.log('shopList', this.shopList);
// console.log('treeData', this.state.treeData);
// console.log('treeData', this.state.treeData);
// console.log('treeData', this.state.treeData);
// console.log('3333333', permissions);
return (
<PageHeaderWrapper>
{canAddNormal || canAddService
{canAddNormal || canAddService || canAddTakeaway
? [
<Button
type="primary"
......@@ -400,98 +454,112 @@ class goodsManage extends Component {
onRef={ref => {
this.searchForm = ref;
}}
treeData={this.state.treeData}
treeData={this.state.categoryTree}
shopList={this.shopList}
checkStock={this.checkEnableUpdateStock}
selectNum={selectedRowKeys.length}
changeProductType={this.changeProductType}
setArea={(isALL, type) => this.setArea(isALL, type)}
/>
</Card>
<Spin spinning={this.state.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={column.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
scroll={{ x: '100%', y: 500 }}
rowSelection={rowSelection}
/>
</Spin>
<br />
{tableData && (
<Pagination
style={{ marginBottom: 10 }}
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<LogModal
visible={this.state.logVisible}
spuId={this.currentLog?.spuId}
id={this.currentLog?.skuId}
onCancel={() => {
this.currentLog = null;
this.setState({ logVisible: false });
}}
/>
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
bodyStyle={{ height: '90%' }}
>
<iframe
src={this.state.src}
frameBorder="0"
height="100%"
width="375"
title="商品预览"
></iframe>
</Drawer>
<UpdateStock
visible={this.state.updateStockVisible}
skuIds={this.state.stockSkuIds}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
<TempleatModal
visible={this.state.templeatModalVisible}
selectedRowKeys={this.state.selectedRowKeys}
total={tableData.total || 0}
onCancel={() => {
this.setState({ templeatModalVisible: false, selectedRowKeys: [] });
this.handleSearch();
}}
isALL={this.state.isAll}
isType={this.state.isType}
templateList={this.state.templateList}
/>
{this.state.serviceVisble && (
<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}
{this.state.productType === 5 ? (
<Takeaway
handleEdit={this.handleTakeawayEdit}
searchValue={this.state.searchValue}
permissions={permissions}
isDraft={this.state.isEditDraft}
refresh={this.state.refresh}
/>
) : (
<>
<Spin spinning={this.state.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={column.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
scroll={{ x: '100%', y: 500 }}
rowSelection={rowSelection}
/>
</Spin>
<br />
{tableData && (
<Pagination
style={{ marginBottom: 10 }}
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<LogModal
visible={this.state.logVisible}
spuId={this.currentLog?.spuId}
id={this.currentLog?.skuId}
onCancel={() => {
this.currentLog = null;
this.setState({ logVisible: false });
}}
/>
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
bodyStyle={{ height: '90%' }}
>
<iframe
src={this.state.src}
frameBorder="0"
height="100%"
width="375"
title="商品预览"
></iframe>
</Drawer>
<UpdateStock
visible={this.state.updateStockVisible}
skuIds={this.state.stockSkuIds}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
<TempleatModal
visible={this.state.templeatModalVisible}
selectedRowKeys={this.state.selectedRowKeys}
total={tableData.total || 0}
onCancel={() => {
this.setState({ templeatModalVisible: false, selectedRowKeys: [] });
this.handleSearch();
}}
isALL={this.state.isAll}
isType={this.state.isType}
templateList={this.state.templateList}
/>
</>
)}
</Spin>
{this.state.serviceVisble && (
<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}
permissions={permissions}
isDraft={this.state.isEditDraft}
productType={this.state.productType}
takeAway={this.state.takeAway}
/>
)}
{this.state.visibleAuditModal && (
<InfoAudit
visible={this.state.visibleAuditModal}
......
......@@ -89,7 +89,7 @@ export async function categoryList() {
}
/**
* 商品分类
* type 商品类型:1-实物类,2-虚拟类,4-服务类
* type 商品类型:1-实物类,2-虚拟类,4-服务类 5 外卖
* */
export async function apiCategoryListType(type) {
return request.get(`/product/category/getByProductType/${type}`, {
......@@ -289,3 +289,91 @@ export async function apiDraftList(data) {
data,
});
}
// 批量操作
export async function apiGoodsActionBatch(data) {
return request.post('/api/merchants/products/sku/batchOperation', {
prefix: goodsApi,
data,
});
}
// 外卖商品列表
export async function apiTakeawayGoods(params) {
return request.post('/product/api/merchant/page', {
prefix: goodsApi,
data: stringify(params),
headers,
});
}
// 外卖商品排序
export async function apiSortTakeawayGoods(data) {
return request.post('/api/merchants/products/sku/batchSort', {
prefix: goodsApi,
data,
});
}
// 外卖商品置顶
export async function apiTopTakeawayGoods(data) {
return request.post('/api/merchants/products/sku/storageRack/topping', {
prefix: goodsApi,
data,
});
}
// 获取供应商门店列表
export async function apiSupplierShopList(params) {
return request.get(`/api/merchants/shops/getBySupplierId?${stringify(params)}`, {
prefix: goodsApi,
});
}
// 分组创建(货架—创建货架)
export async function apiCreateStorage(data) {
return request.post('/api/merchants/products/storageRack/create', {
prefix: goodsApi,
data,
});
}
// 编辑分组(货架—编辑货架)
export async function apiEditStorage(data) {
return request.post('/api/merchants/products/storageRack/edit', {
prefix: goodsApi,
data,
});
}
// 分组详情(货架—货架详情)
export async function apiStorageInfo(params) {
return request.post('/api/merchants/products/storageRack/QueryByShopIdAndStorageRackId', {
prefix: goodsApi,
data: stringify(params),
headers,
});
}
// 删除分组(货架—删除货架)
export async function apiDelStorage(params) {
return request.post('/api/merchants/products/storageRack/removeByShopIdAndId', {
prefix: goodsApi,
data: stringify(params),
headers,
});
}
// 分组排序(货架—排序货架)
export async function apiSortStorage(data) {
return request.post('/api/merchants/products/storageRack/batchSort', {
prefix: goodsApi,
data,
});
}
// 分组列表(货架—货架列表)
export async function apiStorageList(params) {
return request.post('/api/merchants/products/storageRack/listByShopIdAndStorageRackIds', {
prefix: goodsApi,
data: stringify(params),
headers,
});
}
// 获取库存信息
export async function apiProductStock(data) {
return request.get('/api/merchants/products/sku/getStockInfo', {
prefix: goodsApi,
params: data,
});
}
import React from 'react';
import { Button, Badge, Switch, Modal } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Badge, Switch, Modal, message } from 'antd';
import { SortableHandle } from 'react-sortable-hoc';
import { ExclamationCircleOutlined, MenuOutlined } from '@ant-design/icons';
import styles from './style.less';
import { resetTime } from '../../utils/utils';
import { apiChangeStateGoods, apiQueryLastAuditRecord } from './service';
import { apiChangeStateGoods, apiGoodsActionBatch } from './service';
const { confirm } = Modal;
export const NormalProduct = 1;
export const ServiceProduct = 4;
export const TakeawayProduct = 5;
export const GoodTypes = {
[NormalProduct]: '实体商品',
[ServiceProduct]: '服务类商品',
[TakeawayProduct]: '外卖商品',
};
export const productType = [
{
value: 1,
......@@ -244,6 +254,190 @@ export function column() {
},
];
}
export function takeawayColumn(actions) {
const onChangeState = async ({ skuId, state }) => {
confirm({
icon: <ExclamationCircleOutlined />,
content: `确认${+state === 6 ? '下架' : '上架'}商品?`,
onOk: async () => {
const res = await apiGoodsActionBatch({
skuIds: [skuId],
shopId: actions.shopId,
productStatus: +state === 6 ? 0 : 1, // 6:上架,7:下架
type: 2,
});
if (res.businessCode === '0000' && res.code === '0000') {
actions.getDataList();
message.success('处理成功!');
}
},
});
};
const DragHandle = SortableHandle(() => (
<MenuOutlined style={{ cursor: 'grab', color: '#999' }} />
));
return [
{
title: '排序',
dataIndex: 'sort',
align: 'center',
width: 70,
className: [styles['drag-visible']],
render: () => <DragHandle />,
},
{
title: 'SKU编码',
dataIndex: 'skuId',
width: 180,
align: 'center',
},
{
title: 'SKU商品名称',
// width: 200,
align: 'center',
dataIndex: 'skuName',
},
{
title: '售卖价格(元)',
dataIndex: 'salePrice',
width: 150,
align: 'center',
},
{
title: '库存',
width: 120,
dataIndex: 'stock',
align: 'center',
},
{
title: '上下架状态',
dataIndex: 'stateDesc', // 5:未上架 ,6 :上架,7:下架
width: 200,
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
width: 400,
align: 'center',
render: (_, row, index) => (
<div className={styles.actionBtn}>
{(row.state === 4 || (row.state >= 5 && row.updateState !== 1)) && (
<Button
key="edit"
type="primary"
className={styles.button}
onClick={() => actions.onEdit(row)}
>
编辑
</Button>
)}
<Button
key="viewP"
type="primary"
className={styles.button}
onClick={() => onChangeState(row)}
>
{+row.state === 6 ? '下架' : '上架'}
</Button>
<Button
key="log"
type="primary"
className={styles.button}
onClick={() => actions.onShowStockModal(row)}
>
修改库存
</Button>
{(index > 0 || actions.pageNo > 1) && (
<Button key="top" className={styles.button} onClick={() => actions.toTop(row)}>
置顶
</Button>
)}
</div>
),
},
];
}
// 外卖商品批量操作
export const batchAction = event => [
{
key: 'up',
type: '2',
label: (
<a type="text" onClick={() => event.onChangeState('up')}>
上架
</a>
),
},
{
key: 'down',
type: '2',
label: (
<a type="text" onClick={() => event.onChangeState('down')}>
下架
</a>
),
},
{
key: 'stock',
type: '7',
label: (
<a type="text" onClick={() => event.onChangeState('stock')}>
修改库存
</a>
),
},
{
key: 'time',
type: '4',
label: (
<a type="text" onClick={() => event.onChangeState('time')}>
修改可售时间
</a>
),
},
// {
// key: 'group',
// type: '3',
// label: (
// <a type="text" onClick={() => event.onChangeState('group')}>
// 修改分组
// </a>
// ),
// },
{
key: 'send',
type: '6',
label: (
<a type="text" onClick={() => event.onChangeState('send')}>
设置单点不送
</a>
),
},
{
key: 'buy',
type: '5',
label: (
<a type="text" onClick={() => event.onChangeState('buy')}>
修改最少购买数量
</a>
),
},
];
// 外卖商品可售星期
export const saleWeeks = [
{ label: '周一', value: 1 },
{ label: '周二', value: 2 },
{ label: '周三', value: 3 },
{ label: '周四', value: 4 },
{ label: '周五', value: 5 },
{ label: '周六', value: 6 },
{ label: '周日', value: 7 },
];
export const disSelectStatus = [2, 5];
export const stateList = [
{ value: 3, label: '待审核' },
......
......@@ -54,8 +54,14 @@
.searchForm {
:global {
.ant-form-item-label {
line-height: 40px;
line-height: 32px;
}
.ant-form-item {
margin-bottom: 12px;
}
}
.button {
margin: 1px 5px;
}
}
.queryBtn {
......@@ -63,7 +69,7 @@
}
.actionBtn {
button {
width: 75px;
min-width: 75px;
}
}
.pagination {
......@@ -130,3 +136,158 @@
text-align: left;
word-break: break-all;
}
.takeawayBox {
margin-top: 20px;
padding-bottom: 20px;
background-color: #fff;
&--page {
padding-top: 10px;
padding-left: 30px;
text-align: left;
}
}
.groupBox {
padding: 0 24px 15px 24px;
&-title {
display: flex;
align-items: center;
padding: 10px 0;
font-size: 18px;
&--name {
margin-right: 15px;
}
}
&-body {
padding: 5px 0;
&--tag {
position: relative;
box-sizing: border-box;
height: 34px;
margin-right: 20px;
padding: 0 15px 0 10px;
font-size: 14px;
line-height: 32px;
&__move {
cursor: move;
}
&__edit {
margin-left: 5px;
color: #1890ff;
cursor: pointer;
}
&__close {
position: absolute;
top: 0;
right: 0;
color: #1890ff;
font-size: 16px;
transform: translate(50%, -50%);
cursor: pointer;
}
&__text {
user-select: none;
}
}
&--tag-normal {
position: relative;
height: 34px;
margin-right: 0;
padding: 0 20px;
font-size: 14px;
line-height: 32px;
cursor: pointer;
}
&--tag-input {
width: 80px;
height: 26px;
margin-right: 20px;
}
&--tag__cur {
color: #fff;
background-color: #1890ff;
border: 1px solid #1890ff;
}
&--new {
height: 34px;
margin-right: 0 !important;
margin-left: 10px;
padding: 0 15px;
line-height: 32px;
cursor: pointer;
}
&--dragbox {
padding: 0;
}
}
}
.row-dragging {
background: #fafafa;
border: 1px solid #ccc;
& td {
padding: 16px;
}
}
.drag-visible {
visibility: visible;
}
.td-center {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 0 10px;
word-break: break-all;
}
// .virtual-table {
// table-layout: auto !important;
// table {
// table-layout: auto !important;
// }
// }
.action-bar-box {
padding: 0 0 15px 24px;
&--down {
margin-left: 10px;
}
}
.week-time-box {
&--icon {
position: relative;
top: 4px;
margin: 0 8px;
color: #999;
font-size: 24px;
cursor: pointer;
transition: all 0.3s;
:hover {
box-shadow: 0 0 4px #ccc;
}
}
}
.primary {
color: #1890ff;
}
.error {
color: #ff4d4f;
}
.stock-box {
position: relative;
&--btns {
position: absolute;
top: 0;
right: 0;
height: 32px;
padding-right: 8%;
line-height: 32px;
}
&--red {
color: #ff1616;
}
&--inputnum {
width: 100%;
}
}
.inputWdith {
width: 100%;
}
......@@ -28,7 +28,17 @@
content: '';
}
}
.week {
background: #f0f0f0;
}
.weekCon {
display: flex;
flex-flow: row nowrap;
color: #319bfe;
}
.weekText {
margin-right: 10px;
}
.prodcutContent {
display: flex;
padding-bottom: 10px;
......@@ -186,3 +196,152 @@
color: #0e75fd;
cursor: pointer;
}
.required {
:global {
.ant-form-item-label > {
::before {
display: inline-block;
margin-right: 4px;
color: #ff4d4f;
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
content: '*';
}
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control-input-content {
display: flex;
}
.ant-form-text {
display: flex;
align-items: center;
justify-content: center;
}
}
}
.itemInline {
:global {
.ant-form-item-control-input-content {
display: flex;
}
.ant-form-item {
margin-bottom: 0;
margin-left: 40px;
}
}
}
.itemInlineModal {
:global {
.ant-form-item-control-input-content {
display: flex;
}
.ant-form-item {
margin-bottom: 0;
}
}
}
.textStyle {
color: red;
}
.multiSpecification {
display: flex;
flex-flow: row wrap;
:global {
.ant-form-item-control {
flex-direction: none;
}
.ant-form-item-control-input-content {
flex: none;
}
}
}
.specsSingularBetween {
display: flex;
align-items: center;
justify-content: flex-start;
width: max-content;
:global {
.ant-form-item-label {
overflow: inherit;
}
.ant-col-sm-3 {
max-width: fit-content;
}
}
}
.specsBetween {
display: flex;
justify-content: flex-end;
:global {
.ant-form-item-label {
overflow: inherit;
}
.ant-col-sm-3 {
max-width: fit-content;
}
}
}
.specRepertory {
height: 20px;
margin: 0 10px;
padding: 0 8px;
color: #fff;
line-height: 20px;
text-align: center;
background-color: #319bfe;
border-radius: 8px;
}
.repertoryLimit {
height: 20px;
color: #fff;
line-height: 20px;
text-align: center;
background-color: #88c0f5;
border-radius: 3px;
}
.deal {
:global {
.ant-form-item-control-input-content {
display: flex;
align-items: center;
}
.ant-form-item {
margin-bottom: 0;
}
}
}
.conBg {
width: fit-content;
min-width: 100%;
background: #f8f8f8;
:global {
.ant-input {
margin: 10px 0;
}
.ant-form-item {
margin-bottom: 8px;
}
}
}
.nameWidth {
width: 300px;
}
.colRow {
display: flex;
flex: none;
max-width: none;
}
.rowWarp {
display: flex;
flex-direction: column;
background: #f8f8f8;
}
:global {
.reactEasyCrop_Container {
height: 550px !important;
}
}
import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';
import { Button, Modal, Form, Switch, Input, Select } from 'antd';
import styles from '../common.less';
import { apiCreateShop } from '../service';
const AddMenusModal = (props, ref) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const [open, setOpen] = useState(false);
const [formProject] = Form.useForm();
const [menusSwitch, setMenusSwitch] = useState(0); // 分组开启状态
// const {
// queryShopList,
// } = props;
useImperativeHandle(ref, () => ({
// changeVal 就是暴露给父组件的方法
setOpen: newVal => {
setOpen(newVal);
},
}));
const handleOk = () => {
formProject
.validateFields()
.then(async values => {
console.log('valuse', values, menusSwitch);
const params = {
name: values?.name,
necessary: menusSwitch,
};
const data = await apiCreateShop(params);
if (data.code === '0000') {
formProject.resetFields(); // 表单清除历史
setConfirmLoading(true);
setTimeout(() => {
setOpen(false);
setConfirmLoading(false);
}, 2000);
}
})
.catch(info => {
// queryShopList()
console.log('保存异常', info);
});
};
const handleCancel = () => {
console.log('Clicked cancel button');
setOpen(false);
};
const onChange = () => {
if (menusSwitch === 0) {
setMenusSwitch(1);
return false;
}
setMenusSwitch(0);
return false;
};
useEffect(() => {
console.log('open', open);
}, [open]);
return (
<>
{open && (
<Modal
title="添加分组"
visible={open}
onOk={handleOk}
confirmLoading={confirmLoading}
initialValues={{
menusSwitch: 0,
}}
onCancel={handleCancel}
>
<Form form={formProject}>
<Form.Item label="Select">
<Select>
<Select.Option value="shopId">Demo</Select.Option>
</Select>
</Form.Item>
<Form.Item label="分组名称" name="name" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="下单必选分组" name="necessary" valuePropName={menusSwitch}>
<Switch
defaultChecked={menusSwitch}
checkedChildren="开启"
unCheckedChildren="关闭"
onChange={onChange}
/>
<div className={styles.textStyle}>
选中后,顾客下单需至少选择1个 “下单必须分组” 商品
</div>
<div className={styles.textStyle}>每店仅可设置1个必点分组</div>
</Form.Item>
</Form>
</Modal>
)}
</>
);
};
export default forwardRef(AddMenusModal);
// import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';
// import { Button, Modal, Radio, Space, Form, InputNumber, Switch, Input } from 'antd';
// import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
// import styles from '../common.less';
// import { ENUM_SET_REPERTORY } from '../config';
// const AddMultiSpecModal = (props, ref) => {
// const [confirmLoading, setConfirmLoading] = useState(false);
// const [modalText, setModalText] = useState('Content of the modal');
// const [multiRepertory, setMultiRepertory] = useState(false);
// // const {
// // open,
// // setOpen,
// // } = props;
// useImperativeHandle(ref, () => ({
// // changeVal 就是暴露给父组件的方法
// setMultiRepertory: newVal => {
// setMultiRepertory(newVal);
// },
// }));
// const onChange3 = () => {};
// const handleOk = () => {
// setModalText('The modal will be closed after two seconds');
// setConfirmLoading(true);
// setTimeout(() => {
// setMultiRepertory(false);
// setConfirmLoading(false);
// }, 2000);
// };
// const handleCancel = () => {
// console.log('Clicked cancel button');
// setMultiRepertory(false);
// };
// useEffect(() => {
// console.log('open', multiRepertory);
// }, [multiRepertory]);
// return (
// <>
// {multiRepertory && (
// <Modal
// title="修改库存"
// visible={multiRepertory}
// onOk={handleOk}
// width={1050}
// confirmLoading={confirmLoading}
// onCancel={handleCancel}
// >
// <Form>
// <Form.Item>
// <div>份量(如大小/小份、微辣/特辣等)</div>
// <Form.List name="users">
// {(fields, { add, remove }) => (
// <>
// {fields.map(({ key, name, ...restField }) => (
// <Space
// key={key}
// style={{
// display: 'flex',
// flexDirection: 'column',
// marginBottom: 8,
// }}
// align="baseline"
// >
// <Form.Item className={styles.deal}>
// <Form.Item
// {...restField}
// name={[name, 'first']}
// rules={[
// {
// required: true,
// message: 'Missing first name',
// },
// ]}
// >
// <Input placeholder="名称" />
// </Form.Item>
// <Form.Item
// {...restField}
// name={[name, 'last']}
// rules={[
// {
// required: true,
// message: 'Missing last name',
// },
// ]}
// >
// <Input placeholder="约 份量(数字)" />
// </Form.Item>
// <Form.Item
// {...restField}
// name={[name, 'last']}
// rules={[
// {
// required: true,
// message: 'Missing last name',
// },
// ]}
// >
// <Input placeholder="约 份量(数字)" />
// </Form.Item>
// <Form.Item
// {...restField}
// name={[name, 'last']}
// rules={[
// {
// required: true,
// message: 'Missing last name',
// },
// ]}
// >
// <Input placeholder="销售价(元)" />
// </Form.Item>
// <Form.Item
// {...restField}
// name={[name, 'last']}
// rules={[
// {
// required: true,
// message: 'Missing last name',
// },
// ]}
// >
// <Input placeholder="活动价(元)" />
// </Form.Item>
// <MinusCircleOutlined onClick={() => remove(name)} />
// </Form.Item>
// <Form.Item>
// <Form.Item>
// <div>添加规格(如加料、甜度、辣度等)</div>
// <Form.Item>
// <Form.List name="names">
// {(fields, { add, remove }, { errors }) => (
// <>
// {fields.map((field, index) => (
// <Form.Item
// // {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
// // label={index === 0 ? 'Passengers' : ''}
// required={false}
// key={field.key}
// >
// <Form.Item
// {...field}
// validateTrigger={['onChange', 'onBlur']}
// className={styles.deal}
// rules={[
// {
// required: true,
// whitespace: true,
// message:
// "Please input passenger's name or delete this field.",
// },
// ]}
// >
// <Input
// placeholder="规格名称"
// className={styles.nameWidth}
// />
// {fields.length > 1 ? (
// <MinusCircleOutlined
// className="dynamic-delete-button"
// onClick={() => remove(field.name)}
// />
// ) : null}
// </Form.Item>
// <Form.Item>
// <Form.List name="sights">
// {(fields, { add, remove }) => (
// <>
// {fields.map(field => (
// <Space key={field.key} align="baseline">
// <Form.Item
// noStyle
// shouldUpdate={(prevValues, curValues) =>
// prevValues.area !== curValues.area ||
// prevValues.sights !== curValues.sights
// }
// >
// {() => (
// <Form.Item
// {...field}
// // label="Sight"
// name={[field.name, 'sight']}
// rules={[
// {
// required: true,
// message: 'Missing sight',
// },
// ]}
// >
// <Input style={{ width: '200px' }} placeholder="加价名称" />
// </Form.Item>
// )}
// </Form.Item>
// <Form.Item
// {...field}
// // label="Price"
// // name={[field.name, 'price']}
// rules={[
// {
// required: true,
// message: 'Missing price',
// },
// ]}
// >
// <InputNumber style={{ width: '200px' }} placeholder="加价名称金额(元)" />
// </Form.Item>
// <MinusCircleOutlined
// onClick={() => remove(field.name)}
// />
// </Space>
// ))}
// <Form.Item>
// <Button
// type="dashed"
// onClick={() => add()}
// block
// icon={<PlusOutlined />}
// >
// 新增加价
// </Button>
// </Form.Item>
// </>
// )}
// </Form.List>
// </Form.Item>
// </Form.Item>
// ))}
// <Form.Item>
// <Button
// type="dashed"
// onClick={() => add()}
// style={{ width: '60%' }}
// icon={<PlusOutlined />}
// >
// 新增规格
// </Button>
// </Form.Item>
// </>
// )}
// </Form.List>
// </Form.Item>
// </Form.Item>
// </Form.Item>
// </Space>
// ))}
// <Form.Item>
// <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
// 新增份量
// </Button>
// </Form.Item>
// </>
// )}
// </Form.List>
// </Form.Item>
// </Form>
// </Modal>
// )}
// </>
// );
// };
// export default forwardRef(AddMultiSpecModal);
import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';
import { Button, Modal, Radio, Form, InputNumber, Switch, Space, message } from 'antd';
import styles from '../common.less';
import { ENUM_SET_REPERTORY } from '../config';
import { debounce } from '@/utils/utils';
import { isCheckPriceTwoDecimal, isIntegerNotZero } from '@/utils/validator';
const AddRepertoryModal = (props, ref) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const [openRepertory, setOpenRepertory] = useState(false);
const [form] = Form.useForm();
const [repertoryState, setRepertoryState] = useState('');
const [initialValues, setInitialValues] = useState({
productStock: 0,
maxStock: 0,
autoStock: false,
});
const [isRequired, setIsRequired] = useState(true);
const { modifiedInventory, intactData, repertoryModel } = props;
const { type, idx, item } = repertoryModel;
useImperativeHandle(ref, () => ({
// changeVal 就是暴露给父组件的方法
setOpenRepertory: newVal => {
setOpenRepertory(newVal);
},
}));
// 自动补全
const onChangeAutoStock = e => {
form.setFieldsValue({
autoStock: e ? 1 : 0,
});
setIsRequired(!e);
};
// 勾选库存设置
const onChangeSetRepertory = e => {
setRepertoryState(`${e.target.value}`);
if (+e.target.value === 0) {
form.setFieldsValue({
productStock: 0,
});
} else {
const { maxStock } = form.getFieldsValue(['maxStock']);
form.setFieldsValue({
productStock: maxStock,
});
}
};
// 最大库存设置
const onChangeMaxStock = e => {
// 已经勾选最大库存 自动更新剩余库存
if (+repertoryState === 1) {
form.setFieldsValue({
productStock: e,
});
}
};
const getFormValues = debounce(() => {
const values = form.getFieldsValue();
}, 400);
const handleOk = async () => {
const values = await form.validateFields();
values.autoStock = values.autoStock ? 1 : 0;
if (+values.autoStock === 1 && +values.maxStock === 0) {
message.error('最大库存不能为0');
return;
}
// 回调库存
modifiedInventory(type, idx, values);
setConfirmLoading(true);
setTimeout(() => {
setOpenRepertory(false);
setConfirmLoading(false);
}, 1000);
};
const handleCancel = () => {
setOpenRepertory(false);
};
useEffect(() => {
if (item?.serviceItem) {
const { productStock = 1, autoStock = 0, maxStock = 2 } = item?.serviceItem;
const params = {
productStock,
autoStock: +autoStock === 1,
maxStock,
};
switch (type) {
case 'all': // 统一设置
form.setFieldsValue(params);
break;
case 'multi': // 多规格设置
form.setFieldsValue(params);
break;
case 'singular': // 单规格设置
// setInitialValues(params);
form.setFieldsValue(params);
break;
default:
break;
}
}
}, [openRepertory, item]);
return (
<>
{openRepertory && (
<Modal
title="修改库存"
visible={openRepertory}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<Form form={form} initialValues={initialValues} onValuesChange={getFormValues}>
<Space>
<Form.Item className={styles.itemInlineModal}>
<Form.Item
name="productStock"
label="剩余库存"
style={{
display: 'flex',
marginRight: '8px',
}}
rules={[
{ required: true, message: '请填写剩余库存' },
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber
min={0}
max={999999999}
style={{ width: 200, display: 'inline-block' }}
placeholder="请输入"
/>
</Form.Item>
<Form.Item style={{ width: 200, display: 'inline-block' }}>
<Radio.Group value={repertoryState} onChange={onChangeSetRepertory}>
<Radio.Button value="0">清空</Radio.Button>
<Radio.Button value="1">最大</Radio.Button>
</Radio.Group>
</Form.Item>
</Form.Item>
</Space>
<Form.Item
name="maxStock"
label="最大库存"
rules={[
{ required: !isRequired, message: '请填写最大库存' },
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber
min={0}
max={999999999}
style={{ width: 200 }}
placeholder="请输入"
onChange={onChangeMaxStock}
/>
</Form.Item>
<Form.Item name="autoStock" label="自动补足" valuePropName="checked">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
// defaultChecked
onChange={onChangeAutoStock}
/>
</Form.Item>
</Form>
<div className={styles.textStyle}>修改成功后,原库存将被替换,请谨慎操作!</div>
</Modal>
)}
</>
);
};
export default forwardRef(AddRepertoryModal);
import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';
import { Button, Modal, Form, Checkbox, Row, Col, time, DatePicker } from 'antd';
import { map } from 'lodash';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import moment from 'moment';
import { ENUM_WEEK } from '../config';
import styles from '../common.less';
const format = 'HH:mm';
const { RangePicker } = DatePicker;
const AddSellTimeModal = (props, ref) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const [modalText, setModalText] = useState('Content of the modal');
const [formProject] = Form.useForm();
const [open, setOpen] = useState(false);
const { saleDates, setSaleDates } = props;
useImperativeHandle(ref, () => ({
// changeVal 就是暴露给父组件的方法
setOpen: newVal => {
setOpen(newVal);
},
}));
const handleOk = () => {
formProject.validateFields().then(async values => {
console.log('valuse', values);
setConfirmLoading(true);
setSaleDates(values);
setTimeout(() => {
setOpen(false);
setConfirmLoading(false);
}, 1000);
});
};
const handleCancel = () => {
console.log('Clicked cancel button');
setOpen(false);
};
useEffect(() => {
console.log('open', open);
// formProject.setFieldsValue({ saleTimes: [['12:00', '13:00']] })
}, [open]);
const onChange = () => {};
return (
<>
{open && (
<Modal
title="售卖时段"
visible={open}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
width={1050}
>
<Form
name="sellTime"
form={formProject}
// {...formItemLayout}
// onFinish={onFinish}
initialValues={saleDates}
>
<Form.Item name="unavailableDate" label="售卖时期(可多选)">
<Checkbox.Group>
<Row>
<Col className={styles.colRow} span={8}>
{ENUM_WEEK.map((item, index) => (
<Checkbox value={item.value}>{item.label}</Checkbox>
))}
</Col>
</Row>
</Checkbox.Group>
</Form.Item>
<Form.Item label="售卖时段">
<Form.List
name="saleTimes"
initialValue={[[]]}
// rules={[
// {
// validator: async (_, times) => {
// if (!times || times.length < 2) {
// return Promise.reject(new Error('At least 2 passengers'));
// }
// },
// },
// ]}
>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Form.Item
// {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
required={false}
key={field.key}
className={styles.deal}
>
<Form.Item
className={styles.deal}
{...field}
// validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
message: '请输入售卖时间',
},
]}
>
<RangePicker picker="time" format={format} onChange={onChange} />
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
style={{ width: '60%' }}
icon={<PlusOutlined />}
>
新增时段
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
</Form>
</Modal>
)}
</>
);
};
export default forwardRef(AddSellTimeModal);
import React, { useState, useContext, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Cascader, Form, Input, Select, Popover, Button, Checkbox } from 'antd';
import React, {
useState,
useContext,
useEffect,
forwardRef,
useImperativeHandle,
useRef,
} from 'react';
import {
Cascader,
Form,
Input,
Select,
Popover,
Button,
Checkbox,
Divider,
Modal,
notification,
} from 'antd';
import { formItemLayout } from '../config';
import { ServiceContext } from '../context';
import { debounce } from '@/utils/utils';
import AddMenusModal from './AddMenusModal';
import UploadImage from './UploadImage';
import UploadCropImage from './UploadCropImage';
import { apiShopIds, apiQueryShopList, getByProductType } from '../service';
import GroupInfo from '../../GoodsManage/Takeaway/components/GroupInfo';
const CreateSelectOption = optionList =>
optionList.map(brandItem => (
......@@ -12,7 +35,6 @@ const CreateSelectOption = optionList =>
));
const fileterBrandOptions = (input, options) => options.children.includes(input);
const filterCategoryOptions = (inputValue, path) =>
path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
......@@ -23,13 +45,21 @@ const FormInformationBasic = forwardRef((props, ref) => {
categoryList,
virtualCategoryList,
brandList,
shopList,
afterAddressList,
specListData,
shopGetByProductType,
groupShopData,
} = props;
const [form] = Form.useForm();
const [noreBrandList, setNoreBrandList] = useState([]);
const customer = useContext(ServiceContext);
const childAddMenusModalRef = useRef(null);
const [takeawayImageList, setTakeawayImageList] = useState([]);
const [shopIds, setShopIds] = useState([]);
const [shopList, setShopList] = useState([]);
const [isEditTakeaway, setIsEditTakeaway] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [shopIdSource, setShopIdSource] = useState('');
const onCheck = async () => {
try {
......@@ -54,15 +84,81 @@ const FormInformationBasic = forwardRef((props, ref) => {
}
};
// 自定义加入菜单
const showModal = () => {
if (shopIdSource) {
setIsModalOpen(true);
} else {
notification.error({ message: '请先选择名店在进行操作!' });
}
};
// 自定义菜单下拉
const dropdownRender = menus => (
<div>
{menus}
<Divider
style={{
margin: 0,
}}
/>
<div
style={{
padding: 8,
background: '#1890ff',
color: '#fff',
textAlign: 'center',
}}
onClick={showModal}
>
添加自定义分组
</div>
</div>
);
const onTakeawayImageList = imgList => {
setTakeawayImageList(imgList);
form.setFieldsValue({
commonImageList: imgList,
});
};
const getFormValues = debounce(() => {
const values = form.getFieldsValue();
// console.log('infoMation', values);
props.onValuesChange({ infoMation: values });
}, 400);
// 查询shopIds
const queryShopIds = async () => {
if (!shopIds.length) {
const result = await apiShopIds();
setShopIds(result.data || []);
}
};
// 查询分组列表
const queryShopList = async params => {
let result = [];
if (params) {
result = await apiQueryShopList(params);
} else {
result = await apiQueryShopList({ shopId: shopIdSource });
}
setShopList(result.data || []);
};
const onChangeShopId = async e => {
if (e) {
setShopList([]);
queryShopList({ shopId: e });
setShopIdSource(e);
form.setFieldsValue({
storageRackIds: '',
});
}
};
useImperativeHandle(ref, () => ({
onCheck,
reset: form.resetFields,
}));
useEffect(() => {
queryShopIds();
}, [customer.productType]);
useEffect(() => {
const noreList = (brandList || []).filter(item => item.name === '虚拟商品');
......@@ -71,9 +167,31 @@ const FormInformationBasic = forwardRef((props, ref) => {
useEffect(() => {
if (!editData) return;
if (editData.productType === 5 && editData?.name) {
shopGetByProductType(5);
queryShopList({ shopId: editData?.productRefShopId });
setShopIdSource(editData?.productRefShopId);
setIsEditTakeaway(true);
}
form.setFieldsValue(editData);
if (editData?.commonImageList?.length) {
onTakeawayImageList(editData?.commonImageList);
}
}, [customer.isEdit, editData]);
useEffect(() => {
setShopList([]);
shopGetByProductType(5);
const { shopId = '', groupId = '' } = groupShopData;
if (shopId) {
setShopIdSource(shopId);
queryShopList({ shopId });
}
const temp = {
productRefShopId: shopId ? [`${shopId}`] : [],
storageRackIds: groupId ? [+groupId] : [],
};
form.setFieldsValue(temp);
}, [groupShopData]);
return (
<Form
{...formItemLayout}
......@@ -84,17 +202,71 @@ const FormInformationBasic = forwardRef((props, ref) => {
name: '',
categoryId: [],
description: '',
productRefShopId: [],
storageRackIds: null,
}}
scrollToFirstError
onValuesChange={getFormValues}
>
<Popover content={form.getFieldValue('name')} trigger="hover">
<Form.Item
key="name"
name="name"
label="商品名称"
rules={[
{
required: true,
min: 2,
max: 30,
message: '请输入大于2个且小于30字符的商品名称',
whitespace: true,
},
]}
>
<Input placeholder="请输入商品名称" disabled={customer.isDisabled} />
</Form.Item>
</Popover>
{customer.isTakeawayService && (
<Form.Item
name="productRefShopId"
label="所属门店"
rules={[{ required: true, message: '请选择所属门店' }]}
>
<Select
fieldNames={{ label: 'name', value: 'id' }}
filterOption={fileterBrandOptions}
placeholder="请选择所属门店"
options={shopIds}
onChange={onChangeShopId}
></Select>
</Form.Item>
)}
{/* 菜单分组 */}
{customer.isTakeawayService && (
<Form.Item
name="storageRackIds"
label="菜单分组"
rules={[{ type: 'array', required: true, message: '请输入菜单分组' }]}
>
<Cascader
placeholder="请选择菜单分组"
disabled={customer.isEdit && customer.isNormal}
showSearch={{ filter: filterCategoryOptions }}
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={shopList}
dropdownRender={dropdownRender}
/>
</Form.Item>
)}
{/* 新增菜单分组弹框 */}
{/* <AddMenusModal ref={childAddMenusModalRef} queryShopList={queryShopList} /> */}
<Form.Item
name="categoryId"
label="商品类目"
rules={[{ type: 'array', required: true, message: '请输入商品类目!' }]}
rules={[{ type: 'array', required: true, message: '请输入商品类目' }]}
>
<Cascader
placeholder="请选择商品类目"
placeholder="请选择商品类目"
disabled={customer.isEdit && customer.isNormal}
showSearch={{ filter: filterCategoryOptions }}
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
......@@ -102,11 +274,35 @@ const FormInformationBasic = forwardRef((props, ref) => {
options={newCategoryList[customer.productType]}
/>
</Form.Item>
{!customer.isCard && (
{/* 外卖-商品图片 */}
{customer.isTakeawayService && (
<Form.Item
name="commonImageList"
label="商品图片"
extra="支持.jpg/png格式图片,建议单张比例1:1,大小200kb左右,最多可以上传5张"
rules={[
{
required: true,
type: 'array',
message: '请上传商品图片',
},
]}
>
<UploadCropImage
name="takeawayImageList"
limit={5}
disabled={customer.isDisabled}
pictures={takeawayImageList}
setPictureList={list => onTakeawayImageList(list)}
/>
</Form.Item>
)}
{!customer.isTakeawayService && !customer.isCard && (
<Form.Item
name="brandId"
label="商品品牌"
rules={[{ required: true, message: '请选择商品品牌!' }]}
rules={[{ required: true, message: '请选择商品品牌' }]}
extra="若需新增品牌请联系业务员"
>
<Select
......@@ -121,19 +317,8 @@ const FormInformationBasic = forwardRef((props, ref) => {
</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 placeholder="请输入商品名称" disabled={customer.isDisabled} />
</Form.Item>
</Popover>
{!customer.isCard && (
{!customer.isTakeawayService && !customer.isCard && (
<Form.Item
name="character"
label="商品卖点"
......@@ -146,7 +331,7 @@ const FormInformationBasic = forwardRef((props, ref) => {
</Form.Item>
)}
{!customer.isCard && (
{!customer.isTakeawayService && !customer.isCard && (
<Form.Item name="afterAddressId" label="售后地址">
<Select showSearch placeholder="请选择售后地址" filterOption={fileterBrandOptions}>
{(afterAddressList || []).map(item => (
......@@ -158,7 +343,8 @@ const FormInformationBasic = forwardRef((props, ref) => {
</Form.Item>
)}
{!customer.isCard &&
{!customer.isTakeawayService &&
!customer.isCard &&
specListData.map((item, index) => (
<Form.Item name={item.specId} key={item.specId} label={item.specName}>
<Checkbox.Group options={item.specValues} />
......@@ -180,11 +366,17 @@ const FormInformationBasic = forwardRef((props, ref) => {
key="description"
name="description"
label="描述"
rules={[{ required: true, message: '请输入描述,100字以内' }]}
rules={[{ required: true, message: '请输入描述,100字以内' }]}
>
<Input.TextArea showCount maxLength={100} placeholder="请输入描述, 100字以内" />
<Input.TextArea showCount maxLength={100} placeholder="请输入描述, 100字以内" />
</Form.Item>
) : null}
<GroupInfo
isModalOpen={isModalOpen}
shopId={shopIdSource}
search={queryShopList}
handleClose={setIsModalOpen}
/>
</Form>
);
});
......
/* eslint-disable consistent-return */
/* eslint-disable react/no-array-index-key */
/* eslint-disable no-shadow */
import React, {
useContext,
useState,
useEffect,
forwardRef,
useImperativeHandle,
useRef,
} from 'react';
import {
Form,
Input,
Select,
Button,
Checkbox,
Radio,
Space,
Modal,
Switch,
Row,
Col,
InputNumber,
Cascader,
Divider,
DatePicker,
message,
} from 'antd';
import moment from 'moment';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Title } from './CommonTemplate';
import { formItemLayout, ENUM_REPERTORY, ENUM_SET_REPERTORY, ENUM_WEEK } from '../config';
import { ServiceContext } from '../context';
import { debounce } from '@/utils/utils';
import { isCheckPriceTwoDecimal, isIntegerNotZero } from '@/utils/validator';
import UploadCropImage from './UploadCropImage';
// import AddSellTimeModal from './AddSellTimeModal';
import styles from '../common.less';
import AddRepertoryModal from './AddRepertoryModal';
import AddMultiSpecModal from './AddMultiSpecModal';
import { apiTagList, apiUnits } from '../service';
import { localAutoSaveKey, calcDescartes } from '../utils';
import localStorage from '@/utils/localStorage';
const createInitValues = () => ({
description: '', // 商品描述
detailImageList: [], // 商品图片
stock: '1', // 库存类型
saleTimeType: 0, // 售卖时间
singleDelivery: 0, // 单点不送
list: 1, // 列出商品
label: [],
minPurchaseNum: 1,
});
const format = 'HH:mm';
const { RangePicker } = DatePicker;
const TakeawayGoodsInfo = forwardRef((props, ref) => {
const { editData, infoMation } = props;
const [form] = Form.useForm();
const [initValue, setInitValue] = useState(createInitValues());
const customer = useContext(ServiceContext);
const [detailImageList, setDetailImageList] = useState([]);
const addSellTimeRef = useRef(null);
const AddRepertoryRef = useRef(null);
const AddMultiSpecRef = useRef(null);
const [saleDates, setSaleDates] = useState({
saleDates: [],
saleTimes: [],
}); // 可售日期
const [timeType, setTimeType] = useState(0);
const [repertoryType, setRepertoryType] = useState('1');
const [tagList, setTagList] = useState([]);
const [unitsList, setUnitsList] = useState([]);
const [takeawayData, setTakeawayData] = useState({});
// eslint-disable-next-line prefer-const
let [multiSpu, setMultiSpu] = useState([]);
const [singularSpu, setSingularSpu] = useState([]);
const [intactData, setIntactData] = useState({});
const [repertoryState, setRepertoryState] = useState('');
const [repertoryModel, setRepertoryModel] = useState({});
const [tempMultiSpu, setTempMultiSpu] = useState([]);
const [tempWeight, setTempWeight] = useState([]);
const [tempSpecs, setTempSpecs] = useState([]);
const [weightUnits, setWeightUnits] = useState([]);
const [peopleUnits, setPeopleUnits] = useState([]);
const [isRequired, setIsRequired] = useState(true);
// 自定义加入菜单
const showModal = () => {
addSellTimeRef.current.setOpen(true);
};
const onCheck = async () => {
try {
const values = await form.validateFields();
return {
...values,
temp: 'takeawayItem',
intactData,
};
} catch (errorInfo) {
return null;
}
};
// // 过滤存在的sku对象
// const objectComparison = (item, itm) => {
// console.log(item, itm, '==========');
// const {
// serviceItem: { maxStock, autoStock, productStock },
// } = itm;
// const params = { maxStock, autoStock, productStock: productStock || itm.productStock };
// const temp = { ...item, ...params };
// return temp;
// };
const takeawayCalc = takeawayData => {
// 商品基本信息编辑商品名称
const { infoMation: name, infoMation, takeawayItem } = takeawayData;
// weight 份量 specs规格 生成sku规则 weight * specs
const {
specs = [],
weight = [],
description,
detailImageList,
list,
minPurchaseNum,
saleDates,
maxStock = 0,
saleTimes = [],
label,
quantity,
productStock = 0,
salePrice,
singleDelivery,
saleTimeType,
autoStock = 0,
skuList,
id,
categoryId,
} = takeawayItem;
let { unit } = takeawayItem;
unit = (unit && (Array.isArray(unit) && unit?.length && unit?.slice(1).toString())) || unit;
const singularSpecList = [{ specGroupName: '份量', generateSku: 1, specs: [] }]; // 单规格
const multiSpecList = [{ specGroupName: '份量', generateSku: 1, specs: [] }]; // 多规格
let singularSpuData = []; // 单库存spu
const saleTimesTemp = [];
if (saleTimes.length) {
saleTimes.forEach(item => {
const startTime = item ? moment(item[0]).format(format) : '';
const endTime = item ? moment(item[1]).format(format) : '';
saleTimesTemp.push({ startTime, endTime });
});
}
const temp = {
salePrice: salePrice || 0,
productStock,
list: 1,
id: skuList?.length === 1 ? skuList[0].id : '', // 单规格默认取第一个
serviceItem: {
description, // 商品描述
maxStock, // 最大库存
productStock,
minPurchaseNum, // 最少购买
saleTimeType, // 售卖时间
singleDelivery, // 单点不送
list, // 是否列出 1 是 0 否(外卖商品必填)
label, // 商品标签id
saleDates, // 可售日期 1-8
saleTimes: saleTimesTemp, // 可售日期 时间段
autoStock, // 弹框设置---自动补足
},
};
// 单规格
if (+repertoryType === 1) {
const specs = {
salePrice,
maxStock,
quantity,
// unit: (Array.isArray(unit) && unit?.length && unit?.splice(unit?.length - 1)[0]) || unit,
unit,
productStock,
specGroupName: '份量',
};
if (peopleUnits.includes(specs.unit)) {
delete specs.quantity;
}
singularSpecList[0].specs = [specs];
singularSpuData = [{ ...temp, specs: [specs] }];
setSingularSpu(singularSpuData);
}
// 多规格
if (+repertoryType === 2) {
// if (name || editData.name) {
if (weight.length) {
let tempSku = [];
multiSpecList[0].specs = weight; // 处理specList
if (specs.length) {
specs.forEach((specsItem, specsIndex) => {
if (specsItem && !specsItem.generateSku) {
specsItem.generateSku = 1;
}
if (specsItem?.specGroupName) {
specsItem.specs.forEach(itm => {
itm.specGroupName = specsItem?.specGroupName;
});
}
multiSpecList.push(specsItem);
});
}
const sepcx = specs.map(item => item.specs);
sepcx.map(item => {
item.unit = '';
return item;
});
// 生成 specs 规格sku
tempSku = [[...weight], ...sepcx].reduce(
(x, y) => {
const arr = [];
x.forEach(x => y.forEach(y => arr.push(x.concat([y]))));
return arr;
},
[[]],
);
multiSpu = tempSku.map((item, index) => {
const obj = {};
obj.salePrice = salePrice;
obj.productStock = productStock;
obj.list = list;
obj.id = multiSpu?.[index]?.id || '';
obj.serviceItem = {
...temp.serviceItem,
productStock: multiSpu?.[index]?.serviceItem?.productStock || productStock,
maxStock: multiSpu?.[index]?.serviceItem?.maxStock || maxStock,
autoStock: multiSpu?.[index]?.serviceItem?.autoStock || autoStock,
};
obj.unique = '';
multiSpecList.forEach((itm, idx) => {
if (item[idx] && !item[idx]?.specGroupName) {
if (multiSpecList[idx].specGroupName) {
item[idx].specGroupName = multiSpecList[idx].specGroupName;
}
}
});
obj.specs = [...item];
return obj;
});
}
multiSpu.forEach(item => {
item.salePrice = 0;
item.unique = '';
item.unique = item?.specs
.map(itm => {
if (itm?.unit) {
itm.unit =
itm?.unit &&
(Array.isArray(itm?.unit) ? itm?.unit.slice(itm?.unit.length - 1)[0] : itm?.unit);
}
if (itm?.unit && peopleUnits.includes(itm.unit)) {
delete itm.quantity;
}
if (itm?.specName) {
return itm.specName;
}
return itm;
})
.toString();
});
// 编辑数据
if (customer.isEdit) {
const uniqueArr = tempMultiSpu.map(item => item.unique);
const multiSpuUnique = multiSpu.map(item => item.unique);
const newArrLen = Array.from(new Set(multiSpuUnique)).length;
if (newArrLen < multiSpuUnique.length) {
message.error('份量名称不能重复!');
}
// 新增对比数据
multiSpu.forEach((item, index) => {
if (!uniqueArr.includes(item.unique)) {
tempMultiSpu.push({
...item,
serviceItem: { ...temp.serviceItem, maxStock: 0, autoStock: 0, productStock: 0 },
id: '',
});
}
});
// // 删除对比数据
while (multiSpu.length !== tempMultiSpu.length) {
tempMultiSpu.forEach((item, index) => {
if (!multiSpuUnique.includes(item.unique)) {
tempMultiSpu.splice(index, 1);
}
});
}
// 同步库存设置
multiSpu.map(item => {
tempMultiSpu.forEach(itm => {
if (item.unique === itm.unique) {
// 修改商品售卖信息参数
const params = {
saleDates: temp.serviceItem.saleDates,
saleTimes: temp.serviceItem.saleTimes,
saleTimeType: temp.serviceItem.saleTimeType,
minPurchaseNum: temp.serviceItem.minPurchaseNum,
};
item.serviceItem = { ...item.serviceItem, ...itm.serviceItem, ...params };
itm.specs = [...item.specs];
}
});
return item;
});
setTempMultiSpu(tempMultiSpu);
setMultiSpu(JSON.parse(JSON.stringify(multiSpu)));
}
// 新增
if (!customer.isEdit) {
setTempMultiSpu(multiSpu);
setMultiSpu(multiSpu);
}
}
// +repertoryType === 1 单规格 2多规格
const intactDataTemp = {
...takeawayItem,
id,
type: 5, // 外卖类型
...infoMation,
label: label && label.toString(),
list,
description,
detailImageList,
repertoryType,
singleDelivery,
specList: +repertoryType === 1 ? singularSpecList : multiSpecList, // 单库存和多库存specList
items: +repertoryType === 1 ? singularSpuData : JSON.parse(JSON.stringify(multiSpu)),
// categoryId:
// (
// infoMation?.categoryId &&
// infoMation?.categoryId?.slice(infoMation?.categoryId?.length - 1)
// )?.toString() ||
// (Array.isArray(categoryId) && categoryId?.slice(categoryId?.length - 1)?.toString()),
};
setIntactData(intactDataTemp);
return intactData;
};
const onChange = () => {};
const getFormValues = debounce(() => {
const values = form.getFieldsValue();
console.log(values, 'values===');
props.onValuesChange({ takeawayItem: JSON.parse(JSON.stringify(values)) });
const takeawayData = customer.isEdit
? { takeawayItem: Object.assign({}, editData, values) }
: { takeawayItem: values };
// : localStorage.get(localAutoSaveKey) || {};
setTakeawayData(takeawayData);
takeawayCalc(takeawayData);
}, 400);
// 设置库存
const modifiedInventory = (type, idx, values) => {
const { productStock, maxStock, autoStock } = values;
if (type === 'multi') {
tempMultiSpu.map(item => {
if (item.unique === idx) {
item.serviceItem.productStock = productStock;
item.serviceItem.autoStock = autoStock;
item.serviceItem.maxStock = maxStock;
}
return item;
});
}
if (type === 'all') {
tempMultiSpu.map(item => {
item.serviceItem.productStock = productStock;
item.serviceItem.autoStock = autoStock;
item.serviceItem.maxStock = maxStock;
return item;
});
}
setTempMultiSpu(tempMultiSpu);
getFormValues();
return false;
};
const filterCategoryOptions = (inputValue, path) =>
path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
useImperativeHandle(ref, () => ({
onCheck,
reset: () => {
setInitValue(createInitValues());
form.resetFields();
},
}));
const startTime = moment(moment().format(format));
const endTime = moment(moment().format(format));
// 上传图片
const onCardSuccessImageList = imgList => {
setDetailImageList(imgList);
form.setFieldsValue({
detailImageList: imgList,
});
};
// 自定义菜单下拉
const dropdownRender = menus => (
<div>
{menus}
<Divider
style={{
margin: 0,
}}
/>
<div
style={{
padding: 8,
background: '#1890ff',
color: '#fff',
textAlign: 'center',
}}
onClick={showModal}
>
添加自定售卖时间
</div>
</div>
);
// 切换库存
const onChangeRepertory = e => {
const { value } = e.target;
setRepertoryType(`${value}`);
if (+value === 1) {
form.setFieldsValue({ weight: [] });
form.setFieldsValue({ specs: [] });
} else {
const params = {
quantity: '',
unit: [],
salePrice: '',
productStock: '',
maxStock: '',
autoStock: false,
};
form.setFieldsValue(params);
}
};
// 勾选库存设置
const onChangeSetRepertory = e => {
setRepertoryState(`${e.target.value}`);
if (+e.target.value === 0) {
form.setFieldsValue({
productStock: 0,
});
} else {
const { maxStock } = form.getFieldsValue(['maxStock']);
form.setFieldsValue({
productStock: maxStock,
});
}
getFormValues();
};
// 最大库存设置
const onChangeMaxStock = e => {
// 已经勾选最大库存 自动更新剩余库存
if (+repertoryState === 1) {
form.setFieldsValue({
productStock: e,
});
}
};
// 切换时间
const onChangeTime = e => {
setTimeType(e.target.value);
};
// 自动补全
const onChangeAutoStock = e => {
form.setFieldsValue({
autoStock: e ? 1 : 0,
});
setIsRequired(!e);
};
// 显示加入库存弹框
const showAddRepertoryModal = (type, idx, item) => {
AddRepertoryRef.current.setOpenRepertory(true);
setRepertoryModel({ type, idx, item });
};
// 拼接sku 名称
const calcLabelName = item => {
let firstName = '';
let lastName = '';
const tempName = `${editData?.name || ''}`;
// const tempSpecName = `${item?.specs[0]?.specName || ''}`;
const tempQuantity = `(${item.specs[0]?.quantity || ''}`;
const tempUnit = `${item.specs[0]?.unit || ''} ${item.specs[0]?.unit ? ')' : ''}`;
// const tempSecondSpecName = `${item.specs[1]?.specName || ''}`;
// const isShow = tempQuantity && tempUnit && '+';
const uniqueName = item?.unique?.split(',') || [];
if (uniqueName && uniqueName.length) {
firstName = uniqueName[0] || '';
lastName = uniqueName?.slice(1).join(' ') || '';
}
// ${tempName}
return `${firstName} ${tempQuantity} ${tempUnit} ${lastName}`;
};
const init = async () => {
if (!tagList.length) {
const res = await apiTagList();
setTagList(res.data || []);
}
if (!unitsList.length) {
const res = await apiUnits();
setUnitsList(res.data || []);
const tempWeight = res.data.filter(item => item.name === '准确重量单位');
const tempPeople = res.data.filter(item => item.name === '适用人数');
if (tempWeight.length && tempWeight[0]?.children) {
const tempWeightName = tempWeight[0]?.children.map(item => item.name);
sessionStorage.setItem('weightUnits', JSON.stringify(tempWeightName || []));
setWeightUnits(tempWeightName);
}
if (tempPeople.length && tempPeople[0]?.children) {
const tempPeopleName = tempPeople[0]?.children.map(item => item.name);
sessionStorage.setItem('peopleUnits', JSON.stringify(tempPeopleName || []));
setPeopleUnits(tempPeopleName);
}
}
setTempMultiSpu([]);
return false;
};
useEffect(() => {
if (customer.isEdit || customer.isUseCache) {
if (!editData) return;
const {
label,
firstCategoryId,
secondCategoryId,
thirdCategoryId,
id,
saleTimes,
saleTimeType,
} = editData;
editData.label = label && label.split(',')?.map(item => +item);
const specList = editData?.specList;
const weight = specList?.filter(item => item.specGroupName === '份量');
const specs = specList?.filter(item => item.specGroupName !== '份量');
const tempWeightName = JSON.parse(sessionStorage.getItem('weightUnits'));
weight.forEach(item => {
if (item?.specs?.length) {
item.specs.forEach(itm => {
if (itm?.unit && tempWeightName.includes(itm?.unit)) {
itm.quantity =
itm?.quantity.indexOf('约') > -1 ? itm?.quantity.slice(1) : itm?.quantity || '';
}
});
}
});
editData.saleTimes = saleTimes?.length
? saleTimes.map(item => [moment(item?.startTime, format), moment(item?.endTime, format)])
: [];
if (editData?.skuList?.length) {
// editData.minPurchaseNum = editData?.skuList[0]?.serviceItem.minPurchaseNum;
// 单规格
if (editData?.skuList?.length === 1) {
const {
productStock,
shopId,
specs,
serviceItem: { maxStock, id, autoStock },
} = editData?.skuList[0];
const { quantity, unit, salePrice } = specs[0];
editData.productStock = productStock;
editData.quantity = `${quantity}`.indexOf('约') > -1 ? quantity.slice(1) : quantity || '';
editData.unit = unit;
editData.salePrice = salePrice;
editData.maxStock = maxStock;
editData.categoryId = [firstCategoryId, secondCategoryId, thirdCategoryId];
editData.autoStock = +autoStock === 1;
editData.productRefShopId = editData.shopId;
editData.skuId = id;
// setSingularSpu(JSON.parse(JSON.stringify(tempMultiSpu)) || []);
form.setFieldsValue(editData);
} else {
// 多规格
setRepertoryType('2');
const specList = editData?.specList;
const weight = specList.filter(item => item.specGroupName === '份量');
const tempWeightName = JSON.parse(sessionStorage.getItem('weightUnits'));
weight.forEach(item => {
if (item?.specs?.length) {
item.specs.forEach(itm => {
if (itm?.unit && tempWeightName.includes(itm?.unit)) {
itm.quantity =
itm?.quantity?.indexOf('约') > -1
? itm?.quantity.slice(1)
: itm?.quantity || '';
}
});
}
});
editData.categoryId = [firstCategoryId, secondCategoryId, thirdCategoryId];
editData.productRefShopId = editData.shopId;
// setTempWeight(weight)
// setTempSpecs(specs)
form.setFieldsValue({ weight: weight[0].specs });
form.setFieldsValue(editData);
form.setFieldsValue({ specs });
const tempMultiSpu = editData?.skuList.map(item => {
if (item) {
item.serviceItem.productStock = item.productStock;
const weightIndex = item.specs.findIndex(itm => itm.specGroupName === '份量');
const tempQuantity = item.specs[weightIndex].quantity;
const tempUnit = item.specs[weightIndex].unit;
const specsNameList = item.specs.map(itm => itm.specName);
const lastName = specsNameList.slice(1)?.join(' ');
item.unique = item?.specs.map(itm => itm.specName).toString();
}
return item;
});
setTempMultiSpu(tempMultiSpu || []);
setMultiSpu(JSON.parse(JSON.stringify(tempMultiSpu)) || []);
}
setTimeType(saleTimeType);
form.setFieldsValue({ productRefShopId: editData.shopId });
onCardSuccessImageList(editData?.detailImageList);
getFormValues();
}
}
}, [customer.isEdit, editData]);
useEffect(() => {
setIntactData(intactData);
}, [intactData]);
useEffect(() => {
init();
}, []);
useEffect(() => {
getFormValues();
}, [repertoryType]);
useEffect(() => {
setTempMultiSpu(tempMultiSpu);
}, [tempMultiSpu]);
return (
<>
<Form
{...formItemLayout}
form={form}
name="takeaway"
initialValues={initValue}
scrollToFirstError
onValuesChange={getFormValues}
>
<Title title="商品详细信息" />
<Form.Item name="description" label="商品描述">
<Input.TextArea
showCount
maxLength={200}
style={{ width: 400 }}
placeholder="请输入商品描述"
/>
</Form.Item>
<Form.Item
name="detailImageList"
label="商品图片"
extra="支持.jpg/png格式图片,建议单张切片宽750像素,大小200kb左右,您可以拖拽图片调整顺序,最多上传5张。"
>
<UploadCropImage
name="detailImageList"
limit={5}
disabled={customer.isDisabled}
pictures={detailImageList}
setPictureList={list => onCardSuccessImageList(list)}
/>
</Form.Item>
<Title title="商品售卖信息" />
<Form.Item
name="minPurchaseNum"
label="最少购买"
rules={[
{ required: true, message: '请输入最少购买数' },
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber min={1} max={999999999} style={{ width: 200 }} placeholder="请输入购买量" />
</Form.Item>
<Form.Item
name="saleTimeType"
label="售卖时间"
// rules={[{ type: 'array', required: true, message: '请输入售卖时间!' }]}
>
<Radio.Group onChange={onChangeTime}>
<Radio value={0}>全时段</Radio>
<Radio value={1}>自定义售卖时间</Radio>
</Radio.Group>
</Form.Item>
{+timeType === 1 && (
<>
<Form.Item name="saleDates" label="售卖时期(可多选)">
<Checkbox.Group>
<Row>
<Col className={styles.colRow} span={8}>
{ENUM_WEEK.map((item, index) => (
<Checkbox value={item.value}>{item.label}</Checkbox>
))}
</Col>
</Row>
</Checkbox.Group>
</Form.Item>
<Form.Item label="售卖时段">
<Form.List name="saleTimes" initialValue={[[]]}>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key} className={styles.deal}>
<Form.Item
className={styles.deal}
{...field}
rules={[
{
required: true,
message: '请输入售卖时间',
},
]}
>
<RangePicker
picker="time"
defaultPickerValue={[startTime, endTime]}
format={format}
onChange={onChange}
showTime={{
format: 'HH:mm',
// defaultValue: [moment('00:00', 'HH:mm'), moment('00:00', 'HH:mm')],
hideDisabledOptions: true,
}}
disabledTime={() => ({
disabledMinutes: () => {
const result = [];
for (let i = 1; i < 60; i++) {
if (i !== 30) {
result.push(i);
}
}
return result;
},
})}
/>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item>
{fields.length < 5 ? (
<Button
type="dashed"
onClick={() => add()}
style={{ width: '60%' }}
icon={<PlusOutlined />}
>
新增售卖时段
</Button>
) : null}
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
</>
)}
<Form.Item name="label" label="商品标签">
<Select
// mode="multiple"
allowClear
placeholder="请选择商品标签"
style={{ width: 400 }}
disabled={customer.isEdit && customer.isNormal}
// showSearch={{ filter: filterCategoryOptions }}
fieldNames={{ label: 'tagName', value: 'tagId' }}
// onChange={props.onCategoryChange}
options={tagList}
/>
</Form.Item>
<Form.Item name="singleDelivery" label="单点不送" extra="开启后顾客点单则此商品不可下单">
<Radio.Group>
<Radio value={1}>是</Radio>
<Radio value={0}>否</Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="list" label="列出商品" extra="开启后平台展示商品">
<Radio.Group>
<Radio value={1}>是</Radio>
<Radio value={0}>否</Radio>
</Radio.Group>
</Form.Item>
<Title title="规格信息" />
{!customer.isEdit && (
<Form.Item label="库存" name="stock">
<Radio.Group
options={ENUM_REPERTORY}
onChange={onChangeRepertory}
value="1"
buttonStyle="solid"
optionType="button"
/>
</Form.Item>
)}
{/* 单规格 */}
{repertoryType === '1' && (
<>
<Form.Item label="份量" className={styles.required}>
{form.getFieldValue(['unit']) &&
Array.isArray(form.getFieldValue(['unit'])) &&
weightUnits.includes(
form
.getFieldValue(['unit'])
.slice(1)
.toString(),
) && <span className="ant-form-text"> 约</span>}
<Form.Item
// noStyle
shouldUpdate={(prevValues, curValues) => false}
>
{form => {
let unit = form.getFieldValue('unit') || '';
unit =
(unit && (Array.isArray(unit) && unit?.length && unit?.slice(1).toString())) ||
unit;
if (!peopleUnits.includes(unit)) {
return (
<Form.Item
name="quantity"
style={{
display: 'inline-block',
}}
rules={[
{ required: true, message: '请输入分量!' },
{
validator: isCheckPriceTwoDecimal,
type: 'number',
message: '保留两位小数',
},
]}
>
<InputNumber
min={1}
max={999999999}
style={{ width: 200 }}
placeholder="请输入数字"
/>
</Form.Item>
);
}
}}
</Form.Item>
<Form.Item
name="unit"
rules={[{ required: true, message: '请选择单位' }]}
// rules={[{ type: 'array', required: true, message: '请选择单位' }]}
style={{
display: 'inline-block',
margin: '0 8px',
}}
>
<Cascader
placeholder="请选择单位"
disabled={customer.isEdit && customer.isNormal}
showSearch={{ filter: filterCategoryOptions }}
fieldNames={{ label: 'name', value: 'name', children: 'children' }}
onChange={() => {
const unit = form.getFieldValue('unit') || [];
if (unit && peopleUnits.includes(unit[1])) {
form.setFieldsValue({ quantity: '' });
}
}}
options={unitsList}
/>
</Form.Item>
</Form.Item>
<Form.Item
name="salePrice"
label="销售价格"
rules={[{ required: true, message: '请输入销售价格' }]}
>
<InputNumber
min={0}
max={999999999}
precision={2}
style={{ width: 200 }}
placeholder="元"
/>
</Form.Item>
<Form.Item className={styles.itemInline}>
<Form.Item
name="productStock"
label="剩余库存"
style={{
display: 'flex',
marginRight: '8px',
}}
rules={[
{ required: true, message: '请填写剩余库存' },
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber
min={0}
max={999999999}
style={{ width: 200, display: 'inline-block' }}
placeholder="请输入"
/>
</Form.Item>
<Form.Item style={{ width: 200, display: 'inline-block' }}>
<Radio.Group value={repertoryState} onChange={onChangeSetRepertory}>
<Radio.Button value="0">清空</Radio.Button>
<Radio.Button value="1">最大</Radio.Button>
</Radio.Group>
</Form.Item>
</Form.Item>
<Form.Item
name="maxStock"
label="最大库存"
rules={[
{ required: !isRequired, message: '请填写最大库存' },
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber
max={999999999}
min={0}
style={{ width: 200 }}
placeholder="请输入"
onChange={onChangeMaxStock}
/>
</Form.Item>
<Form.Item name="autoStock" label="自动补足" valuePropName="checked">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
// defaultUnChecked
onChange={onChangeAutoStock}
/>
</Form.Item>
<div className={styles.rowWarp}>
{singularSpu.length > 0 &&
singularSpu[0]?.specs[0]?.unit &&
singularSpu.map((item, index) => (
<div className={styles.specsSingularBetween}>
<Form.Item label={calcLabelName(item, 'singular')}>
<div className={styles.specsSingularBetween}>
<span className={styles.repertoryLimit}>
{item?.specs[0]?.productStock}/{item?.specs[0]?.maxStock}
</span>
{/* <div
className={styles.specRepertory}
onClick={() => {
showAddRepertoryModal('singular', index); // 单规格库存
}}
>
设置库存
</div> */}
</div>
</Form.Item>
</div>
))}
</div>
</>
)}
{/* 多规格 */}
{repertoryType === '2' && (
<>
<Form.Item>
<div>份量(如大小/小份、微辣/特辣等)</div>
<Form.List name="weight" initialValue={[{}]}>
{(weightFields, { add: weightAdd, remove: weightRemove }) => (
<>
{weightFields.map((weightField, index) => (
<Space key={weightField.key} align="baseline" className={styles.conBg}>
<Form.Item
{...weightField}
name={[weightField.name, 'specName']}
rules={[
{
required: true,
message: '请输入名称',
},
]}
>
<Input style={{ width: '200px' }} placeholder="名称" />
</Form.Item>
{weightUnits.includes(form.getFieldValue(['weight'])?.[index]?.unit) && (
<span className="ant-form-text"> 约</span>
)}
<Form.Item
// noStyle
shouldUpdate={(prevValues, curValues) => false}
>
{form => {
let unit = form.getFieldValue(['weight'])?.[index]?.unit || '';
unit =
(unit &&
(Array.isArray(unit) &&
unit?.length &&
unit?.slice(1).toString())) ||
unit;
if (!peopleUnits.includes(unit)) {
return (
<Form.Item
style={{
display:
peopleUnits.includes(
form.getFieldValue(['weight'])?.[index]?.unit,
) && 'none',
}}
{...weightField}
name={[weightField.name, 'quantity']}
rules={[
{
required: !peopleUnits.includes(
form.getFieldValue(['weight'])?.[index]?.unit,
),
message: '份量',
},
{
validator: isIntegerNotZero,
type: 'number',
message: '请输入大于0的整数',
},
]}
>
<InputNumber
style={{ width: '120px' }}
max={999999999}
min={0}
placeholder="约 份量(数字)"
/>
</Form.Item>
);
}
}}
</Form.Item>
<Form.Item
{...weightField}
name={[weightField.name, 'unit']}
rules={[
{
// type: 'array',
required: true,
message: '请选择单位',
},
]}
>
<Cascader
placeholder="请选择单位"
disabled={customer.isEdit && customer.isNormal}
showSearch={{ filter: filterCategoryOptions }}
fieldNames={{
label: 'name',
value: 'name',
children: 'children',
}}
// onChange={props.onCategoryChange}
options={unitsList}
/>
</Form.Item>
<Form.Item
{...weightField}
name={[weightField.name, 'salePrice']}
rules={[
{
required: true,
message: '销售价',
},
]}
>
<InputNumber
max={999999999}
min={0}
precision={2}
style={{ width: '120px' }}
placeholder="销售价(元)"
/>
</Form.Item>
<Form.Item
{...weightField}
name={[weightField.name, 'activityPrice']}
rules={[
{
required: false,
message: '活动价',
},
]}
>
<InputNumber
max={999999999}
min={0}
precision={2}
style={{ width: '120px' }}
placeholder="活动价(元)"
/>
</Form.Item>
{weightFields.length > 1 ? (
<MinusCircleOutlined onClick={() => weightRemove(weightField.name)} />
) : null}
</Space>
))}
<Form.Item>
<Button
type="primary"
onClick={() => weightAdd()}
block
style={{ width: '400px' }}
icon={<PlusOutlined />}
>
新增份量
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
{/* takeawayData?.takeawayItem?.weight?.length > 0 && */}
{
<>
<Form.Item>
<div>添加规格(如加料、甜度、辣度等)</div>
<Form.List name="specs" initialValue={[]}>
{(specsFields, { add, remove }) => (
<>
{specsFields.map((specsField, index) => (
<Form.Item key={specsField.key} className={styles.conBg}>
<Form.Item
{...specsField}
// validateTrigger={['onChange', 'onBlur']}
name={[specsField.name, 'specGroupName']}
rules={[
{
required: true,
whitespace: true,
message: '请输入规格名称',
},
]}
noStyle
>
<Input placeholder="规格名称" className={styles.nameWidth} />
</Form.Item>
{/* {specsFields.length > 1 ? ( */}
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(specsField.name)}
/>
{/* ) : null} */}
<Form.List
{...specsField}
name={[specsField.name, 'specs']}
initialValue={[{}]}
>
{(specsInfoFields, { add: specsAdd, remove: specsRemove }) => (
<>
{specsInfoFields.map(specsInfofield => (
<Space key={specsInfofield.key} align="baseline">
<Form.Item
{...specsInfofield}
name={[specsInfofield.name, 'specName']}
rules={[
{
required: true,
message: '请输入加价名称',
},
]}
>
<Input style={{ width: '200px' }} placeholder="加价名称" />
</Form.Item>
<Form.Item
{...specsInfofield}
name={[specsInfofield.name, 'salePrice']}
rules={[
{
required: true,
message: '请输入加价金额(元)',
},
]}
>
<InputNumber
max={999999999}
min={0}
precision={2}
style={{ width: '200px' }}
placeholder="加价名称金额(元)"
/>
</Form.Item>
{specsInfoFields.length > 1 ? (
<MinusCircleOutlined
onClick={() => specsRemove(specsInfofield.name)}
/>
) : null}
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => specsAdd()}
block
style={{ width: '400px' }}
icon={<PlusOutlined />}
>
新增加价
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
))}
<Form.Item>
<Button
type="primary"
onClick={() => add()}
style={{ width: '400px' }}
icon={<PlusOutlined />}
>
新增规格
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
<div className={styles.textStyle}>修改成功后,原库存将被替换,请谨慎操作!</div>
<Form.Item
name="receptionVolume"
label="多规格库存"
className={styles.multiSpecification}
>
<Button
size="small"
danger
style={{ marginBottom: '10px' }}
onClick={() => {
showAddRepertoryModal('all'); // 统一库存
}}
shape="round"
>
统一设置库存
</Button>
<div className={styles.rowWarp}>
{tempMultiSpu.length > 0 &&
tempMultiSpu?.map((item, idx) => (
<>
{item.unique && (
<>
<div key={idx} className={styles.specsBetween}>
<Form.Item>
<div className={styles.specsBetween}>
<div>{calcLabelName(item)} :</div>
<span className={styles.repertoryLimit}>
{item?.serviceItem?.productStock}/
{item?.serviceItem?.maxStock}
</span>
<div
className={styles.specRepertory}
onClick={() => {
showAddRepertoryModal('multi', `${item.unique}`, item); // 多个库存
}}
>
去设置
</div>
</div>
</Form.Item>
</div>
</>
)}
</>
))}
</div>
</Form.Item>
</>
}
</>
)}
</Form>
{/* 加入库存 */}
<AddRepertoryModal
ref={AddRepertoryRef}
modifiedInventory={modifiedInventory}
intactData={intactData}
repertoryModel={repertoryModel}
/>
{/* 加入多规格 */}
<AddMultiSpecModal ref={AddMultiSpecRef} />
</>
);
});
export default TakeawayGoodsInfo;
......@@ -5,9 +5,13 @@ import commonStyle from '../common.less';
export const TaskTypeSelect = props => {
const customer = useContext(ServiceContext);
const typeConfig = TaskList(customer.canAddService, customer.canAddNormal);
const typeConfig = TaskList(
customer.canAddService,
customer.canAddNormal,
customer.canTakeawayService,
);
const selectTabs = task => {
if (!customer.isEdit) {
if (!customer.isEdit && Object.keys(props.takeAway).length === 0) {
props.onChange(task);
}
};
......
import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
import { Modal, Upload, notification, Spin } from 'antd';
import React, { useState, useEffect, useRef, forwardRef } from 'react';
import lodash from 'lodash';
import { ReactSortable } from 'react-sortablejs';
import ImgCrop from 'antd-img-crop';
import 'antd/es/modal/style';
import 'antd/es/slider/style';
import { merchantUpload } from '../service';
import styles from '../common.less';
const UploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传图片</div>
</div>
);
const UploadCropImage = 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 [activeImgIndex, setActiveImgIndex] = useState(null);
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 cleanArray = (actual = []) =>
actual.reduce((prev, cur) => {
cur && prev.push(cur);
return prev;
}, []);
const warningTip = description => {
notification.warning({
message: '图片上传失败',
description,
});
};
const getBase64 = (img, callback) => {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
};
const ImageInfo = file =>
new Promise((resolve, reject) => {
const LtMB = file.size / 1024 / 1024;
if (LtMB > 2) {
warningTip(`[${file.name}] 图片不可以大于2MB`);
resolve(null);
}
getBase64(file, url => {
const image = new Image();
image.addEventListener('load', () => {
const { width } = image;
const { height } = image;
file.width = width;
file.height = height;
file.LtMB = LtMB;
resolve(file);
});
image.addEventListener('error', () => {
warningTip(`${file.name}图片上传失败!`);
resolve(null);
});
image.src = url;
});
});
const CheckImageInfoList = async files => {
const promiseImage = files.map(file => ImageInfo(file));
const clearImage = await Promise.all(promiseImage);
return cleanArray(clearImage);
};
const isUploadNext = async imgFileList => {
const filterImage = imgFileList.filter(img => {
if (
(imgOptions.maxWidth && img.width > imgOptions.maxWidth) ||
(imgOptions.maxHeight && img.height > imgOptions.maxHeight)
) {
warningTip(`[${img.name}] ${imgOptions.superTips}`);
return false;
}
return true;
});
return filterImage;
};
const checkFile = files => {
const fileType = ['jpg', 'jpeg', 'png'];
const filterImage = files.filter(file => {
const curType = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
if (!fileType.includes(curType)) {
warningTip('图片格式须为jpg、jpeg、png!');
return false;
}
return true;
});
return filterImage;
};
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 () => {
console.log('defaultBeforeUpload :>> ', 11111);
if (limit && fileListRef.current.length + fileArray.length > limit) {
Modal.warning({
maskClosable: true,
title: '超出上传个数',
});
return Upload.LIST_IGNORE;
}
const flies = checkFile(fileArray);
const optionsArray = await CheckImageInfoList(flies);
const checkFiles = await isUploadNext(optionsArray);
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}>
<div className={styles.imgContent}>
{fileList.length > 0 && (
<ReactSortable animation={300} list={fileList} setList={list => bundleChange(list)}>
{fileList.map((item, index) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={index}
className={styles.sortImg}
onMouseEnter={() => setActiveImgIndex(index)}
onMouseLeave={() => setActiveImgIndex(null)}
>
<div style={{ width: '90%', height: '90%', overflow: 'hidden' }}>
<img width="100%" key={item.uid} src={item.url} alt="" />
</div>
{activeImgIndex === index && (
<div className={styles.mask}>
<EyeOutlined className={styles.maskIcon} onClick={() => handlePreview(item)} />
{!disabled && (
<DeleteOutlined
className={styles.maskIcon}
onClick={() => handleRemove(item)}
/>
)}
</div>
)}
</div>
))}
</ReactSortable>
)}
</div>
{limit !== null && fileList.length >= limit ? (
''
) : (
<ImgCrop rotationSlider modalWidth={500} modalHeight={500} quality={0.5} showReset>
<Upload
{...uploadParams}
disabled={Boolean(disabled)}
multiple={multiple}
name={name}
customRequest={() => {}}
listType="picture-card"
beforeUpload={defaultBeforeUpload}
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
showUploadList={false}
>
{UploadButton}
</Upload>
</ImgCrop>
)}
<Modal visible={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</Spin>
);
});
export default UploadCropImage;
......@@ -7,7 +7,7 @@ export const formItemLayout = {
},
};
export const TaskList = (canAddService, canAddNormal) => [
export const TaskList = (canAddService, canAddNormal, canTakeawayService) => [
{
name: '实体商品',
type: 1,
......@@ -33,36 +33,68 @@ export const TaskList = (canAddService, canAddNormal) => [
},
},
},
// {
// name: '虚拟商品',
// type: 2,
// desc: '无需物流',
// hide: !canAddNormal,
// 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,
name: '服务类商品',
type: 4,
desc: '无需物流',
hide: !canAddNormal,
hide: !canAddService,
imgConfig: {
commonImageList: {
title: '公共滑动图',
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
title: '封面图片',
rule: true,
limit: 1,
renderExtra(leng) {
return `建议尺寸: ##宽##高 (${leng} / 1) 封面图第一张 `;
},
},
imageList: {
rule: false,
limit: null,
renderExtra: () => '(图片最大上传2M)',
cardImageList: {
title: '商品图片',
rule: true,
limit: 11,
renderExtra(leng) {
return `建议尺寸: ##宽##高,sku商品轮播图(${leng} / 11)`;
},
},
detailImageList: {
title: '详情图',
rule: true,
limit: null,
renderExtra: () => '(图片最大上传2M)',
title: '商品详情图',
// rule: true,
limit: 30,
renderExtra() {
return '最多上传30张';
},
},
},
},
{
name: '电子卡卷',
type: 4,
name: '外卖商品',
type: 5,
desc: '无需物流',
hide: !canAddService,
hide: !canTakeawayService,
imgConfig: {
commonImageList: {
title: '封面图片',
......@@ -291,3 +323,15 @@ export const StaticColumns = customer => [
disabeldRender: () => customer.isDisabled,
},
];
export const ENUM_REPERTORY = [{ label: '单规格', value: '1' }, { label: '多规格', value: '2' }];
export const ENUM_SET_REPERTORY = [{ label: '清零', value: '0' }, { label: '最大', value: '1' }];
export const ENUM_WEEK = [
{ value: '1', label: '周一' },
{ value: '2', label: '周二' },
{ value: '3', label: '周三' },
{ value: '4', label: '周四' },
{ value: '5', label: '周五' },
{ value: '6', label: '周六' },
{ value: '7', label: '周日' },
// { value: 8, label: '法定假日' },
];
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { Spin, Button, Modal, message, notification } from 'antd';
import moment from 'moment';
import PubSub from 'pubsub-js';
import { Title, WrapperContainer } from './components/CommonTemplate';
import { TaskTypeSelect } from './components/TaskTypeSelect';
import FormInformationBasic from './components/FormInformationBasic';
......@@ -9,6 +10,7 @@ import FormRuleSetting from './components/FormRuleSetting';
import FormRuleVPictures from './components/FormRuleVPictures';
import FormSettlementOthers from './components/FormSettlementOthers';
import FormAttr from './components/FormAttr';
import FormTakeaway from './components/FormTakeaway';
import localStorage from '@/utils/localStorage';
import {
merchantBrandList,
......@@ -21,6 +23,7 @@ import {
getByProductType,
apiCreateDraft,
apiEditDraft,
apiGetShopDetail,
} from './service';
import { isUrl, filterSendData, clearCurrent, onAutoSaveValue, localAutoSaveKey } from './utils';
import { ServiceContext } from './context';
......@@ -33,22 +36,33 @@ import styles from './common.less';
* @returns ReactDOM
*/
const ServiceGoods = options => {
const { SourceData, categoryList, virtualCategoryList, specListData, permissions } = options;
const {
SourceData,
categoryList,
virtualCategoryList,
specListData,
permissions,
takeAway,
} = options;
const canAddService = permissions[GOOD_MANAGE.ADD_SERVICE_GOODS];
const canAddNormal = permissions[GOOD_MANAGE.ADD_NORMAL_GOODS];
const canTakeawayService = permissions[GOOD_MANAGE.ADD_TAKEAWAY_GOODS];
// const canTakeawayService = true
const basicRef = useRef(null);
const stockRef = useRef(null);
const settingRef = useRef(null);
const picturesRef = useRef(null);
const settleOtrRef = useRef(null);
const attrRef = useRef(null);
const takeawayRef = useRef(null);
const [pageId, setPageId] = useState(null);
const [categoryIds, setCategoryIds] = useState([]); // 商品品类ID
const [isEdit, setIsEdit] = useState(false); // 是否是编辑状态
const [isUseCache, setIsUseCache] = useState(false); // 是否使用缓存
const [productType, setProductType] = useState(canAddNormal ? 1 : 4); // 商品状态
let ptype = canTakeawayService ? 5 : 4;
ptype = canAddNormal ? 1 : ptype;
const [productType, setProductType] = useState(ptype); // 商品状态
const [pageLoading, setPageLoading] = useState(false); // 页面加载状态
const [afterAddressList, setAfterAddressList] = useState([]); // 售后地址
const [supplierIdList, setSupplierIdList] = useState([]); // 适用们店列表
......@@ -57,7 +71,10 @@ const ServiceGoods = options => {
const [specList, setSpecList] = useState([]); // 规格列表
const [editData, setEditData] = useState({}); // 编辑保存数据
const [newCategoryList, setNewCategoryList] = useState({});
const [takeawayEditData, setTakeawayEditData] = useState({}); // 外卖编辑保存数据
const [groupShopData, setGroupShopData] = useState(''); // 从分组进入创建
const [visibleCacheEdit, setVisibleCacheEdit] = useState(false); // 显示有缓存未保存提示
const [takeawayInfoMation, setTakeawayInfoMation] = useState({});
const [checkFormList] = useState([
basicRef,
attrRef,
......@@ -65,6 +82,7 @@ const ServiceGoods = options => {
settingRef,
settleOtrRef,
picturesRef,
takeawayRef,
]);
const [specKeyList, setSpecKeyList] = useState([]); // 记录一级规格key字段
......@@ -73,17 +91,19 @@ const ServiceGoods = options => {
const onValuesChange = e => {
if (!isEdit) {
const typeObj = {
type: productType,
};
if (visibleCacheEdit) {
setVisibleCacheEdit(false);
localStorage.remove(localAutoSaveKey);
onAutoSaveValue(
{
type: productType,
},
!0,
);
onAutoSaveValue(typeObj, !0);
}
onAutoSaveValue(e);
onAutoSaveValue(Object.assign(typeObj, e));
}
if (productType === 5) {
setTakeawayInfoMation(e);
// console.log('takeawayInfoMation', takeawayInfoMation);
}
};
......@@ -103,32 +123,80 @@ const ServiceGoods = options => {
},
!0,
);
stockRef.current.onFinish();
stockRef.current && stockRef.current.onFinish();
}, 1000);
};
// 查询外卖商品详情
const querGetShopDetail = async params => {
const { spuId, shopId, skuId } = params;
const temp = {
id: spuId,
shopId,
skuId,
};
const { data } = await apiGetShopDetail(temp);
const {
name,
storageRackIds,
firstCategoryId,
secondCategoryId,
thirdCategoryId,
commonImageList,
} = data;
const categoryId = [firstCategoryId, secondCategoryId, thirdCategoryId].filter(item => !!item);
const editInfoMation = {
infoMation: {
name,
productRefShopId: `${shopId}`,
commonImageList,
categoryId,
storageRackIds,
productType: 5,
},
};
setEditData(editInfoMation);
setTakeawayEditData(data);
setIsEdit(true);
setPageLoading(false);
};
const onResetData = refresh => {
setPageId(null);
setIsEdit(false);
setProductType(canAddNormal ? 1 : 4);
setProductType(ptype);
setEditData({});
setSpecKeyList([]);
resetForm();
options.onChange(false, refresh);
};
const handleCancel = refresh => {
const refreshPageList = (refresh, sendData) => {
onResetData(refresh);
// 新增外卖商品 关闭之后列表到跳转对应分组的下
if (
!isEdit &&
sendData &&
+sendData.type === 5 &&
sendData.storageRackIds &&
sendData.storageRackIds.length > 0
) {
PubSub.publish('refreshTakeAway', {
groupId: sendData.storageRackIds[0],
});
}
};
const handleCancel = (refresh, sendData) => {
const info = localStorage.get(localAutoSaveKey);
if (info && Object.keys(info).length > 1) {
Modal.confirm({
title: '确认提示',
content: '商品信息还未保存,确认关闭弹窗?',
onOk() {
onResetData(refresh);
refreshPageList(refresh, sendData);
},
});
} else {
onResetData(refresh);
refreshPageList(refresh, sendData);
}
};
......@@ -146,7 +214,6 @@ const ServiceGoods = options => {
setSpecList(result.data || []);
}
};
const getAfterSalesAddrsPage = async () => {
if (!afterAddressList.length) {
const result = await afterSalesAddrsPage();
......@@ -162,7 +229,7 @@ const ServiceGoods = options => {
if (addResponse.data) {
message.success(`${isEdit ? '修改' : '添加'}成功!`);
localStorage.remove(localAutoSaveKey);
handleCancel(true);
handleCancel(true, sendData);
}
setPageLoading(false);
} catch (error) {
......@@ -188,7 +255,6 @@ const ServiceGoods = options => {
});
}
};
// 保存商品
const submitEvent = async () => {
const checkPromiseList = clearCurrent(checkFormList).map(({ current }) => current.onCheck());
......@@ -196,11 +262,21 @@ const ServiceGoods = options => {
console.log('resuslt :>> ', resuslt);
if (!resuslt.includes(null)) {
const params = resuslt.reduce((origin, item) => {
console.log('origin', origin, item);
const { temp, ...other } = item;
origin[temp] = other;
return origin;
}, {});
const sendData = filterSendData(productType, params);
if (productType === 5) {
console.log(sendData, 'sendData');
if (+sendData?.repertoryType === 2 && sendData?.items?.length < 2) {
message.error('最少生成2个sku');
return;
}
sendMerchantProductHttpRequest(sendData);
return;
}
if (isEdit) {
sendData.id = pageId;
}
......@@ -285,7 +361,7 @@ const ServiceGoods = options => {
}
} else {
// 默认生成一条规格数据
stockRef.current.onFinish();
stockRef.current && stockRef.current.onFinish();
}
setPageLoading(false);
})();
......@@ -309,7 +385,7 @@ const ServiceGoods = options => {
}, [isEdit, options.visible]);
useEffect(() => {
setProductType(canAddNormal ? 1 : 4);
setProductType(ptype);
}, [canAddNormal]);
const onSpecCommonImgEvent = useCallback(
......@@ -318,6 +394,19 @@ const ServiceGoods = options => {
},
[specKeyList],
);
useEffect(() => {
// 外卖类型
if (Object.keys(takeAway)?.length) {
const { spuId = '', groupId = '' } = takeAway;
if (spuId) {
querGetShopDetail(takeAway);
}
if (groupId) {
setGroupShopData(takeAway);
}
setProductType(5);
}
}, [takeAway]);
const onEventBus = (event, params) => {
if (event === 'cloneImg') {
......@@ -396,14 +485,15 @@ const ServiceGoods = options => {
},
});
};
const providerValue = {
pageId,
isEdit,
productType,
canAddService, // 是否可以添加服务商品(电子卡券)
canAddNormal, // 是否可以添加实物商品
canTakeawayService, // 是否可以添加外卖商品
isCard: productType === 4,
isTakeawayService: productType === 5,
// 0, "商品删除" 1, "新建" 2, "提交审核" 3, "待审核" 4, "驳回" 5, "未上架" 6, "已上架" 7, "已下架"
isNormal: SourceData.state && SourceData.state !== 4, // 商品不是驳回状态
// 当商品进行编辑 & 类型不为电子卡券 & 商品状态不为驳回 禁用当前功能
......@@ -423,9 +513,13 @@ const ServiceGoods = options => {
maskClosable={false}
keyboard={false}
footer={[
<Button key="draft" type="primary" ghost loading={pageLoading} onClick={onSaveDraft}>
保存草稿
</Button>,
productType === 5 ? (
''
) : (
<Button key="draft" type="primary" ghost loading={pageLoading} onClick={onSaveDraft}>
保存草稿
</Button>
),
<Button key="submit" type="primary" loading={pageLoading} onClick={submitEvent}>
提交
</Button>,
......@@ -446,13 +540,19 @@ const ServiceGoods = options => {
)}
<ServiceContext.Provider value={providerValue}>
<Title title="商品类型" />
<TaskTypeSelect productType={productType} onChange={productChange} />
<TaskTypeSelect
productType={productType}
takeAway={takeAway}
onChange={productChange}
/>
<Title title="商品基本信息编辑" />
<FormInformationBasic
ref={basicRef}
editData={editData.infoMation}
groupShopData={groupShopData}
newCategoryList={newCategoryList}
shopGetByProductType={shopGetByProductType}
categoryList={categoryList}
virtualCategoryList={virtualCategoryList}
brandList={brandList}
......@@ -460,6 +560,7 @@ const ServiceGoods = options => {
specListData={specListData}
onCategoryChange={onCategoryChange}
onValuesChange={onValuesChange}
// queryShopList={queryShopList}
/>
{[1, 2].includes(productType) && [
......@@ -472,32 +573,43 @@ const ServiceGoods = options => {
onValuesChange={onValuesChange}
/>,
]}
<Title title="价格与库存" />
<FormPriceOrStock
ref={stockRef}
specList={specList}
onSpecChange={onSpecCommonImgEvent}
editData={editData.infoSpecData}
skuList={editData.skuList}
onValuesChange={onValuesChange}
/>
<Title title="规则设置" />
{productType === 4 && (
<FormRuleSetting
ref={settingRef}
editData={editData.serviceItem}
supplierIdList={supplierIdList}
onValuesChange={onValuesChange}
/>
{productType !== 5 && (
<>
<Title title="价格与库存" />
<FormPriceOrStock
ref={stockRef}
specList={specList}
onSpecChange={onSpecCommonImgEvent}
editData={editData.infoSpecData}
skuList={editData.skuList}
onValuesChange={onValuesChange}
/>
</>
)}
{productType !== 5 && (
<>
<Title title="规则设置" />
{productType === 4 && (
<FormRuleSetting
ref={settingRef}
editData={editData.serviceItem}
supplierIdList={supplierIdList}
onValuesChange={onValuesChange}
/>
)}
</>
)}
{productType !== 5 && (
<>
<FormRuleVPictures
ref={picturesRef}
specKeyItem={specKeyList}
editData={editData.infoImageData}
onValuesChange={onValuesChange}
/>
</>
)}
<FormRuleVPictures
ref={picturesRef}
specKeyItem={specKeyList}
editData={editData.infoImageData}
onValuesChange={onValuesChange}
/>
{productType === 4 && (
<FormSettlementOthers
ref={settleOtrRef}
......@@ -505,6 +617,18 @@ const ServiceGoods = options => {
onValuesChange={onValuesChange}
/>
)}
{productType === 5 && (
<>
<FormTakeaway
ref={takeawayRef}
takeAway={takeAway}
editData={takeawayEditData}
takeawayInfoMation={takeawayInfoMation}
supplierIdList={supplierIdList}
onValuesChange={onValuesChange}
/>
</>
)}
</ServiceContext.Provider>
</WrapperContainer>
</Spin>
......
......@@ -117,3 +117,53 @@ export const apiEditDraft = data =>
prefix: goodsApi,
data,
});
// 菜单分组 http://yapi.quantgroups.com/project/389/interface/api/64044
export const apiQueryShopList = data =>
request.post('/api/merchants/products/storageRack/listByShopIdAndStorageRackIds', {
prefix: goodsApi,
data: stringify(_.omitBy(data, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
// 新建分组
export const apiCreateShop = data =>
request.post('/api/merchants/products/storageRack/create', {
prefix: goodsApi,
data,
});
// 新增外卖商品 http://yapi.quantgroups.com/project/389/interface/api/57324
export const apiAddTakeawayProducts = data =>
request.post('/v1/channels/products/add', {
prefix: goodsApi,
data,
});
// 商品标签列表 http://yapi.quantgroups.com/project/389/interface/api/57979
export const apiTagList = data =>
request.post('/api/merchants/products/tag/getAll', {
prefix: goodsApi,
data,
});
// 单位列表 http://yapi.quantgroups.com/project/389/interface/api/57179
export const apiUnits = data =>
request.get('/api/merchants/products/units', {
prefix: goodsApi,
data,
});
// 获取shopids http://yapi.quantgroups.com/project/389/interface/api/38056
export const apiShopIds = data =>
request.get('/api/merchants/shops/getBySupplierId?state=1&productBusiness=1', {
prefix: goodsApi,
data,
});
// 获取店铺详情 http://yapi.quantgroups.com/project/389/interface/api/57589
export const apiGetShopDetail = data =>
request.post('/product/api/merchant/detail', {
prefix: goodsApi,
data: stringify(_.omitBy(data, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
......@@ -109,7 +109,52 @@ const filterItems = (type, props) => {
};
export const filterSendData = (type, params) => {
const { infoMation, infoImageData, attributeApplyList } = params;
// takeawayItem 外卖商品
const { infoMation, infoImageData, attributeApplyList, takeawayItem } = params;
// 外卖商品
if (type === 5) {
const temp = Object.assign({}, takeawayItem?.intactData, infoMation);
const deepTemp = JSON.parse(JSON.stringify(temp));
deepTemp.categoryId =
Array.isArray(deepTemp.categoryId) &&
deepTemp.categoryId?.slice(deepTemp.categoryId?.length - 1)?.toString();
deepTemp.productRefShopId = deepTemp.productRefShopId.toString();
const tempWeightName = JSON.parse(sessionStorage.getItem('weightUnits'));
deepTemp.items.forEach(item => {
item.autoStock = item.autoStock ? 1 : 0;
item.serviceItem.autoStock = item?.serviceItem?.autoStock ? 1 : 0;
item.productStock = item?.serviceItem?.productStock;
item.list = deepTemp.list;
if (item?.specs?.length) {
item.specs.forEach(itm => {
if (itm?.unit && tempWeightName.includes(itm?.unit)) {
itm.quantity =
`${itm?.quantity}`.indexOf('约') > -1 ? itm?.quantity : `${itm?.quantity}`;
}
});
}
});
deepTemp.specList &&
deepTemp.specList.forEach(item => {
if (item?.specs?.length) {
item.specs.forEach(itm => {
if (itm?.unit) {
itm.unit =
(Array.isArray(itm?.unit) &&
itm?.unit?.length >= 2 &&
itm?.unit?.splice(itm?.unit?.length - 1)[0]) ||
itm?.unit;
}
if (itm?.unit && tempWeightName.includes(itm?.unit)) {
itm.quantity =
`${itm?.quantity}`.indexOf('约') > -1 ? itm?.quantity : `${itm?.quantity}`;
}
});
}
});
return deepTemp;
}
const items = filterItems(type, params);
const commonImageList = type === 4 ? [] : infoImageData.commonImageList;
const obj = {
......@@ -128,6 +173,7 @@ export const filterSendData = (type, params) => {
if (attributeApplyList && attributeApplyList.attributeApplyList) {
obj.attributeApplyList = attributeApplyList.attributeApplyList;
}
return obj;
};
......@@ -258,6 +304,11 @@ export const createProductData = (props, isEdit, skuList) => {
export const localAutoSaveKey = 'good-info-auto-save';
export const onAutoSaveValue = (e, isClear) => {
// 暂时去掉外卖类型
if (e && e.type === 5) {
localStorage.remove(localAutoSaveKey);
return;
}
const keys = Object.keys(e);
if (
e &&
......@@ -272,6 +323,25 @@ export const onAutoSaveValue = (e, isClear) => {
localStorage.set(localAutoSaveKey, Object.assign({}, e));
} else {
const info = localStorage.get(localAutoSaveKey) || {};
localStorage.set(localAutoSaveKey, Object.assign({ type: 1 }, info, e));
localStorage.set(localAutoSaveKey, Object.assign({}, info, e));
}
};
export const calcDescartes = array => {
console.log(array, 'array');
if (array.length < 2) return array[0] || [];
return [].reduce.call(array, (col, set) => {
const res = [];
col.forEach(c => {
set.forEach(s => {
const t = [].concat(Array.isArray(c) ? c : [c]);
t.push(s);
res.push(t);
});
});
return res;
});
};
import React from 'react';
import moment from 'moment';
import { PlusOutlined } from '@ant-design/icons';
import { notification, Tag, Tooltip, Input } from 'antd';
import {
uploadFile,
apiCategoryList,
apiAddrArea,
apiServiceFacility,
apiBusinessDetail,
apiBankList,
} from '../service';
export const wrapperCol = {
xs: { span: 24 },
sm: { span: 12 },
};
export const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
export const businessTypeDesc = {
1: '到家业务',
2: '实物业务',
3: '到店业务',
};
// 业务模式
export const businessModel = [
{ label: '到家外卖业务(外卖配送业务)', value: 1 },
{ label: '实物商品业务员', value: 2 },
{ label: '到店业务(服务类业务)', value: 3 },
];
export const signDateTypeList = [{ label: '自商品售卖起默认一年', value: 1 }];
export const legalPersonList = [{ label: '长期', value: '长期' }];
// 获取申请信息
export async function getInfo() {
const data = await apiBusinessDetail();
let settlementType = 1;
const res = data.data;
if (res) {
if (res.facilities) {
res.customList = res.facilities.customList || [];
res.selfList = res.facilities.selfList || [];
}
res.signDateType = res.signDateType ? [res.signDateType] : [];
res.accountOpenPermitImage = res.accountOpenPermitImage
? [{ uid: 0, url: res.accountOpenPermitImage }]
: [];
res.businessLicenseImage = res.businessLicenseImage
? [{ uid: 0, url: res.businessLicenseImage }]
: [];
res.idCardEmblemImage = res.idCardEmblemImage ? [{ uid: 0, url: res.idCardEmblemImage }] : [];
res.idCardPortraitImage = res.idCardPortraitImage
? [{ uid: 0, url: res.idCardPortraitImage }]
: [];
res.primaryImage = res.primaryImage ? [{ uid: 0, url: res.primaryImage }] : [];
res.sealImage = res.sealImage ? [{ uid: 0, url: res.sealImage }] : [];
res.brandCertificate = res.brandCertificate ? [{ uid: 0, url: res.brandCertificate }] : [];
// 非同名结算授权文件
res.differentNameAuthorizationImage = res.differentNameAuthorizationImage
? [{ uid: 0, url: res.brandCertificate }]
: [];
// 户口本本人页
res.householdRegisterImage = res.householdRegisterImage
? [{ uid: 0, url: res.householdRegisterImage }]
: [];
// 工商局
res.icbProofImage = res.icbProofImage ? [{ uid: 0, url: res.icbProofImage }] : [];
if (res.categoryQualificateImage && res.categoryQualificateImage.length) {
res.categoryQualificateImage = res.categoryQualificateImage.map((item, index) => ({
uid: index,
url: item,
}));
}
if (res.otherImage && res.otherImage.length) {
res.otherImage = res.otherImage.map((item, index) => ({
uid: index,
url: item,
}));
}
res.addr = [res.provinceId, res.cityId, res.countyId];
if (res.townId) {
res.addr.push(res.townId);
}
if (settlementType !== null) {
settlementType = +res.settlementType;
}
}
// 身份证有效期处理
let checkboxDisabled = false;
if (res.legalPersonPeriod) {
const date = res.legalPersonPeriod.split('-') || [];
res.legalPersonStart = date[0] && moment(date[0]);
res.legalPersonEnd = date[1] && date[1] !== '长期' ? moment(date[1]) : '';
res.checked = date[1] && date[1] === '长期' ? ['长期'] : [];
checkboxDisabled = date[1] && date[1] === '长期';
}
res.businessLicensePeriod = res.businessLicensePeriod && moment(res.businessLicensePeriod);
const companyNamedis = !!res.companyName;
// 获取主营类目
let categoryList = [];
if (res.productBusiness?.length) {
const dataList = await apiCategoryList(res.productBusiness);
categoryList = dataList.data;
}
this.setState({
settlementType,
businessInfo: res,
checkboxDisabled,
companyNamedis,
categoryList,
mainCategoryId: res.mainCategoryId,
companyType: res.companyType,
});
}
// 删除图片
function delImg(keyName, e) {
this.setState(state => {
if (keyName === 'categoryQualificateImage') {
state.businessInfo.categoryQualificateImage.forEach((item, index) => {
if (item.uid === e.uid) {
state.businessInfo.categoryQualificateImage.splice(index, 1);
}
});
} else {
state.businessInfo[keyName].splice(e.uid, 1);
}
return {
businessInfo: state.businessInfo,
};
});
}
// 上传图片
export function uploadPropsFn(
{ keyName, limit = 1, maxSize = 1024 * 1024 * 5, maxSizeMsg = '文件大小不能超过5M!', type = 0 },
successCallback = () => {},
) {
const that = this;
let timer = null;
return {
maxSize,
async customRequest(info) {
const { file } = info;
if (info.file.size > maxSize) {
notification.error({ message: maxSizeMsg });
return;
}
if (that.state.businessInfo[keyName] && that.state.businessInfo[keyName].length >= limit) {
notification.error({ message: `最多只能上传${limit}个文件!` });
return;
}
const res = await uploadFile([file], type);
if (res.businessCode !== '0000') {
return;
}
const url = res.data?.[0];
that.setState(state => {
const imgList = state.businessInfo[keyName] || [];
const attachment = {
uid: imgList.length || 0,
name: file?.name,
status: 'done',
url,
};
imgList.push(attachment);
if (imgList.length <= limit) {
state.businessInfo[keyName] = imgList;
} else {
notification.error({ message: `最多只能上传${limit}个文件!` });
}
return {
businessInfo: state.businessInfo,
};
});
successCallback(keyName, url);
// 需要把上传的数据存储下来
},
disabled: this.state.disabled,
multiple: limit > 1,
listType: 'picture-card',
showUploadList: true,
accept: '.png,.bmp,.gif,.jpeg,.jpg',
onRemove: e => delImg.call(this, keyName, e),
beforeUpload: (e, flist) => {
const imgList = that.state.businessInfo[keyName] || [];
if (flist.length + imgList.length > limit) {
clearTimeout(timer);
timer = setTimeout(() => {
notification.error({ message: `最多只能上传${limit}个文件!` });
}, 100);
return false;
}
return true;
},
};
}
/** **** 自定义服务设施 start ***************************** */
// 关闭标签输入框
function handleClose(removedTag) {
this.setState(state => {
state.businessInfo.customList = state.businessInfo.customList.filter(tag => tag !== removedTag);
return {
businessInfo: state.businessInfo,
};
});
}
// 显示标签输入框
function showInput() {
this.setState({ tagVisible: true }, () => this.input.focus());
}
// 标签内容改变
function handleInputChange(e) {
if (e && e.target) {
this.setState({ tagValue: e.target.value });
}
}
// 保存标签
function handleInputConfirm() {
const { tagValue } = this.state;
if (tagValue) {
let { customList } = this.state.businessInfo;
if (tagValue && customList.indexOf(tagValue) === -1) {
customList = [...customList, tagValue];
}
this.setState(state => {
state.businessInfo.customList = customList;
return {
businessInfo: state.businessInfo,
tagVisible: false,
tagValue: '',
};
});
}
}
// 渲染标签
export function renderServiceTags() {
const that = this;
return (
<div>
{that.state.businessInfo.customList &&
that.state.businessInfo.customList.map(tag => {
const isLongTag = tag.length > 20;
const tagElem = (
<Tag key={tag} closable onClose={() => handleClose.call(that, tag)}>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>
{tagElem}
</Tooltip>
) : (
tagElem
);
})}
{that.state.tagVisible && (
<Input
// eslint-disable-next-line react/no-this-in-sfc
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 78 }}
value={that.state.tagValue}
onChange={e => handleInputChange.call(that, e)}
onBlur={e => handleInputConfirm.call(that, e)}
onPressEnter={e => handleInputConfirm.call(that, e)}
/>
)}
{!that.state.tagVisible && (
<Tag
onClick={() => showInput.call(that)}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<PlusOutlined /> 新增
</Tag>
)}
</div>
);
}
/** **** 自定义服务设施 end ***************************** */
// 获取服务设施
export async function getServiceFacility() {
const res = await apiServiceFacility();
if (res.data) {
this.setState({
serviceFacilitys: res.data,
});
}
}
// 开户行
export async function getBankList() {
const res = await apiBankList();
if (res.data) {
this.setState({
bankList: res.data,
});
}
}
// 获取地址省
export async function getAreaAddr() {
const data = await apiAddrArea();
const areaAddr = data.data || null;
if (areaAddr) {
const res = areaAddr.map(item => ({
isLeaf: false,
loading: false,
label: item.addrName,
value: item.addrId,
}));
// 编辑时 回显 市、区、街道
if (this.state.businessInfo.provinceId) {
const pros = res.filter(item => +item.value === +this.state.businessInfo.provinceId);
await loadData.call(this, pros);
const citys = pros[0].children.filter(
item => +item.value === +this.state.businessInfo.cityId,
);
await loadData.call(this, citys);
if (this.state.businessInfo.townId) {
const countys = citys[0].children.filter(
item => +item.value === +this.state.businessInfo.countyId,
);
await loadData.call(this, countys);
}
}
this.setState({
areaAddr: res,
});
}
}
// 获取市区街道
export async function loadData(selectedOptions) {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const data = await apiAddrArea({
parentId: targetOption.value,
});
const areaAddr = data.data || null;
if (areaAddr) {
const children = areaAddr.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;
}
this.setState({
// eslint-disable-next-line react/no-access-state-in-setstate
areaAddr: [...this.state.areaAddr],
});
}
}
import React, { Component } from 'react';
import { history } from 'umi';
import moment from 'moment';
import { ArrowRightOutlined, UploadOutlined } from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Select,
Input,
Radio,
Checkbox,
DatePicker,
Upload,
Button,
Card,
Row,
Col,
Cascader,
notification,
Spin,
Tabs,
} from 'antd';
import styles from './style.less';
import {
wrapperCol,
uploadPropsFn,
formItemLayout,
renderServiceTags,
loadData,
getAreaAddr,
getServiceFacility,
businessModel,
getInfo,
getBankList,
businessTypeDesc,
signDateTypeList,
legalPersonList,
} from './data';
import { validatePhone, validateEMail, validNumber } from '@/utils/validator';
import { getErrorMessage } from '@/utils/utils';
import MapModal from '@/components/BaiduMap';
import { apiCategoryList, apiEditStoreInfo, apiRecognize, apiRevision } from '../service';
import {
infoTypeFind,
infoTypeChecked,
infoTypeEdit,
infoTypeRevision,
infoTypeInfo,
} from '../staticdata';
const { Option } = Select;
const { Item: FormItem } = Form;
const { RangePicker } = DatePicker;
const { TabPane } = Tabs;
const carID = 301008; // 汽车类商家
const fileterBrandOptions = (input, options) => options.children.includes(input);
class BusinessInfo extends Component {
getFieldDecorator = this.props?.form?.getFieldDecorator;
setFieldsValue = this.props?.form?.setFieldsValue;
state = {
id: null,
type: infoTypeFind,
businessInfo: {
customList: [],
settlementType: 1,
companyType: '',
},
areaAddr: [], // 四级地址列表
categoryList: [], // 主要类目
serviceFacilitys: [], // 服务设施
bankList: [], // 开户行
// eslint-disable-next-line react/no-unused-state
tagVisible: false,
// eslint-disable-next-line react/no-unused-state
tagValue: '',
settlementType: 1,
loading: false,
visibleMap: false,
visibleLoading: false,
calculateType: [1, 1],
checkboxDisabled: false,
mainCategoryId: null,
companyNamedis: false, // 公司名称
companyType: null, // 企业类型
};
async componentDidMount() {
await getInfo.call(this);
getAreaAddr.call(this);
getServiceFacility.call(this);
getBankList.call(this);
}
// 主营类目
onMainCategory(e) {
this.setState({
mainCategoryId: e,
});
}
// 长期
oncheckedChange = e => {
const str = e.toString();
this.setState({
checkboxDisabled: str === '长期',
});
this.props.form.setFieldsValue({
legalPersonEnd: '',
});
};
// eslint-disable-next-line react/sort-comp
getDataInfo = type => ({
key: type,
name: businessTypeDesc[type],
chargeMethod: 1,
calculateType: 1,
});
// 计费类型改变
onChangeCalculateType = (e, i) => {
this.setState(state => {
const { calculateType } = state;
calculateType[i] = +e;
return {
calculateType,
};
});
};
static getDerivedStateFromProps() {
return null;
}
// 帐号类型切换
onChangeSettlType(e) {
this.setState({
settlementType: e,
});
}
// 切换企业类型
onChangeCompanyType = e => {
const { value } = e.target;
this.setState({
companyType: value,
});
let type = null;
if ([1, 3].includes(value)) {
type = 1;
} else {
type = 2;
}
this.onChangeSettlType(type);
this.props.form.setFieldsValue({
settlementType: type,
});
};
// 处理图片
dealImgInfo = async (type, url) => {
const imgType = {
businessLicenseImage: 2,
idCardPortraitImage: 5,
idCardEmblemImage: 4,
};
if (!url) {
return;
}
this.setState({
visibleLoading: true,
});
const data = await apiRecognize({
imageUrl: url,
imageType: imgType[type],
});
if (data.businessCode === '0000') {
const res = data.data;
const info = {};
this.setState(state => {
if (type === 'businessLicenseImage') {
info.socialCode = res.socialCode || '';
info.companyName = res.companyName || '';
info.businessLicensePeriod = res.businessLicensePeriod
? moment(res.businessLicensePeriod)
: '';
} else if (type === 'idCardPortraitImage') {
info.legalPersonName = res.legalPersonName || '';
info.legalPersonIdCard = res.legalPersonIdCard || '';
} else if (type === 'idCardEmblemImage' && res.legalPersonPeriod) {
// 身份证有效期处理
const date = res.legalPersonPeriod.split('-') || [];
info.legalPersonStart = date[0] && moment(date[0]);
info.legalPersonEnd = date[1] && date[1] !== '长期' ? moment(date[1]) : '';
info.checked = date[1] && date[1] === '长期' ? ['长期'] : [];
const checkboxDisabled = date[1] && date[1] === '长期';
this.setState({
checkboxDisabled,
});
// info.legalPersonPeriod = res.legalPersonPeriod
// .replace(/\./g, '/')
// .split('-')
// .map(o => moment(o));
}
return {
businessInfo: Object.assign(state.businessInfo, info),
};
});
this.props.form.setFieldsValue({
...info,
});
} else {
notification.error({ message: data.msg });
}
this.setState({
visibleLoading: false,
});
};
/* eslint-disable no-return-assign */
saveInputRef = input => (this.input = input);
// 返回
onCancel = () => {
getInfo.call(this);
};
// 提交
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields(async (err, values) => {
const obj = Object.assign({}, values); // , this.state.businessInfo
if (!err) {
const imgs = this.state.businessInfo;
obj.businessLicensePeriod = moment(obj.businessLicensePeriod).format('YYYY-MM-DD');
const legalPersonStart = moment(obj.legalPersonStart).format('YYYY/MM/DD');
const legalPersonEnd = this.state.checkboxDisabled
? '长期'
: moment(obj.legalPersonEnd).format('YYYY/MM/DD');
obj.legalPersonPeriod = `${legalPersonStart}-${legalPersonEnd}`;
obj.facilities = {
customList: this.state.businessInfo.customList,
selfList: obj.selfList,
};
const areaArr = ['provinceId', 'cityId', 'countyId', 'townId'];
obj.addr.forEach((item, i) => {
obj[areaArr[i]] = item;
});
obj.accountOpenPermitImage =
(imgs?.accountOpenPermitImage?.length && imgs.accountOpenPermitImage[0].url) || '';
if (obj.supplierRateDTOList && obj.supplierRateDTOList.length) {
obj.supplierRateDTOList.forEach(item => {
if (item.ratedate && item.ratedate.length) {
item.startDate = moment(item.ratedate[0]).format('YYYY-MM-DD');
item.endDate = moment(item.ratedate[1]).format('YYYY-MM-DD');
delete item.ratedate;
}
});
}
obj.businessLicenseImage =
(imgs?.businessLicenseImage?.length && imgs?.businessLicenseImage[0].url) || '';
obj.idCardEmblemImage =
(imgs?.idCardEmblemImage?.length && imgs?.idCardEmblemImage[0].url) || '';
obj.idCardPortraitImage =
(imgs?.idCardPortraitImage?.length && imgs?.idCardPortraitImage[0].url) || '';
obj.primaryImage = (imgs?.primaryImage?.length && imgs?.primaryImage[0].url) || '';
obj.sealImage = (imgs?.sealImage?.length && imgs?.sealImage[0].url) || '';
// 新增
obj.brandCertificate =
(imgs?.brandCertificate?.length && imgs?.brandCertificate[0]?.url) || '';
obj.differentNameAuthorizationImage =
(imgs?.differentNameAuthorizationImage?.length &&
imgs?.differentNameAuthorizationImage[0]?.url) ||
'';
obj.householdRegisterImage =
(imgs?.householdRegisterImage?.length && imgs?.householdRegisterImage[0]?.url) || '';
obj.icbProofImage = (imgs?.icbProofImage?.length && imgs?.icbProofImage[0]?.url) || '';
obj.applySource = 1;
obj.signDateType = 1;
if (imgs.otherImage && imgs.otherImage.length) {
obj.otherImage = imgs.otherImage.map(item => item.url);
} else {
delete obj.otherImage;
}
if (imgs.categoryQualificateImage && imgs.categoryQualificateImage.length) {
const list = [];
imgs.categoryQualificateImage.forEach(item => {
list.push(item.url);
});
obj.categoryQualificateImage = list;
} else {
delete obj.categoryQualificateImage;
}
obj.legalPersonIdCard = obj.legalPersonIdCard?.toLocaleUpperCase() || '';
obj.bankAccountLicenseNum = obj.bankAccountLicenseNum?.toLocaleUpperCase() || '';
obj.socialCode = obj.socialCode?.toLocaleUpperCase() || '';
obj.id = this.state.id;
obj.headImage = obj.primaryImage;
this.setState({
loading: true,
});
const data = await apiEditStoreInfo(obj);
if (data.businessCode === '0000') {
notification.success({ message: `${data.msg}!~` });
}
this.setState({
loading: false,
});
} else {
const message = getErrorMessage(err);
notification.warning({ message });
}
});
};
onSetPoint = e => {
this.setFieldsValue({
latitude: e.lat,
longitude: e.lng,
});
};
// 修改公司名称
setCompanyName = () => {
const val = this.state.companyNamedis;
this.setState({
companyNamedis: !val,
});
if (!val) {
this.props.form.setFieldsValue({
companyName: this.state.businessInfo.companyName,
icbProofImage: '',
householdRegisterImage: '',
});
}
};
openMap(e, disabled) {
if (!disabled) {
this.setState({
visibleMap: e,
});
}
}
// eslint-disable-next-line class-methods-use-this
disabledDate(current) {
return current && current < moment(moment().format('YYYY-MM-DD'));
}
onChangeBusinessModel = e => {
this.getCategoryList(e);
this.props.form.setFieldsValue({
mainCategoryId: '',
});
};
// 获取主营类目
async getCategoryList(e) {
const data = await apiCategoryList(e);
if (!data.data) return;
this.setState({ categoryList: data.data });
}
render() {
const {
form: { getFieldDecorator },
} = this.props;
const {
businessInfo,
type,
checkboxDisabled,
mainCategoryId,
companyNamedis,
companyType,
} = this.state;
return (
<div className={styles.infoBox}>
<Form className="login-form" onSubmit={this.handleSubmit} {...formItemLayout}>
<Card title="店铺基本信息">
<Row gutter={24}>
<Col span={24}>
<FormItem label="业务模式" labelCol={{ span: 4 }}>
{getFieldDecorator('productBusiness', {
rules: [{ required: true, message: '请选择业务模式!' }],
initialValue: businessInfo.productBusiness,
})(
<Checkbox.Group
options={businessModel}
onChange={e => this.onChangeBusinessModel(e)}
disabled={+mainCategoryId === carID}
/>,
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="主营类目" labelCol={{ span: 4 }}>
{getFieldDecorator('mainCategoryId', {
rules: [{ required: true, message: '请选择主营类目!' }],
initialValue: businessInfo.mainCategoryId,
})(
<Select
onChange={e => this.onMainCategory(e)}
showSearch
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
disabled={+businessInfo.mainCategoryId === 301008}
>
{this.state.categoryList.map(item => (
<Option value={item.id} key={item.id}>
{item.name}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="商户名称" labelCol={{ span: 4 }}>
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入商户名称!' }],
initialValue: businessInfo.name,
})(<Input maxLength={32} />)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="店铺区域" labelCol={{ span: 4 }}>
{getFieldDecorator('addr', {
rules: [{ required: true, type: 'array', message: '请选择店铺区域!' }],
initialValue: businessInfo.addr,
})(
<Cascader
options={this.state.areaAddr}
loadData={e => loadData.call(this, e)}
changeOnSelect
/>,
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="经营地址" labelCol={{ span: 4 }}>
{getFieldDecorator('detailAddress', {
rules: [{ required: true, message: '请输入经营地址!' }],
initialValue: businessInfo.detailAddress,
})(<Input maxLength={500} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="联系人姓名" labelCol={{ span: 8 }}>
{getFieldDecorator('contactName', {
rules: [
{ required: true, message: '请输入联系人姓名!' },
{ pattern: /^[A-Za-z\u4e00-\u9fa5]+$/, message: '请输入汉字或字母!' },
],
initialValue: businessInfo.contactName,
})(<Input />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="联系人手机号" labelCol={{ span: 5 }}>
{getFieldDecorator('contactPhone', {
rules: [
{ required: true, message: '请输入联系人手机号!' },
{ validator: validatePhone, message: '请输入正确的手机号!' },
],
initialValue: businessInfo.contactPhone,
})(<Input maxLength={11} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="联系人邮箱" labelCol={{ span: 8 }}>
{getFieldDecorator('contactEmail', {
rules: [
{ required: true, message: '请输入联系人邮箱!' },
{ validator: validateEMail, message: '请输入正确的邮箱!' },
],
initialValue: businessInfo.contactEmail,
})(<Input />)}
</FormItem>
</Col>
{+mainCategoryId !== carID && (
<>
<Col span={12}>
<FormItem label="客服电话" labelCol={{ span: 5 }}>
{getFieldDecorator('servicePhone', {
rules: [{ required: true, message: '请输入客服电话!' }],
initialValue: businessInfo.servicePhone,
})(<Input maxLength={11} />)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="签约日期" labelCol={{ span: 4 }}>
{getFieldDecorator('signDateType', {
rules: [{ required: true, message: '请选择签约日期' }],
initialValue: businessInfo.signDateType,
})(<Checkbox.Group options={signDateTypeList} />)}
</FormItem>
</Col>
</>
)}
</Row>
</Card>
<Card title="店铺营业信息">
<Col span={12} className={styles.imgList}>
<FormItem label="商户头图" labelCol={{ span: 8 }} wrapperCol={wrapperCol}>
{getFieldDecorator('primaryImage', {
rules: [{ required: true, message: '请上传商户头图!' }],
initialValue: businessInfo.primaryImage,
})(
<Upload
{...uploadPropsFn.call(this, { keyName: 'primaryImage', type: 1 })}
fileList={businessInfo.primaryImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
{+mainCategoryId !== carID && (
<>
<Col span={24}>
<FormItem label="服务设施" labelCol={{ span: 4 }}>
{getFieldDecorator('selfList', {
initialValue: businessInfo.selfList,
})(
<Select mode="multiple" filterOption={fileterBrandOptions}>
{this.state.serviceFacilitys.map(item => (
<Option value={item.code} key={item.code}>
{item.desc}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="自定义服务设施" labelCol={{ span: 4 }}>
{getFieldDecorator('customList', {
initialValue: businessInfo.customList,
})(renderServiceTags.call(this))}
</FormItem>
</Col>
</>
)}
</Card>
<Card title="证照信息录入区">
<Row gutter={24}>
{+mainCategoryId !== carID && (
<Col span={24}>
<FormItem label="企业类型" labelCol={{ span: 4 }}>
{getFieldDecorator('companyType', {
rules: [{ required: true, message: '请选择企业类型!' }],
initialValue: businessInfo.companyType,
})(
<Radio.Group
onChange={this.onChangeCompanyType}
disabled={businessInfo.companyType}
>
<Radio value={1}>一般纳税人</Radio>
<Radio value={2}>小规模</Radio>
<Radio value={3}>个体工商</Radio>
</Radio.Group>,
)}
</FormItem>
</Col>
)}
<Col span={24} className={styles.imgList}>
<FormItem label="营业执照" labelCol={{ span: 4 }} wrapperCol={wrapperCol}>
{getFieldDecorator('businessLicenseImage', {
rules: [{ required: true, message: '请上传营业执照!' }],
initialValue: businessInfo.businessLicenseImage,
})(
<Upload
{...uploadPropsFn.call(
this,
{ keyName: 'businessLicenseImage', type: 2 },
this.dealImgInfo,
)}
fileList={businessInfo.businessLicenseImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
{+mainCategoryId !== carID && (
<>
<Col span={12}>
<Row>
<Col span={4} style={{ marginTop: '4px' }}>
{businessInfo.companyName && (
<Button onClick={this.setCompanyName}>修改</Button>
)}
</Col>
<Col span={12}>
<FormItem label="公司名称" labelCol={{ span: 8 }}>
{getFieldDecorator('companyName', {
rules: [{ required: true, message: '请输入公司名称!' }],
initialValue: businessInfo.companyName,
})(<Input maxLength={50} disabled={companyNamedis} />)}
</FormItem>
</Col>
</Row>
</Col>
<Col span={12}>
<FormItem label="统一社会信用代码" labelCol={{ span: 8 }}>
{getFieldDecorator('socialCode', {
rules: [
{ required: true, message: '请输入统一社会信用代码!' },
{ pattern: /^[A-Za-z0-9]*$/, message: '仅支持输入数字和字母' },
],
initialValue: businessInfo.socialCode,
})(<Input maxLength={18} />)}
</FormItem>
</Col>
{(!companyNamedis &&
businessInfo.companyType &&
businessInfo.companyType !== 2) ||
(!companyNamedis && businessInfo?.icbProofImage?.length) ? (
<Col span={12}>
<FormItem
label="工商局变更证明"
labelCol={{ span: 8 }}
wrapperCol={wrapperCol}
>
{getFieldDecorator('icbProofImage', {
rules: [{ required: true, message: '请上传工商局变更证明!' }],
initialValue: businessInfo.icbProofImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'icbProofImage',
limit: 1,
})}
fileList={businessInfo.icbProofImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
) : (
''
)}
{(!companyNamedis &&
businessInfo.companyType &&
businessInfo.companyType === 2) ||
(!companyNamedis && businessInfo?.householdRegisterImage?.length) ? (
<Col span={12}>
<FormItem label="户口本本人页" labelCol={{ span: 8 }} wrapperCol={wrapperCol}>
{getFieldDecorator('householdRegisterImage', {
rules: [{ required: true, message: '请上传户口本本人页!' }],
initialValue: businessInfo.householdRegisterImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'householdRegisterImage',
limit: 1,
})}
fileList={businessInfo.householdRegisterImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
) : (
''
)}
<Col span={24}>
<FormItem label="营业执照有效期" labelCol={{ span: 4 }}>
{getFieldDecorator('businessLicensePeriod', {
rules: [{ required: true, message: '请选择营业执照有效期!' }],
initialValue: businessInfo.businessLicensePeriod,
})(<DatePicker maxLength={50} />)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="类目所需资质" labelCol={{ span: 4 }} wrapperCol={wrapperCol}>
{getFieldDecorator('categoryQualificateImage', {
initialValue: businessInfo.categoryQualificateImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'categoryQualificateImage',
type: 6,
limit: 9,
})}
fileList={businessInfo.categoryQualificateImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="电子签章" labelCol={{ span: 4 }} wrapperCol={wrapperCol}>
{getFieldDecorator('sealImage', {
rules: [{ required: true, message: '请上传电子签章!' }],
initialValue: businessInfo.sealImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'sealImage',
type: 3,
maxSize: 1024 * 200,
maxSizeMsg: '文件大小不能超过200K',
limit: 1,
})}
fileList={businessInfo.sealImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
</>
)}
{+mainCategoryId === carID && (
<>
<Col span={24}>
<FormItem label="授权品牌" labelCol={{ span: 4 }}>
{getFieldDecorator('brand', {
initialValue: businessInfo.brand,
})(<Input maxLength={32} />)}
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="品牌经销商授权书"
labelCol={{ span: 4 }}
wrapperCol={wrapperCol}
>
{getFieldDecorator('brandCertificate', {
initialValue: businessInfo.brandCertificate,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'brandCertificate',
limit: 1,
})}
fileList={businessInfo.brandCertificate}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
</>
)}
</Row>
</Card>
<Card title="法人信息录入区">
<Row gutter={24}>
{+mainCategoryId !== carID && (
<>
<Col span={12} className={styles.imgList}>
<FormItem
label="法人身份证人像面"
labelCol={{ span: 8 }}
wrapperCol={wrapperCol}
>
{getFieldDecorator('idCardPortraitImage', {
rules: [{ required: true, message: '请上传身份证人像面!' }],
initialValue: businessInfo.idCardPortraitImage,
})(
<Upload
{...uploadPropsFn.call(
this,
{ keyName: 'idCardPortraitImage', type: 5 },
this.dealImgInfo,
)}
fileList={businessInfo.idCardPortraitImage}
>
<UploadOutlined />
<div>身份证人像面</div>
</Upload>,
)}
</FormItem>
</Col>
<Col span={12} className={styles.imgList}>
<FormItem
label="法人身份证国徽面"
labelCol={{ span: 8 }}
wrapperCol={wrapperCol}
>
{getFieldDecorator('idCardEmblemImage', {
rules: [{ required: true, message: '请上传身份证国徽面!' }],
initialValue: businessInfo.idCardEmblemImage,
})(
<Upload
{...uploadPropsFn.call(
this,
{ keyName: 'idCardEmblemImage', type: 4 },
this.dealImgInfo,
)}
fileList={businessInfo.idCardEmblemImage}
>
<UploadOutlined />
<div>身份证国徽面</div>
</Upload>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="证件姓名" labelCol={{ span: 8 }}>
{getFieldDecorator('legalPersonName', {
rules: [{ required: true, message: '请输入证件姓名!' }],
initialValue: businessInfo.legalPersonName,
})(<Input maxLength={8} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="身份证号码" labelCol={{ span: 8 }}>
{getFieldDecorator('legalPersonIdCard', {
rules: [
{ required: true, message: '请输入身份证号码!' },
{ pattern: /^[A-Za-z0-9]*$/, message: '请输入数字或字母' },
],
initialValue: businessInfo.legalPersonIdCard,
})(<Input maxLength={18} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="身份证起始有效期" labelCol={{ span: 8 }}>
{getFieldDecorator('legalPersonStart', {
rules: [{ required: true, message: '身份证起始有效期!' }],
initialValue: businessInfo.legalPersonStart,
})(<DatePicker style={{ width: '200px' }} />)}
</FormItem>
</Col>
<Col span={12}>
<Row gutter={24}>
<Col span={19}>
<FormItem
label="身份证结束有效期"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
>
{getFieldDecorator('legalPersonEnd', {
rules: [{ required: !checkboxDisabled, message: '身份证结束有效期!' }],
initialValue: businessInfo.legalPersonEnd,
})(<DatePicker style={{ width: '200px' }} disabled={checkboxDisabled} />)}
</FormItem>
</Col>
<Col span={5}>
<FormItem>
{getFieldDecorator('checked', {
initialValue: businessInfo.checked,
})(
<Checkbox.Group
options={legalPersonList}
className={styles.radio}
onChange={e => this.oncheckedChange(e)}
/>,
)}
</FormItem>
</Col>
</Row>
</Col>
</>
)}
<Col span={12}>
<FormItem label="手机号" labelCol={{ span: 8 }}>
{getFieldDecorator('legalPersonPhone', {
rules: [
{ required: true, message: '请输入手机号!' },
{ validator: validatePhone, message: '请输入正确的手机号!' },
],
initialValue: businessInfo.legalPersonPhone,
})(<Input maxLength={11} />)}
</FormItem>
</Col>
{+mainCategoryId !== carID && (
<Col span={24}>
<FormItem label="其它证照" labelCol={{ span: 4 }} wrapperCol={wrapperCol}>
{getFieldDecorator('otherImage', {
initialValue: businessInfo.otherImage,
})(
<Upload
{...uploadPropsFn.call(this, { keyName: 'otherImage', limit: 9, type: 7 })}
fileList={businessInfo.otherImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
)}
</Row>
</Card>
<Card title="结算信息录入区">
{+mainCategoryId !== carID && (
<>
<Row gutter={24}>
<Col span={24}>
<FormItem label="账户类型" labelCol={{ span: 4 }} wrapperCol={wrapperCol}>
{getFieldDecorator('settlementType', {
rules: [{ required: true, message: '请选择账户类型!' }],
initialValue: businessInfo.settlementType,
})(
<Radio.Group onChange={e => this.onChangeSettlType(e?.target?.value)}>
<Radio value={1} disabled={[2].includes(companyType)}>
对公
</Radio>
<Radio value={2} disabled={[1, 3].includes(companyType)}>
对私(小微商户)
</Radio>
</Radio.Group>,
)}
</FormItem>
</Col>
</Row>
{this.state.settlementType === 1 && (
<Row gutter={24}>
<Col span={12}>
<FormItem label="开户许可证" labelCol={{ span: 8 }} wrapperCol={wrapperCol}>
{getFieldDecorator('accountOpenPermitImage', {
rules: [{ required: true, message: '请上传开户许可证!' }],
initialValue: businessInfo.accountOpenPermitImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'accountOpenPermitImage',
type: 8,
})}
fileList={businessInfo.accountOpenPermitImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户许可证编号" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccountLicenseNum', {
rules: [
{ required: true, message: '请输入开户许可证编号!' },
{ pattern: /^[A-Za-z0-9]*$/, message: '请输入数字或字母' },
],
initialValue: businessInfo.bankAccountLicenseNum,
})(<Input />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户名称" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccountName', {
rules: [{ required: true, message: '请输入开户名称!' }],
initialValue: businessInfo.bankAccountName,
})(<Input maxLength={32} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户行" labelCol={{ span: 8 }}>
{getFieldDecorator('accountBankName', {
rules: [{ required: true, message: '请选择开户行!' }],
initialValue: businessInfo.accountBankName,
})(
<Select
showSearch
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
>
{this.state.bankList.map(item => (
<Option value={item.bankName} key={item.bankName}>
{item.bankName}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="银行帐号" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccount', {
rules: [
{ required: true, message: '请输入银行帐号!' },
{ pattern: /^[0-9]*$/, message: '请输入数字' },
],
initialValue: businessInfo.bankAccount,
})(<Input />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="联行号" labelCol={{ span: 8 }}>
{getFieldDecorator('interbankNum', {
rules: [{ pattern: /^[0-9]*$/, message: '请输入数字' }],
initialValue: businessInfo.interbankNum,
})(<Input maxLength={50} />)}
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="非同名结算授权文件"
labelCol={{ span: 4 }}
wrapperCol={wrapperCol}
>
{getFieldDecorator('differentNameAuthorizationImage', {
initialValue: businessInfo.differentNameAuthorizationImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'differentNameAuthorizationImage',
limit: 1,
})}
fileList={businessInfo.differentNameAuthorizationImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
</Row>
)}
{this.state.settlementType === 2 && (
<Row gutter={24}>
<Col span={12}>
<FormItem label="银行卡类型" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccountType', {
rules: [{ required: true, message: '请选择银行卡类型!' }],
initialValue: businessInfo.bankAccountType,
})(
<Select>
<Option value={1} key={1}>
借记卡
</Option>
</Select>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户名称" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccountName', {
rules: [{ required: true, message: '请输入开户名称!' }],
initialValue: businessInfo.bankAccountName,
})(<Input maxLength={32} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户行" labelCol={{ span: 8 }}>
{getFieldDecorator('accountBankName', {
rules: [{ required: true, message: '请选择开户行!' }],
initialValue: businessInfo.accountBankName,
})(
<Select
showSearch
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
>
{this.state.bankList.map(item => (
<Option value={item.bankName} key={item.bankName}>
{item.bankName}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="银行帐号" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccount', {
rules: [
{ required: true, message: '请输入银行帐号!' },
{ pattern: /^[0-9]*$/, message: '请输入数字' },
],
initialValue: businessInfo.bankAccount,
})(<Input />)}
</FormItem>
</Col>
</Row>
)}
</>
)}
{+mainCategoryId === carID && (
<Row gutter={24}>
<Col span={12}>
<FormItem label="开户许可证" labelCol={{ span: 8 }} wrapperCol={wrapperCol}>
{getFieldDecorator('accountOpenPermitImage', {
rules: [{ required: true, message: '请上传开户许可证!' }],
initialValue: businessInfo.accountOpenPermitImage,
})(
<Upload
{...uploadPropsFn.call(this, {
keyName: 'accountOpenPermitImage',
type: 8,
})}
fileList={businessInfo.accountOpenPermitImage}
>
<UploadOutlined /> 上传文件
</Upload>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户名称" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccountName', {
rules: [{ required: true, message: '请输入开户名称!' }],
initialValue: businessInfo.bankAccountName,
})(<Input maxLength={32} />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="开户行" labelCol={{ span: 8 }}>
{getFieldDecorator('accountBankName', {
rules: [{ required: true, message: '请选择开户行!' }],
initialValue: businessInfo.accountBankName,
})(
<Select
showSearch
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
>
{this.state.bankList.map(item => (
<Option value={item.bankName} key={item.bankName}>
{item.bankName}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="银行帐号" labelCol={{ span: 8 }}>
{getFieldDecorator('bankAccount', {
rules: [
{ required: true, message: '请输入银行帐号!' },
{ pattern: /^[0-9]*$/, message: '请输入数字' },
],
initialValue: businessInfo.bankAccount,
})(<Input />)}
</FormItem>
</Col>
</Row>
)}
</Card>
{+mainCategoryId === carID && (
<Card title="其他信息">
<Row gutter={24}>
<Col span={24}>
<FormItem label="礼包内容" labelCol={{ span: 4 }}>
{getFieldDecorator('giftPackageContent', {
rules: [{ required: true, message: '请输入礼包内容!' }],
initialValue: businessInfo.giftPackageContent,
})(<Input style={{ width: '50%' }} />)}
</FormItem>
</Col>
</Row>
</Card>
)}
<div className={styles.formBtns}>
<Button type="primary" size="large" htmlType="submit" loading={this.state.loading}>
修改并提交
</Button>
<Button size="large" onClick={this.onCancel}>
取消
</Button>
</div>
</Form>
<MapModal
visible={this.state.visibleMap}
onCancel={() => this.openMap(false)}
onSetPoint={e => this.onSetPoint(e)}
></MapModal>
{this.state.visibleLoading && (
<div className={styles.spinBox}>
<div className={styles.spinBoxWrapper}>
<Spin tip="Loading..." />
</div>
</div>
)}
</div>
);
}
}
export default Form.create()(BusinessInfo);
.textArea {
:global {
.ant-form-item-label {
width: 16%;
}
.ant-form-item-control-wrapper {
width: 80%;
}
}
}
.imgList {
:global {
.ant-upload-list-picture-card-container {
width: 200px;
}
.ant-upload-list-item {
width: 200px;
}
// .ant-upload.ant-upload-select-picture-card {
// width: 200px;
// }
}
}
.infoBox {
position: relative;
}
.spinBox {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
.spinBoxWrapper {
position: fixed;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 80px;
text-align: center;
transform: translate(-100px, -80px);
}
.addLink {
display: block;
margin: 20px 50px;
}
.formBtns {
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
padding: 20px 0;
background-color: #fff;
}
const Model = {
namespace: 'BusinessManage',
state: {},
effects: {},
reducers: {},
};
export default Model;
import request from '@/utils/request';
import config from '../../../config/env.config';
import qs from 'qs';
import { da } from 'date-fns/locale';
const { kdspApi, goodsApi } = config;
// 获取地址
export const apiAddrArea = params =>
request.get(`/api/merchants/addresses/list?${qs.stringify(params)}`, {
prefix: kdspApi,
});
// 主营类目
export async function apiCategoryList(param) {
return request.post('/api/merchants/suppliers/main-category/list', {
data: param,
prefix: goodsApi,
});
}
// 服务设施
export const apiServiceFacility = () =>
request.get('/api/merchants/suppliers/facilities/list', { prefix: kdspApi });
// 银行
export const apiBankList = () =>
request.get('/api/merchants/suppliers/banks/list', { prefix: kdspApi });
// 订正
export const apiRevision = params =>
request.post('/api/consoles/suppliers/register/revision', { prefix: kdspApi, data: params });
/**
* 上传文件
* imageType: 1-主头图,2-营业执照,3-电子签章,4-法人身份证国徽面,5-法人身份证人像面,6-类目所需资质,7-其它证件,8-开户许可证
* 0: 默认值 不校验
* */
export async function uploadFile(files, imageType = 0) {
const params = new FormData();
files.map(file => params.append('file', file));
params.append('imageType', imageType);
// image/upload
const data = await request.post('/api/merchants/images/upload', {
prefix: kdspApi,
data: params,
canRepeat: true,
});
return data;
}
// 图片内容识别
export const apiRecognize = params =>
request.get(`/api/merchants/images/recognize?${qs.stringify(params)}`, { prefix: kdspApi });
// 编辑商户信息
export const apiEditStoreInfo = params =>
request.post('/api/merchants/suppliers/edit', { prefix: kdspApi, data: params });
// 查询商户详情
export const apiBusinessDetail = businessId =>
request.get('/api/merchants/suppliers/pops/detail', { prefix: kdspApi });
import { Button, Popconfirm } from 'antd';
import React from 'react';
// import router from 'umi/router';
import { history } from 'umi';
// 状态:0-填写未完成,1-待审核,2-待易宝入网,3-易宝入网中,4-待易宝签约,5-待平台签约,6-完成,7-驳回
export const checkStatus = {
// 0: { text: '填写未完成' },
1: { text: '待审核' },
// 2: { text: '待易宝入网' },
3: { text: '易宝入网中' },
4: { text: '待易宝签约' },
5: { text: '待平台签约' },
// 6: { text: '完成' },
// 7: { text: '驳回' },
};
// 详情类型
export const infoTypeFind = 'find'; // 查看
export const infoTypeChecked = 'checked'; // 审核
export const infoTypeEdit = 'edit'; // 编辑
export const infoTypeRevision = 'revision'; // 订正
export const infoTypeInfo = 'info'; // 审核之后查看
export const infoTypeAdd = 'add'; // 添加
// 商户类型
export const supplierType = {
pop: { text: 'POP商户' },
供应商: { text: '供应商' },
};
export const toInfo = ({ id }, type = 'find') => {
history.push(`/BusinessInfo?id=${id}&type=${type}`);
};
export const businessTypeDesc = {
1: '到家业务',
2: '实物业务',
3: '到店业务',
};
.table {
:global {
.ant-table-row-expand-icon-cell {
visibility: hidden;
}
.ant-table-expanded-row {
.ant-pro-table {
margin-left: -17px;
}
}
}
}
.logBtn {
display: inherit;
margin: 20px auto;
}
.pageHeader {
background-color: #fff;
border: 1px solid rgb(235, 237, 240);
}
.listHeader {
display: flex;
&--item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 40px;
height: 40px;
margin-right: 20px;
font-size: 14px;
line-height: 14px;
text-align: center;
border: 1px solid #333;
border-radius: 50%;
&::after {
position: absolute;
right: -21px;
width: 20px;
height: 1px;
background-color: #333;
content: '';
}
&:last-child::after {
width: 0;
height: 0;
}
}
}
......@@ -2,12 +2,13 @@ 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, TimePicker, Checkbox, Cascader, Radio, notification } from 'antd';
import { Modal, Input, TimePicker, Checkbox, Cascader, Radio, notification, Button } from 'antd';
import { apiAddrArea, apiCreatStore, apiEditStore } from '../services';
import { weekOptions, weekDefault, layout } from '../data';
import MapModal from '@/components/GaoDeMap';
import style from './style.less';
import { isCheckNumberLine } from '@/utils/validator';
import Upload from '../../components/upload';
const FormItem = Form.Item;
......@@ -17,6 +18,9 @@ const StoreModal = props => {
onCancel,
form: { getFieldDecorator, setFieldsValue, getFieldsValue, validateFields, resetFields },
formInfo,
status,
productBusiness,
existTakewayShop,
} = props;
const [areaAddr, setAreaAddr] = useState([]);
const [visibleMap, setVisibleMap] = useState(false);
......@@ -26,13 +30,20 @@ const StoreModal = props => {
provice: '',
address: '',
});
const [disabled, setDisabled] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
const [businessModel, setBusinessModel] = useState([
{ label: '到家外卖业务(外卖配送业务)', value: 1 },
{ label: '实物商品业务员', value: 2 },
{ label: '到店业务(服务类业务)', value: 3 },
]);
const divDom = useRef();
const handleCancel = isSuccess => {
resetFields();
onCancel(isSuccess);
};
const onSubmit = () => {
validateFields(async (error, fieldsValue) => {
if (!error) {
......@@ -43,6 +54,10 @@ const StoreModal = props => {
params[areaArr[i]] = item;
});
}
if (params?.shopHeadImage?.length) {
// eslint-disable-next-line prefer-destructuring
params.shopHeadImage = params.shopHeadImage[0];
}
if (params.lnglat) {
const arr = params.lnglat.split(',');
if (arr.length === 2) {
......@@ -197,10 +212,44 @@ const StoreModal = props => {
setAreaAddr([...arr]);
};
const normFile = fileList => fileList;
const timerWrapper = async (time, timeString, callback) => {
if (timeString && timeString.length) {
const begin = moment(timeString[0]).format('HH:mm');
const end = moment(timeString[1]).format('HH:mm');
if (begin === end) {
callback(new Error('选择开始时间结束时间不能一样'));
}
} else {
callback();
}
};
const onBusiness = e => {
const val = e.includes(1);
setIsDisabled(val);
};
useEffect(() => {
if (visible) {
resetFields();
const info = Object.assign({}, formInfo);
const val = info?.productBusiness?.includes(1);
setIsDisabled(val);
if (status === 'look') {
setDisabled(true);
} else {
setDisabled(false);
}
const data = [...businessModel];
data.forEach(item => {
item.disabled = !productBusiness.includes(item.value);
if (existTakewayShop && item.value === 1 && !val) {
item.disabled = true;
}
});
console.log(data, '.........');
setBusinessModel(data);
if (info && info.id) {
info.week = info.businessHours.weeks;
const hours = info.businessHours.hoursItems;
......@@ -213,8 +262,10 @@ const StoreModal = props => {
});
});
}
info.lnglat = `${info.longitude},${info.latitude}`;
info.addr = [info.provinceId, info.cityId, info.countyId];
info.shopHeadImage = (info.shopHeadImage && [info.shopHeadImage]) || [];
if (info.townId) {
info.addr.push(info.townId);
}
......@@ -234,13 +285,40 @@ const StoreModal = props => {
<Modal
title="门店信息"
visible={visible}
width="800px"
width="1000px"
destroyOnClose
maskClosable={false}
onOk={() => onSubmit()}
onCancel={() => handleCancel()}
footer={
<>
<Button key="back" onClick={() => handleCancel()}>
取消
</Button>
{!disabled && (
<Button key="submit" type="primary" onClick={() => onSubmit()}>
确定
</Button>
)}
</>
}
>
<Form {...layout} name="formData">
<FormItem label="业务模式">
{getFieldDecorator('productBusiness', {
rules: [{ required: true, message: '请选择业务模式' }],
initialValue: formData.productBusiness,
})(<Checkbox.Group disabled={disabled} options={businessModel} onChange={onBusiness} />)}
</FormItem>
{isDisabled && (
<FormItem label="门店头像">
{getFieldDecorator('shopHeadImage', {
rules: [{ required: true, message: '请上传门店头像' }],
initialValue: formData.shopHeadImage || [],
valuePropName: 'fileList',
getValueFromEvent: normFile,
})(<Upload max={1} accept=".jpg,.png,.jpeg" disabled={disabled} />)}
</FormItem>
)}
<FormItem
label="门店名称"
name="name"
......@@ -249,7 +327,7 @@ const StoreModal = props => {
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入门店名称!' }],
initialValue: formData.name,
})(<Input placeholder="请输入门店名称" allowClear maxLength={20} />)}
})(<Input placeholder="请输入门店名称" allowClear maxLength={20} disabled={disabled} />)}
</FormItem>
<FormItem label="门店电话" name="phone">
{getFieldDecorator('phone', {
......@@ -258,7 +336,7 @@ const StoreModal = props => {
{ validator: isCheckNumberLine, message: '请输入正确的门店电话!' },
],
initialValue: formData.phone,
})(<Input placeholder="请输入门店电话" allowClear maxLength={20} />)}
})(<Input placeholder="请输入门店电话" allowClear maxLength={20} disabled={disabled} />)}
</FormItem>
<FormItem label="营业时间" required>
{times &&
......@@ -267,20 +345,30 @@ const StoreModal = props => {
<div>
<FormItem name={item.name}>
{getFieldDecorator(item.name, {
rules: [{ required: true, message: '请选择营业时间!' }],
rules: [
{ required: true, message: '请选择营业时间!' },
{ validator: timerWrapper, message: '选择开始时间结束时间不能一样' },
],
initialValue: formData[item.name],
})(<TimePicker.RangePicker format="HH:mm" />)}
})(<TimePicker.RangePicker format="HH:mm" disabled={disabled} />)}
</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>
)}
{!disabled &&
(i > 0 ? (
<div className={style.timerWrapperRight} onClick={() => onTimeMinus(i)}>
<MinusSquareFilled
style={{ color: '#ff4d4f', fontSize: '30px' }}
disabled={disabled}
/>
</div>
) : (
<div className={style.timerWrapperRight} onClick={() => onTimePlus()}>
<PlusSquareFilled
style={{ color: '#1890ff', fontSize: '30px' }}
disabled={disabled}
/>
</div>
))}
</div>
))}
</FormItem>
......@@ -288,7 +376,7 @@ const StoreModal = props => {
{getFieldDecorator('week', {
rules: [{ required: true, message: '请选择营业日!' }],
initialValue: formData.week || weekDefault,
})(<Checkbox.Group options={weekOptions} />)}
})(<Checkbox.Group options={weekOptions} disabled={disabled} />)}
</FormItem>
<FormItem label="店铺区域">
{getFieldDecorator('addr', {
......@@ -301,6 +389,7 @@ const StoreModal = props => {
placeholder="请选择店铺区域"
loadData={e => loadData(e)}
changeOnSelect
disabled={disabled}
/>,
)}
</FormItem>
......@@ -308,7 +397,14 @@ const StoreModal = props => {
{getFieldDecorator('address', {
rules: [{ required: true, message: '请输入详细地址!' }],
initialValue: formData.address,
})(<Input placeholder="请输入详细地址,不包含省市区地址" allowClear maxLength={50} />)}
})(
<Input
placeholder="请输入详细地址,不包含省市区地址"
allowClear
maxLength={50}
disabled={disabled}
/>,
)}
</FormItem>
<FormItem label="经纬度">
{getFieldDecorator('lnglat', {
......@@ -320,6 +416,7 @@ const StoreModal = props => {
readOnly
onClick={() => openMap(true)}
maxLength={100}
disabled={disabled}
/>,
)}
</FormItem>
......@@ -328,12 +425,19 @@ const StoreModal = props => {
rules: [{ required: true, message: '请选择是否启用!' }],
initialValue: formData.state,
})(
<Radio.Group>
<Radio.Group disabled={disabled}>
<Radio value={1}></Radio>
<Radio value={0}></Radio>
</Radio.Group>,
)}
</FormItem>
{isDisabled && (
<FormItem label="门店公告">
{getFieldDecorator('publicNotice', {
initialValue: formData.publicNotice,
})(<Input placeholder="请输入门店公告" maxLength={120} disabled={disabled} />)}
</FormItem>
)}
</Form>
<MapModal
visible={visibleMap}
......
......@@ -17,3 +17,11 @@ export const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
};
export const businessList = [
{ label: '外卖', value: 1 },
{ label: '实物类', value: 2 },
{ label: '服务类', value: 3 },
];
// 营业日
export const businessStatus = [{ label: '休息中', value: 2 }, { label: '营业中', value: 1 }];
import React, { useState, useRef, useEffect } from 'react';
import { notification, Button, Form, Input, Cascader, Col, Row, Table, Pagination } from 'antd';
import {
notification,
Button,
Form,
Input,
Cascader,
Col,
Row,
Table,
Pagination,
Modal,
Select,
} from 'antd';
import _ from 'lodash';
import { searchList, apiEnableStore, apiAddrArea } from './services';
import { stateDesc, weeks, layout } from './data';
import { el } from 'date-fns/locale';
import { searchList, apiEnableStore, apiAddrArea, apiproductBusiness } from './services';
import { stateDesc, weeks, layout, businessStatus, businessList } from './data';
import StoreModal from './components/storeModal';
import style from './style.less';
const { Option } = Select;
const { confirm } = Modal;
export default () => {
const [visible, setVisible] = useState(false);
const [storeInfo, setStoreInfo] = useState({});
const [areaAddr, setAreaAddr] = useState([]);
const [status, setStatus] = useState(false);
const [dataList, setDataList] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [totalNum, setTotalNum] = useState(0);
const [pageSize, setPageSize] = useState(20);
const [productBusiness, setProductBusiness] = useState([]);
const [existTakewayShop, setExistTakewayShop] = useState(false);
const refSearch = useRef();
const divDom = useRef();
// 获取业务模式
const getBusiness = async () => {
const res = await apiproductBusiness();
if (res) {
setProductBusiness(res.productBusiness || []);
setExistTakewayShop(res.existTakewayShop);
}
setVisible(true);
};
const onCreate = () => {
setStatus('add');
setStoreInfo({});
setVisible(true);
getBusiness();
};
const onShowInfo = info => {
const onShowInfo = (info, val) => {
setStoreInfo(info);
setVisible(true);
setStatus(val);
getBusiness();
};
// 获取市区街道
......@@ -92,16 +122,25 @@ export default () => {
params[ids[i]] = item;
});
}
if (val.businessStatus) {
params.businessStatus = val.businessStatus;
}
if (val.productBusiness) {
params.productBusiness = val.productBusiness;
}
getList(params);
};
const closeModal = isReload => {
if (isReload) {
onSearch(refSearch.current?.getFieldValue?.() || {});
}
setStatus(false);
setStoreInfo({});
setVisible(false);
};
const onEnableState = async ({ id, state }) => {
const enableStore = async (id, state) => {
const enable = +state === 1 ? 0 : 1;
const res = await apiEnableStore({ id, state: enable });
if (res === '0000') {
......@@ -109,11 +148,25 @@ export default () => {
onSearch(refSearch.current?.getFieldValue?.() || {});
}
};
const onEnableState = async ({ id, state }) => {
if (state) {
confirm({
title: '确认要禁用这家门店吗?',
content: '禁用后用户将无法看到该店铺',
onOk() {
enableStore(id, state);
},
});
} else {
enableStore(id, state);
}
};
const onReset = () => {
if (refSearch.current && refSearch.current.resetFields) {
refSearch.current.resetFields();
}
getList();
};
const onPageChange = (e, size) => {
......@@ -164,83 +217,71 @@ export default () => {
const columns = [
{
title: '门店名称',
dataIndex: 'name',
title: '业务模式',
dataIndex: 'productBusiness',
width: 120,
},
{
title: '门店电话',
dataIndex: 'phone',
hideInSearch: true,
width: 120,
},
{
title: '营业时间',
dataIndex: 'businessHours',
hideInSearch: true,
width: 150,
render: businessHours => (
<div>
<div>{getWeekSlots(businessHours.weeks)}</div>
align: 'center',
render: (v, value) =>
value?.productBusiness?.length && (
<div>
{businessHours.hoursItems.map(item => (
<div key={item.begin}>
<span>{item.begin}</span>-<span>{item.end}</span>
</div>
{v.map(it => (
<span>{businessList.find(item => +it === +item.value).label}&nbsp;</span>
))}
</div>
</div>
),
),
},
{
title: '地区',
dataIndex: 'addr',
width: 200,
hideInSearch: true,
render: (addr, r) => (
<span>{`${r.provinceName}${r.cityName}${r.countyName}${r.townName || ''}`}</span>
),
title: '门店名称',
dataIndex: 'name',
width: 120,
align: 'center',
},
{
title: '详细地址',
dataIndex: 'address',
hideInSearch: true,
width: 150,
title: '营业状态',
dataIndex: 'businessStatus',
width: 80,
align: 'center',
render: val => businessStatus.find(item => item.value === val)?.label || '-',
},
{
title: '经纬度',
dataIndex: 'latlng',
hideInSearch: true,
title: '创建时间',
dataIndex: 'createdAt',
width: 120,
render: (latlng, r) => <span>{`${r.longitude},${r.latitude}`}</span>,
align: 'center',
},
{
title: '状态',
dataIndex: 'state',
hideInSearch: true,
width: 120,
render: v => <span>{`${stateDesc[v]}`}</span>,
title: '门店电话',
dataIndex: 'phone',
align: 'center',
width: 100,
},
{
title: '创建时间',
dataIndex: 'createdAt',
title: '门店状态',
dataIndex: 'state',
hideInSearch: true,
width: 120,
align: 'center',
width: 80,
render: v => <span>{`${stateDesc[v]}`}</span>,
},
{
title: '操作',
hideInSearch: true,
dataIndex: 'action',
width: 170,
width: 200,
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>,
],
render: (val, r) => (
<div className={style.actionBtn}>
<Button key="look" onClick={() => onShowInfo(r, 'look')}>
查看
</Button>
<Button key="seek" onClick={() => onShowInfo(r, 'edit')}>
修改
</Button>
<Button key="enable1" onClick={() => onEnableState(r)} type="primary">
{+r.state ? '禁用' : '启用'}
</Button>
</div>
),
},
];
return (
......@@ -264,17 +305,39 @@ export default () => {
</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>
<Form.Item label="营业状态" name="businessStatus">
<Select>
{businessStatus.map(item => (
<Option label={item.label} value={item.value} key={item.value}>
{item.label}
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="业务模式" name="productBusiness">
<Select>
{businessList.map(item => (
<Option label={item.label} value={item.value} key={item.value}>
{item.label}
</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<div className={style.search}>
<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>
</div>
</Form>
</div>
<Table
......@@ -285,7 +348,7 @@ export default () => {
pagination={false}
scroll={{ x: '100%' }}
/>
{dataList && dataList.length && (
{dataList && dataList.length ? (
<div className={style.pageBox}>
<Pagination
style={{ marginBottom: 10 }}
......@@ -298,8 +361,15 @@ export default () => {
onShowSizeChange={onPageChange}
/>
</div>
)}
<StoreModal visible={visible} onCancel={closeModal} formInfo={storeInfo} />
) : null}
<StoreModal
visible={visible}
onCancel={closeModal}
formInfo={storeInfo}
status={status}
productBusiness={productBusiness}
existTakewayShop={existTakewayShop}
/>
</div>
);
};
......@@ -65,3 +65,11 @@ export async function apiEditStore(params) {
});
return data.businessCode;
}
// 获取商户业务模式
export async function apiproductBusiness() {
const data = await request.get('/api/merchants/suppliers/product-business/list', {
prefix: kdspApi,
});
return data.data;
}
.serachForm {
margin-bottom: 20px;
padding: 20px 50px 0 10px;
padding: 20px 50px 10px 10px;
background-color: #fff;
}
.search {
display: flex;
justify-content: end;
}
.searchBtn {
margin: 0 10px;
}
......@@ -13,3 +17,14 @@
border: 1px solid #f0f0f0;
border-top: 0;
}
.actionBtn {
display: flex;
align-items: center;
// flex-direction: column;
justify-content: space-around;
button {
width: 75px;
margin-bottom: 5px;
}
}
import { PlusOutlined } from '@ant-design/icons';
import { Upload, Modal, message } from 'antd';
import { Upload, Modal, notification } from 'antd';
import React from 'react';
import config from '../../../config/env.config';
import { qiniuToken } from '@/services/qiniu';
......@@ -7,7 +7,6 @@ import { qiniuToken } from '@/services/qiniu';
const qiniu = require('@/utils/qiniu.min.js');
const { qiniuHost } = config;
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
......@@ -26,9 +25,26 @@ class PicturesWall extends React.Component {
};
async componentDidMount() {
this.initFileList(this.props.fileList || []);
token = await qiniuToken();
}
static getDerivedStateFromProps(nextProps) {
new PicturesWall(nextProps).initFileList();
return null;
}
initFileList = fileList => {
const fileLists =
fileList?.map((item, index) => ({
url: item,
name: '',
uid: index,
status: 'done',
})) || [];
this.setState({ fileList: fileLists });
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async file => {
......@@ -43,17 +59,37 @@ class PicturesWall extends React.Component {
});
};
handleChange = info => {
const lastFile = (info.fileList.length && info.fileList[info.fileList.length - 1]) || [];
const types = ['application/pdf', 'image/png', 'image/jpeg'];
if (lastFile && types.indexOf(lastFile.type) === -1) {
message.error('文件格式错误!');
}
const fileList = info.fileList.filter(item => types.indexOf(item.type) !== -1);
this.setState({ fileList });
handleChange = ({ fileList }) => {
const file = fileList.filter(item => item.url);
this.setState({ fileList: [...file] });
const urlList = file.map(item => item.url);
this.props.onChange(urlList);
};
customRequest = ({ file, onError, onSuccess }) => {
imageSize = async file =>
new Promise((resolve, reject) => {
const fileObj = file;
// 获取上传的图片的宽高
const reader = new FileReader();
reader.readAsDataURL(fileObj);
reader.onload = evt => {
const replaceSrc = evt.target.result;
const imageObj = new Image();
imageObj.src = replaceSrc;
imageObj.onload = () => {
const { width } = imageObj;
const { height } = imageObj;
file.width = width;
file.height = height;
resolve(file);
};
};
reader.onerror = error => reject(error);
});
customRequest = async ({ file, onError, onSuccess }) => {
const imgSize = await this.imageSize(file);
const vm = this;
const observable = qiniu.upload(file, null, token);
const observer = {
next() {
......@@ -67,6 +103,17 @@ class PicturesWall extends React.Component {
const comFile = file;
const url = `${qiniuHost}/${res.hash}`;
comFile.url = url;
const list = vm.state.fileList;
list.push({
url: file.url,
name: file.name,
uid: list.length,
status: 'done',
});
vm.setState({ fileList: [...list] });
const urlList = list.map(item => item.url);
vm.props.onChange(urlList);
onSuccess(comFile);
// ...
},
......@@ -94,15 +141,17 @@ class PicturesWall extends React.Component {
</div>
);
const { max } = this.props;
return (
<div className="clearfix">
<Upload
{...this.props}
customRequest={this.customRequest}
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
{...this.props}
multiple
>
{max && fileList.length >= max ? null : uploadButton}
</Upload>
......
import React, { useState, useRef } from 'react';
import ProTable from '@ant-design/pro-table';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { notification, Button, Modal, Image, message } from 'antd';
import { query } from './services';
import style from './style.less';
const ContractView = () => {
const [visible, setVisible] = useState(false);
const [url, setUrl] = useState();
// 查看
const onLook = ({ contractPdfUrl }) => {
if (contractPdfUrl) {
setUrl(contractPdfUrl);
setVisible(true);
} else {
message.error('暂无合同');
}
};
// 下载
const ondown = ({ contractPdfUrl }) => {
if (contractPdfUrl) {
window.open(contractPdfUrl);
} else {
message.error('暂无合同');
}
};
const columns = [
{
title: '合同名称',
dataIndex: 'contractName',
key: 'contractName',
align: 'center',
},
{
title: '签约时间',
dataIndex: 'signDate',
key: 'signDate',
align: 'center',
hideInSearch: true,
render: (val, data) => {
if (data.signDate && data?.signDate.length) {
let date = '';
data.signDate.forEach((item, index) => {
if (item && item.toString().length === 1) {
item = `0${item}`;
}
if ([0, 1].includes(index)) {
date += `${item}-`;
} else if ([3, 4].includes(index)) {
date += `${item}:`;
} else {
date += `${item} `;
}
});
return date;
}
return '-';
},
},
{
title: '合同状态',
dataIndex: 'state',
key: 'state',
align: 'center',
filters: false,
valueEnum: {
0: '失效',
1: '有效',
},
},
{
title: '操作',
dataIndex: 'action',
width: 200,
align: 'center',
hideInSearch: true,
render: (text, record) => (
<>
<Button
key="edit"
type="primary"
onClick={() => onLook(record)}
style={{ marginRight: '10px' }}
>
查看
</Button>
<Button key="del" type="primary" onClick={() => ondown(record)}>
下载
</Button>
</>
),
},
];
return (
<PageHeaderWrapper>
{!visible && (
<ProTable
search={{
collapsed: false,
collapseRender: () => null,
}}
columns={columns}
request={params => query({ ...params })}
rowKey={r => r.appealNo}
expandIconColumnIndex={10}
bordered
toolBarRender={false}
scroll={{ x: '100%', y: 400 }}
pagination={false}
/>
)}
{visible && (
<div className={style.contract}>
<iframe src={url} height="600" width="100%" title="合同"></iframe>
<Button type="primary" onClick={() => setVisible(false)}>
取消
</Button>
</div>
)}
</PageHeaderWrapper>
);
};
export default ContractView;
import { stringify } from 'querystring';
import request from '@/utils/request';
import _ from 'lodash';
import config from '../../../config/env.config';
const { goodsApi } = config;
// 分页查询所有数据
export async function query(params) {
const param = {
...params,
pageIndex: params.current,
pageSize: params.pageSize || 20,
};
const data = await request.post('/api/merchants/suppliers/contract/list', {
prefix: goodsApi,
data: param,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
if (data.data) {
return {
data: data.data,
};
}
return {
total: 0,
data: [],
};
}
.contract {
text-align: center;
}
/* eslint-disable prefer-destructuring */
import { parse } from 'querystring';
import pathRegexp from 'path-to-regexp';
import moment from 'moment';
......@@ -126,4 +127,47 @@ export const getClientInfo = () => {
};
};
// 字符串或json去空格
export const stringOrObjectTrim = params => {
if (isClass(params) === 'String') {
return params.trim();
}
if (isClass(params) === 'Object') {
Object.keys(params).forEach(key => {
if (isClass(params[key]) === 'String') {
params[key] = params[key].trim();
} else if (isClass(params[key]) === 'Object') {
stringOrObjectTrim(params[key]);
} else if (isClass(params[key]) === 'Array') {
params[key].forEach((item, i) => {
params[key][i] = stringOrObjectTrim(item);
});
}
});
}
return params;
};
export const getObjectType = v => Object.prototype.toString.call(v).replace(/\[object |]/g, '');
// 获取长表单错误提示
export const getErrorMessage = err => {
const message = '请检查表单数据!';
// eslint-disable-next-line wrap-iife
(function getMsg(v) {
if (Array.isArray(v)) {
getMsg(v[0]);
} else {
const keys = Object.keys(v);
if (Array.isArray(v[keys[0]])) {
getMsg(v[keys[0]][0]);
} else if (v[keys[0]].errors && v[keys[0]].errors.length) {
// eslint-disable-next-line no-const-assign
message = v[keys[0]].errors[0].message;
} else {
getMsg(v);
}
}
})(err);
return message;
};
......@@ -419,3 +419,19 @@ export function isCheckPriceOneDecimal(rule, value, callback) {
callback();
}
}
// 验证价格保留小数点2位
export function isCheckPriceTwoDecimal(rule, value, callback) {
const num = +value;
const arr = `${value}`.split('.');
if (!value) {
callback();
} else if (Number.isNaN(num)) {
callback(new Error('请输入数字'));
} else if (num < 0) {
callback(new Error('请输入大于0的数字'));
} else if (arr.length === 2 && arr[1].length > 2) {
callback(new Error('小数点保留2位'));
} 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