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 = { ...@@ -15,5 +15,6 @@ module.exports = {
'import/no-unresolved': 0, 'import/no-unresolved': 0,
'import/extensions': 0, 'import/extensions': 0,
'no-unused-expressions': ['error', { allowShortCircuit: true }], 'no-unused-expressions': ['error', { allowShortCircuit: true }],
'template-curly-spacing': 'off',
}, },
}; };
...@@ -273,6 +273,18 @@ export default { ...@@ -273,6 +273,18 @@ export default {
name: 'systemManageLog', name: 'systemManageLog',
component: './systemManage/Log', component: './systemManage/Log',
}, },
{
title: '商户管理后台-合同查看',
path: '/contractView',
name: 'contractView',
component: './contractView',
},
{
title: '商户管理后台-商家资料',
path: '/businessInfo',
name: 'businessInfo',
component: './businessManage/info',
},
{ {
component: './404', component: './404',
}, },
......
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const isPre = process.env.PRE_ENV === 'pre'; const isPre = process.env.PRE_ENV === 'pre';
const environment = 'xyqb'; const environment = 'yxm2';
const envAPi = { const envAPi = {
api: `https://security-${environment}.liangkebang.net`, //'https://security-xyqb.liangkebang.net', api: `https://security-${environment}.liangkebang.net`, //'https://security-xyqb.liangkebang.net',
kdspOpApi: `https://sc-merchant-api-${environment}.liangkebang.net`, kdspOpApi: `https://sc-merchant-api-${environment}.liangkebang.net`,
......
...@@ -5,6 +5,7 @@ export const GOOD_MANAGE = { ...@@ -5,6 +5,7 @@ export const GOOD_MANAGE = {
EDITABLE: '020102', // 新增/修改 EDITABLE: '020102', // 新增/修改
ADD_SERVICE_GOODS: '020103', // 新增服务商品 ADD_SERVICE_GOODS: '020103', // 新增服务商品
ADD_NORMAL_GOODS: '020104', // 新增实物商品 ADD_NORMAL_GOODS: '020104', // 新增实物商品
ADD_TAKEAWAY_GOODS: '020105', // 新增外卖商品
}; };
// 配送区域 // 配送区域
......
...@@ -6960,6 +6960,39 @@ ...@@ -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": { "antd-mobile": {
"version": "2.3.4", "version": "2.3.4",
"resolved": "http://npmprivate.quantgroups.com/antd-mobile/-/antd-mobile-2.3.4.tgz", "resolved": "http://npmprivate.quantgroups.com/antd-mobile/-/antd-mobile-2.3.4.tgz",
...@@ -7170,6 +7203,11 @@ ...@@ -7170,6 +7203,11 @@
"integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
"dev": true "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": { "array-reduce": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "http://npmprivate.quantgroups.com/array-reduce/-/array-reduce-0.0.0.tgz", "resolved": "http://npmprivate.quantgroups.com/array-reduce/-/array-reduce-0.0.0.tgz",
...@@ -8730,6 +8768,11 @@ ...@@ -8730,6 +8768,11 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true "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": { "component-classes": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "http://npmprivate.quantgroups.com/component-classes/-/component-classes-1.2.6.tgz", "resolved": "http://npmprivate.quantgroups.com/component-classes/-/component-classes-1.2.6.tgz",
...@@ -18381,6 +18424,11 @@ ...@@ -18381,6 +18424,11 @@
"object-visit": "^1.0.0" "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": { "markdown-escapes": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "http://npmprivate.quantgroups.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz", "resolved": "http://npmprivate.quantgroups.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
...@@ -19334,6 +19382,11 @@ ...@@ -19334,6 +19382,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true "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": { "normalize.css": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "http://npmprivate.quantgroups.com/normalize.css/-/normalize.css-7.0.0.tgz", "resolved": "http://npmprivate.quantgroups.com/normalize.css/-/normalize.css-7.0.0.tgz",
...@@ -20916,6 +20969,11 @@ ...@@ -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": { "pump": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "http://npmprivate.quantgroups.com/pump/-/pump-3.0.0.tgz", "resolved": "http://npmprivate.quantgroups.com/pump/-/pump-3.0.0.tgz",
...@@ -22115,6 +22173,15 @@ ...@@ -22115,6 +22173,15 @@
"resolved": "http://npmprivate.quantgroups.com/react-amap/-/react-amap-1.2.8.tgz", "resolved": "http://npmprivate.quantgroups.com/react-amap/-/react-amap-1.2.8.tgz",
"integrity": "sha512-uHPEUXti+CcwFyCeqGGqR0ACnXJA9D8S/lQYal9AG3XEOrwkaOFbWUavrvXxjcfAclROIWg8uKxzlRpMQnkHFg==" "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": { "react-copy-to-clipboard": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "http://npmprivate.quantgroups.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", "resolved": "http://npmprivate.quantgroups.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
...@@ -22420,7 +22487,7 @@ ...@@ -22420,7 +22487,7 @@
}, },
"react-router-dom": { "react-router-dom": {
"version": "5.1.2", "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==", "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.1.2",
...@@ -22479,6 +22546,16 @@ ...@@ -22479,6 +22546,16 @@
"shallowequal": "^1.0.1" "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": { "react-sortablejs": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "http://npmprivate.quantgroups.com/react-sortablejs/-/react-sortablejs-6.0.0.tgz", "resolved": "http://npmprivate.quantgroups.com/react-sortablejs/-/react-sortablejs-6.0.0.tgz",
...@@ -22498,6 +22575,15 @@ ...@@ -22498,6 +22575,15 @@
"tween-functions": "^1.0.1" "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": { "read-pkg": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "http://npmprivate.quantgroups.com/read-pkg/-/read-pkg-2.0.0.tgz", "resolved": "http://npmprivate.quantgroups.com/read-pkg/-/read-pkg-2.0.0.tgz",
/* eslint-disable no-console */ /* eslint-disable no-console */
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import localStorage from '@/utils/localStorage'; 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 { try {
Sentry.init({ Sentry.init({
dsn: 'https://b3f60c62e1234e26a5b851b9f26fba07@sentry.q-gp.com/34', 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 { ...@@ -12,6 +12,7 @@ import {
Divider, Divider,
} from 'antd'; } from 'antd';
import React, { Component, useState } from 'react'; import React, { Component, useState } from 'react';
import { SwapRightOutlined } from '@ant-design/icons';
import { connect } from 'dva'; import { connect } from 'dva';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { format } from 'date-fns'; import { format } from 'date-fns';
...@@ -31,6 +32,7 @@ class goodsManage extends Component { ...@@ -31,6 +32,7 @@ class goodsManage extends Component {
state = { state = {
loading: false, loading: false,
productType: null,
}; };
componentDidMount() { componentDidMount() {
...@@ -59,6 +61,10 @@ class goodsManage extends Component { ...@@ -59,6 +61,10 @@ class goodsManage extends Component {
const form = this.formRef.current; const form = this.formRef.current;
form.resetFields(); form.resetFields();
this.props.onReset(); this.props.onReset();
this.props.changeProductType(1);
this.setState({
productType: 1,
});
}; };
addSpu = () => { addSpu = () => {
...@@ -78,6 +84,24 @@ class goodsManage extends Component { ...@@ -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 () => { onExportGoodsInfo = async () => {
this.setState({ this.setState({
...@@ -105,7 +129,6 @@ class goodsManage extends Component { ...@@ -105,7 +129,6 @@ class goodsManage extends Component {
const { treeData, permissions } = this.props; const { treeData, permissions } = this.props;
const selectW = { width: 250 }; const selectW = { width: 250 };
const iptNumWidth = { width: 118 }; const iptNumWidth = { width: 118 };
const that = this;
const canEditable = permissions[GOOD_MANAGE.EDITABLE]; const canEditable = permissions[GOOD_MANAGE.EDITABLE];
const content = ( const content = (
<div> <div>
...@@ -130,46 +153,29 @@ class goodsManage extends Component { ...@@ -130,46 +153,29 @@ class goodsManage extends Component {
</Button> </Button>
</div> </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); const filterOption = (input, op) => op.props.children.includes(input);
return ( return (
<Form <Form
ref={this.formRef} ref={this.formRef}
name="horizontal_login" name="horizontal_login"
initialValues={{ productType: 1 }}
layout="inline" layout="inline"
className={styles.searchForm} className={styles.searchForm}
> >
<FormItem label="SKU编码" name="skuId"> <FormItem label="SKU编码" name="skuId">
<Input placeholder="请输入SKU编码" allowClear style={selectW} /> <InputNumber placeholder="请输入SKU编码" max={99999999999999999} style={selectW} />
</FormItem> </FormItem>
<FormItem label="商品名称" name="skuName"> <FormItem label="商品名称" name="skuName">
<Input placeholder="请输入商品名称" allowClear style={selectW} /> <Input placeholder="请输入商品名称" allowClear style={selectW} />
</FormItem> </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"> <FormItem label="类目" name="productCategoryId">
<Cascader <Cascader
placeholder="请选择类目" placeholder="请选择类目"
...@@ -180,82 +186,79 @@ class goodsManage extends Component { ...@@ -180,82 +186,79 @@ class goodsManage extends Component {
options={treeData} options={treeData}
/> />
</FormItem> </FormItem>
<FormItem label="审核状态" name="state"> {this.state.productType !== 5 && (
<Select <>
style={selectW} <FormItem label="审核状态" name="state">
placeholder="请选择审核状态" <Select
allowClear style={selectW}
filterOption={filterOption} placeholder="请选择审核状态"
> allowClear
{stateList?.map(item => ( filterOption={filterOption}
<Option key={item.value} value={item.value}> >
{item.label} {stateList?.map(item => (
</Option> <Option key={item.value} value={item.value}>
))} {item.label}
</Select> </Option>
</FormItem> ))}
<FormItem label="供货价区间"> </Select>
<FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle> </FormItem>
<InputNumber placeholder="请输入" style={iptNumWidth} /> <FormItem label="供货价区间">
</FormItem> <FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle>
<span>--</span> <InputNumber placeholder="请输入" min={0} max={999999999} style={iptNumWidth} />
<FormItem name="supplyPriceMax" className={styles.iptNumRight} noStyle> </FormItem>
<InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} /> <span>
</FormItem> <SwapRightOutlined />
</FormItem> </span>
<FormItem label="商品类型" name="productType"> <FormItem name="supplyPriceMax" className={styles.iptNumRight} noStyle>
<Select style={selectW} placeholder="请选择商品类型"> <InputNumber
<Option value={1}>实体商品</Option> style={iptNumWidth}
<Option value={2}>虚拟商品</Option> min={0}
<Option value={4}>服务类商品</Option> max={999999999}
</Select> placeholder="请输入"
</FormItem> onChange={this.valueMin}
<FormItem name="thirdSkuNo" label="第三方SKU编码"> />
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} /> </FormItem>
</FormItem> </FormItem>
<FormItem name="thirdSkuNo" label="第三方SKU编码">
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />
</FormItem>
</>
)}
<FormItem className={styles.queryBtn}> <FormItem className={styles.queryBtn}>
<Button onClick={() => this.handleSearch()} type="primary" className={styles.button}> <Button onClick={() => this.handleSearch()} type="primary" className={styles.button}>
查询 查询
</Button> </Button>
<Button onClick={() => this.onReset()} type="primary" className={styles.button}> <Button onClick={() => this.onReset()} className={styles.button}>
重置 重置
</Button> </Button>
<Button {this.state.productType !== 5 && (
loading={this.state.loading} <>
onClick={() => this.onExportGoodsInfo()} <Button
className={styles.button} loading={this.state.loading}
> onClick={() => this.onExportGoodsInfo()}
导出 type="primary"
</Button> ghost
</FormItem> className={styles.button}
{canEditable ? ( >
<FormItem style={{ float: 'right' }}> 导出
<Popover content={content} onVisibleChange={this.handleVisibleChange}>
<Button type="primary" className={styles.button}>
批量设置
</Button> </Button>
</Popover> {canEditable ? (
{this.props.selectNum > 0 && <Tag color="green">已选商品 {this.props.selectNum}</Tag>} <FormItem style={{ float: 'right' }}>
{/* <Button <Popover content={content} onVisibleChange={this.handleVisibleChange}>
className={styles.button} <Button type="primary" className={styles.button}>
type="primary" 批量设置
icon="download" </Button>
ghost </Popover>
onClick={() => { {this.props.selectNum > 0 && (
window.location.href = 'https://kdspstatic.q-gp.com/批量修改库存模板.xlsx'; <Tag color="green">已选商品 {this.props.selectNum}</Tag>
}} )}
> </FormItem>
模版 ) : (
</Button> ''
<Upload {...uploadProps}> )}
<Button type="primary" className={styles.button}> </>
批量库存修改 )}
</Button> </FormItem>
</Upload> */}
</FormItem>
) : (
''
)}
</Form> </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;
This diff is collapsed.
...@@ -89,7 +89,7 @@ export async function categoryList() { ...@@ -89,7 +89,7 @@ export async function categoryList() {
} }
/** /**
* 商品分类 * 商品分类
* type 商品类型:1-实物类,2-虚拟类,4-服务类 * type 商品类型:1-实物类,2-虚拟类,4-服务类 5 外卖
* */ * */
export async function apiCategoryListType(type) { export async function apiCategoryListType(type) {
return request.get(`/product/category/getByProductType/${type}`, { return request.get(`/product/category/getByProductType/${type}`, {
...@@ -289,3 +289,91 @@ export async function apiDraftList(data) { ...@@ -289,3 +289,91 @@ export async function apiDraftList(data) {
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 React from 'react';
import { Button, Badge, Switch, Modal } from 'antd'; import { Button, Badge, Switch, Modal, message } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { SortableHandle } from 'react-sortable-hoc';
import { ExclamationCircleOutlined, MenuOutlined } from '@ant-design/icons';
import styles from './style.less'; import styles from './style.less';
import { resetTime } from '../../utils/utils'; import { resetTime } from '../../utils/utils';
import { apiChangeStateGoods, apiQueryLastAuditRecord } from './service'; import { apiChangeStateGoods, apiGoodsActionBatch } from './service';
const { confirm } = Modal; const { confirm } = Modal;
export const NormalProduct = 1;
export const ServiceProduct = 4;
export const TakeawayProduct = 5;
export const GoodTypes = {
[NormalProduct]: '实体商品',
[ServiceProduct]: '服务类商品',
[TakeawayProduct]: '外卖商品',
};
export const productType = [ export const productType = [
{ {
value: 1, value: 1,
...@@ -244,6 +254,190 @@ export function column() { ...@@ -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 disSelectStatus = [2, 5];
export const stateList = [ export const stateList = [
{ value: 3, label: '待审核' }, { value: 3, label: '待审核' },
......
...@@ -54,8 +54,14 @@ ...@@ -54,8 +54,14 @@
.searchForm { .searchForm {
:global { :global {
.ant-form-item-label { .ant-form-item-label {
line-height: 40px; line-height: 32px;
} }
.ant-form-item {
margin-bottom: 12px;
}
}
.button {
margin: 1px 5px;
} }
} }
.queryBtn { .queryBtn {
...@@ -63,7 +69,7 @@ ...@@ -63,7 +69,7 @@
} }
.actionBtn { .actionBtn {
button { button {
width: 75px; min-width: 75px;
} }
} }
.pagination { .pagination {
...@@ -130,3 +136,158 @@ ...@@ -130,3 +136,158 @@
text-align: left; text-align: left;
word-break: break-all; 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 @@ ...@@ -28,7 +28,17 @@
content: ''; content: '';
} }
} }
.week {
background: #f0f0f0;
}
.weekCon {
display: flex;
flex-flow: row nowrap;
color: #319bfe;
}
.weekText {
margin-right: 10px;
}
.prodcutContent { .prodcutContent {
display: flex; display: flex;
padding-bottom: 10px; padding-bottom: 10px;
...@@ -186,3 +196,152 @@ ...@@ -186,3 +196,152 @@
color: #0e75fd; color: #0e75fd;
cursor: pointer; 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);
This diff is collapsed.
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);
This diff is collapsed.
...@@ -5,9 +5,13 @@ import commonStyle from '../common.less'; ...@@ -5,9 +5,13 @@ import commonStyle from '../common.less';
export const TaskTypeSelect = props => { export const TaskTypeSelect = props => {
const customer = useContext(ServiceContext); const customer = useContext(ServiceContext);
const typeConfig = TaskList(customer.canAddService, customer.canAddNormal); const typeConfig = TaskList(
customer.canAddService,
customer.canAddNormal,
customer.canTakeawayService,
);
const selectTabs = task => { const selectTabs = task => {
if (!customer.isEdit) { if (!customer.isEdit && Object.keys(props.takeAway).length === 0) {
props.onChange(task); 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 = { ...@@ -7,7 +7,7 @@ export const formItemLayout = {
}, },
}; };
export const TaskList = (canAddService, canAddNormal) => [ export const TaskList = (canAddService, canAddNormal, canTakeawayService) => [
{ {
name: '实体商品', name: '实体商品',
type: 1, type: 1,
...@@ -33,36 +33,68 @@ export const TaskList = (canAddService, canAddNormal) => [ ...@@ -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: '虚拟商品', name: '服务类商品',
type: 2, type: 4,
desc: '无需物流', desc: '无需物流',
hide: !canAddNormal, hide: !canAddService,
imgConfig: { imgConfig: {
commonImageList: { commonImageList: {
title: '公共滑动图', title: '封面图片',
rule: false, rule: true,
limit: null, limit: 1,
renderExtra: () => '(图片最大上传2M)', renderExtra(leng) {
return `建议尺寸: ##宽##高 (${leng} / 1) 封面图第一张 `;
},
}, },
imageList: { cardImageList: {
rule: false, title: '商品图片',
limit: null, rule: true,
renderExtra: () => '(图片最大上传2M)', limit: 11,
renderExtra(leng) {
return `建议尺寸: ##宽##高,sku商品轮播图(${leng} / 11)`;
},
}, },
detailImageList: { detailImageList: {
title: '详情图', title: '商品详情图',
rule: true, // rule: true,
limit: null, limit: 30,
renderExtra: () => '(图片最大上传2M)', renderExtra() {
return '最多上传30张';
},
}, },
}, },
}, },
{ {
name: '电子卡卷', name: '外卖商品',
type: 4, type: 5,
desc: '无需物流', desc: '无需物流',
hide: !canAddService, hide: !canTakeawayService,
imgConfig: { imgConfig: {
commonImageList: { commonImageList: {
title: '封面图片', title: '封面图片',
...@@ -291,3 +323,15 @@ export const StaticColumns = customer => [ ...@@ -291,3 +323,15 @@ export const StaticColumns = customer => [
disabeldRender: () => customer.isDisabled, 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: '法定假日' },
];
This diff is collapsed.
...@@ -117,3 +117,53 @@ export const apiEditDraft = data => ...@@ -117,3 +117,53 @@ export const apiEditDraft = data =>
prefix: goodsApi, prefix: goodsApi,
data, 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) => { ...@@ -109,7 +109,52 @@ const filterItems = (type, props) => {
}; };
export const filterSendData = (type, params) => { 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 items = filterItems(type, params);
const commonImageList = type === 4 ? [] : infoImageData.commonImageList; const commonImageList = type === 4 ? [] : infoImageData.commonImageList;
const obj = { const obj = {
...@@ -128,6 +173,7 @@ export const filterSendData = (type, params) => { ...@@ -128,6 +173,7 @@ export const filterSendData = (type, params) => {
if (attributeApplyList && attributeApplyList.attributeApplyList) { if (attributeApplyList && attributeApplyList.attributeApplyList) {
obj.attributeApplyList = attributeApplyList.attributeApplyList; obj.attributeApplyList = attributeApplyList.attributeApplyList;
} }
return obj; return obj;
}; };
...@@ -258,6 +304,11 @@ export const createProductData = (props, isEdit, skuList) => { ...@@ -258,6 +304,11 @@ export const createProductData = (props, isEdit, skuList) => {
export const localAutoSaveKey = 'good-info-auto-save'; export const localAutoSaveKey = 'good-info-auto-save';
export const onAutoSaveValue = (e, isClear) => { export const onAutoSaveValue = (e, isClear) => {
// 暂时去掉外卖类型
if (e && e.type === 5) {
localStorage.remove(localAutoSaveKey);
return;
}
const keys = Object.keys(e); const keys = Object.keys(e);
if ( if (
e && e &&
...@@ -272,6 +323,25 @@ export const onAutoSaveValue = (e, isClear) => { ...@@ -272,6 +323,25 @@ export const onAutoSaveValue = (e, isClear) => {
localStorage.set(localAutoSaveKey, Object.assign({}, e)); localStorage.set(localAutoSaveKey, Object.assign({}, e));
} else { } else {
const info = localStorage.get(localAutoSaveKey) || {}; 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;
});
};
This diff is collapsed.
This diff is collapsed.
.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 });
This diff is collapsed.
This diff is collapsed.
...@@ -17,3 +17,11 @@ export const layout = { ...@@ -17,3 +17,11 @@ export const layout = {
labelCol: { span: 6 }, labelCol: { span: 6 },
wrapperCol: { span: 16 }, wrapperCol: { span: 16 },
}; };
export const businessList = [
{ label: '外卖', value: 1 },
{ label: '实物类', value: 2 },
{ label: '服务类', value: 3 },
];
// 营业日
export const businessStatus = [{ label: '休息中', value: 2 }, { label: '营业中', value: 1 }];
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
.contract {
text-align: center;
}
This diff is collapsed.
This diff is collapsed.
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