Commit 4ebdcb61 authored by 郭志伟's avatar 郭志伟

Merge branch 'feature/goodsManage' into 'master'

商户管理后台-新增商品管理--商品库页面

See merge request !18
parents 648a86d8 5f31f16c
......@@ -2,4 +2,4 @@
/scripts
/config
.history
/src/utils/qiniu.min.js
/src/utils/qiniu.min.js
\ No newline at end of file
......@@ -7,6 +7,9 @@ module.exports = {
},
rules: {
'max-len': ['error', { code: 200 }],
'no-param-reassign': 0,
'no-console': 0,
'@typescript-eslint/camelcase': ['off'],
'@typescript-eslint/no-unused-vars': ['off'],
},
};
......@@ -150,6 +150,17 @@ export default {
name: 'cancelBillManage',
component: './cancelBillManage',
},
{
path: '/goodsManage',
name: 'goodsManage',
icon: 'smile',
component: './GoodsManage',
},
// {
// path: '/GoodsManage-new',
// name: 'GoodsManageNew',
// component: './GoodsManage-new',
// },
{
component: './404',
},
......@@ -169,6 +180,7 @@ export default {
'primary-color': primaryColor,
},
define: {
'process.env.PRE_ENV': process.env.PRE_ENV,
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION:
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION || '', // preview.pro.ant.design only do not use in your production ; preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
},
......
const isProduction = process.env.NODE_ENV === 'production';
const isPre = process.env.PRE_ENV === 'pre';
let envAPi = {
api: '//backstms-gyl.liangkebang.net',
kdspOpApi: 'https://kdsp-operation-gyl.liangkebang.net',
kdspApi: 'https://sc-op-api-gyl.liangkebang.net',
const envAPi = {
api: '//backstms-test1.liangkebang.net',
kdspOpApi: 'https://kdsp-operation-test1.liangkebang.net',
kdspApi: 'https://sc-op-api-test1.liangkebang.net',
goodsApi: 'https://sc-op-api-test1.liangkebang.net',
// goodsApi: '//192.168.28.107:7000',
prologueDomain: 'https://prologue-test1.liangkebang.net',
qiniuHost: 'https://appsync.lkbang.net',
opapiHost: 'https://opapi-gyl.liangkebang.net',
// opapiHost: 'http://192.168.29.45:7000',
opapiHost: 'https://opapi-test1.liangkebang.net',
};
let prodApi = {
const prodApi = {
api: '//backstms.q-gp.com',
kdspOpApi: '//kdsp-operation.q-gp.com',
prologueDomain: '//prologue.q-gp.com',
kdspApi: '//sc-op-api.q-gp.com',
// goodsApi: 'https://sc-op-api.q-gp.com', // 测试环境打包域名,
goodsApi: 'https://sc-merchant-api.q-gp.com', // 线上环境打包域名
qiniuHost: 'https://appsync.lkbang.net',
opapiHost: 'https://opapi.xyqb.com',
};
let exportApi;
isProduction ? (exportApi = prodApi) : (exportApi = envAPi);
const preProdApi = {
api: '//backstms-pre.xyqb.com',
kdspOpApi: '//kdsp-operation-pre.q-gp.com',
prologueDomain: '//prologue.q-gp.com',
kdspApi: '//sc-op-api-pre.q-gp.com',
goodsApi: 'https://sc-merchant-api-pre.q-gp.com',
qiniuHost: 'https://appsync.lkbang.net',
opapiHost: 'https://opapi-pre.q-gp.com',
};
let exportApi = envAPi;
if (isPre) {
exportApi = preProdApi;
} else if (isProduction) {
exportApi = prodApi;
}
module.exports = exportApi;
// let exportApi;
// isProduction ? (exportApi = prodApi) : (exportApi = envAPi);
export default exportApi;
// export default exportApi;
......@@ -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": {
"version": "0.3.0",
"resolved": "http://npmprivate.quantgroups.com/any-observable/-/any-observable-0.3.0.tgz",
......@@ -20109,6 +20117,15 @@
"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": {
"version": "16.14.0",
"resolved": "http://npmprivate.quantgroups.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz",
......@@ -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": {
"version": "2.0.1",
"resolved": "http://npmprivate.quantgroups.com/source-list-map/-/source-list-map-2.0.1.tgz",
......@@ -6,6 +6,7 @@
"scripts": {
"analyze": "cross-env ANALYZE=1 umi build",
"build": "umi build",
"build:pre": "cross-env PRE_ENV=pre umi build",
"deploy": "npm run site && npm run gh-pages",
"fetch:blocks": "pro fetch-blocks && npm run prettier",
"format-imports": "cross-env import-sort --write '**/*.{js,jsx,ts,tsx}'",
......@@ -52,6 +53,7 @@
"@ant-design/pro-table": "^1.0.31",
"@antv/data-set": "^0.10.2",
"antd": "^3.23.6",
"antd-virtual-select": "^1.1.2",
"classnames": "^2.2.6",
"date-fns": "^2.16.1",
"dva": "^2.4.1",
......@@ -65,8 +67,10 @@
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1",
"react-sortablejs": "^6.0.0",
"redux": "^4.0.1",
"slash2": "^2.0.0",
"sortablejs": "^1.13.0",
"umi": "^2.13.0",
"umi-plugin-pro-block": "^1.3.4",
"umi-plugin-react": "^1.10.1",
......
import { Modal, Table, Button, Pagination, Tabs } from 'antd';
import React, { useState, useEffect } from 'react';
import styles from '../style.less';
import { changeLog, productMerchantLog } from '../service';
const LogModal = props => {
const [tabActiveKey, setTabActiveKey] = useState('0');
const [tableData, setTableData] = useState([]);
const [pageNo, setPageNo] = useState(1);
const [pageSize] = useState(20);
const [merchantList, setMerchantList] = useState([]);
const columns = [
{
title: '时间',
dataIndex: 'createdAt',
align: 'center',
},
{
title: '变更字段',
align: 'center',
dataIndex: 'changeType',
},
{
title: '变更后内容',
dataIndex: 'afterChangeValue',
align: 'center',
},
{
title: '变更前内容',
dataIndex: 'beforeChangeValue',
align: 'center',
},
{
title: '变更原因',
dataIndex: 'reason',
align: 'center',
},
{
title: '操作账号',
dataIndex: 'operator',
align: 'center',
},
];
const columnsMerchant = [
{
title: '时间',
dataIndex: 'createdAt',
align: 'center',
},
{
title: '审核结果',
align: 'center',
dataIndex: 'operation',
render: value => ([5, 6, 7].includes(value) ? '审核通过' : '驳回'),
},
{
title: '驳回原因',
dataIndex: 'rejectReason',
align: 'center',
},
{
title: '操作账号',
dataIndex: 'createdBy',
align: 'center',
},
];
const handleSearch = async (page = 1) => {
setPageNo(page);
const { data = {} } = await changeLog({ id: props.id, pageNo: page, pageSize });
setTableData(data);
};
const onPageChange = page => {
handleSearch(page);
};
const getProductMerchantLog = async () => {
const { data = [] } = await productMerchantLog(props.spuId);
setMerchantList(data); // merchantList
};
const bundleOnTabChange = key => {
if (key === '1') {
getProductMerchantLog();
} else {
onPageChange(1);
}
setTabActiveKey(key);
};
const bundleOnCancel = () => {
setMerchantList([]);
setTabActiveKey('0');
props.onCancel();
};
useEffect(() => {
if (!props.id) return;
handleSearch();
}, [props.id]);
const { visible } = props;
return (
<Modal title="日志详情" visible={visible} footer={null} onCancel={bundleOnCancel} width="800px">
<Tabs type="card" onChange={bundleOnTabChange} activeKey={tabActiveKey}>
<Tabs.TabPane tab="商品详情" key="0">
<Table
dataSource={tableData.records}
bordered
columns={columns}
rowKey={record => record.id}
pagination={false}
scroll={{ y: 300 }}
/>
{tableData.records && (
<Pagination
onChange={onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
className={styles.pagination}
/>
)}
</Tabs.TabPane>
<Tabs.TabPane tab="审核详情" key="1">
<Table
dataSource={merchantList}
bordered
columns={columnsMerchant}
rowKey={record => record.id}
pagination={false}
scroll={{ y: 300 }}
/>
</Tabs.TabPane>
</Tabs>
<Button type="primary" onClick={bundleOnCancel} className={styles.logBtn}>
关闭
</Button>
</Modal>
);
};
export default LogModal;
import { 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 { uploadFile } 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();
};
addSpu = () => {
this.props.addSpu();
};
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 placeholder="请输入SKU编码" allowClear style={selectW} />,
)}
</FormItem>
<FormItem label="商品名称">
{getFieldDecorator('skuName', {})(
<Input placeholder="请输入商品名称" allowClear style={selectW} />,
)}
</FormItem>
<FormItem label="类目">
{getFieldDecorator('productCategoryId', {})(
<Cascader
placeholder="请选择类目"
style={selectW}
showSearch
changeOnSelect
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={treeData}
/>,
)}
</FormItem>
<FormItem label="审核状态">
{getFieldDecorator('state', {})(
<Select
style={selectW}
placeholder="请选择审核状态"
allowClear
filterOption={filterOption}
>
{stateList?.map(item => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>,
)}
</FormItem>
<FormItem label="供货价区间">
<FormItem className={styles.iptNumRight}>
{getFieldDecorator('supplyPriceMin', {})(
<InputNumber placeholder="请输入" style={iptNumWidth} />,
)}
</FormItem>
<span>--</span>
<FormItem className={styles.iptNumRight}>
{getFieldDecorator('supplyPriceMax', {})(
<InputNumber
style={iptNumWidth}
placeholder="请输入"
min={getFieldValue('supplyPriceMin')}
/>,
)}
</FormItem>
</FormItem>
<FormItem label="第三方SKU编码">
{getFieldDecorator('thirdSkuNo', {})(
<Input placeholder="请输入第三方SKU编码" allowClear style={selectW} />,
)}
</FormItem>
<FormItem className={styles.queryBtn}>
<Button onClick={() => this.handleSearch()} type="primary" className={styles.button}>
查询
</Button>
<Button onClick={() => this.onReset()} type="primary" className={styles.button}>
重置
</Button>
</FormItem>
<FormItem style={{ float: '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/批量修改库存模板.xlsx';
}}
>
模版
</Button>
<Upload {...uploadProps}>
<Button type="primary" className={styles.button}>
批量库存修改
</Button>
</Upload> */}
</FormItem>
</Form>
);
}
}
export default Form.create()(goodsManage);
import { Modal, InputNumber, notification, Form, Input, Radio } from 'antd';
import React from 'react';
import { updateStock } from '../service';
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="请输入库存" style={{ width: 200 }} />)}
</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;
// if (!isEdit) item.productStock = data.productStock; // 编辑状态不可修改库存
item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
item.productStockWarning = data.productStockWarning;
});
}
if (data.firstSpecValue && !data.secondSpecValue) {
editData.forEach(item => {
if (item.firstSpecValue === data.firstSpecValue) {
item.marketPrice = data.marketPrice;
item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
item.productStockWarning = data.productStockWarning;
}
});
}
if (!data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (item.secondSpecValue === data.secondSpecValue) {
item.marketPrice = data.marketPrice;
item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
item.productStockWarning = data.productStockWarning;
}
});
}
if (data.firstSpecValue && data.secondSpecValue) {
editData.forEach(item => {
if (
item.firstSpecValue === data.firstSpecValue &&
item.secondSpecValue === data.secondSpecValue
) {
item.marketPrice = data.marketPrice;
item.productStock = data.productStock;
item.supplyPrice = data.supplyPrice;
item.weight = data.weight;
item.productStockWarning = data.productStockWarning;
}
});
}
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: 130 }}
/>,
)}
</FormItem>
)}
<FormItem>
{getFieldDecorator('productStock', {})(
<InputNumber
precision={0}
step={1}
// eslint-disable-next-line radix
formatter={val => parseInt(val, '10') || ''}
placeholder="库存"
style={{ width: 100 }}
/>,
)}
</FormItem>
{productType === 1 && (
<FormItem>
{getFieldDecorator('productStockWarning', {})(
<InputNumber
placeholder="库存预警"
maxLength={5}
min={0}
precision={0}
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 () => {
try {
const { data } = await getSpecList();
if (data) {
this.setState({ specList: data });
}
} catch (e) {
console.log(e);
}
};
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 () => {
try {
const { data } = await getBrandList();
const list = data && data.filter(item => item.name === '虚拟商品');
this.setState({ normalBrandList: data, brandList: list });
} catch (e) {
console.log(e);
}
};
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 = isEdit => {
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) => {
console.log(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 (values?.imageList) {
item.imageList = values?.imageList[item.firstSpecValue] || values.commonImageList;
} else {
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: null,
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,
initCascader: null,
});
this.props.form.resetFields();
this.BatchSetting.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();
};
validateToInputName = (rule, value, callback) => {
if (value.trim().length < 2) {
callback(new Error('商品名称不可小于2个字符'));
}
callback();
};
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();
let 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);
// ---------------------驳回编辑规格时候,过滤掉colors中为null的数据---------------------
if (isEdit && !initData.editData[0]?.firstSpec && !initData.editData[0]?.secondSpec) {
colorKeys = colorKeys.length > 1 ? colorKeys.filter(item => item !== 'null') : colorKeys;
}
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
placeholder="请选择类目,支持模糊搜索"
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: 280 }}
filterOption={filterOption}
placeholder="请选择商品品牌,支持模糊搜索"
>
{brandListArray?.length &&
brandListArray.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</SuperSelect>,
)}
</FormItem>
</Col>
<span style={{ display: 'inline-block', lineHeight: '35px', color: '#999' }}>
若需新增品牌请联系业务员
</span>
</Row>
<Row type="flex">
<Col span={24}>
<Popover content={name} trigger="hover">
<FormItem label="商品名称" labelCol={{ span: 2 }}>
{getFieldDecorator('name', {
initialValue: initData.name,
validateTrigger: ['onBlur'],
rules: [
{
required: true,
message: '请输入商品名称',
},
{
validator: this.validateToInputName,
},
],
})(
<Input
style={{ width: 690 }}
maxLength="100"
placeholder="请输入商品名称"
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 }}
placeholder="请选择一级规格"
disabled={isEdit && initData.firstSpecId}
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 && initData.secondSpecId}
placeholder="请选择二级规格"
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(isEdit)}
>
生成商品信息
</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 [];
// if (initData.) {}
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 CHECK_OBJECT = {
supplyPrice: '供货价',
marketPrice: '市场价',
productStock: '库存',
thirdSkuNo: '商品自编码',
};
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;
}
console.log(copy);
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) {
// console.log(values);
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);
} else {
const list1 = first.length
? createNewList(first, initData.secondSpecList, values.firstSpecId, values.secondSpecId)
: [];
const list2 = second.length
? createNewList(newFirst, second, values.firstSpecId, values.secondSpecId)
: [];
// console.log('list1=================>', list1);
// console.log('list2=================>', list2);
// console.log('initData===========>', initData);
// console.log('first===========>', first);
// console.log('second===========>', second);
// 初次添加规格时没有选择规格,添加一条没有规格的商品,被驳回之后编辑时选择规格之后需要重新创建
// 编辑时回显时的id需要给重新创建的数据第一条加上 回显的id
if (!initData.editData[0]?.firstSpec && !initData.editData[0]?.secondSpec) {
// 这种情况出现时items中只有一条数据,且没有一级规格和二级规格
// 第一次无规格时,重新生成的数据,必须保留第一条数据的id是items里面第一条数据的id
list = createNewList(first, second, values.firstSpecId, values.secondSpecId);
list[0].id = initData.editData[0].id;
} else if (initData.editData[0]?.firstSpec && !initData.editData[0]?.secondSpec) {
// console.log('只有一级规格================>');
// 只有一级规格的情况下需要
// 重新更具规格创建表格,但是需要把历史记录赋值给表格的其中几项
// 其中新建的记录和历史编辑记录
const createList = createNewList(
[...initData.firstSpecList, ...first],
second,
values.firstSpecId,
values.secondSpecId,
);
const setp = createList.length / initData.firstSpecList.concat(first).length;
list = createList.map((item, index) => {
if (index % setp === 0) {
const newItem =
initData.editData.find(val => val.firstSpecValue === item.firstSpecValue) || {};
item = Object.assign({}, item, newItem, {
secondSpec: initData.secondSpecName,
secondSpecId: item.secondSpecId,
secondSpecValue: item.secondSpecValue,
length: setp,
});
}
return item;
});
list = sortBy(list, item => item.firstSpecValue);
} else {
list = sortBy(initData.editData.concat(list1.concat(list2)), item => item.firstSpecValue);
}
}
console.log(list);
}
return list;
}
export function validateSpuInfo(values, initData, editData, productType) {
const newCheckObject =
productType === 1 ? { ...CHECK_OBJECT, weight: '重量(kg)' } : { ...CHECK_OBJECT };
const checkKey = Object.keys(newCheckObject);
const checkString = [];
editData.forEach((item, index) => {
const rowCheckString = checkKey.reduce((checkMessage, val, ind) => {
const checkItem = item[val];
if (checkItem === null || checkItem === undefined || checkItem === '') {
checkMessage += `${newCheckObject[val]}; `;
}
return checkMessage;
}, '');
if (rowCheckString) {
checkString.push(<div key={index.toString()}>{`第${index + 1}行: ${rowCheckString}`}</div>);
}
});
if (checkString.length) {
notification.warning({
message: '请完善表格:',
description: checkString,
});
return true;
}
return false;
}
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}
/>
),
},
{
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)}
/>
),
},
{
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)}
/>
),
},
{
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: '京东链接',
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 UpdateStock from './UpdateStock';
import { spuDetail, categoryList, getVirtualCategory } from './service';
import LogModal from './LogModal';
import CreateModal from './createModal';
import { column, JDSHOPID } from './staticdata';
import SearchForm from './SearchForm';
@connect(({ goodsManage }) => ({
goodsManage,
}))
class goodsManage extends Component {
state = {
pageNo: 1,
loading: false,
treeData: [],
virtualTreeData: [],
pageSize: 20,
priceInfo: {},
logVisible: false,
previewVisible: false,
createVisible: false, // 新增or编辑普通商品modal
selectedRowKeys: [],
updateStockVisible: false,
initData: {},
createloading: false,
};
currentLog = null;
supplierId = null;
shopList = [];
componentDidMount() {
this.props.goodsManage.tableData = {};
this.categoryList();
this.getVirtualCategory();
}
handleSearch = page => {
const currentPage = this.state.pageNo;
this.setState(
{
pageNo: page || currentPage,
loading: true,
},
() => {
const { dispatch } = this.props;
const { pageSize, pageNo } = this.state;
dispatch({
type: 'goodsManage/getList',
payload: {
pageNo,
pageSize,
...this.searchForm.getFieldsValue(),
},
}).finally(() => {
this.setState({
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, msg } = 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,
});
} else {
this.setState({
createloading: false,
});
notification.warning({
message: msg,
});
}
};
onLoad = error => {
if (!error) {
notification.success({ message: '操作成功' });
this.setState({
selectedRowKeys: [],
});
this.handleSearch();
}
};
viewLog = async rows => {
this.currentLog = rows;
this.setState({
logVisible: true,
});
};
audit = skuId => {
this.setState({
previewVisible: true,
src: `${configApi.prologueDomain}/goods/${skuId}?h=0&token=${LocalStroage.get(
'token',
)}&hideReport=1&time=${Date.now()}`,
});
};
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 };
}
this.setState({
...visible,
priceInfo: {
id: skuId,
stock,
productStock,
marketableStock,
supplyPrice,
marketPrice,
salePrice,
supplierId,
},
});
};
cancel = query => {
this.setState({ updateStockVisible: false });
if (query) {
this.handleSearch();
}
};
categoryList = async () => {
try {
const { data: treeData } = await categoryList();
if (!treeData) return;
this.setState({ treeData });
} catch (e) {
console.log(e);
}
};
getVirtualCategory = async () => {
try {
const { data: virtualTreeData } = await getVirtualCategory();
if (!virtualTreeData) return;
this.setState({ virtualTreeData });
} catch (e) {
console.log(e);
}
};
render() {
const {
goodsManage: { tableData = {} },
} = this.props;
const { pageNo, pageSize, selectedRowKeys } = this.state;
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.state.loading}>
<Table
dataSource={tableData?.records}
bordered
columns={column.call(this)}
rowKey={record => record.skuId}
pagination={false}
className={styles.tabletop}
scroll={{ x: '100%', y: 500 }}
/>
</Spin>
<br />
{tableData && (
<Pagination
style={{ marginBottom: 10 }}
onChange={this.onPageChange}
total={tableData.total}
showTotal={total => `共${total}条`}
current={pageNo}
pageSize={pageSize}
showSizeChanger
onShowSizeChange={this.onPageSizeChange}
/>
)}
<LogModal
visible={this.state.logVisible}
spuId={this.currentLog?.spuId}
id={this.currentLog?.skuId}
onCancel={() => {
this.currentLog = null;
this.setState({ logVisible: false });
}}
/>
<Drawer
visible={this.state.previewVisible}
width="450"
onClose={() => {
this.setState({ previewVisible: false });
}}
title="商品预览"
bodyStyle={{ height: '90%' }}
>
<iframe
src={this.state.src}
frameBorder="0"
height="100%"
width="375"
title="商品预览"
></iframe>
</Drawer>
<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>
<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,
},
});
},
*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 fileSaver from 'file-saver';
import request from '@/utils/request';
import config from '../../../config/env.config';
import { stringify } from 'qs';
import _ from 'lodash';
const { goodsApi } = config;
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
export async function searchList(params) {
return request.post('/product/api/merchant/page', {
prefix: goodsApi,
data: stringify(params),
headers,
role: true,
});
}
// 新增商品
export async function addGoods(params) {
return request.post('/product/api/merchant/add', {
prefix: goodsApi,
data: params,
});
}
// 编辑商品
export async function editGoods(params) {
return request.post('/product/api/merchant/edit', {
prefix: goodsApi,
data: params,
});
}
// 获取商品品牌
export async function getBrandList() {
return request.post('/product/brand/api/merchant/list', {
prefix: goodsApi,
});
}
// 编辑--获取详情
export async function spuDetail(params) {
return request.post('/product/api/merchant/detail', {
prefix: goodsApi,
params,
headers,
});
}
// 商品规格
export async function getSpecList() {
return request.post('/product/spec/api/merchant/list', {
prefix: goodsApi,
});
}
// 查询图片素材
export async function getImageInfo(spuNo) {
return request.post('/product/api/merchant/imageInfo', {
params: { spuNo },
prefix: goodsApi,
headers,
});
}
// 商品分类
export async function categoryList() {
return request.post('/product/category/api/merchant/getAll', {
prefix: goodsApi,
});
}
// 批量修改
export async function uploadFile(file) {
const params = new FormData();
params.append('file', file);
const data = await request.post('/product/api/merchant/BatchUpdateStock', {
prefix: goodsApi,
data: params,
notTip: true,
});
return data;
}
// 商品修改日志
export async function changeLog(params) {
return request.post('/product/logs/api/merchant/page', {
data: stringify(params),
prefix: goodsApi,
headers,
});
}
// 审核详情日志
export async function productMerchantLog(productId) {
return request.get('/product/api/merchant/auditLog', {
params: {
productId,
},
prefix: goodsApi,
});
}
// 拉去京东图片
export async function getJdPicList(params) {
const { data } = await request.post('/product/api/merchant/item/getJdPicList', {
data: stringify(params),
prefix: goodsApi,
headers,
});
return data;
}
// 图片上传
export async function uploadImg(files) {
const params = new FormData();
files.forEach(file => params.append('file', file));
const data = await request.post('/image/api/merchant/upload', {
prefix: goodsApi,
data: params,
});
return data;
}
// 修改商品库存
export async function updateStock(params) {
const data = await request.post('/product/item/api/merchant/updateStock', {
prefix: goodsApi,
data: stringify(params),
headers,
});
if (data.businessCode === '0000') {
return null;
}
return data.msg;
}
// 获取虚拟商品类目
export async function getVirtualCategory() {
const data = await request.post('/product/category/api/merchant/getByParentId', {
prefix: goodsApi,
data: stringify({ id: 100018 }),
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 || 0).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) => {
// const stockView =
// row.state !== 4 ? (
// <a onClick={() => this.openModal(row, 'productStock')}>{row.productStock}</a>
// ) : (
// <span>{row.productStock}</span>
// );
const stockView = row.productStock;
return (
<>
<p>当前库存:{stockView}</p>
<p>可售库存:{_}</p>
{row.type === 1 && row.productStockWarning > 0 && (
<p>预警值:{row.productStockWarning}</p>
)}
</>
);
},
},
{
title: '审核状态',
dataIndex: 'stateDesc',
width: 200,
align: 'center',
render: (_, row) => <span>{row.state >= 5 ? '审核通过' : _}</span>,
},
{
title: '操作',
dataIndex: 'action',
width: 120,
align: 'center',
render: (_, row) => (
<div className={styles.actionBtn}>
{row.state === 4 && (
<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)}
>
查看日志
</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, notification } from 'antd';
import React from 'react';
import { ReactSortable } from 'react-sortablejs';
import lodash from 'lodash';
import { uploadImg } from '../../GoodsManage/service';
import styles from './styles.less';
const DETAIL_IMG_MAX_WIDTH = 750;
const LOOP_IMG_WIDTH_HEIGHT = 1200;
const cleanArray = actual => {
const newArray = [];
// eslint-disable-next-line no-plusplus
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i]);
}
}
return newArray;
};
const warningTip = description => {
notification.warning({
// duration: null,
message: '图片上传失败',
description,
});
};
const getBase64 = (img, callback) => {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
};
const ImageInfo = file =>
new Promise((resolve, reject) => {
const LtMB = file.size / 1024 / 1024;
if (LtMB > 2) {
warningTip(`[${file.name}] 图片不可以大于2MB`);
resolve(null);
}
getBase64(file, url => {
const image = new Image();
image.addEventListener('load', () => {
const { width } = image;
const { height } = image;
file.width = width;
file.height = height;
file.LtMB = LtMB;
console.log(file);
resolve(file);
});
image.addEventListener('error', () => {
warningTip(`${file.name}图片上传失败!`);
resolve(null);
});
image.src = url;
});
});
const CheckImageInfoList = async files => {
const promiseImage = files.map(file => ImageInfo(file));
const clearImage = await Promise.all(promiseImage);
return cleanArray(clearImage);
};
const isUploadNext = async (imgFileList, id) => {
const filterImage = imgFileList.filter(({ width, height, name }) => {
if (id === 'detailImageList') {
if (width > DETAIL_IMG_MAX_WIDTH) {
warningTip(`[${name}] 详情图宽度不可大于${DETAIL_IMG_MAX_WIDTH}`);
return false;
}
return true;
}
if (width > LOOP_IMG_WIDTH_HEIGHT || height > LOOP_IMG_WIDTH_HEIGHT) {
warningTip(`[${name}] 滑动图尺寸不可大于 ${LOOP_IMG_WIDTH_HEIGHT}*${LOOP_IMG_WIDTH_HEIGHT}`);
return false;
}
return true;
});
return filterImage;
};
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: lodash.debounce(async (file, files) => {
const optionsArray = await CheckImageInfoList(files);
const onRightImgList = await isUploadNext(optionsArray, that.props.id);
if (!onRightImgList.length) {
return;
}
console.log('校验通过的队列=======>', onRightImgList);
this.setState(
state => ({
newFile: [...state.newFile, ...onRightImgList],
}),
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);
},
);
},
);
// eslint-disable-next-line consistent-return
return false;
}, 500),
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 @@
width: 78%;
}
.logistics {
width: 100%;
display: flex;
justify-content: center;
height: 30px;
align-items: center;
margin-top: 24px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 30px;
margin-top: 24px;
}
.logisticsIcon {
font-size: 25px;
&:first-child {
margin-right: 10px;
}
font-size: 25px;
&:first-child {
margin-right: 10px;
}
}
import * as api from './service';
const tableDate = [{
}];
const tableDate = [{}];
const Model = {
namespace: 'pendingDeliveryOrder',
......@@ -13,12 +11,12 @@ const Model = {
},
effects: {
*getSubjectList({ payload }, { call, put }) {
yield put({
type: 'saveData',
payload: {
tableData: tableDate,
},
});
yield put({
type: 'saveData',
payload: {
tableData: tableDate,
},
});
// const response = yield call(api.subjectList, payload);
// if (response.code === 2000) {
// yield put({
......
......@@ -102,6 +102,12 @@ request.interceptors.response.use(async (response, options) => {
}
const data = await response.clone().json();
if (data.code === 4033) {
// TODO 该接口是否需要提示权限信息
if (options.role) {
notification.warning({
message: '没有权限访问!',
});
}
// token过期
const url = response.url.split(config.api)[1];
return refreshRequest(url, options);
......@@ -116,7 +122,7 @@ request.interceptors.response.use(async (response, options) => {
});
window.location.href = loginPath;
}
if (data.businessCode && data.businessCode !== '0000') {
if (data.businessCode && data.businessCode !== '0000' && !options.notTip) {
notification.warning({
message: data.detail || data.msg || '操作失败',
});
......
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