Commit e9f94655 authored by 武广's avatar 武广

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

Merge branch 'feature/goods-list' of git.quantgroup.cn:ui/merchant-manage-ui into feature/20230327_public_takeaway
parents 87d0fb79 06a12267
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -31,6 +31,7 @@ class goodsManage extends Component { ...@@ -31,6 +31,7 @@ class goodsManage extends Component {
state = { state = {
loading: false, loading: false,
productType: null,
}; };
componentDidMount() { componentDidMount() {
...@@ -78,6 +79,13 @@ class goodsManage extends Component { ...@@ -78,6 +79,13 @@ class goodsManage extends Component {
} }
}; };
onChangeProductType = (v = null) => {
this.props.changeProductType(v);
this.setState({
productType: v,
});
};
// 导出明细 // 导出明细
onExportGoodsInfo = async () => { onExportGoodsInfo = async () => {
this.setState({ this.setState({
...@@ -105,7 +113,6 @@ class goodsManage extends Component { ...@@ -105,7 +113,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 +137,35 @@ class goodsManage extends Component { ...@@ -130,46 +137,35 @@ 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编码" 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="请选择商品类型"
allowClear
onChange={this.onChangeProductType}
>
<Option value={1}>实体商品</Option>
<Option value={2}>虚拟商品</Option>
<Option value={4}>服务类商品</Option>
<Option value={5}>外卖商品</Option>
</Select>
</FormItem>
<FormItem label="类目" name="productCategoryId"> <FormItem label="类目" name="productCategoryId">
<Cascader <Cascader
placeholder="请选择类目" placeholder="请选择类目"
...@@ -180,6 +176,7 @@ class goodsManage extends Component { ...@@ -180,6 +176,7 @@ class goodsManage extends Component {
options={treeData} options={treeData}
/> />
</FormItem> </FormItem>
{this.state.productType !== 5 && (
<FormItem label="审核状态" name="state"> <FormItem label="审核状态" name="state">
<Select <Select
style={selectW} style={selectW}
...@@ -194,6 +191,7 @@ class goodsManage extends Component { ...@@ -194,6 +191,7 @@ class goodsManage extends Component {
))} ))}
</Select> </Select>
</FormItem> </FormItem>
)}
<FormItem label="供货价区间"> <FormItem label="供货价区间">
<FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle> <FormItem name="supplyPriceMin" className={styles.iptNumRight} noStyle>
<InputNumber placeholder="请输入" style={iptNumWidth} /> <InputNumber placeholder="请输入" style={iptNumWidth} />
...@@ -203,16 +201,11 @@ class goodsManage extends Component { ...@@ -203,16 +201,11 @@ class goodsManage extends Component {
<InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} /> <InputNumber style={iptNumWidth} placeholder="请输入" onChange={this.valueMin} />
</FormItem> </FormItem>
</FormItem> </FormItem>
<FormItem label="商品类型" name="productType"> {this.state.productType !== 5 && (
<Select style={selectW} placeholder="请选择商品类型">
<Option value={1}>实体商品</Option>
<Option value={2}>虚拟商品</Option>
<Option value={4}>服务类商品</Option>
</Select>
</FormItem>
<FormItem name="thirdSkuNo" label="第三方SKU编码"> <FormItem name="thirdSkuNo" label="第三方SKU编码">
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} /> <Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />
</FormItem> </FormItem>
)}
<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}>
查询 查询
...@@ -220,6 +213,7 @@ class goodsManage extends Component { ...@@ -220,6 +213,7 @@ class goodsManage extends Component {
<Button onClick={() => this.onReset()} type="primary" className={styles.button}> <Button onClick={() => this.onReset()} type="primary" className={styles.button}>
重置 重置
</Button> </Button>
{this.state.productType !== 5 && (
<Button <Button
loading={this.state.loading} loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()} onClick={() => this.onExportGoodsInfo()}
...@@ -227,6 +221,7 @@ class goodsManage extends Component { ...@@ -227,6 +221,7 @@ class goodsManage extends Component {
> >
导出 导出
</Button> </Button>
)}
</FormItem> </FormItem>
{canEditable ? ( {canEditable ? (
<FormItem style={{ float: 'right' }}> <FormItem style={{ float: 'right' }}>
...@@ -236,22 +231,6 @@ class goodsManage extends Component { ...@@ -236,22 +231,6 @@ class goodsManage extends Component {
</Button> </Button>
</Popover> </Popover>
{this.props.selectNum > 0 && <Tag color="green">已选商品 {this.props.selectNum}</Tag>} {this.props.selectNum > 0 && <Tag color="green">已选商品 {this.props.selectNum}</Tag>}
{/* <Button
className={styles.button}
type="primary"
icon="download"
ghost
onClick={() => {
window.location.href = 'https://kdspstatic.q-gp.com/批量修改库存模板.xlsx';
}}
>
模版
</Button>
<Upload {...uploadProps}>
<Button type="primary" className={styles.button}>
批量库存修改
</Button>
</Upload> */}
</FormItem> </FormItem>
) : ( ) : (
'' ''
......
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 => {
Modal.confirm({
icon: <ExclamationCircleOutlined />,
content: `确认${+state === 6 ? '下架' : '上架'}商品?`,
onOk: async () => {
const res = await apiGoodsActionBatch({
ids: options.selectedRowKeys,
type: 2,
productState: +state === 6 ? 7 : 6, // 6:上架,7:下架
});
if (res.businessCode === '0000' && res.code === '0000') {
options.handleSearch();
}
},
});
};
/**
* 批量操作
* 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']}>
<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 }) => {
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) => {
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) return; // 如果回到自己的坑,那就什么都不做
endChangePosition(dragIndex, hoverIndex); // 调用传入的方法完成交换
},
});
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 } 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 = options => {
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 user = localStorage.getItem('user');
const json = JSON.parse(user);
const res = await apiSupplierShopList(json.id);
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);
}
};
const getGroupList = async () => {
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);
}
};
const handleEdit = async id => {
setStorageId(id || 0);
setIsModalOpen(true);
};
const handleDelete = async id => {
await apiDelStorage({
shopId: options.shopId,
id,
});
getGroupList();
};
// 更换位置
const changePosition = async (dragIndex, hoverIndex) => {
const data = tags.slice();
const temp = data[dragIndex];
// 交换位置
data[dragIndex] = data[hoverIndex];
data[hoverIndex] = temp;
setTags(data);
};
const endChangePosition = async (dragIndex, hoverIndex) => {
const data = tags.slice();
const temp = data[dragIndex];
// 交换位置
data[dragIndex] = data[hoverIndex];
data[hoverIndex] = temp;
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]);
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';
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;
await api({
name,
necessary: necessary ? 1 : 0,
shopId: options.shopId,
id: options.id,
});
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个“下单必选分组”商品每店仅可设置1个必点分组"
type="error"
/>
);
return (
<Modal
title="分组信息"
visible={options.isModalOpen}
destroyOnClose
maskClosable={false}
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, Input } from 'antd';
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}
confirmLoading={options.loading}
destroyOnClose
onCancel={handleCancel}
>
<Form
name="basic"
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
autoComplete="off"
>
<Form.Item
label="最少购买/份"
name="minPurchaseNum"
rules={[{ required: true, message: '请输入最少购买数量!' }]}
>
<Input maxLength={6} type="number" />
</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}
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, Input, Checkbox, Radio, Switch } from 'antd';
import { deepClone } from '@/utils/utils';
import styles from '../../style.less';
const StockModal = options => {
const [stockType, setStockType] = useState(0);
const [maxStock, setMaxStock] = useState(0);
const [form] = Form.useForm();
const onChangeType = v => {
setStockType(v === stockType ? 0 : v);
if (v === 1) {
form.setFieldsValue({
productStock: 0,
});
}
};
const onChangeMaxStock = ({ target: { 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;
console.log('values :>> ', values);
options.confirm({
type: 7,
...values,
});
};
const initialValues = Object.assign(
{
productStock: '',
autoStockStep: '',
autoStock: false,
},
options.initialValues,
);
useEffect(() => {
if (stockType === 2) {
form.setFieldsValue({
productStock: maxStock,
});
}
}, [maxStock, stockType]);
return (
<Modal
visible={options.visible}
title="修改库存"
onOk={handleOk}
maskClosable={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: '请输入剩余库存!' }]}
>
<Input type="number" 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={[{ required: true, message: '请输入最大库存!' }]}
>
<Input type="number" onChange={onChangeMaxStock} />
</Form.Item>
<Form.Item label="自动补足" name="autoStock">
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</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}
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 } 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(1);
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 values = await form.validateFields();
const params = deepClone(values);
if (params.saleTimes && params.saleTimes.length) {
params.saleTimes = values.saleTimes.map(item => {
if (item && item.length > 1) {
item[0] = moment(item[0]).format('HH:mm');
item[1] = moment(item[1]).format('HH:mm');
}
return item;
});
}
options.confirm({
type: 4,
...params,
});
};
const initialValues = Object.assign(
{
saleTimeType: 1,
saleDates: [],
saleTimes: [[]],
},
options.initialValues,
);
return (
<Modal
visible={options.visible}
title="售卖时间"
onOk={handleOk}
maskClosable={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="saleTimeType"
rules={[{ required: true, message: '请选择售卖时间段类型!' }]}
>
<Radio.Group
options={radioOptions}
onChange={onChangeType}
value={type}
optionType="button"
buttonStyle="solid"
/>
</Form.Item>
{type === 2 ? (
<>
<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: '请选择售卖时间',
},
]}
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, useCallback, useMemo } from 'react';
import { Spin, Table, Modal, message, notification } from 'antd';
import { MenuOutlined, HolderOutlined, FormOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { arrayMoveImmutable } from 'array-move';
import GoodsGroup from './components/GoodsGroup';
import { apiTakeawayGoods, apiGoodsActionBatch, apiSortTakeawayGoods } 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 [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 rowSelection = {
selectedRowKeys,
onChange: setSelectedRowKeys,
};
const getDataList = async () => {
setLoading(true);
const params = {
pageNo: 1,
productType: 5,
pageSize: 100000,
storageRackId: groupId,
};
const res = await apiTakeawayGoods(params);
setLoading(false);
if (res && res.data) {
setTableData(res.data.records);
}
};
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: index + 1,
}));
const params = {
storageRackId: groupId,
type: 1,
shopId,
skuSorts,
};
await apiSortTakeawayGoods(params);
getDataList();
// 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 params2 = {
...json,
...params,
};
console.log('params2 :>> ', params2);
const res = await apiGoodsActionBatch(Object.assign({}, json, params));
setActionLoading(false);
getDataList();
message.success('处理成功!');
};
// 显示弹窗
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 }) => {
options.handleEdit({
shopId,
spuId,
});
};
// 新建商品
const onNew = () => {
options.handleEdit({
shopId,
groupId,
});
};
// 置顶
const toTop = row => {};
useEffect(() => {
if (groupId) {
getDataList();
}
}, [groupId]);
const actions = {
onShowStockModal,
toTop,
onEdit,
};
return (
<div className={styles.takeawayBox}>
<Spin spinning={loading}>
<GoodsGroup shopId={shopId} changeShop={setShopId} changeGroup={setGroupId} />
<ActionBar
selectedRowKeys={selectedRowKeys}
handleSearch={getDataList}
openModal={openModal}
newGoods={onNew}
/>
<Table
dataSource={tableData}
bordered
columns={takeawayColumn(actions)}
rowKey={record => record.skuId}
pagination={false}
scroll={{ x: '100%', y: 1000 }}
rowSelection={rowSelection}
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
},
}}
/>
</Spin>
<WeekTime
visible={visibleWeekTime}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleWeekTime}
/>
<StockModal
visible={visibleStock}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleStock}
/>
<SendModal
visible={visibleSend}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleSend}
/>
<MinimumPurchaseModal
visible={visibleBuy}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleBuy}
/>
<SwitchGroupModal
visible={visibleSwitchGroup}
loading={actionLoading}
confirm={handleBatchAction}
cancel={setVisibleSwitchGroup}
/>
</div>
);
};
export default Takeaway;
...@@ -12,6 +12,7 @@ import UpdateStock from './UpdateStock'; ...@@ -12,6 +12,7 @@ import UpdateStock from './UpdateStock';
import { import {
spuDetail, spuDetail,
categoryList, categoryList,
apiCategoryListType,
getVirtualCategory, getVirtualCategory,
getTemplateList, getTemplateList,
specList, specList,
...@@ -26,6 +27,7 @@ import TempleatModal from './TempleatModal'; ...@@ -26,6 +27,7 @@ import TempleatModal from './TempleatModal';
import ServiceGoods from '../ServiceGoods'; import ServiceGoods from '../ServiceGoods';
import InfoAudit from './infoAudit'; import InfoAudit from './infoAudit';
import DraftModal from './DraftModal'; import DraftModal from './DraftModal';
import Takeaway from './Takeaway';
import { GOOD_MANAGE } from '@/../config/permission.config'; import { GOOD_MANAGE } from '@/../config/permission.config';
...@@ -59,6 +61,8 @@ class goodsManage extends Component { ...@@ -59,6 +61,8 @@ class goodsManage extends Component {
auditRow: {}, // 查看审核信息使用 auditRow: {}, // 查看审核信息使用
isVisibleDraft: false, // 显示隐藏草稿箱 isVisibleDraft: false, // 显示隐藏草稿箱
isEditDraft: false, // 是否编辑草稿 isEditDraft: false, // 是否编辑草稿
productType: 1, // 商品类型
takeAway: {}, // 弹窗外卖商品参数
}; };
currentLog = null; currentLog = null;
...@@ -74,6 +78,7 @@ class goodsManage extends Component { ...@@ -74,6 +78,7 @@ class goodsManage extends Component {
this.categoryList(); this.categoryList();
this.getVirtualCategory(); this.getVirtualCategory();
this.specList(); this.specList();
this.setState({ productType: 5 });
} }
handleSearch = page => { handleSearch = page => {
...@@ -265,9 +270,10 @@ class goodsManage extends Component { ...@@ -265,9 +270,10 @@ class goodsManage extends Component {
} }
}; };
categoryList = async () => { categoryList = async type => {
const api = type ? apiCategoryListType : categoryList;
try { try {
const { data: treeData } = await categoryList(); const { data: treeData } = await api(type);
if (!treeData) return; if (!treeData) return;
this.setState({ treeData }); this.setState({ treeData });
} catch (e) { } catch (e) {
...@@ -275,6 +281,13 @@ class goodsManage extends Component { ...@@ -275,6 +281,13 @@ class goodsManage extends Component {
} }
}; };
changeProductType = e => {
this.setState({
productType: e || 1,
});
this.categoryList(e);
};
getVirtualCategory = async () => { getVirtualCategory = async () => {
try { try {
const { data: virtualTreeData } = await getVirtualCategory(); const { data: virtualTreeData } = await getVirtualCategory();
...@@ -355,6 +368,14 @@ class goodsManage extends Component { ...@@ -355,6 +368,14 @@ class goodsManage extends Component {
this.serviceVisbleChange(this.state.auditRow); this.serviceVisbleChange(this.state.auditRow);
}; };
// 显示外卖商品弹窗
handleTakeawayEdit = params => {
this.setState({
takeAway: params,
serviceVisble: true,
});
};
render() { render() {
const { const {
goodsManage: { tableData = {} }, goodsManage: { tableData = {} },
...@@ -411,9 +432,14 @@ class goodsManage extends Component { ...@@ -411,9 +432,14 @@ class goodsManage extends Component {
shopList={this.shopList} shopList={this.shopList}
checkStock={this.checkEnableUpdateStock} checkStock={this.checkEnableUpdateStock}
selectNum={selectedRowKeys.length} selectNum={selectedRowKeys.length}
changeProductType={this.changeProductType}
setArea={(isALL, type) => this.setArea(isALL, type)} setArea={(isALL, type) => this.setArea(isALL, type)}
/> />
</Card> </Card>
{this.state.productType === 5 ? (
<Takeaway handleEdit={this.handleTakeawayEdit} />
) : (
<>
<Spin spinning={this.state.loading}> <Spin spinning={this.state.loading}>
<Table <Table
dataSource={tableData?.records} dataSource={tableData?.records}
...@@ -485,6 +511,9 @@ class goodsManage extends Component { ...@@ -485,6 +511,9 @@ class goodsManage extends Component {
isType={this.state.isType} isType={this.state.isType}
templateList={this.state.templateList} templateList={this.state.templateList}
/> />
</>
)}
</Spin>
{this.state.serviceVisble && ( {this.state.serviceVisble && (
<ServiceGoods <ServiceGoods
visible={this.state.serviceVisble} visible={this.state.serviceVisble}
...@@ -496,9 +525,10 @@ class goodsManage extends Component { ...@@ -496,9 +525,10 @@ class goodsManage extends Component {
specListData={this.state.specListData} specListData={this.state.specListData}
permissions={permissions} permissions={permissions}
isDraft={this.state.isEditDraft} isDraft={this.state.isEditDraft}
productType={this.state.productType}
takeAway={this.state.takeAway}
/> />
)} )}
</Spin>
{this.state.visibleAuditModal && ( {this.state.visibleAuditModal && (
<InfoAudit <InfoAudit
visible={this.state.visibleAuditModal} visible={this.state.visibleAuditModal}
......
...@@ -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}`, {
...@@ -290,10 +290,76 @@ export async function apiDraftList(data) { ...@@ -290,10 +290,76 @@ export async function apiDraftList(data) {
}); });
} }
// 新增外卖商品 // 批量操作
export async function addTakeOutGoods(params) { export async function apiGoodsActionBatch(data) {
return request.post('/v1/channels/products/add', { return request.post('/api/merchants/products/sku/batchOperation', {
prefix: goodsApi, prefix: goodsApi,
data: params, 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 apiSupplierShopList() {
return request.get('/api/merchants/shops/getBySupplierId?state=1', {
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,
}); });
} }
import React from 'react'; import React from 'react';
import { Button, Badge, Switch, Modal } from 'antd'; import { Button, Badge, Switch, Modal } 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 } from './service';
const { confirm } = Modal; const { confirm } = Modal;
...@@ -244,6 +245,199 @@ export function column() { ...@@ -244,6 +245,199 @@ export function column() {
}, },
]; ];
} }
export function takeawayColumn(actions) {
const onChangeState = async ({ skuId, state }) => {
confirm({
icon: <ExclamationCircleOutlined />,
content: `确认${+state === 6 ? '下架' : '上架'}商品?`,
onOk: async () => {
const res = await apiChangeStateGoods({
skuIds: skuId,
productStatus: +state === 6 ? 7 : 6, // 6:上架,7:下架
});
if (res.businessCode === '0000' && res.code === '0000') {
this.handleSearch();
}
},
});
};
const onShowAudit = row => {
this.setState({
auditRow: row,
visibleAuditModal: true,
});
};
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: 'marketPrice',
width: 150,
align: 'center',
render: (_, row) => (
<div className={styles.price}>
<div>供货价:{(row.supplyPrice || 0).toFixed(2)}</div>
<div>市场价:{(row.marketPrice || 0).toFixed(2)}</div>
</div>
),
},
{
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 && (
<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,147 @@ ...@@ -130,3 +136,147 @@
text-align: left; text-align: left;
word-break: break-all; word-break: break-all;
} }
.takeawayBox {
margin-top: 20px;
padding-bottom: 40px;
background-color: #fff;
}
.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;
}
}
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