Commit e020b0fb authored by 武广's avatar 武广

Merge branch 'feature/bidding' into 'master'

Feature/bidding

See merge request !103
parents acc5a333 63bb8102
...@@ -294,6 +294,12 @@ export default { ...@@ -294,6 +294,12 @@ export default {
name: 'brandManage', name: 'brandManage',
component: './BrandManage', component: './BrandManage',
}, },
{
title: '商户管理后台-自营商品供货价更新',
path: '/supplyPriceUpdate',
name: 'supplyPriceUpdate',
component: './GoodsManage/SupplyPriceUpdate',
},
...groupMealRoute, ...groupMealRoute,
{ {
component: './404', component: './404',
......
import { Form, Button, Input, Select, notification, Cascader, InputNumber } from 'antd';
import React, { Component } from 'react';
import { SwapRightOutlined } from '@ant-design/icons';
import { connect } from 'dva';
import styles from '../../style.less';
import { stateList } from '../../staticdata';
const FormItem = Form.Item;
const { Option } = Select;
@connect(({ goodsManage, menu }) => ({
goodsManage,
permissions: menu.permissions,
}))
class goodsManage extends Component {
formRef = React.createRef();
state = {
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,
});
};
render() {
const { treeData } = this.props;
const selectW = { width: 250 };
const iptNumWidth = { width: 118 };
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>
</FormItem>
</Form>
);
}
}
export default goodsManage;
import React, { useState, createContext, useEffect } from 'react';
import { Modal, Table, Form } from 'antd';
import { column } from '../staticdata';
import styles from '../style.less';
import { apiProductBiddingUpdate } from '../../service';
/**
* 更新供货价
* * */
const UpdatePriceStock = options => {
const { visible, skuData, productId } = options;
const [loading, setLoading] = useState(false);
const [specArr, setSpecArr] = useState([]); // 规格列
const [form] = Form.useForm();
const onSubmit = async () => {
const value = await form.validateFields();
if (!value?.data?.length) return;
const params = {
productId,
skus: value.data,
};
setLoading(true);
await apiProductBiddingUpdate(params);
setLoading(false);
options.refresh();
options.onCancel();
};
const EditableContext = createContext(null);
useEffect(() => {
if (options.visible) {
let firstSpecTitle = '';
let secondSpecTitle = '';
const arr = [];
skuData.forEach(item => {
if (item.firstSpec && item.firstSpecValue) {
firstSpecTitle = item.firstSpec;
}
if (item.secondSpec && item.secondSpecValue) {
secondSpecTitle = item.secondSpec;
}
});
if (firstSpecTitle) {
arr.push({
title: firstSpecTitle,
width: 135,
align: 'center',
dataIndex: 'firstSpecValue',
});
}
if (secondSpecTitle) {
arr.push({
title: secondSpecTitle,
width: 135,
align: 'center',
dataIndex: 'secondSpecValue',
});
}
setSpecArr(arr);
}
}, [visible]);
return (
<Modal
title="修改供货价"
open={visible}
onCancel={options.onCancel}
onOk={onSubmit}
confirmLoading={loading}
width={1000}
className={styles.priceStockTable}
destroyOnClose
>
<Form form={form} scrollToFirstError component={false}>
<EditableContext.Provider value={form}>
<Table
className={styles.priceStockTable}
rowKey="id"
columns={column.call(this, specArr)}
dataSource={skuData}
pagination={false}
/>
</EditableContext.Provider>
</Form>
</Modal>
);
};
export default UpdatePriceStock;
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Card, Pagination, Table, notification, Drawer, Spin } from 'antd';
import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { connect } from 'dva';
import styles from '../style.less';
import { apiCategoryListType, apiProductBiddingInfo } from '../service';
import { JDSHOPID } from '../staticdata';
import { columnManage } from './staticdata';
import SearchForm from './components/SearchForm';
import UpdatePriceStock from './components/UpdatePriceStock';
import Takeaway from '../Takeaway';
import { GOOD_MANAGE } from '@/../config/permission.config';
import LocalStroage from '@/utils/localStorage';
import configApi from '@/../config/env.config';
@connect(({ SupplyPrice, menu }) => ({
SupplyPrice,
permissions: menu.permissions,
}))
class supplyPriceUpdate extends Component {
state = {
pageNo: 1,
loading: false,
categoryTree: [],
pageSize: 20,
previewVisible: false,
createloading: false,
selectedRowKeys: [],
productType: 1, // 商品类型
searchValue: {}, // 搜索条件
refresh: '', // 外卖刷新
visibleUpdatePrice: false, // 修改价格弹窗
skuPriceStockList: [], // 修改价格弹窗数据
spuId: '',
};
currentLog = null;
supplierId = null;
shopList = [];
canEditable = false;
componentDidMount() {
this.props.SupplyPrice.tableData = {};
this.categoryListByType(this.state.productType);
}
handleSearch = page => {
const searchValue = this.searchForm.getFieldsValue() || {};
this.setState({ searchValue });
if (searchValue.productType !== 5) {
this.onSelectChange([]);
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
loading: true,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'SupplyPrice/getList',
payload: {
pageNo,
pageSize,
...searchValue,
},
}).finally(() => {
this.setState({
loading: false,
});
});
},
);
} else {
this.setState({
refresh: new Date().getTime(),
searchValue,
});
}
};
onPageChange = page => {
this.handleSearch(page);
};
audit = skuId => {
this.setState({
previewVisible: true,
src: `${configApi.prologueDomain}/goods/${skuId}?h=0&token=${LocalStroage.get(
'token',
)}&hideReport=1&time=${Date.now()}`,
});
};
onPageSizeChange = (current, size) => {
this.setState(
{
pageSize: size,
},
() => this.handleSearch(),
);
};
onReset = () => {
this.setState({
pageNo: 1,
pageSize: 20,
selectedRowKeys: [],
});
this.handleSearch();
};
onLoad = error => {
if (!error) {
notification.success({ message: '操作成功' });
this.setState({
selectedRowKeys: [],
});
this.handleSearch();
}
};
filterShopList = (list = [], isEdit) =>
list.filter(item => isEdit || !JDSHOPID.includes(item.id));
categoryListByType = async type => {
try {
const { data: categoryTree } = await apiCategoryListType(type);
if (!categoryTree) return;
this.setState({ categoryTree });
} catch (e) {
console.log(e);
}
};
changeProductType = e => {
this.setState({
productType: e || 1,
});
this.categoryListByType(e);
if (e !== 5) {
this.handleSearch(1);
}
};
onSelectChange = selectedRowKeys => {
this.setState({
selectedRowKeys,
});
};
// 显示更新供货价弹窗
serviceVisbleChange = async row => {
try {
const { data } = await apiProductBiddingInfo({
productId: row.spuId,
});
if (!data) return;
this.setState({
spuId: row.spuId,
skuPriceStockList: data,
visibleUpdatePrice: true,
});
} catch (e) {
console.log(e);
}
};
render() {
const {
SupplyPrice: { tableData = {} },
permissions,
} = this.props;
const rowSelection = {
selectedRowKeys: this.state.selectedRowKeys,
onChange: this.onSelectChange,
};
const { pageNo, pageSize, selectedRowKeys } = this.state;
this.canEditable = permissions[GOOD_MANAGE.EDITABLE];
return (
<PageHeaderWrapper>
<Spin spinning={this.state.createloading}>
<Card>
<SearchForm
handleSearch={this.handleSearch}
onReset={this.onReset}
onLoad={this.onLoad}
onRef={ref => {
this.searchForm = ref;
}}
treeData={this.state.categoryTree}
shopList={this.shopList}
checkStock={this.checkEnableUpdateStock}
selectNum={selectedRowKeys.length}
changeProductType={this.changeProductType}
setArea={(isALL, type) => this.setArea(isALL, type)}
/>
</Card>
{this.state.productType === 5 ? (
<Takeaway
handleEdit={this.handleTakeawayEdit}
searchValue={this.state.searchValue}
permissions={permissions}
refresh={this.state.refresh}
/>
) : (
<>
<Spin spinning={this.state.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={columnManage.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
scroll={{ x: '100%', y: 500 }}
rowSelection={rowSelection}
/>
</Spin>
<br />
{tableData && (
<Pagination
style={{ marginBottom: 10 }}
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
bodyStyle={{ height: '90%' }}
>
<iframe
src={this.state.src}
frameBorder="0"
height="100%"
width="375"
title="商品预览"
></iframe>
</Drawer>
</>
)}
</Spin>
{this.state.visibleUpdatePrice && (
<UpdatePriceStock
visible={this.state.visibleUpdatePrice}
onCancel={() => {
this.setState({ visibleUpdatePrice: false });
}}
skuData={this.state.skuPriceStockList}
productId={this.state.spuId}
refresh={this.handleSearch}
/>
)}
</PageHeaderWrapper>
);
}
}
export default Form.create()(supplyPriceUpdate);
import * as api from '../service';
const Model = {
namespace: 'SupplyPrice',
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.apiBiddingList, 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;
import React from 'react';
import { Input, Form, InputNumber, Button } from 'antd';
import { isIntegerNotMust, isCheckPriceTwoDecimal } from '@/utils/validator';
import styles from './style.less';
export function column(specArr = []) {
return [
...specArr,
{
title: '供货价',
dataIndex: 'supplyPrice',
width: 150,
align: 'center',
render: (_, row, index) => (
<div>
<Form.Item
label=""
key="supplyPrice"
name={['data', index, 'supplyPrice']}
initialValue={row.supplyPrice || 0}
rules={[
{ required: true, message: '请输入供货价!' },
{ validator: isCheckPriceTwoDecimal },
]}
>
<Input allowClear />
</Form.Item>
</div>
),
},
{
title: '市场价',
width: 135,
align: 'center',
dataIndex: 'marketPrice',
render: (_, row, index) => (
<div>
<span>{row.marketPrice}</span>
<div className={styles.hidden}>
<Form.Item label="" key="id" name={['data', index, 'skuId']} initialValue={row.id}>
<input />
</Form.Item>
</div>
</div>
),
},
{
title: '重量(kg)',
width: 135,
align: 'center',
dataIndex: 'weight',
},
{
title: '库存',
width: 120,
dataIndex: 'productStock',
align: 'center',
render: (_, row, index) => (
<div>
<Form.Item
label=""
key="stock"
name={['data', index, 'stock']}
initialValue={row.productStock || 0}
rules={[{ required: true, message: '请输入库存!' }, { validator: isIntegerNotMust }]}
>
<InputNumber min={0} max={500} />
</Form.Item>
</div>
),
},
{
title: '库存预警',
width: 120,
dataIndex: 'productStockWarning',
align: 'center',
},
{
title: '商品自编码',
dataIndex: 'thirdSkuNo',
width: 200,
align: 'center',
},
];
}
export function columnManage() {
return [
{
title: 'SKU编码',
dataIndex: 'skuId',
width: 160,
align: 'center',
},
{
title: 'SKU商品名称',
align: 'center',
dataIndex: 'skuName',
},
{
title: '供应商价格',
dataIndex: 'marketPrice',
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>
),
},
{
title: '操作',
dataIndex: 'action',
width: 150,
align: 'center',
render: (_, row) => (
<div className={styles.actionBtn}>
{this.canEditable && (row.state === 4 || (row.state >= 5 && row.updateState !== 1)) && (
<Button
key="edit"
type="primary"
size="small"
className={styles.button}
onClick={() => {
this.serviceVisbleChange(row);
}}
>
修改
</Button>
)}
<Button
key="viewP"
type="primary"
size="small"
className={styles.button}
onClick={() => this.audit(row.skuId)}
>
预览
</Button>
</div>
),
},
];
}
.priceStockTable {
:global(.ant-table-cell) {
padding: 5px 0 0 0 !important;
line-height: 32px !important;
vertical-align: top !important;
}
}
.hidden {
width: 0;
height: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
.button {
margin: 0 5px;
}
...@@ -387,3 +387,37 @@ export async function apiProductStock(data) { ...@@ -387,3 +387,37 @@ export async function apiProductStock(data) {
params: data, params: data,
}); });
} }
/**
* 获取竞价详情
* yApi: http://yapi.quantgroups.com/project/389/interface/api/67169
* * */
export async function apiProductBiddingInfo(params) {
return request.get('/api/merchants/products/bidding/detail', {
prefix: goodsApi,
params,
});
}
/**
* 供应商更新商品供货价库存
* yApi: http://yapi.quantgroups.com/project/389/interface/api/67139
* * */
export async function apiProductBiddingUpdate(params) {
return request.post('/api/merchants/products/bidding/edit', {
prefix: goodsApi,
data: params,
});
}
/**
* 可竞价sku列表
* yApi: http://yapi.quantgroups.com/project/389/interface/api/67164
* * */
export async function apiBiddingList(params) {
return request.post('/api/merchants/products/bidding/page', {
prefix: goodsApi,
data: stringify(params),
headers,
});
}
...@@ -193,21 +193,17 @@ export const isNumberSection = (rule, value, callback) => { ...@@ -193,21 +193,17 @@ export const isNumberSection = (rule, value, callback) => {
// 验证是否整数,非必填 // 验证是否整数,非必填
export function isIntegerNotMust(rule, value, callback) { export function isIntegerNotMust(rule, value, callback) {
if (!value) { if (!value) {
callback(); return Promise.resolve();
} }
setTimeout(() => { if (!Number(value)) {
if (!Number(value)) { return Promise.reject(new Error('请输入正整数'));
callback(new Error('请输入正整数')); }
} else { const re = /^[0-9]*[1-9][0-9]*$/;
const re = /^[0-9]*[1-9][0-9]*$/; const rsCheck = re.test(value);
const rsCheck = re.test(value); if (!rsCheck) {
if (!rsCheck) { return Promise.reject(new Error('请输入正整数'));
callback(new Error('请输入正整数')); }
} else { return Promise.resolve();
callback();
}
}
}, 1000);
} }
// 验证是否是[0-1]的小数 // 验证是否是[0-1]的小数
......
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