前端:商品编辑,部分逻辑

This commit is contained in:
YunaiV 2019-05-02 12:47:25 +08:00
parent 788d6c381a
commit e05f19ddc6
9 changed files with 243 additions and 198 deletions

View File

@ -92,6 +92,11 @@ export default [
name: 'product-spu-add', name: 'product-spu-add',
component: './Product/ProductSpuAddOrUpdate', component: './Product/ProductSpuAddOrUpdate',
}, },
{
path: '/product/product-spu-update',
name: 'product-spu-update',
component: './Product/ProductSpuAddOrUpdate',
},
{ {
path: '/product/product-category-list', path: '/product/product-category-list',
name: 'product-category-list', name: 'product-category-list',

View File

@ -6,6 +6,9 @@ import {Icon, Modal, Upload} from "antd";
import styles from './PicturesWall.less'; import styles from './PicturesWall.less';
/**
* 照片墙带有上传图片到七牛
*/
class PicturesWall extends React.Component { class PicturesWall extends React.Component {
state = { state = {
token: undefined, // 七牛 token token: undefined, // 七牛 token

View File

@ -48,6 +48,7 @@ export default {
'menu.product': '商品管理', 'menu.product': '商品管理',
'menu.product.product-spu-list': '商品管理', 'menu.product.product-spu-list': '商品管理',
'menu.product.product-spu-add': '商品添加', 'menu.product.product-spu-add': '商品添加',
'menu.product.product-spu-update': '商品编辑',
'menu.product.product-category-list': '商品分类', 'menu.product.product-category-list': '商品分类',
// 订单 // 订单
'menu.order': '订单管理', 'menu.order': '订单管理',

View File

@ -1,5 +1,5 @@
import { message } from 'antd'; import { message } from 'antd';
import { productSpuPage, productAttrTree, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product'; import { productAttrTree } from '../../services/product';
export default { export default {
namespace: 'productAttrList', namespace: 'productAttrList',
@ -54,7 +54,7 @@ export default {
*tree({ payload }, { call, put }) { *tree({ payload }, { call, put }) {
const { queryParams } = payload; const { queryParams } = payload;
const response = yield call(productAttrTree, queryParams); const response = yield call(productAttrTree, queryParams);
message.info('查询成功!'); // message.info('查询成功!');
yield put({ yield put({
type: 'treeSuccess', type: 'treeSuccess',
payload: { payload: {

View File

@ -1,12 +1,24 @@
import { message } from 'antd'; import { message } from 'antd';
import { productCategoryTree, productSpuAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product'; import {
productCategoryTree,
productSpuAdd,
productCategoryUpdate,
productCategoryUpdateStatus,
productCategoryDelete,
productSpuInfo
} from '../../services/product';
export default { export default {
namespace: 'productSpuAddOrUpdate', namespace: 'productSpuAddOrUpdate',
state: { state: {
list: [], // list: [],
attrTree: [ loading: false,
spu: { // 商品 SPU
},
attrTree: [ // 商品规格
// { // {
// id: // // id: //
// name: // // name: //
@ -16,7 +28,7 @@ export default {
// }] // }]
// } // }
], ],
skus: [ skus: [ // 商品 SKU
// { // {
// attrs: [{ // attrs: [{
// id: // 规格值编号 // id: // 规格值编号
@ -29,17 +41,6 @@ export default {
}, },
effects: { effects: {
// *add({ payload }, { call, put }) {
// const { callback, body } = payload;
// const response = yield call(productCategoryAdd, body);
// if (callback) {
// callback(response);
// }
// yield put({
// type: 'tree',
// payload: {},
// });
// },
// *update({ payload }, { call, put }) { // *update({ payload }, { call, put }) {
// const { callback, body } = payload; // const { callback, body } = payload;
// const response = yield call(productCategoryUpdate, body); // const response = yield call(productCategoryUpdate, body);
@ -51,25 +52,87 @@ export default {
// payload: {}, // payload: {},
// }); // });
// }, // },
// *updateStatus({ payload }, { call, put }) { *info({ payload }, { call, put }) {
// const { callback, body } = payload; // 显示加载中
// const response = yield call(productCategoryUpdateStatus, body); yield put({
// if (callback) { type: 'changeLoading',
// callback(response); payload: true,
// } });
// yield put({
// type: 'tree', // 请求
// payload: {}, const response = yield call(productSpuInfo, {
// }); id: payload,
// }, });
// *delete({ payload }, { call, put }) { // 响应
// const response = yield call(productCategoryDelete, payload); let skus = [];
// message.info('删除成功!'); let attrTree = [];
// yield put({ // SKU
// type: 'tree', for (let i in response.data.skus) {
// payload: {}, let sku = response.data.skus[i];
// }); // 处理 sku
// }, {
let attrs = [];
for (let j in sku.attrs) {
let attr = sku.attrs[j];
attrs.push({
id: attr.attrValueId,
name: attr.attrValueName,
});
}
let newSku = {
...sku,
attrs,
};
skus.push(newSku);
}
// 处理 attrTree
{
for (let j in sku.attrs) {
// debugger;
let attr = sku.attrs[j];
let attrTreeNode = undefined;
for (let k in attrTree) {
let item = attrTree[k];
if (item.id === attr.attrId) {
attrTreeNode = item;
break;
}
}
if (!attrTreeNode) {
attrTreeNode = {
id: attr.attrId,
name: attr.attrName,
values: [{
id: attr.attrValueId,
name: attr.attrValueName,
}]
};
attrTree.push(attrTreeNode);
} else {
// let values = attrTreeNode.values;
// for (let k in ) {
//
// }
}
}
}
}
// debugger;
yield put({
type: 'setAll',
payload: {
spu: response.data,
skus: skus,
attrTree: attrTree,
},
});
// 隐藏加载中
yield put({
type: 'changeLoading',
payload: false,
});
},
*addAttr({ payload }, { call, put }) { *addAttr({ payload }, { call, put }) {
// const { queryParams } = payload; // const { queryParams } = payload;
// const response = yield call(productCategoryTree, queryParams); // const response = yield call(productCategoryTree, queryParams);
@ -212,11 +275,31 @@ export default {
...state ...state
} }
}, },
treeSuccess(state, { payload }) { clear(state, {payload}) {
return {
...state,
skus: [],
attrTree: [],
}
},
changeLoading(state, { payload }) {
return {
...state,
listLoading: payload,
};
},
// 设置所有属性
setAll(state, { payload }) {
return { return {
...state, ...state,
...payload, ...payload,
}; };
}, }
// treeSuccess(state, { payload }) {
// return {
// ...state,
// ...payload,
// };
// },
}, },
}; };

View File

@ -55,6 +55,11 @@ export default {
// const { callback, body } = payload; // const { callback, body } = payload;
yield put(routerRedux.replace('/product/product-spu-add')); yield put(routerRedux.replace('/product/product-spu-add'));
}, },
*redirectToUpdate({ payload }, { call, put }) {
// const { callback, body } = payload;
debugger;
yield put(routerRedux.replace('/product/product-spu-update?id=' + payload));
},
*page({ payload }, { call, put }) { *page({ payload }, { call, put }) {
// const { queryParams } = payload; // const { queryParams } = payload;
const response = yield call(productSpuPage, payload); const response = yield call(productSpuPage, payload);

View File

@ -5,7 +5,7 @@ import React, {PureComponent, Fragment, Component} from 'react';
// import fs from 'fs'; // import fs from 'fs';
import { connect } from 'dva'; import { connect } from 'dva';
import moment from 'moment'; import moment from 'moment';
import {Card, Form, Input, Radio, Button, Modal, Select, Upload, Icon} from 'antd'; import {Card, Form, Input, Radio, Button, Modal, Select, Upload, Icon, Spin} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper'; import PageHeaderWrapper from '@/components/PageHeaderWrapper';
// import * as qiniu from 'qiniu-js' // import * as qiniu from 'qiniu-js'
@ -14,7 +14,7 @@ import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './ProductSpuAddOrUpdate.less'; import styles from './ProductSpuAddOrUpdate.less';
import ProductAttrSelectFormItem from "../../components/Product/ProductAttrSelectFormItem"; import ProductAttrSelectFormItem from "../../components/Product/ProductAttrSelectFormItem";
import ProductSkuAddOrUpdateTable from "../../components/Product/ProductSkuAddOrUpdateTable"; import ProductSkuAddOrUpdateTable from "../../components/Product/ProductSkuAddOrUpdateTable";
import {fileGetQiniuToken} from "../../services/admin"; // import {fileGetQiniuToken} from "../../services/admin";
import PicturesWall from "../../components/Image/PicturesWall"; import PicturesWall from "../../components/Image/PicturesWall";
const FormItem = Form.Item; const FormItem = Form.Item;
@ -22,12 +22,14 @@ const RadioGroup = Radio.Group;
const Option = Select.Option; const Option = Select.Option;
// roleList // roleList
@connect(({ productSpuList, productAttrList, productSpuAddOrUpdate, loading }) => ({ @connect(({ productAttrList, productSpuAddOrUpdate, }) => ({
// list: productSpuList.list.spus, // list: productSpuList.list.spus,
// loading: loading.models.productSpuList, // loading: loading.models.productSpuList,
productAttrList, productAttrList,
productSpuAddOrUpdate, productSpuAddOrUpdate,
allAttrTree: productAttrList.tree, allAttrTree: productAttrList.tree,
loading: productSpuAddOrUpdate.loading,
spu: productSpuAddOrUpdate.spu,
attrTree: productSpuAddOrUpdate.attrTree, attrTree: productSpuAddOrUpdate.attrTree,
skus: productSpuAddOrUpdate.skus, skus: productSpuAddOrUpdate.skus,
})) }))
@ -35,15 +37,26 @@ const Option = Select.Option;
@Form.create() @Form.create()
class ProductSpuAddOrUpdate extends Component { class ProductSpuAddOrUpdate extends Component {
state = { state = {
modalVisible: false, // modalVisible: false,
modalType: 'add', //add update modalType: 'add', //add update
initValues: {}, // initValues: {},
roleAssignVisible: false,
roleAssignRecord: {},
}; };
componentDidMount() { componentDidMount() {
const { dispatch } = this.props; const { dispatch } = this.props;
// 判断是否是更新
const params = new URLSearchParams(this.props.location.search);
if (params.get("id")) {
let id = params.get("id");
this.setState({
modalType: 'update',
})
dispatch({
type: 'productSpuAddOrUpdate/info',
payload: parseInt(id),
})
}
// 获得规格列表
dispatch({ dispatch({
type: 'productAttrList/tree', type: 'productAttrList/tree',
payload: { payload: {
@ -52,6 +65,10 @@ class ProductSpuAddOrUpdate extends Component {
pageSize: 10, pageSize: 10,
}, },
}); });
// 重置表单
dispatch({
type: 'productSpuAddOrUpdate/clear',
})
} }
handleAddAttr = e => { handleAddAttr = e => {
@ -62,7 +79,7 @@ class ProductSpuAddOrUpdate extends Component {
payload: { payload: {
}, },
}); });
} };
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
@ -110,59 +127,11 @@ class ProductSpuAddOrUpdate extends Component {
} }
}); });
// console.log(fields); // console.log(fields);
} };
// handleSelectAttr = (value, option) => {
// // console.log(value);
// // console.log(option);
// // debugger;
// const { dispatch } = this.props;
// let attrIndex = option.key.substring(option.key.indexOf('option-attr-') + 'option-attr-'.length, option.key.lastIndexOf('-'));
// // console.log('attrIndex: ' + attrIndex);
// // debugger;
// dispatch({
// type: 'productSpuAddOrUpdate/selectAttr',
// payload: {
// attrIndex: attrIndex,
// attr: {
// id: option.props.value,
// name: option.props.children,
// values: []
// }
// },
// });
// }
//
// handleSelectAttrValue = (values, options) => {
// let attrValues = [];
// const { dispatch } = this.props;
// debugger;
// // console.log('x' + this.children[0]);
// let firstOption = this.children[0];
// // let attrIndex = firstOption.key.substring(firstOption.key.indexOf('option-attr-value-') + 'option-attr-value-'.length, firstOption.key.lastIndexOf('-'));
// let attrIndex = 0;
// for (let i in options) {
// let option = options[i];
// attrValues.push({
// id: parseInt(option.props.value),
// name: option.props.children,
// });
// }
// dispatch({
// type: 'productSpuAddOrUpdate/selectAttrValues',
// payload: {
// attrIndex: attrIndex,
// attrValues: attrValues,
// },
// });
// // debugger;
//
// // console.log(value);
// }
render() { render() {
// debugger; // debugger;
const { form, skus, attrTree, allAttrTree, dispatch } = this.props; const { form, skus, attrTree, allAttrTree, loading, spu, dispatch } = this.props;
// const that = this; // const that = this;
// 添加规格 // 添加规格
@ -188,47 +157,7 @@ class ProductSpuAddOrUpdate extends Component {
attrTreeHTML.push(<ProductAttrSelectFormItem {...itemProps} />); attrTreeHTML.push(<ProductAttrSelectFormItem {...itemProps} />);
} }
} }
// if (attrTree && attrTree.length > 0) {
// for (let i in attrTree) {
// let attr = attrTree[i];
// // console.log('i: ' + i);
// // 1. 规格
// let attrOptions = [];
// for (let j in allAttrTree) {
// let attr = allAttrTree[j];
// attrOptions.push(<Option key={`option-attr-${i}-${attr.id}`} value={attr.id}>{attr.name}</Option>);
// }
// // 2. 规格值
// let attrValueOptions = [];
// // debugger;
// if (attr.id) {
// // 2.1 先招到规格值的数组
// let attrValues = [];
// for (let j in allAttrTree) {
// let allAttr = allAttrTree[j];
// if (attr.id === allAttr.id) {
// attrValues = allAttr.values;
// break;
// }
// }
// // 2.2 生成规格值的 HTML
// for (let j in attrValues) {
// let attrValue = attrValues[j];
// attrValueOptions.push(<Option key={`option-attr-value-${i}-${attrValue.id}`} value={attrValue.id + ''}>{attrValue.name}</Option>); // + '' 的原因是,多选必须是字符串
// }
// }
// // 3. 拼装最终,添加到 attrTreeHTML 中
// attr = <div key={`div-attr-${i}`}>
// <Select key={`select-attr-${i}`} style={{ width: 120 }} placeholder='请选择规格' onChange={that.handleSelectAttr}>
// {attrOptions}
// </Select>
// <Select key={`select-attr-value-${i}`} mode={"tags"} style={{ width: 260 }} placeholder='请选择规格值' onChange={that.handleSelectAttrValue}>
// {attrValueOptions}
// </Select>
// </div>;
// attrTreeHTML.push(attr);
// }
// }
// 规格明细 // 规格明细
let productSkuProps = { let productSkuProps = {
attrTree: attrTree, attrTree: attrTree,
@ -239,66 +168,70 @@ class ProductSpuAddOrUpdate extends Component {
return ( return (
<PageHeaderWrapper title=""> <PageHeaderWrapper title="">
<Card bordered={false}> <Spin spinning={loading}>
<Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}> <Card bordered={false}>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品名"> <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
{form.getFieldDecorator('name', { <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品名">
rules: [{ required: true, message: '请输入商品名!', min: 2 }], {form.getFieldDecorator('name', {
initialValue: '', // TODO 修改 rules: [{ required: true, message: '请输入商品名!', min: 2 }],
})(<Input placeholder="请输入" />)} initialValue: spu.name,
</FormItem> })(<Input placeholder="请输入" />)}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品卖点"> </FormItem>
{form.getFieldDecorator('sellPoint', { <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品卖点">
rules: [{ required: true, message: '请输入商品卖点!' }], {form.getFieldDecorator('sellPoint', {
initialValue: '', // TODO 修改 rules: [{ required: true, message: '请输入商品卖点!' }],
})(<Input placeholder="请输入" />)} initialValue: spu.sellPoint,
</FormItem> })(<Input placeholder="请输入" />)}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类编号"> </FormItem>
{form.getFieldDecorator('cid', { <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类编号">
rules: [{ required: true, message: '请输入分类编号!' }], {form.getFieldDecorator('cid', {
initialValue: '', // TODO 修改 // TODO 芋艿,和面做成下拉框 rules: [{ required: true, message: '请输入分类编号!' }],
})(<Input placeholder="请输入" />)} initialValue: spu.cid, // TODO 芋艿,和面做成下拉框
</FormItem> })(<Input placeholder="请输入" />)}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品主图" </FormItem>
extra="建议尺寸800*800PX单张大小不超过 2M最多可上传 10 张"> <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品主图"
{/*{form.getFieldDecorator('picUrls', {*/} extra="建议尺寸800*800PX单张大小不超过 2M最多可上传 10 张">
{/* initialValue: '', // TODO 修改 // TODO 芋艿,做成上传组件*/} {/*{form.getFieldDecorator('picUrls', {*/}
{/*})(<Input placeholder="请输入" />)}*/} {/* initialValue: '', // TODO 修改 // TODO 芋艿,做成上传组件*/}
<PicturesWall ref="picturesWall" maxLength={10} /> {/*})(<Input placeholder="请输入" />)}*/}
</FormItem> <PicturesWall ref="picturesWall" maxLength={10} />
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="是否上架"> </FormItem>
{form.getFieldDecorator('visible', { <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="是否上架">
initialValue: 1, // TODO 修改 {form.getFieldDecorator('visible', {
})( initialValue: spu.visible,
<RadioGroup> })(
<Radio value={1}></Radio> <RadioGroup>
<Radio value={2}></Radio> <Radio value={true}></Radio>
</RadioGroup> <Radio value={false}></Radio>
)} </RadioGroup>
</FormItem> )}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品规格"> </FormItem>
{form.getFieldDecorator('visible', { <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品规格">
initialValue: 1, // TODO 修改 {form.getFieldDecorator('visible', {
})( initialValue: 1, // TODO 修改
<div> })(
{attrTreeHTML} <div>
<Button onClick={this.handleAddAttr}>添加规格项目</Button> {attrTreeHTML}
</div> <Button onClick={this.handleAddAttr}>添加规格项目</Button>
)} </div>
</FormItem> )}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="规格明细"> </FormItem>
{/*<Table defaultExpandAllRows={true} columns={columns} rowKey="id" />*/} {
<ProductSkuAddOrUpdateTable {...productSkuProps} /> attrTree.length > 0 ? <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="规格明细">
</FormItem> {/*<Table defaultExpandAllRows={true} columns={columns} rowKey="id" />*/}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品描述"> <ProductSkuAddOrUpdateTable {...productSkuProps} />
{form.getFieldDecorator('description', { </FormItem> : ''
rules: [{ required: true, message: '请输入商品描述!' }], }
initialValue: '', // TODO 修改 <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="商品描述">
})(<Input.TextArea placeholder="请输入" />)} {form.getFieldDecorator('description', {
<Button type="primary" htmlType="submit" style={{ marginLeft: 8 }} onSubmit={this.handleSubmit}>保存</Button> rules: [{ required: true, message: '请输入商品描述!' }],
</FormItem> initialValue: spu.description, // TODO 修改
</Form> })(<Input.TextArea placeholder="请输入" />)}
</Card> <Button type="primary" htmlType="submit" style={{ marginLeft: 8 }} onSubmit={this.handleSubmit}>保存</Button>
</FormItem>
</Form>
</Card>
</Spin>
</PageHeaderWrapper> </PageHeaderWrapper>
); );
} }

View File

@ -47,6 +47,14 @@ class ProductSpuList extends PureComponent {
}); });
}; };
redirectToUpdate = (spuId) => {
const { dispatch } = this.props;
dispatch({
type: 'productSpuList/redirectToUpdate',
payload: spuId,
});
};
render() { render() {
// debugger; // debugger;
const { list, data } = this.props; const { list, data } = this.props;
@ -103,7 +111,8 @@ class ProductSpuList extends PureComponent {
width: 200, width: 200,
render: (text, record) => ( render: (text, record) => (
<Fragment> <Fragment>
<a onClick={() => this.handleModalVisible(true, 'update', record)}>更新</a> {/*<a onClick={() => this.handleModalVisible(true, 'update', record)}>更新</a>*/}
<a onClick={() => this.redirectToUpdate(record.id)}>更新</a>
</Fragment> </Fragment>
), ),
}, },

View File

@ -52,6 +52,12 @@ export async function productSpuAdd(params) {
}); });
} }
export async function productSpuInfo(params) {
return request(`/product-api/admins/spu/info?${stringify(params)}`, {
method: 'GET',
});
}
// product attr + attr value // product attr + attr value
export async function productAttrTree(params) { export async function productAttrTree(params) {