Commit a722eb43 authored by 武广's avatar 武广

Merge branch 'feature/bidding-two' into 'master'

Feature/bidding two

See merge request !104
parents e020b0fb 327a1353
import { DownloadOutlined } from '@ant-design/icons';
import { Modal, Button, Empty, Pagination } from 'antd';
import React, { useState, useEffect } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import styles from './index.less';
import { apiUploadResult } from './service';
const ImportListModal = props => {
const { visible } = props;
const [data, setData] = useState([]);
const [total, setTotal] = useState(0);
const [pageInfo, setPageInfo] = useState({
pageNo: 1,
pageSize: 2,
});
const getImportData = async params => {
const res = await apiUploadResult({
type: props.type,
...params,
});
if (res?.data) {
unstable_batchedUpdates(() => {
setData(res.data.records);
setTotal(res.data.total);
});
}
};
const onChangePage = (pageNo, pageSize) => {
const obj = { pageNo, pageSize };
setPageInfo(obj);
getImportData(obj);
};
const onDownload = url => {
window.location.href = url;
};
useEffect(() => {
getImportData(pageInfo);
}, []);
return (
<Modal
title={
<div>
查看导入记录<span className={styles.subTitle}>(仅展示近半年记录)</span>
</div>
}
open={visible}
onCancel={props.onCancel}
width="800px"
footer={false}
>
{data.length ? (
<div>
<div className={styles.tableBody}>
{data.map(item => (
<div className={styles.card} key={item.id}>
<div>
<div className={styles.top}>
<span>导入数据:{item.total}</span>
<span>成功数:{item.successCount}</span>
<span>失败数:{item.failedCount}</span>
</div>
<div>
<span>导入时间:{item.createdAt}</span>
<span>操作人:{item.createdBy}</span>
</div>
</div>
<div className={styles.errWrappper}>
{item.failedCount > 0 && (
<Button type="primary" onClick={() => onDownload(item.failedFileUrl)}>
<DownloadOutlined />
下载错误数据
</Button>
)}
{item.status && +item.status.code === 3 && (
<div className={styles.errorMessage}>{item.failedMessage}</div>
)}
</div>
</div>
))}
</div>
<div className={styles.pagewrapper}>
<Pagination
total={total}
showTotal={e => `总共 ${e} 条数据`}
pageSize={pageInfo.pageSize}
defaultCurrent={pageInfo.pageNo}
onChange={onChangePage}
onShowSizeChange={onChangePage}
showQuickJumper
// showSizeChanger
/>
</div>
</div>
) : (
<Empty />
)}
</Modal>
);
};
export default ImportListModal;
// 导入配置
export const ImportConfig = {
// 竞价商品批量选品
binding: {
title: '自营商品供货价库存批量更新',
type: 6,
tempPath: '',
limitNum: 5000,
hideDownload: true,
tip: 'SKU',
},
};
import React, { useState, useEffect } from 'react';
import { Form } from '@ant-design/compatible';
import { Modal, Button, Upload, notification } from 'antd';
import styles from './index.less';
import { apiImportGoods } from './service';
import ImportListModal from './ImportListModal';
import { ImportConfig } from './config';
const ImportGoods = React.memo(props => {
const [importFile, setImportFile] = useState([]);
const [loading, setLoading] = useState(false);
const [visibleRecord, setVisibleRecord] = useState(false);
const [config, setConfig] = useState({});
// 关闭弹窗
const onCancel = () => {
props.onHide();
};
// 查看导入记录
const onShowRecord = () => {
setVisibleRecord(true);
};
// 下载模板
const onDownTemplate = () => {
if (config.tempPath) {
window.location.href = config.tempPath;
} else if (props.onDownload) {
props.onDownload(config);
}
};
// 导入
const onImport = async () => {
if (!importFile.length) {
notification.error({
message: '请选择导入文件',
});
return;
}
setLoading(true);
const result = await apiImportGoods(importFile[0], config.type);
setLoading(false);
if (result?.success) {
setImportFile([]);
notification.success({
message: '导入成功',
});
}
};
// 导入按钮配置属性
const uploadFileAttr = {
name: 'file',
maxCount: 1,
fileList: importFile,
async customRequest(info) {
setImportFile([info.file]);
return false;
},
accept: '.xlsx',
showUploadList: true,
onRemove: () => {
setImportFile([]);
},
};
useEffect(() => {
if (props.visible) {
setConfig(ImportConfig[props.importType]);
}
}, [props.visible]);
return (
<>
<Modal
title={config.title}
open={props.visible}
onCancel={onCancel}
footer={[
<Button type="link" onClick={onShowRecord} key="btnlook">
查看导入记录
</Button>,
!config.hideDownload && (
<Button type="link" onClick={onDownTemplate} key="btndown">
下载模板
</Button>
),
<Button onClick={onCancel} key="btncancel">
关闭
</Button>,
<Button type="primary" loading={loading} onClick={onImport} key="btnimprot">
导入
</Button>,
]}
>
<Form>
<Form.Item label="导入文件">
<Upload {...uploadFileAttr}>
<Button type="link" key="btnsel">
选择文件
</Button>
</Upload>
<div className={styles.textDesc}>
<div>1、仅支持按商品{config.tip}导入,纵向排列</div>
<div>2、支持Excel格式文件,导入数量限制{config.limitNum}</div>
</div>
</Form.Item>
</Form>
</Modal>
{visibleRecord && (
<ImportListModal
visible={visibleRecord}
type={config.type}
onCancel={() => setVisibleRecord(false)}
/>
)}
</>
);
});
export default Form.create()(ImportGoods);
.card {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
padding: 20px;
background-color: #fafafa;
span {
display: inline-block;
padding-right: 15px;
}
}
.top {
margin-bottom: 15px;
font-weight: 600;
font-size: 16px;
}
.subTitle {
color: #333;
font-weight: normal;
font-size: 14px;
}
.tableBody {
min-height: 244px;
}
.pagewrapper {
text-align: right;
}
.errWrappper {
display: flex;
align-items: flex-end;
justify-content: flex-end;
}
.errorMessage {
max-width: 300px;
}
.textDesc {
color: #999;
line-height: 32px;
}
import request from '@/utils/request';
import config from '@/../config/env.config';
import qs from 'qs';
const { goodsApi } = config;
// 商品导入接口
export async function apiImportGoods(file, type) {
const params = new FormData();
params.append('file', file);
params.append('type', type);
const res = await request.post('/api/merchants/importFile/excel', {
data: params,
prefix: goodsApi,
});
return res;
}
// 分页查询上传文件结果
export async function apiUploadResult(params) {
const data = await request.get(
`/api/merchants/importFile/info/page${qs.stringify(params, { addQueryPrefix: true })}`,
{
prefix: goodsApi,
},
);
return data;
}
......@@ -2,8 +2,9 @@ import { Form, Button, Input, Select, notification, Cascader, InputNumber } from
import React, { Component } from 'react';
import { SwapRightOutlined } from '@ant-design/icons';
import { connect } from 'dva';
import ImportGoodsModal from '@/components/ImportGoodsModal';
import styles from '../../style.less';
import { stateList } from '../../staticdata';
import { apiDownBiddingTemplate, apiUploadGoodsFile } from '../../service';
const FormItem = Form.Item;
const { Option } = Select;
......@@ -16,6 +17,7 @@ class goodsManage extends Component {
state = {
productType: null,
visibleImport: false,
};
componentDidMount() {
......@@ -32,6 +34,13 @@ class goodsManage extends Component {
this.props.handleSearch(1);
};
// 下载模版
onDownload = () => {
const form = this.formRef.current;
const values = form.getFieldsValue();
apiDownBiddingTemplate(values);
};
valueMin = value => {
const { getFieldValue, setFieldsValue } = this.formRef.current;
const minVal = getFieldValue('supplyPriceMin');
......@@ -85,13 +94,48 @@ class goodsManage extends Component {
});
};
onShowImport = importType => {
this.setState({
visibleImport: true,
importType,
});
};
onHideImport = () => {
this.setState({
visibleImport: false,
});
};
// 上传文件设置
uploadConfig = () => {
const that = this;
return {
name: 'file',
async customRequest(info) {
that.setState({
loading: true,
});
const result = await apiUploadGoodsFile(info.file);
if (result.businessCode === '0000') {
notification.success({ message: '导入成功' });
}
that.setState({
loading: false,
});
},
accept: '.xlsx',
showUploadList: false,
};
};
render() {
const { treeData } = this.props;
const selectW = { width: 250 };
const iptNumWidth = { width: 118 };
const filterOption = (input, op) => op.props.children.includes(input);
return (
<div>
<Form
ref={this.formRef}
name="horizontal_login"
......@@ -106,7 +150,11 @@ class goodsManage extends Component {
<Input placeholder="请输入商品名称" allowClear style={selectW} />
</FormItem>
<FormItem label="商品类型" name="productType">
<Select style={selectW} placeholder="请选择商品类型" onChange={this.onChangeProductType}>
<Select
style={selectW}
placeholder="请选择商品类型"
onChange={this.onChangeProductType}
>
<Option value={1}>实体商品</Option>
<Option value={4}>服务类商品</Option>
<Option value={5}>外卖商品</Option>
......@@ -124,20 +172,6 @@ class goodsManage extends Component {
</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} />
......@@ -167,8 +201,27 @@ class goodsManage extends Component {
<Button onClick={() => this.onReset()} className={styles.button}>
重置
</Button>
<Button
type="primary"
className={styles.button}
onClick={() => this.onShowImport('binding')}
>
导入商品
</Button>
<Button onClick={() => this.onDownload()} type="link" className={styles.button}>
下载模板
</Button>
</FormItem>
</Form>
{this.state.visibleImport && (
<ImportGoodsModal
visible={this.state.visibleImport}
importType={this.state.importType}
onHide={this.onHideImport}
/>
)}
</div>
);
}
}
......
......@@ -8,7 +8,7 @@ import { apiProductBiddingUpdate } from '../../service';
* 更新供货价
* * */
const UpdatePriceStock = options => {
const { visible, skuData, productId } = options;
const { visible, skuData, productId, spuName } = options;
const [loading, setLoading] = useState(false);
const [specArr, setSpecArr] = useState([]); // 规格列
const [form] = Form.useForm();
......@@ -69,10 +69,11 @@ const UpdatePriceStock = options => {
onCancel={options.onCancel}
onOk={onSubmit}
confirmLoading={loading}
width={1000}
width={800}
className={styles.priceStockTable}
destroyOnClose
>
<div className={styles.modalTitle}>SPU名称:{spuName}</div>
<Form form={form} scrollToFirstError component={false}>
<EditableContext.Provider value={form}>
<Table
......
......@@ -29,13 +29,13 @@ class supplyPriceUpdate extends Component {
pageSize: 20,
previewVisible: false,
createloading: false,
selectedRowKeys: [],
productType: 1, // 商品类型
searchValue: {}, // 搜索条件
refresh: '', // 外卖刷新
visibleUpdatePrice: false, // 修改价格弹窗
skuPriceStockList: [], // 修改价格弹窗数据
spuId: '',
spuName: '',
};
currentLog = null;
......@@ -55,7 +55,6 @@ class supplyPriceUpdate extends Component {
const searchValue = this.searchForm.getFieldsValue() || {};
this.setState({ searchValue });
if (searchValue.productType !== 5) {
this.onSelectChange([]);
const currentPage = this.state.pageNo;
this.setState(
{
......@@ -113,7 +112,6 @@ class supplyPriceUpdate extends Component {
this.setState({
pageNo: 1,
pageSize: 20,
selectedRowKeys: [],
});
this.handleSearch();
};
......@@ -121,9 +119,6 @@ class supplyPriceUpdate extends Component {
onLoad = error => {
if (!error) {
notification.success({ message: '操作成功' });
this.setState({
selectedRowKeys: [],
});
this.handleSearch();
}
};
......@@ -151,12 +146,6 @@ class supplyPriceUpdate extends Component {
}
};
onSelectChange = selectedRowKeys => {
this.setState({
selectedRowKeys,
});
};
// 显示更新供货价弹窗
serviceVisbleChange = async row => {
try {
......@@ -166,7 +155,8 @@ class supplyPriceUpdate extends Component {
if (!data) return;
this.setState({
spuId: row.spuId,
skuPriceStockList: data,
spuName: data.productName,
skuPriceStockList: data.itemList,
visibleUpdatePrice: true,
});
} catch (e) {
......@@ -179,11 +169,7 @@ class supplyPriceUpdate extends Component {
SupplyPrice: { tableData = {} },
permissions,
} = this.props;
const rowSelection = {
selectedRowKeys: this.state.selectedRowKeys,
onChange: this.onSelectChange,
};
const { pageNo, pageSize, selectedRowKeys } = this.state;
const { pageNo, pageSize } = this.state;
this.canEditable = permissions[GOOD_MANAGE.EDITABLE];
return (
......@@ -200,7 +186,6 @@ class supplyPriceUpdate extends Component {
treeData={this.state.categoryTree}
shopList={this.shopList}
checkStock={this.checkEnableUpdateStock}
selectNum={selectedRowKeys.length}
changeProductType={this.changeProductType}
setArea={(isALL, type) => this.setArea(isALL, type)}
/>
......@@ -223,7 +208,6 @@ class supplyPriceUpdate extends Component {
pagination={false}
className={styles.tabletop}
scroll={{ x: '100%', y: 500 }}
rowSelection={rowSelection}
/>
</Spin>
<br />
......@@ -268,6 +252,7 @@ class supplyPriceUpdate extends Component {
}}
skuData={this.state.skuPriceStockList}
productId={this.state.spuId}
spuName={this.state.spuName}
refresh={this.handleSearch}
/>
)}
......
import React from 'react';
import { Input, Form, InputNumber, Button } from 'antd';
import { Input, Form, InputNumber, Button, notification } from 'antd';
import { isIntegerNotMust, isCheckPriceTwoDecimal } from '@/utils/validator';
import styles from './style.less';
......@@ -44,16 +44,10 @@ export function column(specArr = []) {
</div>
),
},
{
title: '重量(kg)',
width: 135,
align: 'center',
dataIndex: 'weight',
},
{
title: '库存',
width: 120,
dataIndex: 'productStock',
width: 80,
dataIndex: 'stock',
align: 'center',
render: (_, row, index) => (
<div>
......@@ -61,7 +55,7 @@ export function column(specArr = []) {
label=""
key="stock"
name={['data', index, 'stock']}
initialValue={row.productStock || 0}
initialValue={row.stock || 0}
rules={[{ required: true, message: '请输入库存!' }, { validator: isIntegerNotMust }]}
>
<InputNumber min={0} max={500} />
......@@ -69,18 +63,6 @@ export function column(specArr = []) {
</div>
),
},
{
title: '库存预警',
width: 120,
dataIndex: 'productStockWarning',
align: 'center',
},
{
title: '商品自编码',
dataIndex: 'thirdSkuNo',
width: 200,
align: 'center',
},
];
}
......@@ -89,7 +71,7 @@ export function columnManage() {
{
title: 'SKU编码',
dataIndex: 'skuId',
width: 160,
width: 200,
align: 'center',
},
{
......@@ -98,17 +80,30 @@ export function columnManage() {
dataIndex: 'skuName',
},
{
title: '供应商价格',
title: '市场价',
dataIndex: 'marketPrice',
width: 160,
align: 'center',
sorter: (a, b) => a.marketPrice - b.marketPrice,
render: (_, row) => <div>{(row.marketPrice || 0).toFixed(2)}</div>,
},
{
title: '供货价',
dataIndex: 'supplyPrice',
width: 160,
align: 'center',
sorter: (a, b) => a.supplyPrice - b.supplyPrice,
render: (_, row) => (
<div className={styles.price}>
<p>市场价:{(row.marketPrice || 0).toFixed(2)}</p>
</div>
<div>{(row.supplyPrice && (row.supplyPrice || 0).toFixed(2)) || '-'}</div>
),
},
{
title: '库存',
dataIndex: 'stock',
width: 160,
align: 'center',
render: (_, row) => <div>{row.stock || '-'}</div>,
},
{
title: '操作',
dataIndex: 'action',
......
.priceStockTable {
:global(.ant-modal-body) {
padding: 0 15px !important;
}
:global(.ant-table-cell) {
padding: 5px 0 0 0 !important;
line-height: 32px !important;
vertical-align: top !important;
}
}
.modalTitle {
height: 40px;
line-height: 40px;
}
.hidden {
width: 0;
height: 0;
......
......@@ -2,6 +2,8 @@
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import { saveAs } from 'file-saver';
import { format } from 'date-fns';
import _ from 'lodash';
const { goodsApi, kdspApi } = config;
......@@ -421,3 +423,35 @@ export async function apiBiddingList(params) {
headers,
});
}
/**
* 导入竞价商品信息
* yApi: http://yapi.quantgroups.com/project/389/interface/api/45896
* * */
export async function apiUploadGoodsFile(file) {
const params = new FormData();
params.append('file', file);
params.append('type', 6);
const data = await request.post('/api/merchants/importFile/excel', {
data: params,
prefix: goodsApi,
});
return data;
}
/**
* 下载竞价商品模版
* yApi: http://yapi.quantgroups.com/project/389/interface/api/67269
* * */
export async function apiDownBiddingTemplate(params) {
const productCategoryId = params?.productCategoryId || [];
params.productCategoryId =
(productCategoryId.length && productCategoryId[productCategoryId.length - 1]) || '';
const res = await request.post('/api/merchants/products/bidding-template/export', {
data: stringify(_.omitBy(params, v => v === undefined && v === null && v === '')),
headers,
prefix: goodsApi,
responseType: 'arrayBuffer',
});
const blob = new Blob([res]);
saveAs(blob, `自营商品供货价更新表-${format(new Date(), 'yyyy-MM-dd')}.xlsx`);
}
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