Commit 32dd7d4a authored by shida.liu's avatar shida.liu

feat: 应付审计而增加的页面和判断,无真实用途

parent cd134bc7
...@@ -302,26 +302,33 @@ export default { ...@@ -302,26 +302,33 @@ export default {
}, },
...groupMealRoute, ...groupMealRoute,
{ {
component: './404', title: '商户管理后台-订单管理-待发货订单',
},
{
title: '商户管理后台-POP商品管理-待发货订单',
path: '/popOrderManage/popPendingDeliveryOrder', path: '/popOrderManage/popPendingDeliveryOrder',
name: 'popPendingDeliveryOrder', name: 'popPendingDeliveryOrder',
component: './PopOrderManage/pendingDeliveryOrder', component: './PopOrderManage/pendingDeliveryOrder',
}, },
{ {
title: '商户管理后台-POP商品管理-已发货订单', title: '商户管理后台-订单管理-已发货订单',
path: '/popOrderManage/popDeliveryOrder', path: '/popOrderManage/popDeliveryOrder',
name: 'popDeliveryOrder', name: 'popDeliveryOrder',
component: './PopOrderManage/deliveryOrder', component: './PopOrderManage/deliveryOrder',
}, },
{ {
title: '商户管理后台-POP商品管理-批量发货', title: '商户管理后台-订单管理-批量发货',
path: '/popOrderManage/popBatchDelivery', path: '/popOrderManage/popBatchDelivery',
name: 'popBatchDeliveryOrder', name: 'popBatchDeliveryOrder',
component: './PopOrderManage/batchDelivery', component: './PopOrderManage/batchDelivery',
}, },
{
title: '商户管理后台-商品管理-商品库',
path: '/popGoodsManage',
name: 'popGoodsManage',
icon: 'smile',
component: './PopGoodsManage',
},
{
component: './404',
},
], ],
}, },
{ {
......
import React, { useState, useEffect } from 'react';
import { Modal, Table, message } from 'antd';
import { columns } from './staticdata';
import { apiDraftList, apiDraftDetail, apiDeleteDraft } from '../service';
const DraftModal = props => {
const [pageInfo, setPageInfo] = useState({
current: 1,
pageSize: 4,
});
const [total, setTotal] = useState(0);
const [dataSource, setdataSource] = useState([]);
const onClose = () => props.onCancel();
const onEdit = async record => {
const res = await apiDraftDetail(record.id);
if (res && res.data) {
const data = JSON.parse(res.data.content);
data.id = record.id;
props.onToDetail(data);
onClose();
}
};
const getDraftList = async params => {
const res = await apiDraftList(params);
if (res && res.data && res.data.records) {
setdataSource(res.data.records);
setTotal(res.data.total);
}
};
const onRefresh = () => {
getDraftList({
pageSize: pageInfo.pageSize,
pageNo: pageInfo.current,
});
};
const onDel = record => {
Modal.confirm({
title: '确认提示',
content: '操作后不可更改,确认是否删除?',
onOk: async () => {
console.log('record :>> ', record);
await apiDeleteDraft(record.id);
message.success('删除成功!');
onRefresh();
},
});
};
const onChange = (current, pageSize) => {
const json = {
current,
pageSize,
};
setPageInfo(json);
json.pageNo = current;
getDraftList(json);
};
const pagination = {
...pageInfo,
total,
showTotal: t => `共 ${t} 项数据`,
onChange,
pageSizeOptions: [4, 20, 50, 100],
showSizeChanger: !0,
onShowSizeChange: onChange,
};
useEffect(() => {
if (props.visible) {
onRefresh();
}
}, [props.visible]);
const res = {
onDel,
onEdit,
};
return (
<Modal
visible={props.visible}
title="草稿箱"
onCancel={onClose}
maskClosable={false}
width="1000px"
footer={[]}
>
<Table
columns={columns(res)}
pagination={pagination}
rowKey={record => record.id}
dataSource={dataSource}
/>
</Modal>
);
};
export default DraftModal;
import React from 'react';
import { Button } from 'antd';
import styles from '../style.less';
const productType = {
1: '普通商品',
2: '虚拟商品',
3: '电子卡卷',
4: '服务商品',
5: '外卖商品',
};
export const columns = ({ onDel, onEdit }) => [
{
title: '草稿ID',
dataIndex: 'id',
width: 85,
align: 'center',
},
{
title: '商品名称',
dataIndex: 'productName',
align: 'center',
render(text) {
return <div className={styles.draftName}>{text}</div>;
},
},
{
title: '所属类目',
dataIndex: 'leimu',
width: 240,
align: 'center',
render(text, record) {
if (record.firstCategoryName) {
return `${record.firstCategoryName}>${record.secondCategoryName}>${record.thirdCategoryName}`;
}
return '-';
},
},
{
title: '商品类型',
dataIndex: 'productType',
width: 100,
align: 'center',
render(text) {
return productType[text];
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 120,
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
width: 130,
align: 'center',
render: (text, record) => (
<>
<Button key="edit" type="link" size="small" onClick={() => onEdit(record)}>
修改
</Button>
<Button key="viewP" type="link" size="small" onClick={() => onDel(record)}>
删除
</Button>
</>
),
},
];
import { Modal, Table, Button, Pagination, Tabs } from 'antd';
import React, { useState, useEffect } from 'react';
import styles from '../style.less';
import { changeLog, productMerchantLog } from '../service';
const LogModal = props => {
const [tabActiveKey, setTabActiveKey] = useState('0');
const [tableData, setTableData] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [pageSize] = useState(20);
const [merchantList, setMerchantList] = useState([]);
const columns = [
{
title: '时间',
dataIndex: 'createdAt',
align: 'center',
},
{
title: '变更字段',
align: 'center',
dataIndex: 'changeType',
},
{
title: '变更后内容',
dataIndex: 'afterChangeValue',
align: 'center',
},
{
title: '变更前内容',
dataIndex: 'beforeChangeValue',
align: 'center',
},
{
title: '变更原因',
dataIndex: 'reason',
align: 'center',
},
{
title: '操作账号',
dataIndex: 'operator',
align: 'center',
},
];
const columnsMerchant = [
{
title: '时间',
dataIndex: 'createdAt',
align: 'center',
},
{
title: '审核结果',
align: 'center',
dataIndex: 'operation',
render: value => ([5, 6, 7].includes(value) ? '审核通过' : '驳回'),
},
{
title: '驳回原因',
dataIndex: 'rejectReason',
align: 'center',
},
{
title: '操作账号',
dataIndex: 'createdBy',
align: 'center',
},
];
const handleSearch = async (page = 1) => {
setPageNo(page);
const { data = {} } = await changeLog({ id: props.id, pageNo: page, pageSize });
setTableData(data);
};
const onPageChange = page => {
handleSearch(page);
};
const getProductMerchantLog = async () => {
const { data = [] } = await productMerchantLog(props.spuId);
setMerchantList(data); // merchantList
};
const bundleOnTabChange = key => {
if (key === '1') {
getProductMerchantLog();
} else {
onPageChange(1);
}
setTabActiveKey(key);
};
const bundleOnCancel = () => {
setMerchantList([]);
setTabActiveKey('0');
props.onCancel();
};
useEffect(() => {
if (!props.id) return;
// 20221108 临时隐藏商品详情,默认切换到审核详情 by liteng
// handleSearch();
bundleOnTabChange('1');
}, [props.id]);
const { visible } = props;
return (
<Modal title="日志详情" visible={visible} footer={null} onCancel={bundleOnCancel} width="800px">
<Tabs type="card" onChange={bundleOnTabChange} activeKey={tabActiveKey}>
{/* 20221108 临时隐藏商品详情 by liteng */}
{/* <Tabs.TabPane tab="商品详情" key="0">
<Table
dataSource={tableData.records}
bordered
columns={columns}
rowKey={record => record.id}
pagination={false}
scroll={{ y: 300 }}
/>
{tableData.records && (
<Pagination
onChange={onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
className={styles.pagination}
/>
)}
</Tabs.TabPane> */}
<Tabs.TabPane tab="审核详情" key="1">
<Table
dataSource={merchantList}
bordered
columns={columnsMerchant}
rowKey={record => record.id}
pagination={false}
scroll={{ y: 300 }}
/>
</Tabs.TabPane>
</Tabs>
<Button type="primary" onClick={bundleOnCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
};
export default LogModal;
import {
Form,
Button,
Input,
Select,
notification,
Upload,
Tag,
Cascader,
InputNumber,
Popover,
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';
import styles from '../style.less';
import { stateList } from '../staticdata';
import { apiGoodsInfosExport } from '../service';
import { GOOD_MANAGE } from '@/../config/permission.config';
const FormItem = Form.Item;
const { Option } = Select;
@connect(({ goodsManage, menu }) => ({
goodsManage,
permissions: menu.permissions,
}))
class goodsManage extends Component {
formRef = React.createRef();
state = {
loading: false,
productType: null,
};
componentDidMount() {
this.props.onRef(this);
this.handleSearch();
}
getFieldsValue() {
const form = this.formRef.current;
return form.getFieldsValue();
}
handleSearch = () => {
this.props.handleSearch(1);
};
valueMin = value => {
const { getFieldValue, setFieldsValue } = this.formRef.current;
const minVal = getFieldValue('supplyPriceMin');
if (minVal && minVal > value) {
setFieldsValue({ supplyPriceMax: minVal });
}
};
onReset = () => {
const form = this.formRef.current;
form.resetFields();
this.props.onReset();
this.props.changeProductType(1);
this.setState({
productType: 1,
});
};
addSpu = () => {
this.props.addSpu();
};
setArea = (isAll, type) => {
this.props.setArea(isAll, type);
};
// 验证是否可以修改库存
checkEnableUpdateStock = () => {
if (this.props.selectNum) {
this.props.checkStock();
} else {
notification.info({ message: '请选择' });
}
};
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({
loading: true,
});
const form = this.formRef?.current?.getFieldValue();
const params = Object.assign({}, form);
const productCategoryId = form?.productCategoryId || [];
if (productCategoryId.length) {
params.productCategoryId = productCategoryId[productCategoryId.length - 1] || '';
}
const res = await apiGoodsInfosExport(params);
this.setState({
loading: false,
});
if (res) {
const blob = new Blob([res]);
saveAs(blob, `商品明细-${format(new Date(), 'yyyyMMdd')}.xlsx`);
} else {
notification.error({ message: '下载失败' });
}
};
render() {
const { treeData, permissions } = this.props;
const selectW = { width: 250 };
const iptNumWidth = { width: 118 };
const canEditable = permissions[GOOD_MANAGE.EDITABLE];
const content = (
<div>
<Button style={{ border: 'none' }} onClick={() => this.setArea(1, 'distribution')}>
全部商品配送区域设置
</Button>
<br />
<Button style={{ border: 'none' }} onClick={() => this.setArea(0, 'distribution')}>
勾选商品配送区域设置
</Button>
<br />
<Button style={{ border: 'none' }} onClick={() => this.setArea(1, 'after')}>
全部商品售后地址设置
</Button>
<br />
<Button style={{ border: 'none' }} onClick={() => this.setArea(0, 'after')}>
勾选商品售后地址设置
</Button>
<br />
<Button style={{ border: 'none' }} onClick={() => this.checkEnableUpdateStock()}>
勾选商品库存修改
</Button>
</div>
);
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">
<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="请选择类目"
style={selectW}
showSearch
changeOnSelect
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={treeData}
/>
</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()} className={styles.button}>
重置
</Button>
{this.state.productType !== 5 && (
<>
<Button
loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()}
type="primary"
ghost
className={styles.button}
>
导出
</Button>
{/* {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>
);
}
}
export default goodsManage;
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' && res.data) {
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 || 0);
}
};
const onPageChange = (page, size) => {
unstable_batchedUpdates(() => {
setPageNo(page);
setPageSize(size);
});
getDataList(page, size);
};
const onSortEnd = async ({ oldIndex, newIndex }) => {
if (oldIndex !== newIndex) {
const sourceGoods = tableData[oldIndex];
const targetGoods = tableData[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,
// }));
if (sourceGoods && sourceGoods.skuId && targetGoods && targetGoods.skuId) {
const params = {
storageRackId: groupId,
shopId,
sourceSkuId: sourceGoods.skuId,
targetSkuId: targetGoods.skuId,
};
await apiSortTakeawayGoods(params);
getDataList(pageNo, pageSize);
}
}
};
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, groupId);
} 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;
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Modal, Input, Select, Cascader, Tag, notification } from 'antd';
import { da } from 'date-fns/locale';
import React, { useState, useEffect } from 'react';
import { getTemplate, getAfterAddress } from '../service';
const { Option } = Select;
const TempleatModal = props => {
const {
visible,
form: { getFieldDecorator, validateFields, resetFields },
selectedRowKeys,
templateList,
isALL,
isType,
total,
} = props;
const formItemLayout = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 16,
},
};
const setAfterAddress = async fieldsValue => {
const data = await getAfterAddress({
skuIds: selectedRowKeys || [],
afterAddressId: fieldsValue.templateId.key,
});
if (data.businessCode === '0000') {
notification.success({ message: '配置成功!' });
resetFields();
props.onCancel();
}
};
const setTemplate = async fieldsValue => {
const data = await getTemplate({
isAll: isALL,
skuIdList: selectedRowKeys,
templateId: fieldsValue.templateId.key,
});
if (data.businessCode === '0000') {
notification.success({ message: '配置成功!' });
resetFields();
props.onCancel();
}
};
const handleOk = async () => {
validateFields(async (error, fieldsValue) => {
if (!error) {
if (isType === 'after') {
setAfterAddress(fieldsValue);
}
if (isType === 'distribution') {
setTemplate(fieldsValue);
}
}
});
};
const title = () => {
if (isType === 'distribution') {
if (isALL) {
return '全部商品配送区域设置';
}
return '勾选商品配送区域设置';
}
if (isType === 'after') {
if (isALL) {
return '全部商品售后地址设置';
}
return '勾选商品售后地址设置';
}
return '';
};
return (
<Modal
title={title()}
visible={visible}
width="500px"
onCancel={props.onCancel}
onOk={() => handleOk()}
>
{!isALL && <p>已选择{selectedRowKeys.length}个商品</p>}
{isALL > 0 && isType === 'after' && <p>已选择{total}个商品</p>}
<Form {...formItemLayout}>
<Form.Item label={isType === 'after' ? '选择售后地址' : '选择模板'}>
{getFieldDecorator('templateId', {
rules: [
{
required: true,
message: `${isType === 'after' ? '请选择售后地址' : '请选择模板'}`,
},
],
})(
<Select
placeholder={isType === 'after' ? '选择售后地址' : '选择模板'}
labelInValue
allowClear
>
{isType === 'distribution' &&
templateList.map(item => (
<Option label={item.templateName} value={item.id} key={item.id}>
{item.templateName}
</Option>
))}
{isType === 'after' &&
templateList.map(item => (
<Option label={item.addressName} value={item.id} key={item.id}>
{item.addressName}
</Option>
))}
</Select>,
)}
</Form.Item>
</Form>
</Modal>
);
};
export default Form.create()(TempleatModal);
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Modal, InputNumber, notification, Input, Radio } from 'antd';
import React, { useState } from 'react';
import { apiCreateGoodsLog } from '../service';
import styles from '../style.less';
import { isNumberSection } from '@/utils/validator';
const UpdateStock = props => {
const { getFieldDecorator, validateFields, resetFields, getFieldValue } = props.form;
const valueInfo = props.info;
const [loading, setLoading] = useState(false);
const submit = async () => {
validateFields(async (err, { stock, changeReason, changeType }) => {
if (err) return;
setLoading(true);
const params = {
afterChange: stock,
supplierId: valueInfo.supplierId,
productIdType: 2,
changeType,
changeReason,
productIds: props.skuIds,
};
console.log('params :>> ', params);
const res = await apiCreateGoodsLog(params);
if (res?.businessCode === '0000') {
notification.success({ message: '库存更改申请已提交!' });
props.onCancel('success');
resetFields();
}
setLoading(false);
});
};
const onCancel = () => {
props.onCancel();
resetFields();
};
const formItemLayout = {
labelCol: {
span: 7,
},
wrapperCol: {
span: 15,
},
};
const validatorCallback = (rule, value, callback) => {
// 减库存存时,校验可售库存-输入值>=0,即可售库存不可为负;
const changeType = getFieldValue('changeType');
const increment = valueInfo.marketableStock - value;
console.log('value :>> ', value, valueInfo.marketableStock);
console.log('valueInfo :>> ', valueInfo, increment);
return +changeType === 29 && increment < 0 ? callback(new Error(rule.message)) : callback();
};
return (
<Modal
title="修改库存"
visible={props.visible}
okButtonProps={{ loading, disabled: valueInfo.status === 1 }}
onCancel={onCancel}
onOk={submit}
width={500}
>
<Form {...formItemLayout}>
{valueInfo.curStock ? (
<Form.Item label="当前库存:">
<span>{valueInfo.curStock}</span>
</Form.Item>
) : (
<Form.Item label="可修改库存商品数:">
<span>{props.skuIds.length}</span>
</Form.Item>
)}
<Form.Item label="变更类型:">
{getFieldDecorator('changeType', {
rules: [{ required: true, message: '请选择类型!' }],
initialValue: valueInfo.changeType || 28,
})(
<Radio.Group disabled={valueInfo.status === 1}>
<Radio value={28}>增库存</Radio>
<Radio value={29}>减库存</Radio>
<Radio value={30}>使用新值</Radio>
</Radio.Group>,
)}
<div className={styles.stockTip}>库存变更审批通过后生效</div>
</Form.Item>
<Form.Item label="库存数:">
{getFieldDecorator('stock', {
rules: [
{ required: true, message: '请输入库存!' },
{ validator: validatorCallback, message: '减库存,输入库存数不可大于可售库存!' },
{ validator: isNumberSection, min: 1, max: 500, message: '请输入1-500的整数' },
],
validateTrigger: ['onSubmit', 'onChange'],
initialValue: valueInfo.stock,
})(
<InputNumber
min={0}
precision={0}
placeholder="请输入库存"
disabled={valueInfo.status === 1}
style={{ width: 200 }}
/>,
)}
</Form.Item>
<Form.Item label="变更原因:">
{getFieldDecorator('changeReason', {
rules: [{ required: true, message: '请输入变更原因!' }],
initialValue: valueInfo.changeReason,
})(<Input.TextArea disabled={valueInfo.status === 1} maxLength={50} />)}
</Form.Item>
{valueInfo.stateDesc && (
<div className={styles.stockErrMsg}>
{valueInfo.stateDesc}{valueInfo.rejectReason}
</div>
)}
</Form>
</Modal>
);
};
export default Form.create()(UpdateStock);
This diff is collapsed.
import React, { useState, useEffect } from 'react';
import { Modal, Button } from 'antd';
import { apiQueryLastAuditRecord } from '../service';
const InfoAudit = props => {
const [audit, setAudit] = useState({});
const getRecord = async () => {
const res = await apiQueryLastAuditRecord(props.skuInfo.skuId);
if (res && res.data) {
console.log('res :>> ', res);
setAudit(res.data);
}
};
const getContent = () => {
const obj = {
1: '-',
2: '审核通过',
3: `审核拒绝,${audit.rejectReason}`,
};
if (audit) {
return obj[audit.status] || '';
}
return '';
};
useEffect(() => {
if (props.visible) {
getRecord();
}
}, [props.visible]);
return (
<Modal
title="商品信息变更审核"
visible={props.visible}
closable={false}
footer={[
audit.status === 3 &&
props.canEditable &&
(props.skuInfo.state === 4 ||
(props.skuInfo.state >= 5 && props.skuInfo.updateState !== 1)) && (
<Button key="back" type="primary" onClick={() => props.onEdit()}>
再次编辑
</Button>
),
<Button key="close" onClick={() => props.onCancel()}>
关闭
</Button>,
]}
>
<p>审核状态:{audit.statusDesc}</p>
<p>申请时间:{audit.createdAt}</p>
<p>审核结果:{getContent()}</p>
</Modal>
);
};
export default InfoAudit;
import * as api from './service';
const Model = {
namespace: 'popGoodsManage',
state: {
tableData: {},
shopList: [],
statusList: [],
cid1List: [],
cid2List: [],
cid3List: [],
treeData: [],
},
effects: {
*getList({ payload }, { call, put }) {
const params = payload;
const productCategoryId = payload?.productCategoryId || [];
params.productCategoryId =
(productCategoryId.length && productCategoryId[productCategoryId.length - 1]) || '';
const res = yield call(api.searchList, params);
if (res && !res.data) return;
yield put({
type: 'saveData',
payload: {
tableData: res.data,
},
});
},
*categoryList({ payload }, { call, put }) {
const [data] = yield call(api.categoryList, payload.value);
if (!data) return;
yield put({
type: 'saveCategory',
payload: {
[payload.categoryNum]: data,
},
});
},
},
reducers: {
saveData(state, action) {
const data = action.payload;
return { ...state, ...data };
},
dataList(state, action) {
const data = action.payload;
return { ...state, ...data };
},
saveCategory(state, action) {
const data = action.payload;
return { ...state, ...data };
},
},
};
export default Model;
This diff is collapsed.
This diff is collapsed.
.button {
margin: 5px;
}
.selectWidth {
width: 200px;
}
.btngroup {
margin: 10px;
}
.filterModal {
margin: 20px 0;
}
.footerButton {
position: absolute;
bottom: 20px;
left: 0;
}
.ptop {
margin-top: 5px;
}
.tabletop {
margin-top: 20px;
}
.logBtn {
display: inherit;
margin: 20px auto;
}
.linkInput {
width: 310px !important;
}
.picBtn {
margin-top: 5px;
margin-left: 10px;
}
.pullBtn {
position: absolute;
top: 30px;
left: 800px;
}
.price {
// text-align: left;
cursor: pointer;
}
.searchForm {
:global {
.ant-form-item-label {
line-height: 32px;
}
.ant-form-item {
margin-bottom: 12px;
}
}
.button {
margin: 1px 5px;
}
}
.queryBtn {
margin-left: 45px;
}
.actionBtn {
button {
min-width: 75px;
}
}
.pagination {
margin-top: 10px;
}
.imgBorder {
margin: 5px 0;
padding-left: 20px;
background: #fff;
border: 1px solid #efefef;
border-radius: 10px;
}
.state {
font-size: 13px;
}
.card {
margin-top: 15px;
margin-bottom: 15px;
}
.modal {
background: #ddd;
}
.warning {
margin-top: -20px;
margin-bottom: 20px;
color: red;
}
.iptNumRight {
margin-right: 0 !important;
}
.sizeTitle {
font-size: 12px;
}
.stockTip {
color: #d9363e;
line-height: 1;
}
.stockErrMsg {
box-sizing: border-box;
padding-left: 30%;
color: #d9363e;
line-height: 1;
}
.cardTitle {
padding: 15px;
font-weight: bold;
font-size: 18px;
}
.stateAuditTxt {
color: #1890ff;
cursor: pointer;
}
.attrbox {
max-height: 384px;
overflow: hidden;
}
.attrboxMore {
max-height: max-content;
}
.draftName {
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%;
}
...@@ -40,14 +40,14 @@ const TableList = props => { ...@@ -40,14 +40,14 @@ const TableList = props => {
hideInSearch: true, hideInSearch: true,
align: 'center', align: 'center',
}, },
{ // {
title: '操作人', // title: '操作人',
dataIndex: 'userName', // dataIndex: 'userName',
key: 'userName', // key: 'userName',
hideInSearch: true, // hideInSearch: true,
width: 120, // width: 120,
align: 'center', // align: 'center',
}, // },
{ {
title: '发货单数', title: '发货单数',
dataIndex: 'totalNum', dataIndex: 'totalNum',
......
...@@ -10,7 +10,7 @@ export async function queryToSend(params) { ...@@ -10,7 +10,7 @@ export async function queryToSend(params) {
try { try {
const { const {
data: { current, records, total, size }, data: { current, records, total, size },
} = await request.post('/api/merchants/orders/list', { } = await request.post('/api/merchants/pops/orders/list', {
prefix: config.kdspApi, prefix: config.kdspApi,
data: stringify(_.omitBy(params, v => !v)), data: stringify(_.omitBy(params, v => !v)),
headers: { headers: {
...@@ -31,7 +31,7 @@ export async function queryToSend(params) { ...@@ -31,7 +31,7 @@ export async function queryToSend(params) {
// 快递公司 // 快递公司
export async function queryExpress() { export async function queryExpress() {
try { try {
const { data } = await request.get('/api/merchants/companies/list', { const { data } = await request.get('/api/merchants/pops/companies/list', {
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
return data; return data;
...@@ -41,7 +41,7 @@ export async function queryExpress() { ...@@ -41,7 +41,7 @@ export async function queryExpress() {
} }
export async function getGoods(orderId) { export async function getGoods(orderId) {
const { data } = await request.get(`/api/merchants/orders/skus/list?orderId=${orderId}`, { const { data } = await request.get(`/api/merchants/pops/orders/skus/list?orderId=${orderId}`, {
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
return data; return data;
...@@ -49,7 +49,7 @@ export async function getGoods(orderId) { ...@@ -49,7 +49,7 @@ export async function getGoods(orderId) {
export async function uploadFile(file) { export async function uploadFile(file) {
const params = new FormData(); const params = new FormData();
params.append('file', file); params.append('file', file);
const data = await request.post('/api/merchants/orders/deliveries/batches/import', { const data = await request.post('/api/merchants/pops/orders/deliveries/batches/import', {
data: params, data: params,
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
...@@ -59,7 +59,7 @@ export function downTemplate() { ...@@ -59,7 +59,7 @@ export function downTemplate() {
window.location.href = 'https://sc-img.q-gp.com/orders/templates/batch_deliveriesV2.xlsx'; window.location.href = 'https://sc-img.q-gp.com/orders/templates/batch_deliveriesV2.xlsx';
} }
export async function downOrder(params) { export async function downOrder(params) {
const data = await request.post('/api/merchants/orders/export', { const data = await request.post('/api/merchants/pops/orders/export', {
data: stringify(_.omitBy(params, v => !v)), data: stringify(_.omitBy(params, v => !v)),
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -82,7 +82,7 @@ export async function queryToBatchSend(params) { ...@@ -82,7 +82,7 @@ export async function queryToBatchSend(params) {
}; };
const { const {
data: { current, records, total, size }, data: { current, records, total, size },
} = await request.get('/api/merchants/deliveries/batchlist', { } = await request.get('/api/merchants/pops/deliveries/batchlist', {
prefix: config.kdspApi, prefix: config.kdspApi,
params: _.omitBy(transformedParam, v => !v), params: _.omitBy(transformedParam, v => !v),
headers: { headers: {
...@@ -97,7 +97,7 @@ export async function queryToBatchSend(params) { ...@@ -97,7 +97,7 @@ export async function queryToBatchSend(params) {
}; };
} }
export async function downUploadeOrder(params) { export async function downUploadeOrder(params) {
const data = await request.get('/api/merchants/deliveries/batchexport', { const data = await request.get('/api/merchants/pops/deliveries/batchexport', {
params, params,
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -115,7 +115,7 @@ export async function downUploadeOrder(params) { ...@@ -115,7 +115,7 @@ export async function downUploadeOrder(params) {
} }
// 延迟发货 // 延迟发货
export function apiDelayDeliverGoods(data) { export function apiDelayDeliverGoods(data) {
return request.post('/api/merchants/orders/logs/add', { return request.post('/api/merchants/pops/orders/logs/add', {
data, data,
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
...@@ -128,7 +128,7 @@ export function apiDelayDeliverGoods(data) { ...@@ -128,7 +128,7 @@ export function apiDelayDeliverGoods(data) {
* @see http://yapi.quantgroups.com/project/389/interface/api/45840 * @see http://yapi.quantgroups.com/project/389/interface/api/45840
*/ */
export function apiQueryOrderInfo(params) { export function apiQueryOrderInfo(params) {
return request.get('/api/merchants/orders/deliveries/packages/detail', { return request.get('/api/merchants/pops/orders/deliveries/packages/detail', {
params, params,
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
...@@ -140,14 +140,14 @@ export function apiQueryOrderInfo(params) { ...@@ -140,14 +140,14 @@ export function apiQueryOrderInfo(params) {
* @see http://yapi.quantgroups.com/project/389/interface/api/45816 * @see http://yapi.quantgroups.com/project/389/interface/api/45816
*/ */
export function apiDeliveriesAdd(data) { export function apiDeliveriesAdd(data) {
return request.post('/api/merchants/orders/deliveries/add', { return request.post('/api/merchants/pops/orders/deliveries/add', {
data, data,
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
} }
export function apiDeliveriesEdit(data) { export function apiDeliveriesEdit(data) {
return request.post('/api/merchants/orders/deliveries/edit', { return request.post('/api/merchants/pops/orders/deliveries/edit', {
data, data,
prefix: config.kdspApi, prefix: config.kdspApi,
}); });
...@@ -159,7 +159,7 @@ export function apiDeliveriesEdit(data) { ...@@ -159,7 +159,7 @@ export function apiDeliveriesEdit(data) {
*/ */
export function apiDeliveriesTraceList(data) { export function apiDeliveriesTraceList(data) {
return request.post('/api/merchants/deliveries/traces/list', { return request.post('/api/merchants/pops/deliveries/traces/list', {
data: stringify(data), data: stringify(data),
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
......
This diff is collapsed.
...@@ -25,6 +25,7 @@ import { ...@@ -25,6 +25,7 @@ import {
apiEditDraft, apiEditDraft,
apiGetShopDetail, apiGetShopDetail,
} from './service'; } from './service';
import { useLocation } from 'react-router-dom';
import { isUrl, filterSendData, clearCurrent, onAutoSaveValue, localAutoSaveKey } from './utils'; import { isUrl, filterSendData, clearCurrent, onAutoSaveValue, localAutoSaveKey } from './utils';
import { ServiceContext } from './context'; import { ServiceContext } from './context';
import { GOOD_MANAGE } from '@/../config/permission.config'; import { GOOD_MANAGE } from '@/../config/permission.config';
...@@ -85,6 +86,7 @@ const ServiceGoods = options => { ...@@ -85,6 +86,7 @@ const ServiceGoods = options => {
picturesRef, picturesRef,
takeawayRef, takeawayRef,
]); ]);
const location = useLocation();
const [specKeyList, setSpecKeyList] = useState([]); // 记录一级规格key字段 const [specKeyList, setSpecKeyList] = useState([]); // 记录一级规格key字段
...@@ -505,6 +507,7 @@ const ServiceGoods = options => { ...@@ -505,6 +507,7 @@ const ServiceGoods = options => {
}, },
}); });
}; };
const isPopGoods = location.pathname.indexOf('popGoodsManage') > 0; // pop商品管理-商品库(应付审计用的, 驳回和修改状态下不能编辑)
const providerValue = { const providerValue = {
pageId, pageId,
isEdit, isEdit,
...@@ -516,9 +519,10 @@ const ServiceGoods = options => { ...@@ -516,9 +519,10 @@ const ServiceGoods = options => {
isTakeawayService: productType === 5, isTakeawayService: productType === 5,
isGold: categoryIds.includes(GoldCategory), // 投资金 重量显示克 isGold: categoryIds.includes(GoldCategory), // 投资金 重量显示克
// 0, "商品删除" 1, "新建" 2, "提交审核" 3, "待审核" 4, "驳回" 5, "未上架" 6, "已上架" 7, "已下架" // 0, "商品删除" 1, "新建" 2, "提交审核" 3, "待审核" 4, "驳回" 5, "未上架" 6, "已上架" 7, "已下架"
isNormal: SourceData.state && SourceData.state !== 4, // 商品不是驳回状态 isNormal: (SourceData.state && SourceData.state !== 4) || isPopGoods, // 商品不是驳回状态
// 当商品进行编辑 & 类型不为电子卡券 & 商品状态不为驳回 禁用当前功能 // 当商品进行编辑 & 类型不为电子卡券 & 商品状态不为驳回 禁用当前功能
isDisabled: isEdit && productType !== 4 && SourceData.state && SourceData.state !== 4, isDisabled:
(isEdit && productType !== 4 && SourceData.state && SourceData.state !== 4) || isPopGoods,
isJDGoods: isEdit && SourceData.pageProductType && +SourceData.pageProductType !== 1, isJDGoods: isEdit && SourceData.pageProductType && +SourceData.pageProductType !== 1,
isUseCache, // 是否使用缓存数据 isUseCache, // 是否使用缓存数据
onEventBus, onEventBus,
......
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