Commit 0adbd8ef authored by beisir's avatar beisir

feat: 商品库,商户录品二期

parent 648a86d8
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
/config /config
.history .history
/src/utils/qiniu.min.js /src/utils/qiniu.min.js
/src/pages/GoodsManage-new
\ No newline at end of file
...@@ -7,6 +7,9 @@ module.exports = { ...@@ -7,6 +7,9 @@ module.exports = {
}, },
rules: { rules: {
'max-len': ['error', { code: 200 }], 'max-len': ['error', { code: 200 }],
'no-param-reassign': 0,
'no-console': 0,
'@typescript-eslint/camelcase': ['off'], '@typescript-eslint/camelcase': ['off'],
'@typescript-eslint/no-unused-vars': ['off'],
}, },
}; };
...@@ -150,6 +150,12 @@ export default { ...@@ -150,6 +150,12 @@ export default {
name: 'cancelBillManage', name: 'cancelBillManage',
component: './cancelBillManage', component: './cancelBillManage',
}, },
{
path: '/goodsManage',
name: 'goodsManage',
icon: 'smile',
component: './GoodsManage',
},
{ {
component: './404', component: './404',
}, },
......
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
let envAPi = { let envAPi = {
api: '//backstms-gyl.liangkebang.net', api: '//backstms-gyl2.liangkebang.net',
kdspOpApi: 'https://kdsp-operation-gyl.liangkebang.net', kdspOpApi: 'https://kdsp-operation-gyl2.liangkebang.net',
kdspApi: 'https://sc-op-api-gyl.liangkebang.net', kdspApi: 'https://sc-op-api-gyl2.liangkebang.net',
prologueDomain: 'https://prologue-gyl2.liangkebang.net',
qiniuHost: 'https://appsync.lkbang.net', qiniuHost: 'https://appsync.lkbang.net',
opapiHost: 'https://opapi-gyl.liangkebang.net', opapiHost: 'https://opapi-gyl2.liangkebang.net',
// opapiHost: 'http://192.168.29.45:7000', // opapiHost: 'http://192.168.29.45:7000',
}; };
let prodApi = { let prodApi = {
api: '//backstms.q-gp.com', api: '//backstms.q-gp.com',
kdspOpApi: '//kdsp-operation.q-gp.com', kdspOpApi: '//kdsp-operation.q-gp.com',
prologueDomain: '//prologue.q-gp.com',
kdspApi: '//sc-op-api.q-gp.com', kdspApi: '//sc-op-api.q-gp.com',
qiniuHost: 'https://appsync.lkbang.net', qiniuHost: 'https://appsync.lkbang.net',
opapiHost: 'https://opapi.xyqb.com', opapiHost: 'https://opapi.xyqb.com',
......
...@@ -4809,6 +4809,14 @@ ...@@ -4809,6 +4809,14 @@
} }
} }
}, },
"antd-virtual-select": {
"version": "1.1.2",
"resolved": "http://npmprivate.quantgroups.com/antd-virtual-select/-/antd-virtual-select-1.1.2.tgz",
"integrity": "sha512-GejZ/ihog9ZwSn16GsfABPBjeM/X9XMktndu570q9kCe1D/LieO07xNME/dLx4x8IaqGxh6yW4eTUzR83+gkKw==",
"requires": {
"moment": "^2.22.2"
}
},
"any-observable": { "any-observable": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "http://npmprivate.quantgroups.com/any-observable/-/any-observable-0.3.0.tgz", "resolved": "http://npmprivate.quantgroups.com/any-observable/-/any-observable-0.3.0.tgz",
...@@ -20109,6 +20117,15 @@ ...@@ -20109,6 +20117,15 @@
"resize-observer-polyfill": "^1.5.0" "resize-observer-polyfill": "^1.5.0"
} }
}, },
"react-sortablejs": {
"version": "6.0.0",
"resolved": "http://npmprivate.quantgroups.com/react-sortablejs/-/react-sortablejs-6.0.0.tgz",
"integrity": "sha512-vzi+TWOnofcYg+dYnC/Iz/ZZkBGG76uM6KaLwuAqBk0349JQxIy3PZizbK0TJdLlK6NnLt4CiEyyQXSSnVYvEw==",
"requires": {
"classnames": "^2.2.6",
"tiny-invariant": "^1.1.0"
}
},
"react-test-renderer": { "react-test-renderer": {
"version": "16.14.0", "version": "16.14.0",
"resolved": "http://npmprivate.quantgroups.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz", "resolved": "http://npmprivate.quantgroups.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz",
...@@ -21811,6 +21828,11 @@ ...@@ -21811,6 +21828,11 @@
} }
} }
}, },
"sortablejs": {
"version": "1.13.0",
"resolved": "http://npmprivate.quantgroups.com/sortablejs/-/sortablejs-1.13.0.tgz",
"integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
},
"source-list-map": { "source-list-map": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "http://npmprivate.quantgroups.com/source-list-map/-/source-list-map-2.0.1.tgz", "resolved": "http://npmprivate.quantgroups.com/source-list-map/-/source-list-map-2.0.1.tgz",
import { Button, Form, Input, Select, notification, Modal, InputNumber } from 'antd';
import React, { Component } from 'react';
import { connect } from 'dva';
import { getJdPicList, addSku, updateSku } from '../service';
import Upload from '../../components/upload';
import style from '../style.less';
const FormItem = Form.Item;
const { Option } = Select;
@connect(({ goodsManage }) => ({
goodsManage,
}))
class goodsManage extends Component {
componentDidMount() {
this.handleDataList();
this.categoryList(null, 'cid1List');
}
shouldComponentUpdate(nextProps) {
// 应该使用这个方法,否则无论props是否有变化都将会导致组件跟着重新渲染
const initForm = nextProps.initForm || {};
if (initForm?.cid1 !== this?.props?.initForm.cid1) {
this.categoryList(initForm?.cid1, 'cid2List');
this.categoryList(initForm?.cid2, 'cid3List');
}
return true;
}
categoryList = (value, categoryNum) => {
const { dispatch } = this.props;
dispatch({
type: 'goodsManage/categoryList',
payload: {
categoryNum,
value,
},
});
};
handleDataList = () => {
const { dispatch } = this.props;
dispatch({
type: 'goodsManage/getDataList',
});
};
handleSearch = () => {
this.props.handleSearch();
};
onReset = () => {
const {
form: { resetFields },
} = this.props;
resetFields();
};
submit = () => {
// 保存
const {
form: { validateFields },
initForm = {},
} = this.props;
validateFields(async (errors, values) => {
if (!errors) {
const api = initForm?.id ?? '' ? updateSku : addSku;
const error = await api({ ...values, id: initForm?.id });
if (!error) {
// 掉接口成功后关闭modal 清空数据
notification.success({ message: '操作成功' });
this.handleSearch();
this.onCancel();
}
}
});
};
// eslint-disable-next-line consistent-return
getPic = async () => {
// 获取图片需要设置轮播图和详情图
const {
form: { setFieldsValue, getFieldsValue },
} = this.props;
const { imageList = [], jdSkuInfoUrl, imageDetailList = [] } = getFieldsValue([
'imageList',
'jdSkuInfoUrl',
'imageDetailList',
]);
if (!jdSkuInfoUrl) {
return notification.warning({ message: '请输入京东链接再获取图片' });
}
const data = await getJdPicList(jdSkuInfoUrl);
if (data) {
const { carouseList = [], detailList = [] } = data[0];
setFieldsValue({
imageList: [...imageList, ...carouseList],
imageDetailList: [...imageDetailList, ...detailList],
});
}
};
normFile = fileList => fileList;
afterClose = () => {
this.onReset();
};
onCancel = () => {
this.props.onCancel();
};
render() {
const {
form: { getFieldDecorator },
goodsManage: { shopList = [], cid1List = [], cid2List = [], cid3List = [] },
initForm = {},
statusList = [],
} = this.props;
const carouseImageList = initForm?.carouseImageList?.map(item => item.imageUrl) || [];
const detailImageList = initForm?.detailImageList?.map(item => item.imageUrl) || [];
const filterOption = (input, op) => op.props.children.includes(input);
const formItemLayout = {
labelCol: { span: 5 },
wrapperCol: { span: 13 },
};
const options =
statusList.length &&
statusList.map(d => (
<Select.Option value={d.status} key={d.status}>
{d.statusDesc}
</Select.Option>
));
const inputNumberSty = {
width: '100%',
};
return (
<Modal
visible={this.props.visible}
width={800}
title={`商品${initForm.id ? '修改' : '添加'}`}
onOk={this.submit}
onCancel={this.onCancel}
afterClose={this.afterClose}
>
<Form {...formItemLayout}>
{initForm.id ? (
<FormItem label="商品编码">
{getFieldDecorator('skuNo', {
initialValue: initForm.skuNo,
})(<Input readOnly />)}
</FormItem>
) : (
''
)}
<FormItem label="商品名称">
{getFieldDecorator('skuName', {
initialValue: initForm.skuName,
rules: [
{
required: true,
message: '请输入商品名称',
},
],
})(<Input allowClear />)}
</FormItem>
<FormItem label="第三方sku编码">
{getFieldDecorator('thirdSkuNo', {
initialValue: initForm.thirdSkuNo,
rules: [
{
required: true,
message: '请输入第三方sku编码',
},
],
})(<Input allowClear readOnly={!!initForm.id} />)}
</FormItem>
<FormItem label="第三方spu编码">
{getFieldDecorator('thirdSpuNo', {
initialValue: initForm.thirdSpuNo,
rules: [
{
required: true,
message: '请输入第三方spu编码',
},
],
})(<Input allowClear readOnly={!!initForm.id} />)}
</FormItem>
<FormItem label="品牌名称">
{getFieldDecorator('brandName', {
initialValue: initForm.brandName,
rules: [
{
required: true,
message: '请输入品牌名称',
},
],
})(<Input allowClear />)}
</FormItem>
<FormItem label="成本价">
{getFieldDecorator('costPrice', {
initialValue: initForm.costPrice,
rules: [
{
required: true,
message: '请输入成本价',
},
],
})(<InputNumber allowClear style={inputNumberSty} precision={2} />)}
</FormItem>
{/* <FormItem label="市场价">
{getFieldDecorator('marketPrice', {
initialValue: initForm.marketPrice,
rules: [
{
required: true,
message: '请输入市场价',
},
],
})(<InputNumber allowClear style={inputNumberSty} precision={2} />)}
</FormItem>
<FormItem label="销售价">
{getFieldDecorator('salePrice', {
initialValue: initForm.salePrice,
rules: [
{
required: true,
message: '请输入销售价',
},
],
})(<InputNumber allowClear style={inputNumberSty} precision={2} />)}
</FormItem> */}
<FormItem label="库存">
{getFieldDecorator('stock', {
initialValue: initForm.stock,
rules: [
{
required: true,
message: '请输入库存',
},
],
})(<InputNumber allowClear style={inputNumberSty} />)}
</FormItem>
{initForm.id ? (
<FormItem label="商品状态">
{getFieldDecorator('status', {
initialValue: initForm.status,
rules: [{ required: true, message: '请输入商品状态' }],
})(<Select allowClear>{options}</Select>)}
</FormItem>
) : (
''
)}
<FormItem label="一级分类">
{getFieldDecorator('cId1', {
initialValue: initForm.cid1,
rules: [
{
required: true,
message: '请选择一级分类',
},
],
})(
<Select
allowClear
onChange={value => this.categoryList(value, 'cid2List')}
showSearch
filterOption={filterOption}
>
{cid1List?.map(item => (
<Option key={item.categoryId} value={item.categoryId}>
{item.categoryName}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="二级分类">
{getFieldDecorator('cId2', {
initialValue: initForm.cid2,
rules: [
{
required: true,
message: '请选择二级分类',
},
],
})(
<Select
allowClear
onChange={value => this.categoryList(value, 'cid3List')}
showSearch
filterOption={filterOption}
>
{cid2List?.map(item => (
<Option key={item.categoryId} value={item.categoryId}>
{item.categoryName}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="三级分类">
{getFieldDecorator('cId3', {
initialValue: initForm.cid3,
rules: [
{
required: true,
message: '请选择三级分类',
},
],
})(
<Select allowClear showSearch filterOption={filterOption}>
{cid3List?.map(item => (
<Option key={item.categoryId} value={item.categoryId}>
{item.categoryName}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="供应商名称">
{getFieldDecorator('shopId', {
initialValue: initForm?.shopId && +initForm?.shopId,
rules: [
{
required: true,
message: '请选择供应商名称',
},
],
})(
<Select allowClear showSearch filterOption={filterOption}>
{shopList?.map(item => (
<Option key={item.shopId} value={item.shopId}>
{item.shopName}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="规格1">
{getFieldDecorator('attValue', {
initialValue: initForm.attValue,
})(<Input allowClear placeholder="格式名称:值" />)}
</FormItem>
<FormItem label="规格2">
{getFieldDecorator('attValue2', {
initialValue: initForm.attValue2,
})(<Input allowClear placeholder="格式名称:值" />)}
</FormItem>
<FormItem label="京东链接">
<div style={{ display: 'flex' }}>
<FormItem>
{getFieldDecorator('jdSkuInfoUrl', {
initialValue: initForm.jdSkuInfoUrl,
})(<Input allowClear className={style.linkInput} />)}
</FormItem>
<Button type="primary" onClick={this.getPic} className={style.picBtn}>
获取图片
</Button>
</div>
</FormItem>
<FormItem label="轮播图">
{getFieldDecorator('imageList', {
initialValue: carouseImageList,
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
rules: [
{
required: true,
message: '请上传轮播图',
},
],
})(<Upload />)}
(提示:轮播图的顺序是从左至右)
</FormItem>
<FormItem label="详情图">
{getFieldDecorator('imageDetailList', {
initialValue: detailImageList,
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
})(<Upload />)}
(提示:详情图的顺序是从左至右)
</FormItem>
</Form>
</Modal>
);
}
}
export default Form.create({})(goodsManage);
/* eslint-disable no-param-reassign */
import React from 'react';
import { Cascader, Select, InputNumber, Button } from 'antd';
import { stateList } from './staticdata';
const filterOption = (input, op) => op.props.children.includes(input);
export const toolBarList = () => [
<Button key="getOffGoodsShelf" type="primary">
新增商品
</Button>,
<Button key="putGoodsShelf">模版</Button>,
<Button key="tags" type="primary">
批量修改库存
</Button>,
];
export const Goodscolumns = ({ categoryData, supplyPrice, setSupplyPrice, supplyPriceRef }) => [
{
title: 'SKU编码',
dataIndex: 'skuId',
key: 'skuId',
},
{
title: 'SKU商品名称',
dataIndex: 'skuName',
key: 'skuName',
},
{
title: '供应商名称',
dataIndex: 'shopId',
key: 'shopId',
hideInTable: true,
valueType: 'cascader',
renderFormItem: () => (
<Cascader
changeOnSelect
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={categoryData}
/>
),
},
{
title: '审核状态',
dataIndex: 'state',
key: 'state',
hideInTable: true,
valueType: 'select',
renderFormItem: () => (
<Select allowClear showSearch filterOption={filterOption}>
{stateList?.map(item => (
<Select.Option key={item.value} value={item.value}>
{item.label}
</Select.Option>
))}
</Select>
),
},
{
title: '供货价区间',
hideInTable: true,
dataIndex: 'supplyPrice',
modalHide: true,
renderFormItem: () => [
<div key="supplyPrice" className="search-profit-margin">
<InputNumber
key="min"
placeholder="请输入"
onChange={value => {
supplyPrice[0] = value;
setSupplyPrice(supplyPrice);
}}
/>
<span className="line">-</span>
<InputNumber
ref={supplyPriceRef}
key="max"
placeholder="请输入"
onChange={value => {
supplyPrice[1] = value;
setSupplyPrice(supplyPrice);
}}
/>
</div>,
],
},
{
title: '第三方SKU编码',
dataIndex: 'thirdSkuNo',
key: 'thirdSkuNo',
hideInTable: true,
},
{
title: '价格',
dataIndex: 'supplyPrice',
key: 'supplyPrice',
hideInSearch: true,
render: (value, row) => (
<>
<p>
供货价:<a>{value}</a>
</p>
<p>
市场价:<a>{row.marketPrice}</a>
</p>
</>
),
},
{
title: '库存',
dataIndex: 'stock',
key: 'stock',
hideInSearch: true,
render: (value, row) => (
<>
<p>
当前库存:<a>{row.productStock}</a>
</p>
<p>可售库存:{value}</p>
{row.type === 1 && row.productStockWarning > -1 && <p>预警值:{row.productStockWarning}</p>}
</>
),
},
{
title: '商品状态',
dataIndex: 'stateDesc',
key: 'stateDesc',
hideInSearch: true,
},
{
title: '操作',
key: 'option',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
modalHide: true,
render: () => (
<>
<Button key="update" type="link" onClick={() => {}}>
修改
</Button>
<Button key="preview" type="link" onClick={() => {}}>
预览
</Button>
<Button key="seelog" type="link" onClick={() => {}}>
查看日志
</Button>
</>
),
},
];
import { Modal, Table, Button, Pagination } from 'antd';
import React, { useState, useEffect } from 'react';
import styles from '../style.less';
import { changeLog } from '../service';
const LogModal = props => {
const [tableData, setTableData] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [pageSize] = useState(20);
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 handleSearch = async (page = 1) => {
setPageNo(page);
const [data] = await changeLog({ id: props.id, pageNo: page, pageSize });
setTableData(data);
};
const onPageChange = page => {
handleSearch(page);
};
useEffect(() => {
if (!props.id) return;
handleSearch();
}, [props.id]);
const { visible } = props;
return (
<Modal title="日志详情" visible={visible} footer={null} onCancel={props.onCancel} width="800px">
<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}
/>
)}
<Button type="primary" onClick={props.onCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
};
export default LogModal;
import React, { useEffect } from 'react';
import { Cascader, Form, Input, Select, InputNumber, Button } from 'antd';
import { stateList } from '../staticdata';
import styled from '../style.less';
const FormItem = Form.Item;
const { Option } = Select;
const ItemWidth = { width: 300 };
const formItemLayout = {
labelCol: {
xs: { span: 8 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 16 },
sm: { span: 16 },
},
};
const WrappedNormalLoginForm = props => {
const { categoryData, form } = props;
const { getFieldDecorator, getFieldValue, getFieldsValue, resetFields } = form;
const filterOption = (input, op) => op.props.children.includes(input);
const onSearch = () => {
const formValue = getFieldsValue();
props.onSearch(formValue);
};
const onReset = () => {
resetFields();
props.onReset();
};
useEffect(() => {
onSearch();
}, []);
return (
<Form layout="inline" {...formItemLayout}>
<FormItem label="SKU编码" className={styled.formItem}>
{getFieldDecorator('skuId', {})(<Input allowClear />)}
</FormItem>
<FormItem label="商品名称" className={styled.formItem}>
{getFieldDecorator('skuName', {})(<Input allowClear />)}
</FormItem>
<FormItem label="类目" className={styled.formItem}>
{getFieldDecorator('productCategoryId', {})(
<Cascader
changeOnSelect
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={categoryData}
/>,
)}
</FormItem>
<FormItem label="审核状态" className={styled.formItem}>
{getFieldDecorator('state', {})(
<Select allowClear showSearch filterOption={filterOption}>
{stateList?.map(item => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="供货价区间" className={styled.formItem}>
<FormItem className={styled.itemSection}>
{getFieldDecorator('supplyPriceMin', {})(<InputNumber style={{ width: '100%' }} />)}
</FormItem>
<span className={styled.itemLine}>-</span>
<FormItem className={styled.itemSection}>
{getFieldDecorator('supplyPriceMax', {})(
<InputNumber style={{ width: '100%' }} min={getFieldValue('supplyPriceMin')} />,
)}
</FormItem>
</FormItem>
<FormItem label="第三方SKU编码" className={styled.formItem}>
{getFieldDecorator('thirdSkuNo', {})(<Input allowClear />)}
</FormItem>
<FormItem className={styled.formItem}>
<Button onClick={() => onSearch()} type="primary" className={styled.button}>
查询
</Button>
<Button onClick={() => onReset()} type="primary" className={styled.button}>
重置
</Button>
</FormItem>
</Form>
);
};
export const SearchForm = Form.create({ name: 'SearchForm' })(WrappedNormalLoginForm);
// import {
// Button,
// Form,
// Input,
// Select,
// Popconfirm,
// notification,
// Upload,
// Cascader,
// InputNumber,
// } from 'antd';
// import React, { Component } from 'react';
// import { connect } from 'dva';
// import styles from '../style.less';
// import { stateList } from '../staticdata';
// import { batchPushedOrOffline, offline, uploadFile, importJdSkus } from '../service';
// const FormItem = Form.Item;
// const { Option } = Select;
// @connect(({ goodsManage }) => ({
// goodsManage,
// }))
// class goodsManage extends Component {
// componentDidMount() {
// this.props.onRef(this);
// this.handleSearch();
// }
// getFieldsValue() {
// const { form } = this.props;
// return form.getFieldsValue();
// }
// handleSearch = () => {
// this.props.handleSearch(1);
// };
// onReset = () => {
// this.props.form.resetFields();
// this.props.onReset();
// };
// onConfirm = async isOffline => {
// const [, error] = isOffline
// ? await offline(this.props.selectedRowKeys)
// : await batchPushedOrOffline({
// ids: this.props.selectedRowKeys.join(),
// type: 2,
// productState: 6,
// offlineReason: '',
// });
// this.props.onLoad(error);
// };
// addSpu = () => {
// this.props.addSpu();
// };
// addVirtualSpu = () => {
// this.props.addVirtualSpu();
// };
// importSkus = async (info, type) => {
// const [result] = await importJdSkus(info.file, type);
// if (result.businessCode === '0000') {
// this.handleSearch();
// notification.success({
// message: '商品上传成功',
// });
// } else {
// notification.warning({
// message: result.msg,
// });
// }
// };
// render() {
// const {
// form: { getFieldDecorator, getFieldValue },
// treeData,
// } = this.props;
// const selectW = {
// width: 250,
// };
// const iptNumWidth = {
// width: 118,
// };
// const that = this;
// const uploadBatch = {
// name: 'file',
// async customRequest(info) {
// that.importSkus(info, 0);
// },
// accept: '.xlsx',
// showUploadList: false,
// };
// 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);
// return (
// <Form name="horizontal_login" layout="inline" className={styles.searchForm}>
// <FormItem label="SKU编码">
// {getFieldDecorator('skuId', {})(<Input allowClear style={selectW} />)}
// </FormItem>
// <FormItem label="商品名称">
// {getFieldDecorator('skuName', {})(<Input allowClear style={selectW} />)}
// </FormItem>
// <FormItem label="供应商名称">
// {getFieldDecorator('supplierId', {})(
// <Select style={selectW} allowClear showSearch filterOption={filterOption}>
// {this.props.shopList?.map(item => (
// <Option key={item.id} value={item.id}>
// {item.name}
// </Option>
// ))}
// </Select>,
// )}
// </FormItem>
// <FormItem label="第三方SKU编码">
// {getFieldDecorator('thirdSkuNo', {})(<Input allowClear style={selectW} />)}
// </FormItem>
// <FormItem label="商品状态">
// {getFieldDecorator('state', {})(
// <Select style={selectW} allowClear showSearch filterOption={filterOption}>
// {stateList?.map(item => (
// <Option key={item.value} value={item.value}>
// {item.label}
// </Option>
// ))}
// </Select>,
// )}
// </FormItem>
// <FormItem label="类目">
// {getFieldDecorator('productCategoryId', {})(
// <Cascader
// style={selectW}
// changeOnSelect
// fieldNames={{ label: 'name', value: 'id', children: 'children' }}
// options={treeData}
// />,
// )}
// </FormItem>
// <FormItem label="供货价区间">
// <FormItem className={styles.iptNumRight}>
// {getFieldDecorator('supplyPriceMin', {})(<InputNumber style={iptNumWidth} />)}
// </FormItem>
// <span>--</span>
// <FormItem className={styles.iptNumRight}>
// {getFieldDecorator('supplyPriceMax', {})(
// <InputNumber style={iptNumWidth} min={getFieldValue('supplyPriceMin')} />,
// )}
// </FormItem>
// </FormItem>
// <FormItem label="重量区间">
// <FormItem className={styles.iptNumRight}>
// {getFieldDecorator('weightMin', {})(<InputNumber style={iptNumWidth} precision={3} />)}
// </FormItem>
// <span>--</span>
// <FormItem className={styles.iptNumRight}>
// {getFieldDecorator('weightMax', {})(
// <InputNumber style={iptNumWidth} min={getFieldValue('weightMin')} precision={3} />,
// )}
// </FormItem>
// </FormItem>
// <FormItem className={styles.queryBtn}>
// <Button onClick={() => this.handleSearch()} type="primary" className={styles.button}>
// 查询
// </Button>
// <Button onClick={() => this.onReset()} type="primary" className={styles.button}>
// 重置
// </Button>
// </FormItem>
// <FormItem style={{ width: '100%', textAlign: 'right' }}>
// <Button type="primary" className={styles.button} onClick={this.addSpu}>
// 新增商品
// </Button>
// {/* <Button type="primary" className={styles.button} onClick={this.addSpu}>
// 新增虚拟商品
// </Button> */}
// <Popconfirm
// placement="topLeft"
// title="确定要上架吗?"
// onConfirm={() => this.onConfirm()}
// okText="确定"
// cancelText="取消"
// >
// <Button
// type="primary"
// className={styles.button}
// disabled={!this.props.selectedRowKeys.length}
// >
// 批量上架
// </Button>
// </Popconfirm>
// <Button.Group>
// <Button
// type="primary"
// icon="download"
// ghost
// onClick={() => {
// window.location.href = 'https://kdspstatic.q-gp.com/batchSkuImportTemplatev4.xlsx';
// }}
// >
// 模版
// </Button>
// <Upload {...uploadBatch}>
// <Button type="primary" icon="upload">
// 批量新增
// </Button>
// </Upload>
// </Button.Group>
// <Button.Group style={{ marginLeft: '10px' }}>
// <Button
// type="primary"
// icon="download"
// ghost
// onClick={() => {
// window.location.href =
// 'https://kdspstatic.q-gp.com/batchUpdateSkuImportTempletv3.xlsx';
// }}
// >
// 模版
// </Button>
// <Upload {...uploadProps}>
// <Button type="primary">批量修改</Button>
// </Upload>
// </Button.Group>
// </FormItem>
// </Form>
// );
// }
// }
// export default Form.create()(goodsManage);
import { Modal, Table, Button } from 'antd';
import React, { Component } from 'react';
import styles from '../style.less';
class DetailModal extends Component {
getColumns = (firstSpe, secondSpe, type) => {
const arr = [
{
title: '供应商',
dataIndex: 'supplierName',
key: 'supplierName',
align: 'center',
width: 50,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.listLength) {
obj.props.rowSpan = row.listLength;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
},
];
if (firstSpe) {
arr.push({
title: firstSpe,
dataIndex: 'firstSpecValue',
key: 'firstSpecValue',
align: 'center',
width: 50,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.length) {
obj.props.rowSpan = row.length;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
});
}
if (secondSpe) {
arr.push({
title: secondSpe,
align: 'center',
key: 'secondSpecValue',
dataIndex: 'secondSpecValue',
width: 50,
});
}
const newTable = arr.concat([
{
title: '供应商编码',
align: 'center',
key: 'supplierCode',
dataIndex: 'supplierCode',
width: 50,
},
{
title: '库存',
align: 'center',
key: 'stock',
dataIndex: 'stock',
width: 50,
},
{
title: '供货价',
align: 'center',
key: 'supplyPrice',
dataIndex: 'supplyPrice',
width: 50,
},
{
title: '市场价',
align: 'center',
key: 'marketPrice',
dataIndex: 'marketPrice',
width: 50,
},
{
title: '国际编码',
align: 'center',
key: 'thirdSkuNo',
dataIndex: 'thirdSkuNo',
width: 50,
},
]);
if (type === 1) {
newTable.splice(newTable.length - 1, 0, {
title: '重量(kg)',
align: 'center',
key: 'weight',
dataIndex: 'weight',
width: 90,
});
}
return newTable;
};
updateStatus = (row, productState) => {
this.props.updateStatus(row, productState);
};
dataInit = list => {
const obj = {};
let finialList = [];
list.map(item => {
obj[item.firstSpecValue] = [];
return obj;
});
list.map(item => obj[item.firstSpecValue].push(item));
const keys = Object.keys(obj);
// eslint-disable-next-line no-return-assign
keys.map(key => {
obj[key].forEach((i, index) => {
if (index === 0) {
i.length = obj[key].length;
}
});
finialList = finialList.concat(obj[key]);
return finialList;
});
if (finialList.length) {
finialList[0].listLength = finialList.length;
}
return finialList;
};
render() {
const { visible, data = [], type } = this.props;
const initdata = this.dataInit(data);
const firstName = data.length ? data[0].firstSpec : '';
const secondName = data.length ? data[0].secondSpec : '';
return (
<Modal
title="供货详情"
visible={visible}
footer={null}
onCancel={this.props.onCancel}
width="900px"
>
<Table
dataSource={initdata}
bordered
columns={this.getColumns(firstName, secondName, type)}
rowKey="id"
pagination={false}
scroll={{ x: '100%' }}
/>
<Button type="primary" onClick={this.props.onCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
}
}
export default DetailModal;
import { Modal, InputNumber, notification } from 'antd';
import React, { useState, useEffect } from 'react';
import { updatePrice } from '../service';
import styles from './style.less';
export default function UpdatePrice(props) {
const [visible, setVisible] = useState(props.visible);
const [supplyPrice, setSupplyPrice] = useState(props.info.supplyPrice);
const [marketPrice, setMarketPrice] = useState(props.info.marketPrice);
// const [salePrice, setSalePrice] = useState(props.info.salePrice);
useEffect(() => {
setVisible(props.visible);
setSupplyPrice(props.info.supplyPrice);
setMarketPrice(props.info.marketPrice);
// setSalePrice(props.info.salePrice);
}, [props]);
const submit = async () => {
if (!supplyPrice || !marketPrice) {
notification.error({ message: '价格不可为空!' });
return;
}
const error = await updatePrice({
// salePrice,
supplyPrice,
marketPrice,
id: props.info.id,
supplierId: props.info.supplierId,
});
if (!error) {
notification.success({ message: '修改成功' });
props.onCancel('success');
}
};
return (
<Modal
title="修改价格"
visible={visible}
onCancel={() => props.onCancel()}
onOk={submit}
width={400}
>
<div className={styles.center}>
供货价:
<InputNumber
min={0}
precision={2}
value={supplyPrice}
onChange={value => setSupplyPrice(value)}
className={styles.inputNW}
/>
<br />
<br />
市场价:
<InputNumber
min={0}
precision={2}
value={marketPrice}
onChange={value => setMarketPrice(value)}
className={styles.inputNW}
/>
<br />
<br />
{/* 销售价:
<InputNumber
min={0}
precision={2}
value={salePrice}
onChange={value => setSalePrice(value)}
className={styles.inputNW}
/> */}
</div>
</Modal>
);
}
.inputNW {
width: 200px;
}
.center {
text-align: center;
}
import { Modal, InputNumber, notification, Form, Input, Radio } from 'antd';
import React from 'react';
import { updateStock } from '../service';
import styles from '../UpdatePrice/style.less';
const UpdateStock = props => {
const { getFieldDecorator, validateFields, resetFields, getFieldValue } = props.form;
const valueInfo = props.info;
getFieldDecorator('stockChangeType', { initialValue: 1 });
const submit = async () => {
validateFields(async (err, { stock, changeReason, stockChangeType }) => {
if (err) return;
const error = await updateStock({
stock,
id: valueInfo.id,
supplierId: valueInfo.supplierId,
stockChangeType,
changeReason,
});
if (!error) {
notification.success({ message: '操作成功!' });
props.onCancel('success');
resetFields();
}
});
};
const onCancel = () => {
props.onCancel();
resetFields();
};
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const validatorCallback = (rule, value, callback) => {
// 减库存存时,校验可售库存-输入值>=0,即可售库存不可为负;
const stockChangeType = getFieldValue('stockChangeType');
const increment = valueInfo.marketableStock - value;
return !stockChangeType && increment < 0 ? callback(new Error(rule.message)) : callback();
};
return (
<Modal title="修改库存" visible={props.visible} onCancel={onCancel} onOk={submit} width={400}>
<Form {...formItemLayout}>
<Form.Item label="变更类型:">
{getFieldDecorator('stockChangeType', {
rules: [{ required: true, message: '请选择类型!' }],
})(
<Radio.Group>
<Radio value={1}>增库存</Radio>
<Radio value={0}>减库存</Radio>
</Radio.Group>,
)}
</Form.Item>
<Form.Item label="库存数:">
{getFieldDecorator('stock', {
rules: [
{ required: true, message: '请输入库存!' },
{ validator: validatorCallback, message: '减库存,输入库存数不可大于可售库存!' },
],
validateTrigger: ['onSubmit'],
})(
<InputNumber
min={0}
precision={0}
placeholder="请输入库存"
className={styles.inputNW}
/>,
)}
</Form.Item>
<Form.Item label="变更原因:">
{getFieldDecorator('changeReason', {
rules: [{ required: true, message: '请输入变更原因!' }],
initialValue: valueInfo.changeReason,
})(<Input.TextArea />)}
</Form.Item>
</Form>
</Modal>
);
};
export default Form.create()(UpdateStock);
import { Modal, Form, Select, Input, InputNumber } from 'antd';
import React, { Component } from 'react';
const FormItem = Form.Item;
class formModal extends Component {
componentDidMount() {
this.props.onRef(this);
}
handleOk = () => {
this.props.form.validateFields((err, fieldsValue) => {
if (err) return;
const value = {
...fieldsValue,
id: this.props.initForm.id ?? '',
};
this.props.submit(value);
});
};
resetFields = () => {
this.props.form.resetFields();
};
render() {
const {
visible,
form: { getFieldDecorator },
statusList = [],
initForm = {},
} = this.props;
const InputNumStyle = { width: '100%' };
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 17 },
},
};
const options =
statusList.length &&
statusList.map(d => (
<Select.Option value={d.status} key={d.status}>
{d.statusDesc}
</Select.Option>
));
return (
<Modal
title="商品信息修改"
visible={visible}
onCancel={this.props.onCancel}
onOk={this.handleOk}
>
<Form name="detail" {...formItemLayout}>
<FormItem label="sku编码">
{getFieldDecorator('skuNo', {
initialValue: initForm.skuNo,
})(<Input readOnly />)}
</FormItem>
<FormItem label="商品名称">
{getFieldDecorator('skuName', {
initialValue: initForm.skuName,
rules: [{ required: true, message: '请输入商品名称' }],
})(<Input allowClear />)}
</FormItem>
<FormItem label="商品规格">
{getFieldDecorator('attValue', {
initialValue: initForm.attValue,
rules: [{ message: '请输入商品规格' }],
})(<Input allowClear />)}
</FormItem>
<FormItem label="成本价">
{getFieldDecorator('costPrice', {
initialValue: initForm.costPrice,
rules: [{ required: true, message: '请输入成本价' }],
})(<InputNumber allowClear precision={2} style={InputNumStyle} />)}
</FormItem>
{/* <FormItem label="市场价">
{getFieldDecorator('marketPrice', {
initialValue: initForm.marketPrice,
rules: [{ required: true, message: '请输入市场价' }],
})(<InputNumber allowClear style={InputNumStyle} precision={2} />)}
</FormItem>
<FormItem label="销售价">
{getFieldDecorator('salePrice', {
initialValue: initForm.salePrice,
rules: [{ required: true, message: '请输入销售价' }],
})(<InputNumber allowClear style={InputNumStyle} precision={2} />)}
</FormItem> */}
<FormItem label="库存">
{getFieldDecorator('stock', {
initialValue: initForm.stock,
rules: [{ required: true, message: '请输入库存' }],
})(<InputNumber allowClear style={InputNumStyle} />)}
</FormItem>
<FormItem label="商品状态">
{getFieldDecorator('status', {
initialValue: initForm.status,
rules: [{ required: true, message: '请输入商品状态' }],
})(<Select allowClear>{options}</Select>)}
</FormItem>
</Form>
</Modal>
);
}
}
export default Form.create()(formModal);
import React from 'react';
import SuperSelect from 'antd-virtual-select';
import { Form, Modal, Card, Cascader, Select, Radio } from 'antd';
import { productTypeList } from '../staticdata';
const { Option } = Select;
const filterOption = (input, op) => op.props.children.includes(input);
const OperationForm = props => {
const {
operationVisible,
setOperationVisible,
categoryData,
// virtualTreeData,
shopList,
barndList,
// virtualBarndList,
form,
} = props;
const { getFieldDecorator } = form;
return (
<Modal
width="1050px"
visible={operationVisible}
onCancel={() => {
setOperationVisible(false);
}}
>
<Form layout="inline">
<Card bordered={false}>
<Form.Item label="商品类型:">
{getFieldDecorator('productType', {
initialValue: 1,
rules: [{ required: true, message: '请选择商品类型' }],
})(
<Radio.Group>
{productTypeList.map(item => (
<Radio key={item.value} value={item.value} disabled={item.disabled}>
{item.label}
</Radio>
))}
</Radio.Group>,
)}
</Form.Item>
<Form.Item label="类目:">
{getFieldDecorator('categoryId', {
rules: [{ required: true, message: '请选择类目' }],
})(
<Cascader
style={{ width: 690 }}
changeOnSelect
showSearch
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={categoryData}
/>,
)}
</Form.Item>
<Form.Item label="供货商">
{getFieldDecorator('supplierId', {
rules: [{ required: true, message: '请选择供货商' }],
})(
<Select allowClear showSearch style={{ width: 190 }} filterOption={filterOption}>
{shopList.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</Select>,
)}
</Form.Item>
<Form.Item label="商品品牌">
{getFieldDecorator('brandId', {
rules: [{ required: true, message: '请选择商品品牌' }],
})(
<SuperSelect allowClear showSearch style={{ width: 190 }} filterOption={filterOption}>
{barndList.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</SuperSelect>,
)}
</Form.Item>
</Card>
</Form>
</Modal>
);
};
export default Form.create()(OperationForm);
/* eslint-disable no-param-reassign */
import { Form, Select, Input, InputNumber, Button } from 'antd';
import React, { Component } from 'react';
// import styles from '../style.less';
const { Option } = Select;
const FormItem = Form.Item;
class goodsManage extends Component {
componentDidMount() {
this.props.onRef(this);
}
setFiled = flag => {
const { form } = this.props;
if (flag === 'firstKeys') {
form.setFieldsValue({ firstSpecValue: '' });
return;
}
form.setFieldsValue({ secondSpecValue: '' });
};
batchSetting = () => {
const { form, editData, isEdit } = this.props;
const data = form.getFieldsValue();
if (!data.firstSpecValue && !data.secondSpecValue) {
editData.forEach(item => {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock; // 编辑状态不可修改库存
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
});
}
if (data.firstSpecValue && !data.secondSpecValue) {
editData.forEach(item => {
if (item.firstSpecValue === data.firstSpecValue) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
if (!data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (item.secondSpecValue === data.secondSpecValue) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
if (data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (
item.firstSpecValue === data.firstSpecValue &&
item.secondSpecValue === data.secondSpecValue
) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
this.props.batchSetting(editData);
};
render() {
const {
firstSpes = [],
secondSpecs = [],
firstSpesName = '',
secondSpesName = '',
productType,
isEdit,
} = this.props;
const { getFieldDecorator } = this.props.form;
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem>
{getFieldDecorator('firstSpecValue', {})(
<Select allowClear style={{ width: 120 }} placeholder={firstSpesName}>
{firstSpes.length > 0 &&
firstSpes.map(
item =>
item &&
typeof item === 'string' && (
<Option key={item} value={item}>
{item}
</Option>
),
)}
</Select>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('secondSpecValue', {})(
<Select allowClear style={{ width: 120 }} placeholder={secondSpesName}>
{secondSpecs.length &&
secondSpecs.map(
item =>
item &&
typeof item === 'string' && (
<Option key={item} value={item}>
{item}
</Option>
),
)}
</Select>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('supplyPrice', {})(
<Input placeholder="供货价" style={{ width: 100 }} />,
)}
</FormItem>
<FormItem>
{getFieldDecorator('marketPrice', {})(
<Input placeholder="市场价" style={{ width: 100 }} />,
)}
</FormItem>
{/* <FormItem>
{getFieldDecorator('salePrice', {})(
<Input placeholder="销售价" style={{ width: 100 }} />,
)}
</FormItem> */}
{productType === 1 && (
<FormItem>
{getFieldDecorator('weight', {})(
<InputNumber
precision={3}
max={999999.999}
// eslint-disable-next-line radix
placeholder="重量"
style={{ width: 150 }}
/>,
)}
</FormItem>
)}
{!isEdit && (
<FormItem>
{getFieldDecorator('productStock', {})(
<InputNumber
precision={0}
step={1}
// eslint-disable-next-line radix
formatter={val => parseInt(val, '10') || ''}
placeholder="库存"
style={{ width: 100 }}
/>,
)}
</FormItem>
)}
<FormItem>
<Button type="primary" htmlType="submit" onClick={this.batchSetting}>
批量设置
</Button>
</FormItem>
</Form>
);
}
}
export default Form.create()(goodsManage);
import { Row, Col, Button } from 'antd';
import React, { Component } from 'react';
import styles from '../style.less';
// eslint-disable-next-line react/prefer-stateless-function
class ButtonGroup extends Component {
render() {
const { initData, confirmLoading } = this.props;
return (
<Row type="flex" justify="center" align="middle" gutter={20}>
<Col>
<Button type="primary" onClick={() => this.props.onCancel()} className={styles.logBtn}>
取消
</Button>
</Col>
<Col key="submit">
<Button
type="primary"
onClick={() => this.props.confirm()}
className={styles.logBtn}
loading={confirmLoading}
disabled={confirmLoading}
>
提交
</Button>
</Col>
{initData && !Object.keys(initData).length && (
<Col key="submit-add">
<Button
type="primary"
onClick={() => this.props.confirm(true)}
className={styles.logBtn}
loading={confirmLoading}
disabled={confirmLoading}
>
提交并继续添加
</Button>
</Col>
)}
</Row>
);
}
}
export default ButtonGroup;
import React from 'react';
import { Input, Button, notification, Popover, InputNumber } from 'antd';
import { sortBy } from 'lodash';
const KEYLIST = [
'id',
'name',
'skuLink',
'imageList',
'firstSpecId',
'secondSpecId',
'supplyPrice',
'productStockWarning',
'firstSpec',
'firstSpecValue',
'secondSpec',
'secondSpecValue',
];
export function normFile(fileList) {
return fileList;
}
export function specValidate(editArr, initArr, isEdit, msg) {
let isSame = false;
if (editArr && editArr.length) {
const list = isEdit ? editArr.concat(initArr) : editArr;
list.forEach((i, index) => {
const index1 = list.indexOf(i);
if (index1 !== index) {
isSame = true;
}
});
}
if (isSame && msg) {
notification.error({
message: msg,
});
}
return isSame;
}
export function createNewList(first, second, firstSpecId, secondSpecId) {
const list = [];
const sku = {
weight: '',
productStockWarning: '',
marketPrice: '',
// salePrice: '',
supplyPrice: '',
// stock: '',
productStock: '',
thirdSkuNo: '',
skuLink: '',
imageList: [],
};
if (first && first.length) {
// 一级规格有值时,生成的编辑表格
first.forEach(fir => {
const copy = Object.assign({}, sku);
copy.firstSpecId = firstSpecId;
copy.firstSpecValue = fir;
if (second.length) {
second.forEach(sec => {
const copySec = Object.assign({}, copy);
copySec.secondSpecId = secondSpecId;
copySec.secondSpecValue = sec;
list.push(copySec);
});
return;
}
list.push(copy);
});
} else if (second.length) {
// 缺少一级规格值,只有二级规格值时,生成的编辑表格
second.forEach(sec => {
const copy = Object.assign({}, sku);
copy.secondSpecId = secondSpecId;
copy.secondSpecValue = sec;
list.push(copy);
});
} else {
// 缺少一级和二级规格时生成的编辑表格
list.push(sku);
}
return list;
}
export function createEditData(values, initData) {
let list = null;
const first = values.first?.filter(item => item && !item.match(/^[ ]*$/)) || [];
const second =
(values.second && values.second.filter(item => item && !item.match(/^[ ]*$/))) || [];
const newFirst = first.concat(initData.firstSpecList || []);
const isFirstSame = specValidate(
first,
initData.firstSpecList,
initData.id,
'一级规格值不可重复',
);
const isSecondSame = specValidate(
second,
initData.secondSpecList,
initData.id,
'二级规格值不可重复',
);
if (!isFirstSame && !isSecondSame) {
if (!initData.id) {
list = createNewList(first, second, values.firstSpecId, values.secondSpecId);
// this.setState({ editData: list });
} else {
const list1 = first.length
? createNewList(first, initData.secondSpecList, values.firstSpecId, values.secondSpecId)
: [];
const list2 = second.length
? createNewList(newFirst, second, values.firstSpecId, values.secondSpecId)
: [];
list = sortBy(initData.editData.concat(list1.concat(list2)), item => item.firstSpecValue);
}
}
return list;
}
export function validateSpuInfo(values, initData, editData, productType) {
const checkKeyList = productType === 1 ? KEYLIST : [...KEYLIST, 'weight'];
let flag = false;
editData.forEach(item => {
const keys = Object.keys(item);
keys.forEach(key => {
if (checkKeyList.includes(key)) {
return;
}
// console.log(key, item, item[key], (item[key] === undefined) || (item[key] === null));
// if (!`${item[key]}`) {
if (item[key] === undefined || item[key] === null || item[key] === '') {
flag = true;
}
});
});
if (flag) {
notification.error({
message: '请完善商品表格!',
});
}
return flag;
}
export function dataInit(list) {
if (!list || !list.length) return;
const obj = {};
let finialList = [];
list.forEach(item => {
obj[item.firstSpecValue] = [];
});
list.forEach(item => obj[item.firstSpecValue].push(item));
const keys = Object.keys(obj);
keys.forEach(key => {
obj[key].forEach((i, index) => {
if (index === 0) {
i.length = obj[key].length;
}
});
finialList = finialList.concat(obj[key]);
return finialList;
});
}
export function verify(initData, categoryId) {
if (categoryId || (initData && initData.thirdCategoryId)) {
return true;
}
notification.error({
message: '请选择三级类目',
});
return false;
}
export function editColumns(methods, firstData, firstSpec, secondSpec, isJDGoods, isEdit) {
const { getJdPicList, inputChange, openModal, productType } = methods;
if (!firstData) return [];
const arr = [
{
title: '供货价',
align: 'center',
key: 'supplyPrice',
dataIndex: 'supplyPrice',
width: 100,
render: (val, row, index) => (
<InputNumber
defaultValue={val}
value={val}
precision={2}
min={0}
onChange={value => inputChange(value, 'supplyPrice', index)}
disabled={isJDGoods}
/>
// <Input
// value={val}
// className={`supplyPrice ${index}`}
// defaultValue={val}
// onChange={evt => inputChange(evt.target.value, 'supplyPrice', index)}
// disabled={isJDGoods}
// />
),
},
{
title: '市场价',
align: 'center',
key: 'marketPrice',
dataIndex: 'marketPrice',
width: 100,
render: (val, row, index) => (
<InputNumber
defaultValue={val}
value={val}
precision={2}
min={0}
onChange={value => inputChange(value, 'marketPrice', index)}
/>
// <Input
// value={val}
// className={`marketPrice ${index}`}
// defaultValue={val}
// onChange={evt => inputChange(evt.target.value, 'marketPrice', index)}
// />
),
},
{
title: '库存',
align: 'center',
key: 'productStock',
dataIndex: 'productStock',
width: 100,
render: (val, row, index) => (
<InputNumber
value={val}
precision={0}
min={0}
onChange={value => inputChange(value, 'productStock', index)}
disabled={isEdit && row.id}
/>
),
},
{
title: '商品自编码',
align: 'center',
key: 'thirdSkuNo',
dataIndex: 'thirdSkuNo',
width: 100,
render: (val, row, index) => (
<Input
value={val}
className={`thirdSkuNo ${index}`}
defaultValue={val}
onChange={evt => inputChange(evt.target.value, 'thirdSkuNo', index)}
/>
),
},
// {
// title: 'sku名称',
// align: 'center',
// key: 'name',
// dataIndex: 'name',
// width: 100,
// render: val => (
// <Popover content={val} trigger="hover">
// <a>查看名称</a>
// </Popover>
// ),
// },
{
title: '京东链接',
align: 'center',
key: 'skuLink',
dataIndex: 'skuLink',
width: 80,
render: (val, row, index) => (
<Popover content={val} trigger="hover">
{
<Input
value={val}
className={`skuLink ${index}`}
defaultValue={val}
onChange={evt => inputChange(evt.target.value, 'skuLink', index)}
/>
}
</Popover>
),
},
{
title: '操作',
align: 'center',
key: 'option',
dataIndex: 'option',
width: 100,
render: (_, row, index) => (
<div>
{row.skuLink && (
<Button type="primary" size="small" onClick={() => getJdPicList(row)}>
拉图片
</Button>
)}
{isJDGoods && (
<Button
type="primary"
size="small"
style={{ marginTop: '5px' }}
onClick={() => openModal(row, index)}
>
修改sku名称
</Button>
)}
</div>
),
},
];
if (productType === 1) {
// 如果是实体商品添加重量
arr.splice(2, 0, {
title: '重量(kg)',
align: 'center',
key: 'weight',
dataIndex: 'weight',
width: 120,
render: (val, row, index) => (
<InputNumber
style={{ width: '100%' }}
value={val}
min={0}
max={999999.999}
precision={3}
onChange={value => inputChange(value, 'weight', index)}
/>
),
});
arr.splice(4, 0, {
title: '库存预警',
align: 'center',
key: 'productStockWarning',
dataIndex: 'productStockWarning',
width: 90,
render: (val, row, index) => (
<InputNumber
defaultValue={0}
value={val}
maxLength={5}
min={0}
precision={0}
onChange={value => inputChange(value, 'productStockWarning', index)}
/>
),
});
}
if (secondSpec && firstData.secondSpecValue) {
arr.unshift({
title: secondSpec,
align: 'center',
key: 'secondSpecValue',
dataIndex: 'secondSpecValue',
width: 100,
});
}
if (firstSpec && firstData.firstSpecValue) {
arr.unshift({
title: firstSpec,
dataIndex: 'firstSpecValue',
key: 'firstSpecValue',
align: 'center',
width: 100,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.length) {
obj.props.rowSpan = row.length;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
});
}
if (isEdit && isJDGoods) {
arr.splice(arr.length - 1, 0, {
title: 'sku名称',
align: 'center',
key: 'name',
dataIndex: 'name',
width: 100,
render: val => (
<Popover content={val} trigger="hover">
<a>查看名称</a>
</Popover>
),
});
}
return arr;
}
export const goodsType = [
{
name: '普通商品',
value: 1,
},
{
name: '虚拟商品',
value: 2,
},
{
name: '电子卡券',
value: 3,
},
];
/* eslint-disable no-param-reassign */
import {
Modal,
Table,
Button,
Form,
Select,
Input,
Icon,
Row,
Col,
Checkbox,
notification,
Cascader,
Popover,
Card,
Radio,
} from 'antd';
import React, { Component } from 'react';
import SuperSelect from 'antd-virtual-select';
import _ from 'lodash';
import ButtonGroup from './buttonGroup';
import {
normFile,
editColumns,
specValidate,
dataInit,
validateSpuInfo,
createEditData,
// verify,
} from './mixin';
import { getSpecList, getJdPicList, getBrandList, addGoods, editGoods } from '../service';
import styles from '../style.less';
import Upload from '../../components/sortablUpload';
// import Upload from '../../components/upload';
import BatchSetting from './batchSetting';
import SkuNameChange from './skuNameChange';
import { productTypeList } from '../staticdata';
const FormItem = Form.Item;
const { Option } = Select;
class goodsManage extends Component {
state = {
// step: 1,
// editStep: 1,
categoryId: null, // 类目选择
specList: [], // 规格
normalBrandList: [], // 品牌
brandList: [], // 虚拟商品的 品牌
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: false,
initCascader: null,
batchDeleteImgObj: {},
editRow: {},
editRowIndex: null,
skuNameVisible: false,
productType: 1,
confirmLoading: false,
};
componentDidMount() {
this.getSpecData();
this.getBrandData();
}
componentWillReceiveProps() {
this.setState({ isCancel: false });
}
// 获取规格列表
getSpecData = async () => {
const { data } = await getSpecList();
if (data) {
this.setState({ specList: data });
}
};
inputChange = (value, key, index) => {
this.setState(prev => {
prev.editData[index][key] = value;
return { editData: prev.editData };
});
};
// eslint-disable-next-line consistent-return
getJdPicList = async row => {
// 获取图片需要设置轮播图和详情图
const {
form: { setFieldsValue, getFieldsValue },
} = this.props;
const { detailImageList = [] } = getFieldsValue(['detailImageList']);
if (!row.skuLink) {
return notification.warning({ message: '请输入京东链接再获取图片' });
}
const { firstSpecId, firstSpecValue, secondSpecId, secondSpecValue, skuLink } = row;
const data = await getJdPicList({
firstSpecId,
firstSpecValue,
secondSpecId,
secondSpecValue,
skuLink,
jdSkuInfoUrl: row.skuLink,
});
if (!data) return false;
const detailList = data.detailList || [];
const { colorImg, editData } = this.state;
colorImg[data.firstSpecValue] = colorImg[data.firstSpecValue]
? colorImg[data.firstSpecValue].concat(data.carouseList || [])
: data.carouseList || [];
editData.forEach(i => {
i.imageList = colorImg[i.firstSpecValue];
return i;
});
this.setState(
() => ({ colorImg, editData }),
() => {
setFieldsValue({
[`imageList[${data.firstSpecValue}]`]: this.state.colorImg[data.firstSpecValue],
detailImageList: [...detailImageList, ...detailList],
});
},
);
};
changeName = name => {
const { editData } = this.state;
editData[this.state.editRowIndex].name = name;
this.setState({ editData, skuNameVisible: false });
};
openSkuNameModal = (row, index) => {
this.setState({
editRowIndex: index,
editRow: row,
skuNameVisible: true,
});
};
getBrandData = async () => {
const { data } = await getBrandList();
const list = data && data.filter(item => item.name === '虚拟商品');
this.setState({ normalBrandList: data, brandList: list });
};
changeStep = () => {
const {
form: { setFieldsValue },
initData,
} = this.props;
if (Object.keys(initData).length) {
this.setState({
initForm: initData,
editData: initData.editData,
productType: initData.productType,
});
setFieldsValue({
detailImageList: initData.detailImageList || [],
imageList: initData.imageList || {},
});
}
};
// 选择规格
specChange = (flag, specName) => {
const { initForm } = this.state;
if (flag === 'first') {
initForm.firstSpecName = specName;
this.setState({ initForm });
return;
}
initForm.secondSpecName = specName;
this.setState({ initForm });
};
treeChange = value => {
this.setState({ categoryId: value[value.length - 1], initCascader: value });
};
remove = (flag, k) => {
const { form } = this.props;
const keys = form.getFieldValue(flag);
if (flag === 'firstKeys') {
form.setFieldsValue(
{
firstKeys: keys.filter(key => key !== k),
},
() => {
const firstKeys = form.getFieldValue('firstKeys');
const first = form.getFieldValue('first');
form.setFieldsValue({
first: _.pick({ first, firstKeys }),
});
},
);
this.BatchSetting.setFiled(flag);
return;
}
form.setFieldsValue(
{
secondKeys: keys.filter(key => key !== k),
},
() => {
const secondKeys = form.getFieldValue('secondKeys');
const second = form.getFieldValue('second');
form.setFieldsValue({
second: _.pick({ second, secondKeys }),
});
},
);
this.BatchSetting.setFiled(flag);
};
add = flag => {
const { form } = this.props;
if (flag === 'firstKeys' && !form.getFieldValue('firstSpecId')) {
notification.error({
message: '请先选择一级规格数据',
});
return;
}
if (flag === 'secondKeys' && !form.getFieldValue('secondSpecId')) {
notification.error({
message: '请先选择二级规格数据',
});
return;
}
const keys = form.getFieldValue(flag);
const nextKeys = keys.concat(+this.state.count + 1);
this.setState(prev => ({ count: +prev.count + 1 }));
if (flag === 'firstKeys') {
form.setFieldsValue({
firstKeys: nextKeys,
});
return;
}
form.setFieldsValue({
secondKeys: nextKeys,
});
};
imgChange = (data, spec) => {
const { colorImg } = this.state;
colorImg[spec] = data;
this.setState({ colorImg });
this.setState(prev => ({
editData: prev.editData.map(item => {
if (`${item.firstSpecValue}` === spec) {
item.imageList = prev.colorImg[spec];
}
return item;
}),
}));
};
batchSet = editData => {
this.setState({ editData });
};
pullImg = (data, spec) => {
this.imgChange(data, spec);
const { form } = this.props;
form.setFieldsValue({
[`imageList[${spec}]`]: data,
});
};
createShopInfo = () => {
const {
form: { getFieldsValue },
initData,
} = this.props;
const values = getFieldsValue();
const editData = createEditData(values, initData);
this.setState({ editData });
};
deleteImg = () => {
const {
form: { setFieldsValue },
} = this.props;
const { batchDeleteImgObj, colorImg, editData } = this.state;
const deleteKeys = Object.keys(batchDeleteImgObj);
deleteKeys.map(spec => {
if (batchDeleteImgObj[spec]) {
batchDeleteImgObj[spec] = false;
if (spec === 'detailImageList') {
setFieldsValue({
detailImageList: [],
});
return false;
}
if (spec === 'commonImageList') {
setFieldsValue({
commonImageList: [],
});
return false;
}
colorImg[spec] = [];
editData.map(item => {
if (item.firstSpecValue === spec) {
item.imageList = [];
}
return item;
});
setFieldsValue({
[`imageList[${spec}]`]: [],
});
}
return spec;
});
this.setState({ colorImg, editData, batchDeleteImgObj });
};
chooseRollImg = spec => {
this.setState(state => {
state.batchDeleteImgObj[spec] = !state.batchDeleteImgObj[spec];
return { batchDeleteImgObj: state.batchDeleteImgObj };
});
};
// 最终提交
confirm = async isContinue => {
const {
form: { validateFields },
initData,
} = this.props;
const { editData, productType } = this.state;
validateFields(async (errors, values) => {
let imgErr = false;
if (!errors) {
if (validateSpuInfo(values, initData, editData, productType)) return;
editData.forEach(item => {
item.productStockWarning = item.productStockWarning < 0 ? 0 : item.productStockWarning;
if (!item.imageList || !item.imageList.length) {
item.imageList = values.commonImageList || [];
}
if (!item.imageList || !item.imageList.length) {
imgErr = true;
}
});
// 虚拟商品时不校验必 滑动图不全,请检查!
if (imgErr && productType === 1) {
notification.error({ message: '滑动图不全,请检查!' });
return;
}
await this.setState(prev => ({
confirmLoading: true,
createData: {
id: initData.id || '',
name: values.name,
items: prev.editData,
brandId: values.brandId,
supplierId: values.supplierId,
detailImageList: values.detailImageList,
commonImageList: values.commonImageList || [],
categoryId: prev.categoryId ? prev.categoryId : initData?.thirdCategoryId,
// productType: values.productType,
type: values.productType,
},
}));
const [data] = initData.id
? await editGoods(this.state.createData)
: await addGoods(this.state.createData);
if (data) {
notification.success({
message: '商品保存成功!',
});
this.props.query();
if (isContinue) {
this.setState({
// step: 2,
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
confirmLoading: false,
});
this.props.form.resetFields();
return;
}
this.onCancel();
}
this.setState({
confirmLoading: false,
});
}
});
};
onCancel = () => {
this.setState(
{
// step: 1,
// editStep: 1,
categoryId: null, // 类目选择
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: true,
initCascader: null,
productType: 1,
},
() => {
this.BatchSetting.props.form.resetFields();
this.props.form.resetFields();
this.props.onCancel();
},
);
};
bundleRadioChange = event => {
this.setState({
categoryId: null, // 类目选择
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: true,
initCascader: null,
productType: event.target.value,
});
this.props.form.resetFields();
this.BatchSetting.props.form.resetFields();
};
render() {
const { visible, form, initData = {}, treeData, virtualTreeData } = this.props;
const isEdit = Object.keys(initData).length !== 0;
const { getFieldDecorator, getFieldsValue } = form;
getFieldDecorator('firstKeys', { initialValue: [] });
getFieldDecorator('secondKeys', { initialValue: [] });
const { first = [], second, commonImageList, firstKeys, secondKeys, name } = getFieldsValue();
const isFirstSame = specValidate(first, initData.firstSpecList, initData.id);
const isSecondSame = specValidate(second, initData.secondSpecList, initData.id);
if (isFirstSame) first.pop();
if (isSecondSame) second.pop();
const colorKeys = isEdit
? initData.firstSpecList.concat(first || this.state.initForm.first || [])
: first || this.state.initForm.first || [];
if (isEdit && !Object.keys(this.state.initForm).length && !this.state.isCancel) {
this.changeStep(1);
}
const isJDGoods = initData.id && initData.pageProductType && +initData.pageProductType !== 1;
// const initCascader = this.state.initCascader
// ? this.state.initCascader
// : [initData.firstCategoryId, initData.secondCategoryId, initData.thirdCategoryId];
// console.log(initCascader);
const initCascader = isEdit
? [initData.firstCategoryId, initData.secondCategoryId, initData.thirdCategoryId]
: this.state.initCascader;
const formItemLayout = {
labelCol: {
span: 5,
},
};
const formItemProduct = {
labelCol: { span: 2 },
wrapperCol: { span: 22 },
};
const { colorImg, productType, normalBrandList, brandList, confirmLoading } = this.state;
const skuSpeFirstKeys = initData.firstSpecList || [];
const skuSpeSecondKeys = initData.secondSpecList || [];
const treeDataArray = productType === 2 ? virtualTreeData : treeData;
const brandListArray = productType === 2 ? brandList : normalBrandList;
dataInit(this.state.editData);
const filterOption = (input, op) => op.props.children.includes(input);
return (
<Modal
title={initData.id ? '修改商品' : '新增商品'}
visible={visible}
footer={null}
onCancel={this.onCancel}
width="1050px"
bodyStyle={{ background: 'rgb(247 249 249)' }}
>
<Form name="horizontal_login">
<div>
<Card className={styles.card} bordered={false}>
<Row type="flex" gutter={24}>
<Col span={24}>
<Form.Item label="商品类型:" {...formItemProduct}>
{getFieldDecorator('productType', {
initialValue: productType,
rules: [{ required: true, message: '请选择商品类型' }],
})(
<Radio.Group disabled={isEdit} onChange={this.bundleRadioChange}>
{productTypeList.map(item => (
<Radio key={item.value} value={item.value} disabled={item.disabled}>
{item.label}
</Radio>
))}
</Radio.Group>,
)}
</Form.Item>
</Col>
<Col span={24}>
<FormItem label="类目:" {...formItemProduct}>
{getFieldDecorator('categoryId', {
initialValue: initCascader,
rules: [
{
required: true,
message: '请选择类目',
},
],
})(
<Cascader
style={{ width: 690 }}
changeOnSelect
showSearch
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={treeDataArray}
onChange={(val, label, ext) => this.treeChange(val, label, ext)}
disabled={isEdit}
/>,
)}
</FormItem>
</Col>
</Row>
</Card>
<Card className={styles.card} bordered={false}>
<Row
gutter={10}
style={{ background: '#fff', borderRadius: '6px', padding: '20px 0 5px 0' }}
>
<Col span={10}>
<FormItem label="供货商" {...formItemLayout}>
{getFieldDecorator('supplierId', {
initialValue: this.state.initForm.supplierId,
rules: [
{
required: true,
message: '请选择供货商',
},
],
})(
<Select
allowClear
showSearch
style={{ width: 190 }}
filterOption={filterOption}
disabled={initData.productType && initData.productType !== 1}
>
{this.props.shopList.length &&
this.props.shopList.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</Select>,
)}
</FormItem>
</Col>
<Col span={10}>
<FormItem label="商品品牌" {...formItemLayout}>
{getFieldDecorator('brandId', {
initialValue: this.state.initForm.brandId,
rules: [
{
required: true,
message: '请选择商品品牌',
},
],
})(
<SuperSelect
allowClear
showSearch
style={{ width: 190 }}
filterOption={filterOption}
>
{brandListArray?.length &&
brandListArray.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</SuperSelect>,
)}
</FormItem>
</Col>
</Row>
<Row type="flex">
<Col span={24}>
<Popover content={name} trigger="hover">
<FormItem label="商品名称" labelCol={{ span: 2 }}>
{getFieldDecorator('name', {
initialValue: initData.name,
rules: [
{
required: true,
message: '请输入商品名称',
},
],
})(<Input style={{ width: 690 }} allowClear />)}
</FormItem>
</Popover>
{isJDGoods && (
<div className={styles.warning}>
*本列表的商品名称仅供搜索使用,不在前端作展示。若要修改APP端展示的商品名称,请在商品信息中修改。
</div>
)}
</Col>
</Row>
</Card>
<Card className={styles.card} bordered={false}>
<Row>
<FormItem label="一级规格">
{getFieldDecorator('firstSpecId', {
initialValue: this.state.initForm.firstSpecId,
})(
<Select
allowClear
showSearch
style={{ width: 200 }}
disabled={isEdit}
onChange={(val, option) =>
this.specChange('first', option?.props.children, val)
}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{this.state.specList.length &&
this.state.specList.map(item => (
<Option key={item.specId} value={item.specId}>
{item.specName}
</Option>
))}
</Select>,
)}
</FormItem>
</Row>
<Row type="flex">
{skuSpeFirstKeys.length > 0 &&
skuSpeFirstKeys.map(
k =>
k !== 'null' && (
<Input
key={k}
value={k}
disabled
placeholder="请输入规格名称"
style={{ width: '15%', margin: 5 }}
/>
),
)}
{firstKeys.map(k => (
<FormItem label="" required={false} key={k}>
{getFieldDecorator(`first[${k}]`, {
initialValue: this.state.initForm.first && this.state.initForm.first[k],
validateTrigger: ['onChange', 'onBlur'],
rules: [
{
required: true,
message: '请输入',
},
],
})(
<Input
key={k}
placeholder="请输入规格名称"
style={{ width: '60%', marginRight: 8 }}
/>,
)}
{firstKeys.length > 0 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove('firstKeys', k)}
/>
) : null}
</FormItem>
))}
<FormItem>
<Button
type="dashed"
disabled={isJDGoods}
onClick={() => this.add('firstKeys')}
style={{ textAlign: 'center' }}
>
<Icon type="plus" />
</Button>
</FormItem>
</Row>
<Row>
<FormItem label="二级规格">
{getFieldDecorator('secondSpecId', {
initialValue: this.state.initForm.secondSpecId,
})(
<Select
allowClear
showSearch
disabled={isEdit}
style={{ width: 200 }}
onChange={(val, option) => this.specChange('second', option?.props.children)}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{this.state.specList.length &&
this.state.specList.map(item => (
<Option key={item.specId} value={item.specId}>
{item.specName}
</Option>
))}
</Select>,
)}
</FormItem>
</Row>
<Row type="flex">
{skuSpeSecondKeys.length > 0 &&
skuSpeSecondKeys.map(k => (
<Input
key={k}
value={k}
disabled
placeholder="请输入规格名称"
style={{ width: '15%', margin: 5 }}
/>
))}
{secondKeys.map(k => (
<FormItem label="" required={false} key={k}>
{getFieldDecorator(`second[${k}]`, {
initialValue:
(this.state.initForm.second && this.state.initForm.second[k]) || '',
validateTrigger: ['onChange', 'onBlur'],
})(<Input key={k} style={{ width: '60%', marginRight: 8 }} />)}
{secondKeys.length > 0 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove('secondKeys', k)}
/>
) : null}
</FormItem>
))}
<FormItem>
<Button
type="dashed"
onClick={() => this.add('secondKeys')}
style={{ textAlign: 'center' }}
disabled={isJDGoods}
>
<Icon type="plus" />
</Button>
</FormItem>
</Row>
</Card>
<Row>
<Button
type="primary"
className={styles.logBtn}
onClick={() => this.createShopInfo()}
>
生成商品信息
</Button>
</Row>
<BatchSetting
firstSpes={first ? skuSpeFirstKeys.concat(first) : skuSpeFirstKeys}
secondSpecs={skuSpeSecondKeys.concat(second || [])}
onRef={ref => {
this.BatchSetting = ref;
}}
editData={this.state.editData}
firstSpesName={this.state.initForm.firstSpecName}
secondSpesName={this.state.initForm.secondSpecName}
batchSetting={this.batchSet}
productType={productType}
isEdit={isEdit}
isJDGoods={isJDGoods}
/>
<Row>
<Table
bordered
rowKey="id"
pagination={false}
scroll={{ y: 300, x: 1200 }}
dataSource={this.state.editData}
columns={editColumns(
{
getJdPicList: this.getJdPicList,
inputChange: this.inputChange,
openModal: this.openSkuNameModal,
productType,
},
this.state.editData?.length ? this.state.editData[0] : null,
this.state.initForm.firstSpecName,
this.state.initForm.secondSpecName,
isJDGoods,
isEdit,
)}
/>
</Row>
<Row>
<Button
type="danger"
size="small"
onClick={this.deleteImg}
style={{ marginTop: '10px' }}
icon="rest"
>
图片批量删除
</Button>
<Row>
<Col span={1}>
<Checkbox
value="commonImageList"
checked={this.state.batchDeleteImgObj.commonImageList}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg('commonImageList')}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem label="公共滑动图">
{getFieldDecorator('commonImageList', {
initialValue: this.state.initForm.commonImageList,
valuePropName: 'fileList',
getValueFromEvent: normFile,
})(<Upload />)}
</FormItem>
</Col>
</Row>
{colorKeys.length > 0 &&
colorKeys.map(color => {
if (JSON.stringify(color) === '{}') return [];
if ((color !== null && !color) || (color && !color.trim())) return [];
return [
<Row>
<Col span={1}>
<Checkbox
value={color}
checked={this.state.batchDeleteImgObj[color]}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg(color)}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem
key={color}
label={`滑动图${color === 'null' ? '' : `(${color})`}:`}
>
{getFieldDecorator(`imageList[${color}]`, {
initialValue:
this.state.initForm.imageList && this.state.initForm.imageList[color],
valuePropName: 'fileList',
getValueFromEvent: normFile,
})(<Upload onChange={val => this.imgChange(val, color)} />)}
<Button
disabled={colorImg[color]?.length}
size="small"
type="primary"
className={styles.pullBtn}
onClick={() => this.pullImg(commonImageList, color)}
>
拉取公共图像
</Button>
</FormItem>
</Col>
</Row>,
];
})}
</Row>
<Row>
<Col span={1}>
<Checkbox
value="detailImageList"
checked={this.state.batchDeleteImgObj.detailImageList}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg('detailImageList')}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem label="详情图">
{getFieldDecorator('detailImageList', {
initialValue: this.state.initForm.detailImageList,
valuePropName: 'fileList',
getValueFromEvent: normFile,
rules: [
{
required: productType !== 2,
message: '请上传详情图',
},
],
})(<Upload />)}
</FormItem>
</Col>
</Row>
</div>
</Form>
<ButtonGroup
key="button-group"
initData={initData}
changeStep={this.changeStep}
onCancel={this.onCancel}
confirm={this.confirm}
confirmLoading={confirmLoading}
></ButtonGroup>
<SkuNameChange
visible={this.state.skuNameVisible}
data={this.state.editRow.name}
changeSkuName={data => this.changeName(data)}
onCancle={() => this.setState({ skuNameVisible: false })}
></SkuNameChange>
</Modal>
);
}
}
export default Form.create()(goodsManage);
import { Form, Input, Modal } from 'antd';
import React, { Component } from 'react';
const { TextArea } = Input;
const FormItem = Form.Item;
// import styles from '../style.less';
class goodsManage extends Component {
// componentDidMount() {
// this.props.onRef(this);
// }
handleOk = () => {
const { form } = this.props;
form.validateFields((err, values) => {
if (!err) {
this.props.changeSkuName(values.name);
this.props.form.resetFields();
}
});
};
handleCancel = () => {
this.props.form.resetFields();
this.props.onCancle();
};
render() {
const { data, visible, form } = this.props;
const { getFieldDecorator } = form;
return (
<Modal title="" visible={visible} onOk={this.handleOk} onCancel={this.handleCancel}>
<Form>
<FormItem label="sku名称">
{getFieldDecorator('name', {
initialValue: data,
rules: [
{
required: true,
message: '请输入',
},
],
})(<TextArea autoSize={{ minRows: 2, maxRows: 6 }} allowClear />)}
</FormItem>
</Form>
</Modal>
);
}
}
export default Form.create()(goodsManage);
import { Card, Form, Pagination, Table, notification, Drawer, Spin } from 'antd';
import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { connect } from 'dva';
import { sortBy } from 'lodash';
import styles from './style.less';
import LocalStroage from '@/utils/localStorage';
import configApi from '../../../config/env.config';
import UpdatePrice from './UpdatePrice';
import UpdateStock from './UpdateStock';
import {
update,
spuDetail,
detail,
getSupplierList,
batchPushedOrOffline,
getImageInfo,
categoryList,
getVirtualCategory,
} from './service';
import LogModal from './LogModal';
import CreateModal from './createModal/normal';
// import CreateVirtualModal from './createModal/virtual';
import SupplyModal from './SupplyModal';
import ReasonModal from './reasonModal';
import ImgModal from './ImgModal';
import { column, JDSHOPID } from './staticdata';
import SearchForm from './SearchForm';
@connect(({ goodsManage }) => ({
goodsManage,
}))
class goodsManage extends Component {
state = {
pageNo: 1,
treeData: [],
virtualTreeData: [],
pageSize: 20,
priceInfo: {},
logVisible: false,
previewVisible: false,
supplyVisible: false,
createVisible: false, // 新增or编辑普通商品modal
// createVirtualVisible: false, // 新增or编辑虚拟普通商品modal
imgVisible: false,
offlineVisible: false,
selectedParams: {},
imgData: [],
detailData: [],
detailSpuId: '',
selectedRowKeys: [],
updatePriceVisible: false,
updateStockVisible: false,
initData: {},
// initVirtualData: {},
createloading: false,
type: 1,
};
loading = false;
currentSkuId = null;
supplierId = null;
shopList = [];
componentDidMount() {
this.props.goodsManage.tableData = {};
this.getShopList();
this.categoryList();
this.getVirtualCategory();
}
openOffline = (ids, type, productState) => {
const obj = { ids, type, productState };
this.setState({ selectedParams: obj });
this.setState({ offlineVisible: true });
};
showImg = async spuNo => {
this.setState({ imgVisible: true });
const [data] = await getImageInfo(spuNo);
this.setState({ imgData: data });
};
showSupply = async (id, type) => {
const [data] = await detail({ id });
this.setState({ supplyVisible: true, detailData: data, type });
};
handleSearch = page => {
this.loading = true;
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'goodsManage/getList',
payload: {
pageNo,
pageSize,
...this.searchForm.getFieldsValue(),
},
});
this.loading = false;
},
);
};
onPageChange = page => {
this.handleSearch(page);
};
onPageSizeChange = (current, size) => {
this.setState(
{
pageSize: size,
},
() => this.handleSearch(),
);
};
onSelectChange = selectedRowKeys => {
this.setState({ selectedRowKeys });
};
onReset = () => {
this.setState({
pageNo: 1,
pageSize: 20,
selectedRowKeys: [],
});
this.handleSearch();
};
onUpdateInfo = async ({ spuId, productType }) => {
this.setState({
createloading: true,
});
const { data } = await spuDetail({ id: spuId });
if (data) {
console.log(productType);
data.pageProductType = productType;
data.categoryId = data.thirdCategoryId;
data.firstSpecId = data.skuList[0].firstSpecId;
data.secondSpecId = data.skuList[0].secondSpecId;
data.firstSpecName = data.skuList[0].firstSpec;
data.secondSpecName = data.skuList[0].secondSpec;
data.firstSpecList = [];
data.secondSpecList = [];
data.colorKeys = [];
data.carouseList.forEach(i => {
if (i.specValue) {
data.colorKeys.push(i.specValue);
}
});
data.skuList.forEach(i => {
if (data.firstSpecList.indexOf(i.firstSpecValue) === -1) {
data.firstSpecList.push(`${i.firstSpecValue}`);
}
if (i.secondSpecValue && data.secondSpecList.indexOf(i.secondSpecValue) === -1) {
data.secondSpecList.push(i.secondSpecValue);
}
});
data.imageList = [];
data.carouseList.forEach(i => {
data.imageList[`${i.specValue}`] = i.skuSpecImageList || [];
});
data.editData = sortBy(data.skuList, item => item.firstSpecValue);
// const createVisible = data.type === 1;
// const createVirtualVisible = data.type !== 1;
// this.setState({
// initData: createVisible ? data : {},
// initVirtualData: createVirtualVisible ? data : {},
// createVisible,
// createVirtualVisible,
// createloading: false,
// });
// data.type = data.productType;
// data.productType = productType;
this.setState({
initData: data,
createVisible: true,
createloading: false,
});
}
};
onUpdate = async updateData => {
const [, error] = await update(updateData);
this.onLoad(error);
};
onLoad = error => {
if (!error) {
notification.success({ message: '操作成功' });
this.setState({
selectedRowKeys: [],
});
this.handleSearch();
}
};
viewLog = async skuId => {
this.currentSkuId = skuId;
this.setState({
logVisible: true,
});
};
updateStatus = async (ids, productState, offlineReason, fromDetail) => {
const [data] = await batchPushedOrOffline({
ids,
productState,
offlineReason,
});
if (data) {
this.handleSearch();
notification.success({
message: productState === 6 ? '上架成功' : '下架成功',
});
this.setState({ offlineVisible: false });
if (fromDetail) {
this.showDetail(this.state.detailSpuId);
}
}
};
audit = skuId => {
this.setState({
previewVisible: true,
src: `${configApi.prologueDomain}/goods/${skuId}?h=0&token=${LocalStroage.get(
'token',
)}&hideReport=1&time=${Date.now()}`,
});
};
offLine = (ids, productState) => {
const obj = { ids, productState };
this.setState({ offlineVisible: true, selectedParams: obj });
};
// 获取供应商列表
getShopList = async () => {
const { data } = await getSupplierList();
this.shopList = data || [];
};
filterShopList = (list = [], isEdit) =>
list.filter(item => isEdit || !JDSHOPID.includes(item.id));
openModal = (
{
skuId,
supplyPrice,
marketPrice,
salePrice,
marketableStock,
supplierId,
stock,
productStock,
},
isStock,
) => {
let visible = {};
if (isStock) {
visible = { updateStockVisible: true };
} else {
visible = { updatePriceVisible: true };
}
this.setState({
...visible,
priceInfo: {
id: skuId,
stock,
productStock,
marketableStock,
supplyPrice,
marketPrice,
salePrice,
supplierId,
},
});
};
cancel = query => {
this.setState({ updatePriceVisible: false, updateStockVisible: false });
if (query) {
this.handleSearch();
}
};
categoryList = async () => {
const { data: treeData } = await categoryList();
if (!treeData) return;
this.setState({ treeData });
};
getVirtualCategory = async () => {
const { virtualTreeData } = await getVirtualCategory();
if (!virtualTreeData) return;
this.setState({ virtualTreeData });
};
render() {
const {
goodsManage: { tableData = {} },
} = this.props;
const { pageNo, pageSize, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: record => ({
disabled: +record.state === 6, // Column configuration not to be checked
}),
};
return (
<PageHeaderWrapper>
<Spin spinning={this.state.createloading}>
<Card>
<SearchForm
handleSearch={this.handleSearch}
onReset={this.onReset}
onLoad={this.onLoad}
selectedRowKeys={this.state.selectedRowKeys}
onRef={ref => {
this.searchForm = ref;
}}
treeData={this.state.treeData}
shopList={this.shopList}
addSpu={() => this.setState({ createVisible: true, initData: {} })}
/>
</Card>
<Spin spinning={this.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={column.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
rowSelection={rowSelection}
scroll={{ x: '100%', y: 500 }}
/>
</Spin>
<br />
{tableData && (
<Pagination
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<LogModal
visible={this.state.logVisible}
id={this.currentSkuId}
onCancel={() => {
this.currentSkuId = null;
this.setState({ logVisible: false });
}}
/>
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
>
<iframe
src={this.state.src}
frameBorder="0"
height="600"
width="375"
title="商品预览"
></iframe>
</Drawer>
<CreateModal
initData={this.state.initData}
visible={this.state.createVisible}
onCancel={() => {
this.setState({ createVisible: false, initData: {} });
}}
query={() => this.handleSearch()}
shopList={this.filterShopList(this.shopList, Object.keys(this.state.initData).length)}
treeData={this.state.treeData}
virtualTreeData={this.state.virtualTreeData}
></CreateModal>
{/* <CreateVirtualModal
initData={this.state.initVirtualData}
visible={this.state.createVirtualVisible}
onCancel={() => {
this.setState({ createVirtualVisible: false, initVirtualData: {} });
}}
query={() => this.handleSearch()}
shopList={this.shopList}
></CreateVirtualModal> */}
<SupplyModal
data={this.state.detailData}
visible={this.state.supplyVisible}
type={this.state.type}
onCancel={() => {
this.setState({ supplyVisible: false });
}}
></SupplyModal>
<ImgModal
data={this.state.imgData || []}
visible={this.state.imgVisible}
onCancel={() => {
this.setState({ imgVisible: false });
}}
></ImgModal>
<ReasonModal
visible={this.state.offlineVisible}
submit={reason => {
const { ids, productState } = this.state.selectedParams;
this.updateStatus(ids, productState, reason);
}}
onCancel={() => {
this.setState({ offlineVisible: false });
}}
></ReasonModal>
<UpdatePrice
visible={this.state.updatePriceVisible}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
<UpdateStock
visible={this.state.updateStockVisible}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
</Spin>
</PageHeaderWrapper>
);
}
}
export default Form.create()(goodsManage);
import React, { useEffect, useState, useRef } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import { Button } from 'antd';
import { categoryList, query, getVirtualCategory, getSupplierList, getBrandList } from './service';
import { Goodscolumns } from './Goodscolumns';
import styled from './style.less';
import OperationModal from './components/operationModal';
export default () => {
/**
* @name screenLoading
* @type boolean
* @desc 整个页面的加载状态
*/
const [categoryData, setCategoryData] = useState([]);
const [operationVisible, setOperationVisible] = useState(false);
const actionRef = useRef();
const supplyPriceRef = useRef();
const [supplyPrice, setSupplyPrice] = useState([]);
const [virtualTreeData, setVirtualTreeData] = useState([]);
const [shopList, setShopList] = useState([]);
const [barndList, setBarndList] = useState([]);
const [virtualBarndList, setVirtualBarndList] = useState([]);
useEffect(() => {
const featchData = async () => {
const { data = [] } = await categoryList();
const { data: virtualData = [] } = await getVirtualCategory();
const { data: shopData = [] } = await getSupplierList();
const { data: barndData = [] } = await getBrandList();
const virtualBarndData = barndData.filter(item => item.name === '虚拟商品');
setCategoryData(data);
setVirtualTreeData(virtualData);
setShopList(shopData);
setBarndList(barndData);
setVirtualBarndList(virtualBarndData);
};
featchData();
}, []);
const toolBarList = [
<Button key="getOffGoodsShelf" type="primary" onClick={() => setOperationVisible(true)}>
新增商品
</Button>,
<Button key="putGoodsShelf">模版</Button>,
<Button key="tags" type="primary">
批量修改库存
</Button>,
];
console.log(operationVisible);
return (
<PageHeaderWrapper>
<ProTable
className={styled.protable}
actionRef={actionRef}
columns={Goodscolumns({ categoryData, supplyPrice, setSupplyPrice, supplyPriceRef })}
params={{ ...supplyPrice }}
request={params => {
// 表单搜索项会从 params 传入,传递给后端接口。
const [supplyPriceMin, supplyPriceMax] = supplyPrice;
return query({ supplyPriceMin, supplyPriceMax, ...params });
}}
rowKey="skuId"
bordered
scroll={{ x: 1500 }}
search={{
collapsed: false,
}}
toolBarRender={() => toolBarList}
pagination={{
showQuickJumper: true,
defaultPageSize: 10,
size: 'default',
showTitle: false,
showTotal: () => null,
}}
onReset={() => {
setSupplyPrice([]);
}}
options={{
density: false,
fullScreen: false,
setting: false,
reload: false,
}}
/>
<OperationModal
operationVisible={operationVisible}
setOperationVisible={setOperationVisible}
categoryData={categoryData} // 实体商品类目
virtualTreeData={virtualTreeData} // 虚拟商品类目
shopList={shopList} // 供货商数据
barndList={barndList}
virtualBarndList={virtualBarndList}
/>
{/* rowSelection={rowSelection} */}
{/* <div>helloworld</div> */}
</PageHeaderWrapper>
);
};
import * as api from './service';
const Model = {
namespace: 'goodsManage',
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 { data } = yield call(api.searchList, params);
if (!data) return;
yield put({
type: 'saveData',
payload: {
tableData: data,
},
});
},
*getDataList({ payload }, { call, put, all }) {
const [[shopList], [statusList]] = yield all([
yield call(api.shopList, payload),
yield call(api.statusList, payload),
]);
if (!shopList && !statusList) return;
yield put({
type: 'dataList',
payload: {
shopList,
statusList,
},
});
},
*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 { Modal, Input, notification } from 'antd';
import React, { Component } from 'react';
const { TextArea } = Input;
// eslint-disable-next-line react/prefer-stateless-function
class reasonModal extends Component {
state = {
remarks: '',
};
inputChange = ({ target: { value } }) => {
this.setState({ remarks: value });
};
onCancel = () => {
this.setState({ remarks: '' });
this.props.onCancel();
};
submit = () => {
if (!this.state.remarks) {
notification.error({
message: '请输入下架原因',
});
return;
}
this.props.submit(this.state.remarks);
this.setState({ remarks: '' });
};
render() {
const { visible } = this.props;
return (
<Modal
title="下架原因"
visible={visible}
onOk={this.submit}
onCancel={() => this.onCancel()}
width="700px"
>
<div>
<TextArea value={this.state.remarks} rows={4} onChange={this.inputChange} />
</div>
</Modal>
);
}
}
export default reasonModal;
// import fileSaver from 'file-saver';
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import _ from 'lodash';
const { kdspApi } = config;
// const kdspApi = 'http://yapi.quantgroups.com/mock/389';
// 分页查询所有数据
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
export const query = async params => {
const tempParams = {
...params,
startDate: params.dateRange?.[0],
endDate: params.dateRange?.[1],
pageNo: params.current,
};
delete tempParams.dateRange;
delete tempParams.current;
try {
const {
data: { current, records, total, size },
} = await request.post('/product/page', {
prefix: config.kdspApi,
data: stringify(_.omitBy(tempParams, v => !v)),
headers,
});
return {
current,
data: records,
total,
pageSize: size,
};
} catch (error) {
return {};
}
};
// export async function searchList(params) {
// return request.post('/product/page', {
// prefix: kdspApi,
// data: stringify(_.omitBy(params, v => !v)),
// headers,
// });
// }
// 新增商品
export async function addGoods(params) {
return request.post('/product/add', {
prefix: kdspApi,
params,
});
}
// 编辑商品
export async function editGoods(params) {
return request.post('/product/edit', {
prefix: kdspApi,
params,
});
}
// 供应商列表
export async function getSupplierList() {
return request.post('/channel/supplier/list', {
prefix: kdspApi,
});
}
// 获取商品品牌
export async function getBrandList() {
return request.post('/product/brand/list', {
prefix: kdspApi,
});
}
// 编辑--获取详情
export async function spuDetail(params) {
return request.post('/product/detail', {
prefix: kdspApi,
params,
headers,
});
}
// 商品规格
export async function getSpecList() {
return request.post('/product/spec/list', {
prefix: kdspApi,
});
}
// 查询图片素材
export async function getImageInfo(spuNo) {
return request.post('/product/imageInfo', {
params: { spuNo },
prefix: kdspApi,
headers,
});
}
// 状态
export async function statusList() {
return request.post('/api/kdsp/op/mch_sku/status_list');
}
// 商品来源
export async function skuSource() {
return request.post('/api/kdsp/sku/sku-info/getSkuSource');
}
// 商品分类
export async function categoryList() {
return request.post('/product/category/getAll', {
prefix: kdspApi,
headers,
});
}
// 批量修改
export async function uploadFile(file) {
const params = new FormData();
params.append('file', file);
const data = await request.post('/product/item/batchUpdate', params, {
rawData: true,
});
return data;
}
// 批量倒入京东skus
export async function importJdSkus(file, sourceType) {
const params = new FormData();
params.append('file', file);
params.append('sourceType', sourceType);
const data = await request.post('/product/importJdSkus', params, {
rawData: true,
});
return data;
}
// 批量上架
export async function pushed(data) {
return request.post('/api/kdsp/op/mch_sku/pushed', data);
}
// 批量下架
export async function offline(data) {
return request.post('/api/kdsp/op/mch_sku/offline', data);
}
// 最新批量上下架
export async function batchPushedOrOffline(data) {
return request.post('/product/pushedOrOffline/batch', data, {
emulateJSON: true,
});
}
// 商品修改
export async function update(params) {
return request.post('/api/kdsp/op/mch_sku/update', params, {
emulateJSON: true,
});
}
// 商品修改日志
export async function changeLog(params) {
return request.post('/product/logs/page', params, {
emulateJSON: true,
});
}
// 商品详情
export async function detail(params) {
return request.post('/product/supply/list', params, {
emulateJSON: true,
});
}
// 拉去京东图片
export async function getJdPicList(params) {
const [data] = await request.post('/product/item/getJdPicList', params, {
emulateJSON: true,
});
return data;
}
// 添加商品
export async function addSku(params) {
const [, error] = await request.post('/api/kdsp/op/mch_sku/add_sku', params);
return error;
}
// 修改商品
export async function updateSku(params) {
const [, error] = await request.post('/api/kdsp/op/mch_sku/update', params);
return error;
}
// 图片上传
export async function uploadImg(files) {
const params = new FormData();
files.map(file => params.append('file', file));
const data = await request.post('/image/upload', params, {
rawData: true,
});
return data;
}
// 修改商品价格
export async function updatePrice(params) {
const [, error] = await request.post('/product/item/updatePrice', params, {
emulateJSON: true,
});
return error;
}
// 修改商品库存
export async function updateStock(params) {
const [, error] = await request.post('/product/item/updateStock', params, {
emulateJSON: true,
});
return error;
}
// 获取虚拟商品类目
export async function getVirtualCategory() {
const data = await request.post('/product/category/getByParentId', {
prefix: kdspApi,
params: { id: 100018 },
headers,
});
return data;
}
import React from 'react';
import { Button, Popconfirm, Badge } from 'antd';
import styles from './style.less';
export const productType = [
{
value: 1,
title: '自营',
},
{
value: 2,
title: '京东开普勒',
},
{
value: 3,
title: '京东联盟',
},
{
value: 4,
title: '众联',
},
{
value: 5,
title: '企业购',
},
{
value: 6,
title: '企业购直连',
},
];
export function column() {
return [
{
title: 'SKU编码',
dataIndex: 'skuId',
width: 125,
align: 'center',
render: (_, row) => {
if (row.type !== 1) {
return (
<Badge
count={
<div
style={{
color: '#fff',
borderRadius: '3px',
background: '#f5222d',
padding: '2px',
fontSize: '10px',
opacity: 0.7,
}}
>
虚拟
</div>
}
>
<div
style={{
background: '#fbfbfb',
borderRadius: '3px',
padding: '2px',
}}
>
{row.skuId}
</div>
</Badge>
);
}
return (
<div
style={{
background: '#fbfbfb',
borderRadius: '3px',
padding: '2px',
}}
>
{row.skuId}
</div>
);
},
},
{
title: 'SKU商品名称',
width: 135,
align: 'center',
dataIndex: 'skuName',
},
{
title: '价格',
dataIndex: 'marketPrice',
width: 150,
align: 'center',
sorter: (a, b) => a.supplyPrice - b.supplyPrice,
render: (_, row) => (
<div className={styles.price} onClick={() => this.openModal(row)}>
<p>
供货价:<a>{row.supplyPrice.toFixed(2)}</a>
</p>
<p>
市场价:<a>{(row.marketPrice || 0).toFixed(2)}</a>
</p>
</div>
),
},
{
title: '库存',
width: 120,
dataIndex: 'stock',
align: 'center',
sorter: (a, b) => a.stock - b.stock,
render: (_, row) => (
<>
<p>
当前库存:<a onClick={() => this.openModal(row, 'productStock')}>{row.productStock}</a>
</p>
<p>可售库存:{_}</p>
{row.type === 1 && row.productStockWarning > -1 && (
<p>预警值:{row.productStockWarning}</p>
)}
</>
),
},
{
title: '商品状态',
dataIndex: 'stateDesc',
width: 200,
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
width: 120,
align: 'center',
render: (_, row) => (
<div className={styles.actionBtn}>
{row.state !== 6 && (
<Popconfirm
key="up"
placement="topLeft"
title="确定要上架吗?"
onConfirm={() => this.updateStatus(row.skuId, 6, '')}
okText="确定"
cancelText="取消"
>
<Button key="up" size="small" type="primary" className={styles.button}>
上架
</Button>
</Popconfirm>
)}
{row.state === 6 && (
<Button
key="down"
size="small"
type="primary"
className={styles.button}
disabled={row.state !== 6}
onClick={() => this.offLine(row.skuId, 7)}
>
下架
</Button>
)}
<Button
key="edit"
type="primary"
size="small"
className={styles.button}
onClick={() => this.onUpdateInfo(row)}
>
编辑
</Button>
<Button
key="detail"
size="small"
type="primary"
className={styles.button}
onClick={() => this.showSupply(row.spuId, row.type)}
>
供货详情
</Button>
<Button
key="viewP"
type="primary"
size="small"
className={styles.button}
onClick={() => this.audit(row.skuId)}
>
预览
</Button>
<Button
key="log"
size="small"
type="primary"
className={styles.button}
onClick={() => this.viewLog(row.skuId)}
>
查看日志
</Button>
</div>
),
},
];
}
export const disSelectStatus = [2, 5];
export const stateList = [
{ value: 5, label: '未上架' },
{ value: 6, label: '已上架' },
{ value: 7, label: '已下架' },
];
export const productTypeList = [
{ value: 1, label: '实体商品' },
{ value: 2, label: '虚拟充值' },
{ value: 3, label: '虚拟卡券', disabled: true },
];
export const JDSHOPID = [3, 5, 6];
.formItem {
width: 300px;
}
.itemSection {
display: inline-block;
width: calc(50% - 12px);
margin-right: 0 !important;
}
.itemLine {
display: inline-block;
width: 24px;
text-align: center;
}
.button {
margin: 5px;
}
.selectWidth {
width: 200px;
}
.btngroup {
margin: 10px;
}
.filterModal {
margin: 20px 0;
}
.footerButton {
position: absolute;
bottom: 20px;
left: 0;
}
.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: 700px;
}
.price {
// text-align: left;
cursor: pointer;
}
.searchForm {
:global {
.ant-form-item-label {
width: 120px;
}
}
}
.queryBtn {
margin-left: 45px;
}
.actionBtn {
button {
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;
color: red;
}
.iptNumRight {
margin-right: 0 !important;
}
import React, { useEffect } from 'react';
import { categoryList } from './service';
// export const useCategory = () => {
// useEffect(() => {
// const { data } = await categoryList();
// return [data];
// }, [])
// };
import { Modal, Button, Row, Col } from 'antd';
import React, { Component } from 'react';
import styles from '../style.less';
// eslint-disable-next-line react/prefer-stateless-function
class imgModal extends Component {
state = {
imgModal: false,
selectImg: '',
};
render() {
const { visible, data = [] } = this.props;
const { detailImageList = [], skuSpecImageList = [] } = data;
return (
<Modal
title="图片素材"
visible={visible}
footer={null}
onCancel={this.props.onCancel}
width="700px"
>
<div>
{skuSpecImageList.map(skuImg => (
<Row type="flex" justify="start" align="middle">
<Col span={4}>{`滑动图${skuImg.colorSpecValue || ''}:`}</Col>
<Col span={20}>
{skuImg.skuSpecImageList.map(item => (
<img
onClick={() => {
this.setState({ imgModal: true, selectImg: item });
}}
key={item}
width={200}
alt=""
src={item}
style={{ margin: 5 }}
></img>
))}
</Col>
</Row>
))}
<Row type="flex" justify="start" align="middle">
<Col span={4}>{detailImageList.length ? '详情图:' : ''}</Col>
<Col span={20}>
{detailImageList.map(item => (
<img
onClick={() => {
this.setState({ imgModal: true, selectImg: item });
}}
key={item}
width={200}
alt=""
src={item}
style={{ margin: 5 }}
></img>
))}
</Col>
</Row>
</div>
<Button type="primary" onClick={this.props.onCancel} className={styles.logBtn}>
取消
</Button>
<Modal
title="图片详情"
visible={this.state.imgModal}
footer={null}
onCancel={() => this.setState({ imgModal: false })}
width="800px"
>
<img width="700px" src={this.state.selectImg} alt=""></img>
</Modal>
</Modal>
);
}
}
export default imgModal;
import { Modal, Table, Button, Pagination } from 'antd';
import React, { useState, useEffect } from 'react';
import styles from '../style.less';
import { changeLog } from '../service';
const LogModal = props => {
const [tableData, setTableData] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [pageSize] = useState(20);
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 handleSearch = async (page = 1) => {
setPageNo(page);
const { data = {} } = await changeLog({ id: props.id, pageNo: page, pageSize });
setTableData(data);
};
const onPageChange = page => {
handleSearch(page);
};
useEffect(() => {
if (!props.id) return;
handleSearch();
}, [props.id]);
const { visible } = props;
return (
<Modal title="日志详情" visible={visible} footer={null} onCancel={props.onCancel} width="800px">
<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}
/>
)}
<Button type="primary" onClick={props.onCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
};
export default LogModal;
import { Button, Form, Input, Select, notification, Upload, Cascader, InputNumber } from 'antd';
import React, { Component } from 'react';
import { connect } from 'dva';
import styles from '../style.less';
import { stateList } from '../staticdata';
import { batchPushedOrOffline, offline, uploadFile, importJdSkus } from '../service';
const FormItem = Form.Item;
const { Option } = Select;
@connect(({ goodsManage }) => ({
goodsManage,
}))
class goodsManage extends Component {
componentDidMount() {
this.props.onRef(this);
this.handleSearch();
}
getFieldsValue() {
const { form } = this.props;
return form.getFieldsValue();
}
handleSearch = () => {
this.props.handleSearch(1);
};
onReset = () => {
this.props.form.resetFields();
this.props.onReset();
};
onConfirm = async isOffline => {
const data = isOffline
? await offline(this.props.selectedRowKeys)
: await batchPushedOrOffline({
ids: this.props.selectedRowKeys.join(),
type: 2,
productState: 6,
offlineReason: '',
});
if (data.businessCode !== '0000') {
this.props.onLoad(data.error);
} else {
this.props.onLoad(null);
}
};
addSpu = () => {
this.props.addSpu();
};
addVirtualSpu = () => {
this.props.addVirtualSpu();
};
importSkus = async (info, type) => {
const result = await importJdSkus(info.file, type);
if (result.businessCode === '0000') {
this.handleSearch();
notification.success({
message: '商品上传成功',
});
} else {
notification.warning({
message: result.msg,
});
}
};
render() {
const {
form: { getFieldDecorator, getFieldValue },
treeData,
} = this.props;
const selectW = { width: 250 };
const iptNumWidth = { width: 118 };
const that = this;
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);
return (
<Form name="horizontal_login" layout="inline" className={styles.searchForm}>
<FormItem label="SKU编码">
{getFieldDecorator('skuId', {})(<Input allowClear style={selectW} />)}
</FormItem>
<FormItem label="商品名称">
{getFieldDecorator('skuName', {})(<Input allowClear style={selectW} />)}
</FormItem>
<FormItem label="类目">
{getFieldDecorator('productCategoryId', {})(
<Cascader
style={selectW}
changeOnSelect
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={treeData}
/>,
)}
</FormItem>
<FormItem label="第三方SKU编码">
{getFieldDecorator('thirdSkuNo', {})(<Input allowClear style={selectW} />)}
</FormItem>
<FormItem label="审核状态">
{getFieldDecorator('state', {})(
<Select style={selectW} allowClear showSearch filterOption={filterOption}>
{stateList?.map(item => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="供货价区间">
<FormItem className={styles.iptNumRight}>
{getFieldDecorator('supplyPriceMin', {})(<InputNumber style={iptNumWidth} />)}
</FormItem>
<span>--</span>
<FormItem className={styles.iptNumRight}>
{getFieldDecorator('supplyPriceMax', {})(
<InputNumber style={iptNumWidth} min={getFieldValue('supplyPriceMin')} />,
)}
</FormItem>
</FormItem>
<FormItem className={styles.queryBtn}>
<Button onClick={() => this.handleSearch()} type="primary" className={styles.button}>
查询
</Button>
<Button onClick={() => this.onReset()} type="primary" className={styles.button}>
重置
</Button>
</FormItem>
<FormItem style={{ width: '100%', textAlign: 'right' }}>
<Button type="primary" className={styles.button} onClick={this.addSpu}>
新增商品
</Button>
<Button
className={styles.button}
type="primary"
icon="download"
ghost
onClick={() => {
window.location.href =
'https://kdspstatic.q-gp.com/batchUpdateSkuImportTempletv3.xlsx';
}}
>
模版
</Button>
<Upload {...uploadProps}>
<Button type="primary" className={styles.button}>
批量库存修改
</Button>
</Upload>
</FormItem>
</Form>
);
}
}
export default Form.create()(goodsManage);
import { Modal, Table, Button } from 'antd';
import React, { Component } from 'react';
import styles from '../style.less';
class DetailModal extends Component {
getColumns = (firstSpe, secondSpe, type) => {
const arr = [
{
title: '供应商',
dataIndex: 'supplierName',
key: 'supplierName',
align: 'center',
width: 50,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.listLength) {
obj.props.rowSpan = row.listLength;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
},
];
if (firstSpe) {
arr.push({
title: firstSpe,
dataIndex: 'firstSpecValue',
key: 'firstSpecValue',
align: 'center',
width: 50,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.length) {
obj.props.rowSpan = row.length;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
});
}
if (secondSpe) {
arr.push({
title: secondSpe,
align: 'center',
key: 'secondSpecValue',
dataIndex: 'secondSpecValue',
width: 50,
});
}
const newTable = arr.concat([
{
title: '供应商编码',
align: 'center',
key: 'supplierCode',
dataIndex: 'supplierCode',
width: 50,
},
{
title: '库存',
align: 'center',
key: 'stock',
dataIndex: 'stock',
width: 50,
},
{
title: '供货价',
align: 'center',
key: 'supplyPrice',
dataIndex: 'supplyPrice',
width: 50,
},
{
title: '市场价',
align: 'center',
key: 'marketPrice',
dataIndex: 'marketPrice',
width: 50,
},
{
title: '国际编码',
align: 'center',
key: 'thirdSkuNo',
dataIndex: 'thirdSkuNo',
width: 50,
},
]);
if (type === 1) {
newTable.splice(newTable.length - 1, 0, {
title: '重量(kg)',
align: 'center',
key: 'weight',
dataIndex: 'weight',
width: 90,
});
}
return newTable;
};
updateStatus = (row, productState) => {
this.props.updateStatus(row, productState);
};
dataInit = list => {
const obj = {};
let finialList = [];
list.map(item => {
obj[item.firstSpecValue] = [];
return obj;
});
list.map(item => obj[item.firstSpecValue].push(item));
const keys = Object.keys(obj);
// eslint-disable-next-line no-return-assign
keys.map(key => {
obj[key].forEach((i, index) => {
if (index === 0) {
i.length = obj[key].length;
}
});
finialList = finialList.concat(obj[key]);
return finialList;
});
if (finialList.length) {
finialList[0].listLength = finialList.length;
}
return finialList;
};
render() {
const { visible, data = [], type } = this.props;
const initdata = this.dataInit(data);
const firstName = data.length ? data[0].firstSpec : '';
const secondName = data.length ? data[0].secondSpec : '';
return (
<Modal
title="供货详情"
visible={visible}
footer={null}
onCancel={this.props.onCancel}
width="900px"
>
<Table
dataSource={initdata}
bordered
columns={this.getColumns(firstName, secondName, type)}
rowKey="id"
pagination={false}
scroll={{ x: '100%' }}
/>
<Button type="primary" onClick={this.props.onCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
}
}
export default DetailModal;
import { Modal, InputNumber, notification } from 'antd';
import React, { useState, useEffect } from 'react';
import { updatePrice } from '../service';
import styles from './style.less';
export default function UpdatePrice(props) {
const [visible, setVisible] = useState(props.visible);
const [supplyPrice, setSupplyPrice] = useState(props.info.supplyPrice);
const [marketPrice, setMarketPrice] = useState(props.info.marketPrice);
// const [salePrice, setSalePrice] = useState(props.info.salePrice);
useEffect(() => {
setVisible(props.visible);
setSupplyPrice(props.info.supplyPrice);
setMarketPrice(props.info.marketPrice);
// setSalePrice(props.info.salePrice);
}, [props]);
const submit = async () => {
if (!supplyPrice || !marketPrice) {
notification.error({ message: '价格不可为空!' });
return;
}
const error = await updatePrice({
// salePrice,
supplyPrice,
marketPrice,
id: props.info.id,
supplierId: props.info.supplierId,
});
if (!error) {
notification.success({ message: '修改成功' });
props.onCancel('success');
}
};
return (
<Modal
title="修改价格"
visible={visible}
onCancel={() => props.onCancel()}
onOk={submit}
width={400}
>
<div className={styles.center}>
供货价:
<InputNumber
min={0}
precision={2}
value={supplyPrice}
onChange={value => setSupplyPrice(value)}
className={styles.inputNW}
/>
<br />
<br />
市场价:
<InputNumber
min={0}
precision={2}
value={marketPrice}
onChange={value => setMarketPrice(value)}
className={styles.inputNW}
/>
<br />
<br />
{/* 销售价:
<InputNumber
min={0}
precision={2}
value={salePrice}
onChange={value => setSalePrice(value)}
className={styles.inputNW}
/> */}
</div>
</Modal>
);
}
.inputNW {
width: 200px;
}
.center {
text-align: center;
}
import { Modal, InputNumber, notification, Form, Input, Radio } from 'antd';
import React from 'react';
import { updateStock } from '../service';
import styles from '../UpdatePrice/style.less';
const UpdateStock = props => {
const { getFieldDecorator, validateFields, resetFields, getFieldValue } = props.form;
const valueInfo = props.info;
getFieldDecorator('stockChangeType', { initialValue: 1 });
const submit = async () => {
validateFields(async (err, { stock, changeReason, stockChangeType }) => {
if (err) return;
const error = await updateStock({
stock,
id: valueInfo.id,
supplierId: valueInfo.supplierId,
stockChangeType,
changeReason,
});
if (!error) {
notification.success({ message: '操作成功!' });
props.onCancel('success');
resetFields();
}
});
};
const onCancel = () => {
props.onCancel();
resetFields();
};
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const validatorCallback = (rule, value, callback) => {
// 减库存存时,校验可售库存-输入值>=0,即可售库存不可为负;
const stockChangeType = getFieldValue('stockChangeType');
const increment = valueInfo.marketableStock - value;
return !stockChangeType && increment < 0 ? callback(new Error(rule.message)) : callback();
};
return (
<Modal title="修改库存" visible={props.visible} onCancel={onCancel} onOk={submit} width={400}>
<Form {...formItemLayout}>
<Form.Item label="变更类型:">
{getFieldDecorator('stockChangeType', {
rules: [{ required: true, message: '请选择类型!' }],
})(
<Radio.Group>
<Radio value={1}>增库存</Radio>
<Radio value={0}>减库存</Radio>
</Radio.Group>,
)}
</Form.Item>
<Form.Item label="库存数:">
{getFieldDecorator('stock', {
rules: [
{ required: true, message: '请输入库存!' },
{ validator: validatorCallback, message: '减库存,输入库存数不可大于可售库存!' },
],
validateTrigger: ['onSubmit'],
})(
<InputNumber
min={0}
precision={0}
placeholder="请输入库存"
className={styles.inputNW}
/>,
)}
</Form.Item>
<Form.Item label="变更原因:">
{getFieldDecorator('changeReason', {
rules: [{ required: true, message: '请输入变更原因!' }],
initialValue: valueInfo.changeReason,
})(<Input.TextArea />)}
</Form.Item>
</Form>
</Modal>
);
};
export default Form.create()(UpdateStock);
/* eslint-disable no-param-reassign */
import { Form, Select, Input, InputNumber, Button } from 'antd';
import React, { Component } from 'react';
// import styles from '../style.less';
const { Option } = Select;
const FormItem = Form.Item;
class goodsManage extends Component {
componentDidMount() {
this.props.onRef(this);
}
setFiled = flag => {
const { form } = this.props;
if (flag === 'firstKeys') {
form.setFieldsValue({ firstSpecValue: '' });
return;
}
form.setFieldsValue({ secondSpecValue: '' });
};
batchSetting = () => {
const { form, editData, isEdit } = this.props;
const data = form.getFieldsValue();
if (!data.firstSpecValue && !data.secondSpecValue) {
editData.forEach(item => {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock; // 编辑状态不可修改库存
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
});
}
if (data.firstSpecValue && !data.secondSpecValue) {
editData.forEach(item => {
if (item.firstSpecValue === data.firstSpecValue) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
if (!data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (item.secondSpecValue === data.secondSpecValue) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
if (data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (
item.firstSpecValue === data.firstSpecValue &&
item.secondSpecValue === data.secondSpecValue
) {
item.marketPrice = data.marketPrice;
// item.salePrice = data.salePrice;
if (!isEdit) item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
}
});
}
this.props.batchSetting(editData);
};
render() {
const {
firstSpes = [],
secondSpecs = [],
firstSpesName = '',
secondSpesName = '',
productType,
isEdit,
} = this.props;
const { getFieldDecorator } = this.props.form;
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem>
{getFieldDecorator('firstSpecValue', {})(
<Select allowClear style={{ width: 120 }} placeholder={firstSpesName}>
{firstSpes.length > 0 &&
firstSpes.map(
item =>
item &&
typeof item === 'string' && (
<Option key={item} value={item}>
{item}
</Option>
),
)}
</Select>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('secondSpecValue', {})(
<Select allowClear style={{ width: 120 }} placeholder={secondSpesName}>
{secondSpecs.length &&
secondSpecs.map(
item =>
item &&
typeof item === 'string' && (
<Option key={item} value={item}>
{item}
</Option>
),
)}
</Select>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('supplyPrice', {})(
<Input placeholder="供货价" style={{ width: 100 }} />,
)}
</FormItem>
<FormItem>
{getFieldDecorator('marketPrice', {})(
<Input placeholder="市场价" style={{ width: 100 }} />,
)}
</FormItem>
{/* <FormItem>
{getFieldDecorator('salePrice', {})(
<Input placeholder="销售价" style={{ width: 100 }} />,
)}
</FormItem> */}
{productType === 1 && (
<FormItem>
{getFieldDecorator('weight', {})(
<InputNumber
precision={3}
max={999999.999}
// eslint-disable-next-line radix
placeholder="重量"
style={{ width: 150 }}
/>,
)}
</FormItem>
)}
{!isEdit && (
<FormItem>
{getFieldDecorator('productStock', {})(
<InputNumber
precision={0}
step={1}
// eslint-disable-next-line radix
formatter={val => parseInt(val, '10') || ''}
placeholder="库存"
style={{ width: 100 }}
/>,
)}
</FormItem>
)}
<FormItem>
<Button type="primary" htmlType="submit" onClick={this.batchSetting}>
批量设置
</Button>
</FormItem>
</Form>
);
}
}
export default Form.create()(goodsManage);
import { Row, Col, Button } from 'antd';
import React, { Component } from 'react';
import styles from '../style.less';
// eslint-disable-next-line react/prefer-stateless-function
class ButtonGroup extends Component {
render() {
const { initData, confirmLoading } = this.props;
return (
<Row type="flex" justify="center" align="middle" gutter={20}>
<Col>
<Button type="primary" onClick={() => this.props.onCancel()} className={styles.logBtn}>
取消
</Button>
</Col>
<Col key="submit">
<Button
type="primary"
onClick={() => this.props.confirm()}
className={styles.logBtn}
loading={confirmLoading}
disabled={confirmLoading}
>
提交
</Button>
</Col>
{initData && !Object.keys(initData).length && (
<Col key="submit-add">
<Button
type="primary"
onClick={() => this.props.confirm(true)}
className={styles.logBtn}
loading={confirmLoading}
disabled={confirmLoading}
>
提交并继续添加
</Button>
</Col>
)}
</Row>
);
}
}
export default ButtonGroup;
/* eslint-disable no-param-reassign */
import {
Modal,
Table,
Button,
Form,
Select,
Input,
Icon,
Row,
Col,
Checkbox,
notification,
Cascader,
Popover,
Card,
Radio,
} from 'antd';
import React, { Component } from 'react';
import SuperSelect from 'antd-virtual-select';
import _ from 'lodash';
import ButtonGroup from './buttonGroup';
import {
normFile,
editColumns,
specValidate,
dataInit,
validateSpuInfo,
createEditData,
} from './mixin';
import { getSpecList, getJdPicList, getBrandList, addGoods, editGoods } from '../service';
import styles from '../style.less';
import Upload from '../../components/sortablUpload';
import BatchSetting from './batchSetting';
import SkuNameChange from './skuNameChange';
import { productTypeList } from '../staticdata';
const FormItem = Form.Item;
const { Option } = Select;
class goodsManage extends Component {
state = {
// step: 1,
// editStep: 1,
categoryId: null, // 类目选择
specList: [], // 规格
normalBrandList: [], // 品牌
brandList: [], // 虚拟商品的 品牌
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: false,
initCascader: null,
batchDeleteImgObj: {},
editRow: {},
editRowIndex: null,
skuNameVisible: false,
productType: 1,
confirmLoading: false,
};
componentDidMount() {
this.getSpecData();
this.getBrandData();
}
componentWillReceiveProps() {
this.setState({ isCancel: false });
}
// 获取规格列表
getSpecData = async () => {
const { data } = await getSpecList();
if (data) {
this.setState({ specList: data });
}
};
inputChange = (value, key, index) => {
this.setState(prev => {
prev.editData[index][key] = value;
return { editData: prev.editData };
});
};
// eslint-disable-next-line consistent-return
getJdPicList = async row => {
// 获取图片需要设置轮播图和详情图
const {
form: { setFieldsValue, getFieldsValue },
} = this.props;
const { detailImageList = [] } = getFieldsValue(['detailImageList']);
if (!row.skuLink) {
return notification.warning({ message: '请输入京东链接再获取图片' });
}
const { firstSpecId, firstSpecValue, secondSpecId, secondSpecValue, skuLink } = row;
const data = await getJdPicList({
firstSpecId,
firstSpecValue,
secondSpecId,
secondSpecValue,
skuLink,
jdSkuInfoUrl: row.skuLink,
});
if (!data) return false;
const detailList = data.detailList || [];
const { colorImg, editData } = this.state;
colorImg[data.firstSpecValue] = colorImg[data.firstSpecValue]
? colorImg[data.firstSpecValue].concat(data.carouseList || [])
: data.carouseList || [];
editData.forEach(i => {
i.imageList = colorImg[i.firstSpecValue];
return i;
});
this.setState(
() => ({ colorImg, editData }),
() => {
setFieldsValue({
[`imageList[${data.firstSpecValue}]`]: this.state.colorImg[data.firstSpecValue],
detailImageList: [...detailImageList, ...detailList],
});
},
);
};
changeName = name => {
const { editData } = this.state;
editData[this.state.editRowIndex].name = name;
this.setState({ editData, skuNameVisible: false });
};
openSkuNameModal = (row, index) => {
this.setState({
editRowIndex: index,
editRow: row,
skuNameVisible: true,
});
};
getBrandData = async () => {
const { data } = await getBrandList();
const list = data && data.filter(item => item.name === '虚拟商品');
this.setState({ normalBrandList: data, brandList: list });
};
changeStep = () => {
const {
form: { setFieldsValue },
initData,
} = this.props;
if (Object.keys(initData).length) {
this.setState({
initForm: initData,
editData: initData.editData,
productType: initData.productType,
});
setFieldsValue({
detailImageList: initData.detailImageList || [],
imageList: initData.imageList || {},
});
}
};
// 选择规格
specChange = (flag, specName) => {
const { initForm } = this.state;
if (flag === 'first') {
initForm.firstSpecName = specName;
this.setState({ initForm });
return;
}
initForm.secondSpecName = specName;
this.setState({ initForm });
};
treeChange = value => {
this.setState({ categoryId: value[value.length - 1], initCascader: value });
};
remove = (flag, k) => {
const { form } = this.props;
const keys = form.getFieldValue(flag);
if (flag === 'firstKeys') {
form.setFieldsValue(
{
firstKeys: keys.filter(key => key !== k),
},
() => {
const firstKeys = form.getFieldValue('firstKeys');
const first = form.getFieldValue('first');
form.setFieldsValue({
first: _.pick({ first, firstKeys }),
});
},
);
this.BatchSetting.setFiled(flag);
return;
}
form.setFieldsValue(
{
secondKeys: keys.filter(key => key !== k),
},
() => {
const secondKeys = form.getFieldValue('secondKeys');
const second = form.getFieldValue('second');
form.setFieldsValue({
second: _.pick({ second, secondKeys }),
});
},
);
this.BatchSetting.setFiled(flag);
};
add = flag => {
const { form } = this.props;
if (flag === 'firstKeys' && !form.getFieldValue('firstSpecId')) {
notification.error({
message: '请先选择一级规格数据',
});
return;
}
if (flag === 'secondKeys' && !form.getFieldValue('secondSpecId')) {
notification.error({
message: '请先选择二级规格数据',
});
return;
}
const keys = form.getFieldValue(flag);
const nextKeys = keys.concat(+this.state.count + 1);
this.setState(prev => ({ count: +prev.count + 1 }));
if (flag === 'firstKeys') {
form.setFieldsValue({
firstKeys: nextKeys,
});
return;
}
form.setFieldsValue({
secondKeys: nextKeys,
});
};
imgChange = (data, spec) => {
const { colorImg } = this.state;
colorImg[spec] = data;
this.setState({ colorImg });
this.setState(prev => ({
editData: prev.editData.map(item => {
if (`${item.firstSpecValue}` === spec) {
item.imageList = prev.colorImg[spec];
}
return item;
}),
}));
};
batchSet = editData => {
this.setState({ editData });
};
pullImg = (data, spec) => {
this.imgChange(data, spec);
const { form } = this.props;
form.setFieldsValue({
[`imageList[${spec}]`]: data,
});
};
createShopInfo = () => {
const {
form: { getFieldsValue },
initData,
} = this.props;
const values = getFieldsValue();
const editData = createEditData(values, initData);
this.setState({ editData });
};
deleteImg = () => {
const {
form: { setFieldsValue },
} = this.props;
const { batchDeleteImgObj, colorImg, editData } = this.state;
const deleteKeys = Object.keys(batchDeleteImgObj);
deleteKeys.map(spec => {
if (batchDeleteImgObj[spec]) {
batchDeleteImgObj[spec] = false;
if (spec === 'detailImageList') {
setFieldsValue({
detailImageList: [],
});
return false;
}
if (spec === 'commonImageList') {
setFieldsValue({
commonImageList: [],
});
return false;
}
colorImg[spec] = [];
editData.map(item => {
if (item.firstSpecValue === spec) {
item.imageList = [];
}
return item;
});
setFieldsValue({
[`imageList[${spec}]`]: [],
});
}
return spec;
});
this.setState({ colorImg, editData, batchDeleteImgObj });
};
chooseRollImg = spec => {
this.setState(state => {
state.batchDeleteImgObj[spec] = !state.batchDeleteImgObj[spec];
return { batchDeleteImgObj: state.batchDeleteImgObj };
});
};
// 最终提交
confirm = async isContinue => {
const {
form: { validateFields },
initData,
} = this.props;
const { editData, productType } = this.state;
validateFields(async (errors, values) => {
let imgErr = false;
if (!errors) {
if (validateSpuInfo(values, initData, editData, productType)) return;
editData.forEach(item => {
item.productStockWarning = item.productStockWarning < 0 ? 0 : item.productStockWarning;
if (!item.imageList || !item.imageList.length) {
item.imageList = values.commonImageList || [];
}
if (!item.imageList || !item.imageList.length) {
imgErr = true;
}
});
// 虚拟商品时不校验必 滑动图不全,请检查!
if (imgErr && productType === 1) {
notification.error({ message: '滑动图不全,请检查!' });
return;
}
await this.setState(prev => ({
confirmLoading: true,
createData: {
id: initData.id || '',
name: values.name,
items: prev.editData,
brandId: values.brandId,
supplierId: values.supplierId,
detailImageList: values.detailImageList,
commonImageList: values.commonImageList || [],
categoryId: prev.categoryId ? prev.categoryId : initData?.thirdCategoryId,
// productType: values.productType,
type: values.productType,
},
}));
const data = initData.id
? await editGoods(this.state.createData)
: await addGoods(this.state.createData);
if (data.businessCode === '0000') {
notification.success({
message: '商品保存成功!',
});
this.props.query();
if (isContinue) {
this.setState({
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
confirmLoading: false,
});
this.props.form.resetFields();
return;
}
this.onCancel();
}
this.setState({
confirmLoading: false,
});
}
});
};
onCancel = () => {
this.setState(
{
categoryId: null, // 类目选择
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: true,
initCascader: null,
productType: 1,
},
() => {
this.BatchSetting.props.form.resetFields();
this.props.form.resetFields();
this.props.onCancel();
},
);
};
bundleRadioChange = event => {
this.setState({
categoryId: null, // 类目选择
editData: [], // sku表格
createData: {}, // 返显
count: -1, // 规格值起始序号+1
colorImg: {}, // 一级规格是颜色时,color: [imgList]
initForm: {}, // 返显
isCancel: true,
initCascader: null,
productType: event.target.value,
});
this.props.form.resetFields();
this.BatchSetting.props.form.resetFields();
};
render() {
const { visible, form, initData = {}, treeData, virtualTreeData } = this.props;
const isEdit = Object.keys(initData).length !== 0;
const { getFieldDecorator, getFieldsValue } = form;
getFieldDecorator('firstKeys', { initialValue: [] });
getFieldDecorator('secondKeys', { initialValue: [] });
const { first = [], second, commonImageList, firstKeys, secondKeys, name } = getFieldsValue();
const isFirstSame = specValidate(first, initData.firstSpecList, initData.id);
const isSecondSame = specValidate(second, initData.secondSpecList, initData.id);
if (isFirstSame) first.pop();
if (isSecondSame) second.pop();
const colorKeys = isEdit
? initData.firstSpecList.concat(first || this.state.initForm.first || [])
: first || this.state.initForm.first || [];
if (isEdit && !Object.keys(this.state.initForm).length && !this.state.isCancel) {
this.changeStep(1);
}
const isJDGoods = initData.id && initData.pageProductType && +initData.pageProductType !== 1;
const initCascader = isEdit
? [initData.firstCategoryId, initData.secondCategoryId, initData.thirdCategoryId]
: this.state.initCascader;
const formItemLayout = {
labelCol: {
span: 5,
},
};
const formItemProduct = {
labelCol: { span: 2 },
wrapperCol: { span: 22 },
};
const { colorImg, productType, normalBrandList, brandList, confirmLoading } = this.state;
const skuSpeFirstKeys = initData.firstSpecList || [];
const skuSpeSecondKeys = initData.secondSpecList || [];
const treeDataArray = productType === 2 ? virtualTreeData : treeData;
const brandListArray = productType === 2 ? brandList : normalBrandList;
dataInit(this.state.editData);
const filterOption = (input, op) => op.props.children.includes(input);
return (
<Modal
title={initData.id ? '修改商品' : '新增商品'}
visible={visible}
footer={null}
onCancel={this.onCancel}
width="1050px"
bodyStyle={{ background: 'rgb(247 249 249)' }}
>
<Form name="horizontal_login">
<div>
<Card className={styles.card} bordered={false}>
<Row type="flex" gutter={24}>
<Col span={24}>
<Form.Item label="商品类型:" {...formItemProduct}>
{getFieldDecorator('productType', {
initialValue: productType,
rules: [{ required: true, message: '请选择商品类型' }],
})(
<Radio.Group disabled={isEdit} onChange={this.bundleRadioChange}>
{productTypeList.map(item => (
<Radio key={item.value} value={item.value} disabled={item.disabled}>
{item.label}
</Radio>
))}
</Radio.Group>,
)}
</Form.Item>
</Col>
<Col span={24}>
<FormItem label="类目:" {...formItemProduct}>
{getFieldDecorator('categoryId', {
initialValue: initCascader,
rules: [
{
required: true,
message: '请选择类目',
},
],
})(
<Cascader
style={{ width: 690 }}
changeOnSelect
showSearch
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={treeDataArray}
onChange={(val, label, ext) => this.treeChange(val, label, ext)}
disabled={isEdit}
/>,
)}
</FormItem>
</Col>
</Row>
</Card>
<Card className={styles.card} bordered={false}>
<Row
gutter={10}
style={{ background: '#fff', borderRadius: '6px', padding: '20px 0 5px 0' }}
>
<Col span={10}>
<FormItem label="商品品牌" {...formItemLayout}>
{getFieldDecorator('brandId', {
initialValue: this.state.initForm.brandId,
rules: [
{
required: true,
message: '请选择商品品牌',
},
],
})(
<SuperSelect
allowClear
showSearch
style={{ width: 190 }}
filterOption={filterOption}
>
{brandListArray?.length &&
brandListArray.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</SuperSelect>,
)}
</FormItem>
</Col>
</Row>
<Row type="flex">
<Col span={24}>
<Popover content={name} trigger="hover">
<FormItem label="商品名称" labelCol={{ span: 2 }}>
{getFieldDecorator('name', {
initialValue: initData.name,
rules: [
{
required: true,
message: '请输入商品名称',
},
],
})(<Input style={{ width: 690 }} allowClear />)}
</FormItem>
</Popover>
{isJDGoods && (
<div className={styles.warning}>
*本列表的商品名称仅供搜索使用,不在前端作展示。若要修改APP端展示的商品名称,请在商品信息中修改。
</div>
)}
</Col>
</Row>
</Card>
<Card className={styles.card} bordered={false}>
<Row>
<FormItem label="一级规格">
{getFieldDecorator('firstSpecId', {
initialValue: this.state.initForm.firstSpecId,
})(
<Select
allowClear
showSearch
style={{ width: 200 }}
disabled={isEdit}
onChange={(val, option) =>
this.specChange('first', option?.props.children, val)
}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{this.state.specList.length &&
this.state.specList.map(item => (
<Option key={item.specId} value={item.specId}>
{item.specName}
</Option>
))}
</Select>,
)}
</FormItem>
</Row>
<Row type="flex">
{skuSpeFirstKeys.length > 0 &&
skuSpeFirstKeys.map(
k =>
k !== 'null' && (
<Input
key={k}
value={k}
disabled
placeholder="请输入规格名称"
style={{ width: '15%', margin: 5 }}
/>
),
)}
{firstKeys.map(k => (
<FormItem label="" required={false} key={k}>
{getFieldDecorator(`first[${k}]`, {
initialValue: this.state.initForm.first && this.state.initForm.first[k],
validateTrigger: ['onChange', 'onBlur'],
rules: [
{
required: true,
message: '请输入',
},
],
})(
<Input
key={k}
placeholder="请输入规格名称"
style={{ width: '60%', marginRight: 8 }}
/>,
)}
{firstKeys.length > 0 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove('firstKeys', k)}
/>
) : null}
</FormItem>
))}
<FormItem>
<Button
type="dashed"
disabled={isJDGoods}
onClick={() => this.add('firstKeys')}
style={{ textAlign: 'center' }}
>
<Icon type="plus" />
</Button>
</FormItem>
</Row>
<Row>
<FormItem label="二级规格">
{getFieldDecorator('secondSpecId', {
initialValue: this.state.initForm.secondSpecId,
})(
<Select
allowClear
showSearch
disabled={isEdit}
style={{ width: 200 }}
onChange={(val, option) => this.specChange('second', option?.props.children)}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{this.state.specList.length &&
this.state.specList.map(item => (
<Option key={item.specId} value={item.specId}>
{item.specName}
</Option>
))}
</Select>,
)}
</FormItem>
</Row>
<Row type="flex">
{skuSpeSecondKeys.length > 0 &&
skuSpeSecondKeys.map(k => (
<Input
key={k}
value={k}
disabled
placeholder="请输入规格名称"
style={{ width: '15%', margin: 5 }}
/>
))}
{secondKeys.map(k => (
<FormItem label="" required={false} key={k}>
{getFieldDecorator(`second[${k}]`, {
initialValue:
(this.state.initForm.second && this.state.initForm.second[k]) || '',
validateTrigger: ['onChange', 'onBlur'],
})(<Input key={k} style={{ width: '60%', marginRight: 8 }} />)}
{secondKeys.length > 0 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove('secondKeys', k)}
/>
) : null}
</FormItem>
))}
<FormItem>
<Button
type="dashed"
onClick={() => this.add('secondKeys')}
style={{ textAlign: 'center' }}
disabled={isJDGoods}
>
<Icon type="plus" />
</Button>
</FormItem>
</Row>
</Card>
<Row>
<Button
type="primary"
className={styles.logBtn}
onClick={() => this.createShopInfo()}
>
生成商品信息
</Button>
</Row>
<BatchSetting
firstSpes={first ? skuSpeFirstKeys.concat(first) : skuSpeFirstKeys}
secondSpecs={skuSpeSecondKeys.concat(second || [])}
onRef={ref => {
this.BatchSetting = ref;
}}
editData={this.state.editData}
firstSpesName={this.state.initForm.firstSpecName}
secondSpesName={this.state.initForm.secondSpecName}
batchSetting={this.batchSet}
productType={productType}
isEdit={isEdit}
isJDGoods={isJDGoods}
/>
<Row>
<Table
bordered
rowKey="id"
pagination={false}
scroll={{ y: 300, x: 1200 }}
dataSource={this.state.editData}
columns={editColumns(
{
getJdPicList: this.getJdPicList,
inputChange: this.inputChange,
openModal: this.openSkuNameModal,
productType,
},
this.state.editData?.length ? this.state.editData[0] : null,
this.state.initForm.firstSpecName,
this.state.initForm.secondSpecName,
isJDGoods,
isEdit,
)}
/>
</Row>
<Row>
<Button
type="danger"
size="small"
onClick={this.deleteImg}
style={{ marginTop: '10px' }}
icon="rest"
>
图片批量删除
</Button>
<Row>
<Col span={1}>
<Checkbox
value="commonImageList"
checked={this.state.batchDeleteImgObj.commonImageList}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg('commonImageList')}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem label="公共滑动图">
{getFieldDecorator('commonImageList', {
initialValue: this.state.initForm.commonImageList,
valuePropName: 'fileList',
getValueFromEvent: normFile,
})(<Upload />)}
</FormItem>
</Col>
</Row>
{colorKeys.length > 0 &&
colorKeys.map(color => {
if (JSON.stringify(color) === '{}') return [];
if ((color !== null && !color) || (color && !color.trim())) return [];
return [
<Row>
<Col span={1}>
<Checkbox
value={color}
checked={this.state.batchDeleteImgObj[color]}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg(color)}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem
key={color}
label={`滑动图${color === 'null' ? '' : `(${color})`}:`}
>
{getFieldDecorator(`imageList[${color}]`, {
initialValue:
this.state.initForm.imageList && this.state.initForm.imageList[color],
valuePropName: 'fileList',
getValueFromEvent: normFile,
})(<Upload onChange={val => this.imgChange(val, color)} />)}
<Button
disabled={colorImg[color]?.length}
size="small"
type="primary"
className={styles.pullBtn}
onClick={() => this.pullImg(commonImageList, color)}
>
拉取公共图像
</Button>
</FormItem>
</Col>
</Row>,
];
})}
</Row>
<Row>
<Col span={1}>
<Checkbox
value="detailImageList"
checked={this.state.batchDeleteImgObj.detailImageList}
style={{ marginTop: '12px' }}
onChange={() => this.chooseRollImg('detailImageList')}
></Checkbox>
</Col>
<Col span={23} className={styles.imgBorder}>
<FormItem label="详情图">
{getFieldDecorator('detailImageList', {
initialValue: this.state.initForm.detailImageList,
valuePropName: 'fileList',
getValueFromEvent: normFile,
rules: [
{
required: productType !== 2,
message: '请上传详情图',
},
],
})(<Upload />)}
</FormItem>
</Col>
</Row>
</div>
</Form>
<ButtonGroup
key="button-group"
initData={initData}
changeStep={this.changeStep}
onCancel={this.onCancel}
confirm={this.confirm}
confirmLoading={confirmLoading}
></ButtonGroup>
<SkuNameChange
visible={this.state.skuNameVisible}
data={this.state.editRow.name}
changeSkuName={data => this.changeName(data)}
onCancle={() => this.setState({ skuNameVisible: false })}
></SkuNameChange>
</Modal>
);
}
}
export default Form.create()(goodsManage);
/* eslint-disable no-param-reassign */
import React from 'react';
import { Input, Button, notification, Popover, InputNumber } from 'antd';
import { sortBy } from 'lodash';
const KEYLIST = [
'id',
'name',
'skuLink',
'imageList',
'firstSpecId',
'secondSpecId',
'supplyPrice',
'productStockWarning',
'firstSpec',
'firstSpecValue',
'secondSpec',
'secondSpecValue',
];
export function normFile(fileList) {
return fileList;
}
export function specValidate(editArr, initArr, isEdit, msg) {
let isSame = false;
if (editArr && editArr.length) {
const list = isEdit ? editArr.concat(initArr) : editArr;
list.forEach((i, index) => {
const index1 = list.indexOf(i);
if (index1 !== index) {
isSame = true;
}
});
}
if (isSame && msg) {
notification.error({
message: msg,
});
}
return isSame;
}
export function createNewList(first, second, firstSpecId, secondSpecId) {
const list = [];
const sku = {
weight: '',
productStockWarning: '',
marketPrice: '',
// salePrice: '',
supplyPrice: '',
// stock: '',
productStock: '',
thirdSkuNo: '',
skuLink: '',
imageList: [],
};
if (first && first.length) {
// 一级规格有值时,生成的编辑表格
first.forEach(fir => {
const copy = Object.assign({}, sku);
copy.firstSpecId = firstSpecId;
copy.firstSpecValue = fir;
if (second.length) {
second.forEach(sec => {
const copySec = Object.assign({}, copy);
copySec.secondSpecId = secondSpecId;
copySec.secondSpecValue = sec;
list.push(copySec);
});
return;
}
list.push(copy);
});
} else if (second.length) {
// 缺少一级规格值,只有二级规格值时,生成的编辑表格
second.forEach(sec => {
const copy = Object.assign({}, sku);
copy.secondSpecId = secondSpecId;
copy.secondSpecValue = sec;
list.push(copy);
});
} else {
// 缺少一级和二级规格时生成的编辑表格
list.push(sku);
}
return list;
}
export function createEditData(values, initData) {
let list = null;
const first = values.first?.filter(item => item && !item.match(/^[ ]*$/)) || [];
const second =
(values.second && values.second.filter(item => item && !item.match(/^[ ]*$/))) || [];
const newFirst = first.concat(initData.firstSpecList || []);
const isFirstSame = specValidate(
first,
initData.firstSpecList,
initData.id,
'一级规格值不可重复',
);
const isSecondSame = specValidate(
second,
initData.secondSpecList,
initData.id,
'二级规格值不可重复',
);
if (!isFirstSame && !isSecondSame) {
if (!initData.id) {
list = createNewList(first, second, values.firstSpecId, values.secondSpecId);
// this.setState({ editData: list });
} else {
const list1 = first.length
? createNewList(first, initData.secondSpecList, values.firstSpecId, values.secondSpecId)
: [];
const list2 = second.length
? createNewList(newFirst, second, values.firstSpecId, values.secondSpecId)
: [];
list = sortBy(initData.editData.concat(list1.concat(list2)), item => item.firstSpecValue);
}
}
return list;
}
export function validateSpuInfo(values, initData, editData, productType) {
const checkKeyList = productType === 1 ? KEYLIST : [...KEYLIST, 'weight'];
let flag = false;
editData.forEach(item => {
const keys = Object.keys(item);
keys.forEach(key => {
if (checkKeyList.includes(key)) {
return;
}
// console.log(key, item, item[key], (item[key] === undefined) || (item[key] === null));
// if (!`${item[key]}`) {
if (item[key] === undefined || item[key] === null || item[key] === '') {
flag = true;
}
});
});
if (flag) {
notification.error({
message: '请完善商品表格!',
});
}
return flag;
}
export function dataInit(list) {
if (!list || !list.length) return;
const obj = {};
let finialList = [];
list.forEach(item => {
obj[item.firstSpecValue] = [];
});
list.forEach(item => obj[item.firstSpecValue].push(item));
const keys = Object.keys(obj);
keys.forEach(key => {
obj[key].forEach((i, index) => {
if (index === 0) {
i.length = obj[key].length;
}
});
finialList = finialList.concat(obj[key]);
return finialList;
});
}
export function verify(initData, categoryId) {
if (categoryId || (initData && initData.thirdCategoryId)) {
return true;
}
notification.error({
message: '请选择三级类目',
});
return false;
}
export function editColumns(methods, firstData, firstSpec, secondSpec, isJDGoods, isEdit) {
const { getJdPicList, inputChange, openModal, productType } = methods;
if (!firstData) return [];
const arr = [
{
title: '供货价',
align: 'center',
key: 'supplyPrice',
dataIndex: 'supplyPrice',
width: 100,
render: (val, row, index) => (
<InputNumber
defaultValue={val}
value={val}
precision={2}
min={0}
onChange={value => inputChange(value, 'supplyPrice', index)}
disabled={isJDGoods}
/>
// <Input
// value={val}
// className={`supplyPrice ${index}`}
// defaultValue={val}
// onChange={evt => inputChange(evt.target.value, 'supplyPrice', index)}
// disabled={isJDGoods}
// />
),
},
{
title: '市场价',
align: 'center',
key: 'marketPrice',
dataIndex: 'marketPrice',
width: 100,
render: (val, row, index) => (
<InputNumber
defaultValue={val}
value={val}
precision={2}
min={0}
onChange={value => inputChange(value, 'marketPrice', index)}
/>
// <Input
// value={val}
// className={`marketPrice ${index}`}
// defaultValue={val}
// onChange={evt => inputChange(evt.target.value, 'marketPrice', index)}
// />
),
},
{
title: '库存',
align: 'center',
key: 'productStock',
dataIndex: 'productStock',
width: 100,
render: (val, row, index) => (
<InputNumber
value={val}
precision={0}
min={0}
onChange={value => inputChange(value, 'productStock', index)}
disabled={isEdit && row.id}
/>
),
},
{
title: '商品自编码',
align: 'center',
key: 'thirdSkuNo',
dataIndex: 'thirdSkuNo',
width: 100,
render: (val, row, index) => (
<Input
value={val}
className={`thirdSkuNo ${index}`}
defaultValue={val}
onChange={evt => inputChange(evt.target.value, 'thirdSkuNo', index)}
/>
),
},
// {
// title: 'sku名称',
// align: 'center',
// key: 'name',
// dataIndex: 'name',
// width: 100,
// render: val => (
// <Popover content={val} trigger="hover">
// <a>查看名称</a>
// </Popover>
// ),
// },
{
title: '京东链接',
align: 'center',
key: 'skuLink',
dataIndex: 'skuLink',
width: 80,
render: (val, row, index) => (
<Popover content={val} trigger="hover">
{
<Input
value={val}
className={`skuLink ${index}`}
defaultValue={val}
onChange={evt => inputChange(evt.target.value, 'skuLink', index)}
/>
}
</Popover>
),
},
{
title: '操作',
align: 'center',
key: 'option',
dataIndex: 'option',
width: 100,
render: (_, row, index) => (
<div>
{row.skuLink && (
<Button type="primary" size="small" onClick={() => getJdPicList(row)}>
拉图片
</Button>
)}
{isJDGoods && (
<Button
type="primary"
size="small"
style={{ marginTop: '5px' }}
onClick={() => openModal(row, index)}
>
修改sku名称
</Button>
)}
</div>
),
},
];
if (productType === 1) {
// 如果是实体商品添加重量
arr.splice(2, 0, {
title: '重量(kg)',
align: 'center',
key: 'weight',
dataIndex: 'weight',
width: 120,
render: (val, row, index) => (
<InputNumber
style={{ width: '100%' }}
value={val}
min={0}
max={999999.999}
precision={3}
onChange={value => inputChange(value, 'weight', index)}
/>
),
});
arr.splice(4, 0, {
title: '库存预警',
align: 'center',
key: 'productStockWarning',
dataIndex: 'productStockWarning',
width: 90,
render: (val, row, index) => (
<InputNumber
defaultValue={0}
value={val}
maxLength={5}
min={0}
precision={0}
onChange={value => inputChange(value, 'productStockWarning', index)}
/>
),
});
}
if (secondSpec && firstData.secondSpecValue) {
arr.unshift({
title: secondSpec,
align: 'center',
key: 'secondSpecValue',
dataIndex: 'secondSpecValue',
width: 100,
});
}
if (firstSpec && firstData.firstSpecValue) {
arr.unshift({
title: firstSpec,
dataIndex: 'firstSpecValue',
key: 'firstSpecValue',
align: 'center',
width: 100,
render: (val, row) => {
const obj = {
children: val,
props: {},
};
if (row.length) {
obj.props.rowSpan = row.length;
} else {
obj.props.rowSpan = 0;
}
return obj;
},
});
}
if (isEdit && isJDGoods) {
arr.splice(arr.length - 1, 0, {
title: 'sku名称',
align: 'center',
key: 'name',
dataIndex: 'name',
width: 100,
render: val => (
<Popover content={val} trigger="hover">
<a>查看名称</a>
</Popover>
),
});
}
return arr;
}
export const goodsType = [
{
name: '普通商品',
value: 1,
},
{
name: '虚拟商品',
value: 2,
},
{
name: '电子卡券',
value: 3,
},
];
import { Form, Input, Modal } from 'antd';
import React, { Component } from 'react';
const { TextArea } = Input;
const FormItem = Form.Item;
// import styles from '../style.less';
class goodsManage extends Component {
// componentDidMount() {
// this.props.onRef(this);
// }
handleOk = () => {
const { form } = this.props;
form.validateFields((err, values) => {
if (!err) {
this.props.changeSkuName(values.name);
this.props.form.resetFields();
}
});
};
handleCancel = () => {
this.props.form.resetFields();
this.props.onCancle();
};
render() {
const { data, visible, form } = this.props;
const { getFieldDecorator } = form;
return (
<Modal title="" visible={visible} onOk={this.handleOk} onCancel={this.handleCancel}>
<Form>
<FormItem label="sku名称">
{getFieldDecorator('name', {
initialValue: data,
rules: [
{
required: true,
message: '请输入',
},
],
})(<TextArea autoSize={{ minRows: 2, maxRows: 6 }} allowClear />)}
</FormItem>
</Form>
</Modal>
);
}
}
export default Form.create()(goodsManage);
import { Card, Form, Pagination, Table, notification, Drawer, Spin } from 'antd';
import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { connect } from 'dva';
import { sortBy } from 'lodash';
import styles from './style.less';
import LocalStroage from '@/utils/localStorage';
import configApi from '../../../config/env.config';
import UpdatePrice from './UpdatePrice';
import UpdateStock from './UpdateStock';
import {
update,
spuDetail,
detail,
getSupplierList,
batchPushedOrOffline,
categoryList,
getVirtualCategory,
} from './service';
import LogModal from './LogModal';
import CreateModal from './createModal';
import SupplyModal from './SupplyModal';
import ReasonModal from './reasonModal';
import { column, JDSHOPID } from './staticdata';
import SearchForm from './SearchForm';
@connect(({ goodsManage }) => ({
goodsManage,
}))
class goodsManage extends Component {
state = {
pageNo: 1,
treeData: [],
virtualTreeData: [],
pageSize: 20,
priceInfo: {},
logVisible: false,
previewVisible: false,
supplyVisible: false,
createVisible: false, // 新增or编辑普通商品modal
offlineVisible: false,
selectedParams: {},
detailData: [],
detailSpuId: '',
selectedRowKeys: [],
updatePriceVisible: false,
updateStockVisible: false,
initData: {},
createloading: false,
type: 1,
};
loading = false;
currentSkuId = null;
supplierId = null;
shopList = [];
componentDidMount() {
this.props.goodsManage.tableData = {};
this.getShopList();
this.categoryList();
this.getVirtualCategory();
}
openOffline = (ids, type, productState) => {
const obj = { ids, type, productState };
this.setState({ selectedParams: obj });
this.setState({ offlineVisible: true });
};
showSupply = async (id, type) => {
const { data } = await detail({ id });
this.setState({ supplyVisible: true, detailData: data, type });
};
handleSearch = page => {
this.loading = true;
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'goodsManage/getList',
payload: {
pageNo,
pageSize,
...this.searchForm.getFieldsValue(),
},
});
this.loading = false;
},
);
};
onPageChange = page => {
this.handleSearch(page);
};
onPageSizeChange = (current, size) => {
this.setState(
{
pageSize: size,
},
() => this.handleSearch(),
);
};
onSelectChange = selectedRowKeys => {
this.setState({ selectedRowKeys });
};
onReset = () => {
this.setState({
pageNo: 1,
pageSize: 20,
selectedRowKeys: [],
});
this.handleSearch();
};
onUpdateInfo = async ({ spuId, productType }) => {
this.setState({
createloading: true,
});
const { data } = await spuDetail({ id: spuId });
if (data) {
data.pageProductType = productType;
data.categoryId = data.thirdCategoryId;
data.firstSpecId = data.skuList[0].firstSpecId;
data.secondSpecId = data.skuList[0].secondSpecId;
data.firstSpecName = data.skuList[0].firstSpec;
data.secondSpecName = data.skuList[0].secondSpec;
data.firstSpecList = [];
data.secondSpecList = [];
data.colorKeys = [];
data.carouseList.forEach(i => {
if (i.specValue) {
data.colorKeys.push(i.specValue);
}
});
data.skuList.forEach(i => {
if (data.firstSpecList.indexOf(i.firstSpecValue) === -1) {
data.firstSpecList.push(`${i.firstSpecValue}`);
}
if (i.secondSpecValue && data.secondSpecList.indexOf(i.secondSpecValue) === -1) {
data.secondSpecList.push(i.secondSpecValue);
}
});
data.imageList = [];
data.carouseList.forEach(i => {
data.imageList[`${i.specValue}`] = i.skuSpecImageList || [];
});
data.editData = sortBy(data.skuList, item => item.firstSpecValue);
this.setState({
initData: data,
createVisible: true,
createloading: false,
});
}
};
onUpdate = async updateData => {
const [, error] = await update(updateData);
this.onLoad(error);
};
onLoad = error => {
if (!error) {
notification.success({ message: '操作成功' });
this.setState({
selectedRowKeys: [],
});
this.handleSearch();
}
};
viewLog = async skuId => {
this.currentSkuId = skuId;
this.setState({
logVisible: true,
});
};
updateStatus = async (ids, productState, offlineReason, fromDetail) => {
const data = await batchPushedOrOffline({
ids,
productState,
offlineReason,
});
if (data.businessCode === '0000') {
this.handleSearch();
notification.success({
message: productState === 6 ? '上架成功' : '下架成功',
});
this.setState({ offlineVisible: false });
if (fromDetail) {
this.showDetail(this.state.detailSpuId);
}
}
};
audit = skuId => {
this.setState({
previewVisible: true,
src: `${configApi.prologueDomain}/goods/${skuId}?h=0&token=${LocalStroage.get(
'token',
)}&hideReport=1&time=${Date.now()}`,
});
};
offLine = (ids, productState) => {
const obj = { ids, productState };
this.setState({ offlineVisible: true, selectedParams: obj });
};
// 获取供应商列表
getShopList = async () => {
const { data } = await getSupplierList();
this.shopList = data || [];
};
filterShopList = (list = [], isEdit) =>
list.filter(item => isEdit || !JDSHOPID.includes(item.id));
openModal = (
{
skuId,
supplyPrice,
marketPrice,
salePrice,
marketableStock,
supplierId,
stock,
productStock,
},
isStock,
) => {
let visible = {};
if (isStock) {
visible = { updateStockVisible: true };
} else {
visible = { updatePriceVisible: true };
}
this.setState({
...visible,
priceInfo: {
id: skuId,
stock,
productStock,
marketableStock,
supplyPrice,
marketPrice,
salePrice,
supplierId,
},
});
};
cancel = query => {
this.setState({ updatePriceVisible: false, updateStockVisible: false });
if (query) {
this.handleSearch();
}
};
categoryList = async () => {
const { data: treeData } = await categoryList();
if (!treeData) return;
this.setState({ treeData });
};
getVirtualCategory = async () => {
const { data: virtualTreeData } = await getVirtualCategory();
if (!virtualTreeData) return;
this.setState({ virtualTreeData });
};
render() {
const {
goodsManage: { tableData = {} },
} = this.props;
const { pageNo, pageSize, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: record => ({
disabled: +record.state === 6, // Column configuration not to be checked
}),
};
return (
<PageHeaderWrapper>
<Spin spinning={this.state.createloading}>
<Card>
<SearchForm
handleSearch={this.handleSearch}
onReset={this.onReset}
onLoad={this.onLoad}
selectedRowKeys={this.state.selectedRowKeys}
onRef={ref => {
this.searchForm = ref;
}}
treeData={this.state.treeData}
shopList={this.shopList}
addSpu={() => this.setState({ createVisible: true, initData: {} })}
/>
</Card>
<Spin spinning={this.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={column.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
rowSelection={rowSelection}
scroll={{ x: '100%', y: 500 }}
/>
</Spin>
<br />
{tableData && (
<Pagination
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<LogModal
visible={this.state.logVisible}
id={this.currentSkuId}
onCancel={() => {
this.currentSkuId = null;
this.setState({ logVisible: false });
}}
/>
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
>
<iframe
src={this.state.src}
frameBorder="0"
height="600"
width="375"
title="商品预览"
></iframe>
</Drawer>
<CreateModal
initData={this.state.initData}
visible={this.state.createVisible}
onCancel={() => {
this.setState({ createVisible: false, initData: {} });
}}
query={() => this.handleSearch()}
shopList={this.filterShopList(this.shopList, Object.keys(this.state.initData).length)}
treeData={this.state.treeData}
virtualTreeData={this.state.virtualTreeData}
></CreateModal>
<SupplyModal
data={this.state.detailData}
visible={this.state.supplyVisible}
type={this.state.type}
onCancel={() => {
this.setState({ supplyVisible: false });
}}
></SupplyModal>
<ReasonModal
visible={this.state.offlineVisible}
submit={reason => {
const { ids, productState } = this.state.selectedParams;
this.updateStatus(ids, productState, reason);
}}
onCancel={() => {
this.setState({ offlineVisible: false });
}}
></ReasonModal>
<UpdatePrice
visible={this.state.updatePriceVisible}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
<UpdateStock
visible={this.state.updateStockVisible}
info={this.state.priceInfo}
onCancel={this.cancel}
/>
</Spin>
</PageHeaderWrapper>
);
}
}
export default Form.create()(goodsManage);
import * as api from './service';
const Model = {
namespace: 'goodsManage',
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 { data } = yield call(api.searchList, params);
if (!data) return;
yield put({
type: 'saveData',
payload: {
tableData: data,
},
});
},
*getDataList({ payload }, { call, put, all }) {
const [[shopList], [statusList]] = yield all([
yield call(api.shopList, payload),
yield call(api.statusList, payload),
]);
if (!shopList && !statusList) return;
yield put({
type: 'dataList',
payload: {
shopList,
statusList,
},
});
},
*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 { Modal, Input, notification } from 'antd';
import React, { Component } from 'react';
const { TextArea } = Input;
// eslint-disable-next-line react/prefer-stateless-function
class reasonModal extends Component {
state = {
remarks: '',
};
inputChange = ({ target: { value } }) => {
this.setState({ remarks: value });
};
onCancel = () => {
this.setState({ remarks: '' });
this.props.onCancel();
};
submit = () => {
if (!this.state.remarks) {
notification.error({
message: '请输入下架原因',
});
return;
}
this.props.submit(this.state.remarks);
this.setState({ remarks: '' });
};
render() {
const { visible } = this.props;
return (
<Modal
title="下架原因"
visible={visible}
onOk={this.submit}
onCancel={() => this.onCancel()}
width="700px"
>
<div>
<TextArea value={this.state.remarks} rows={4} onChange={this.inputChange} />
</div>
</Modal>
);
}
}
export default reasonModal;
// import fileSaver from 'file-saver';
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import _ from 'lodash';
const { kdspApi } = config;
// const kdspApi = 'http://yapi.quantgroups.com/mock/389';
// 分页查询所有数据
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
export async function searchList(params) {
return request.post('/product/page', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers,
});
}
// 新增商品
export async function addGoods(params) {
return request.post('/product/add', {
prefix: kdspApi,
data: params,
});
}
// 编辑商品
export async function editGoods(params) {
return request.post('/product/edit', {
prefix: kdspApi,
data: params,
});
}
// 供应商列表
export async function getSupplierList() {
return request.post('/channel/supplier/list', {
prefix: kdspApi,
});
}
// 获取商品品牌
export async function getBrandList() {
return request.post('/product/brand/list', {
prefix: kdspApi,
});
}
// 编辑--获取详情
export async function spuDetail(params) {
return request.post('/product/detail', {
prefix: kdspApi,
params,
headers,
});
}
// 商品规格
export async function getSpecList() {
return request.post('/product/spec/list', {
prefix: kdspApi,
});
}
// 查询图片素材
export async function getImageInfo(spuNo) {
return request.post('/product/imageInfo', {
params: { spuNo },
prefix: kdspApi,
headers,
});
}
// 状态
export async function statusList() {
return request.post('/api/kdsp/op/mch_sku/status_list');
}
// 商品来源
export async function skuSource() {
return request.post('/api/kdsp/sku/sku-info/getSkuSource');
}
// 商品分类
export async function categoryList() {
return request.post('/product/category/getAll', {
prefix: kdspApi,
headers,
});
}
// 批量修改
export async function uploadFile(file) {
const params = new FormData();
params.append('file', file);
const data = await request.post('/product/item/batchUpdate', {
prefix: kdspApi,
data: params,
rawData: true,
});
return data;
}
// 批量倒入京东skus
export async function importJdSkus(file, sourceType) {
const params = new FormData();
params.append('file', file);
params.append('sourceType', sourceType);
const data = await request.post('/product/importJdSkus', {
data: params,
prefix: kdspApi,
// rawData: true,
});
return data;
}
// 批量上架
export async function pushed(data) {
return request.post('/api/kdsp/op/mch_sku/pushed', data);
}
// 批量下架
export async function offline(data) {
return request.post('/api/kdsp/op/mch_sku/offline', data);
}
// 最新批量上下架
export async function batchPushedOrOffline(data) {
return request.post('/product/pushedOrOffline/batch', {
prefix: kdspApi,
data: stringify(_.omitBy(data, v => !v)),
headers,
});
}
// 商品修改
export async function update(params) {
return request.post('/api/kdsp/op/mch_sku/update', params, {
emulateJSON: true,
});
}
// 商品修改日志
export async function changeLog(params) {
return request.post('/product/logs/page', {
data: stringify(_.omitBy(params, v => !v)),
prefix: kdspApi,
headers,
});
}
// 商品详情
export async function detail(params) {
return request.post('/product/supply/list', {
data: stringify(_.omitBy(params, v => !v)),
prefix: kdspApi,
headers,
});
}
// 拉去京东图片
export async function getJdPicList(params) {
const { data } = await request.post('/product/item/getJdPicList', {
data: stringify(_.omitBy(params, v => !v)),
prefix: kdspApi,
headers,
});
return data;
}
// 添加商品
export async function addSku(params) {
const [, error] = await request.post('/api/kdsp/op/mch_sku/add_sku', params);
return error;
}
// 修改商品
export async function updateSku(params) {
const [, error] = await request.post('/api/kdsp/op/mch_sku/update', params);
return error;
}
// 图片上传
export async function uploadImg(files) {
const params = new FormData();
files.forEach(file => params.append('file', file));
const data = await request.post('/image/upload', {
prefix: kdspApi,
data: params,
});
return data;
}
// 修改商品价格
export async function updatePrice(params) {
const data = await request.post('/product/item/updatePrice', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers,
});
if (data.businessCode === '0000') {
return null;
}
return data.msg;
}
// 修改商品库存
export async function updateStock(params) {
const data = await request.post('/product/item/updateStock', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers,
});
if (data.businessCode === '0000') {
return null;
}
return data.msg;
}
// 获取虚拟商品类目
export async function getVirtualCategory() {
const data = await request.post('/product/category/getByParentId', {
prefix: kdspApi,
data: stringify(_.omitBy({ id: 100018 }, v => !v)),
headers,
});
return data;
}
import React from 'react';
import { Button, Badge } from 'antd';
import styles from './style.less';
export const productType = [
{
value: 1,
title: '自营',
},
{
value: 2,
title: '京东开普勒',
},
{
value: 3,
title: '京东联盟',
},
{
value: 4,
title: '众联',
},
{
value: 5,
title: '企业购',
},
{
value: 6,
title: '企业购直连',
},
];
export function column() {
return [
{
title: 'SKU编码',
dataIndex: 'skuId',
width: 125,
align: 'center',
render: (_, row) => {
if (row.type !== 1) {
return (
<Badge
count={
<div
style={{
color: '#fff',
borderRadius: '3px',
background: '#f5222d',
padding: '2px',
fontSize: '10px',
opacity: 0.7,
}}
>
虚拟
</div>
}
>
<div
style={{
background: '#fbfbfb',
borderRadius: '3px',
padding: '2px',
}}
>
{row.skuId}
</div>
</Badge>
);
}
return (
<div
style={{
background: '#fbfbfb',
borderRadius: '3px',
padding: '2px',
}}
>
{row.skuId}
</div>
);
},
},
{
title: 'SKU商品名称',
width: 135,
align: 'center',
dataIndex: 'skuName',
},
{
title: '价格',
dataIndex: 'marketPrice',
width: 150,
align: 'center',
sorter: (a, b) => a.supplyPrice - b.supplyPrice,
render: (_, row) => (
<div className={styles.price}>
<p>供货价:{row.supplyPrice.toFixed(2)}</p>
<p>市场价:{(row.marketPrice || 0).toFixed(2)}</p>
</div>
),
},
{
title: '库存',
width: 120,
dataIndex: 'stock',
align: 'center',
sorter: (a, b) => a.stock - b.stock,
render: (_, row) => (
<>
<p>
当前库存:<a onClick={() => this.openModal(row, 'productStock')}>{row.productStock}</a>
</p>
<p>可售库存:{_}</p>
{row.type === 1 && row.productStockWarning > -1 && (
<p>预警值:{row.productStockWarning}</p>
)}
</>
),
},
{
title: '审核状态',
dataIndex: 'stateDesc',
width: 200,
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
width: 120,
align: 'center',
render: (_, row) => (
<div className={styles.actionBtn}>
<Button
key="edit"
type="primary"
size="small"
className={styles.button}
onClick={() => this.onUpdateInfo(row)}
>
编辑
</Button>
<Button
key="viewP"
type="primary"
size="small"
className={styles.button}
onClick={() => this.audit(row.skuId)}
>
预览
</Button>
<Button
key="log"
size="small"
type="primary"
className={styles.button}
onClick={() => this.viewLog(row.skuId)}
>
查看日志
</Button>
</div>
),
},
];
}
export const disSelectStatus = [2, 5];
export const stateList = [
{ value: 3, label: '待审核' },
{ value: 4, label: '申请驳回' },
{ value: 5, label: '未上架' },
];
// AUDITING(3, "待审核"),REJECTED(4, "申请驳回"),WAIT_SELL(5, "未上架"),ON_SELL(6, "已上架"),OFF_SHELVES(7, "已下架"))
export const productTypeList = [
{ value: 1, label: '实体商品' },
{ value: 2, label: '虚拟充值' },
{ value: 3, label: '虚拟卡券', disabled: true },
];
export const JDSHOPID = [3, 5, 6];
.button {
margin: 5px;
}
.selectWidth {
width: 200px;
}
.btngroup {
margin: 10px;
}
.filterModal {
margin: 20px 0;
}
.footerButton {
position: absolute;
bottom: 20px;
left: 0;
}
.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: 700px;
}
.price {
// text-align: left;
cursor: pointer;
}
.searchForm {
:global {
.ant-form-item-label {
width: 120px;
}
}
}
.queryBtn {
margin-left: 45px;
}
.actionBtn {
button {
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;
color: red;
}
.iptNumRight {
margin-right: 0 !important;
}
import { Upload, Icon, Modal } from 'antd';
import React from 'react';
import { ReactSortable } from 'react-sortablejs';
import { uploadImg } from '../../GoodsManage/service';
import styles from './styles.less';
class PicturesWall extends React.Component {
state = {
previewVisible: false,
previewImage: '',
fileList: [],
newFile: [],
activeImgIndex: null,
};
async componentDidMount() {
this.initFileList(this.props.fileList || []);
}
componentWillReceiveProps(nextProps) {
this.initFileList(nextProps.fileList || []);
}
initFileList = fileList => {
const fileLists =
fileList.map((item, index) => ({
url: item,
name: index,
uid: index,
status: 'done',
})) || [];
this.setState({ fileList: fileLists });
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async img => {
this.setState({
previewImage: img.url,
previewVisible: true,
});
};
clearFileList = () => {
this.setState({
fileList: [],
});
};
sortChange = list => {
this.setState({ fileList: list });
const urlList = list.map(item => item.url);
this.props.onChange(urlList);
};
onRemoveImg = file => {
this.setState(
state => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
},
() => {
const urlList = this.state.fileList.map(item => item.url);
this.props.onChange(urlList);
},
);
};
render() {
// TODO 图片限制大小
const { previewVisible, previewImage, fileList } = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">上传图片</div>
</div>
);
const { max } = this.props;
const that = this;
const uploadProps = {
beforeUpload: file => {
console.log(file);
this.setState(
state => ({
newFile: [...state.newFile, file],
}),
async () => {
const data = await uploadImg(that.state.newFile);
this.setState({ newFile: [] });
if (!data || !data.data) return;
const { length } = this.state.fileList;
const list = data.data.map((img, index) => ({
url: img,
uid: index + length,
name: index + length,
status: 'done',
}));
this.setState(
state => ({ fileList: [...state.fileList, ...list] }),
() => {
const urlList = this.state.fileList.map(item => item.url);
this.props.onChange(urlList);
},
);
},
);
return false;
},
listType: 'picture-card',
};
return (
<div className="clearfix">
<div className={styles.imgContent}>
{fileList.length > 0 && (
<ReactSortable list={fileList} setList={list => this.sortChange(list)}>
{fileList.map((item, index) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={index}
className={styles.sortImg}
onMouseEnter={() => this.setState({ activeImgIndex: index })}
onMouseLeave={() => this.setState({ activeImgIndex: null })}
>
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
<img width="100%" key={item.uid} src={item.url} alt="" />
</div>
{this.state.activeImgIndex === index && (
<div className={styles.mask}>
<Icon
type="eye"
className={styles.maskIcon}
onClick={() => this.handlePreview(item)}
/>
<Icon
type="delete"
className={styles.maskIcon}
onClick={() => this.onRemoveImg(item)}
/>
</div>
)}
</div>
))}
</ReactSortable>
)}
</div>
<Upload multiple {...uploadProps} fileList={fileList} showUploadList={false}>
{max && fileList.length >= max ? null : uploadButton}
</Upload>
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
);
}
}
export default PicturesWall;
.sortImg {
position: relative;
display: inline-block;
width: 105px;
height: 105px;
margin-right: 10px;
margin-bottom: 10px;
padding: 9px;
border: 1px solid #ddd;
border-radius: 6px;
}
.mask {
position: absolute;
top: 9px;
right: 9px;
bottom: 9px;
left: 9px;
padding-top: 27%;
text-align: center;
// width: 100%;
// height: 100%;
background: #000;
opacity: 0.5;
}
.maskIcon {
margin-right: 5px;
color: #efefef;
font-size: 16px;
cursor: pointer;
}
...@@ -60,16 +60,16 @@ ...@@ -60,16 +60,16 @@
width: 78%; width: 78%;
} }
.logistics { .logistics {
width: 100%; display: flex;
display: flex; align-items: center;
justify-content: center; justify-content: center;
height: 30px; width: 100%;
align-items: center; height: 30px;
margin-top: 24px; margin-top: 24px;
} }
.logisticsIcon { .logisticsIcon {
font-size: 25px; font-size: 25px;
&:first-child { &:first-child {
margin-right: 10px; margin-right: 10px;
} }
} }
import * as api from './service'; import * as api from './service';
const tableDate = [{ const tableDate = [{}];
}];
const Model = { const Model = {
namespace: 'pendingDeliveryOrder', namespace: 'pendingDeliveryOrder',
...@@ -13,12 +11,12 @@ const Model = { ...@@ -13,12 +11,12 @@ const Model = {
}, },
effects: { effects: {
*getSubjectList({ payload }, { call, put }) { *getSubjectList({ payload }, { call, put }) {
yield put({ yield put({
type: 'saveData', type: 'saveData',
payload: { payload: {
tableData: tableDate, tableData: tableDate,
}, },
}); });
// const response = yield call(api.subjectList, payload); // const response = yield call(api.subjectList, payload);
// if (response.code === 2000) { // if (response.code === 2000) {
// yield put({ // yield put({
......
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