diff --git a/admin-web/src/components/Product/ProductAttrSelectFormItem.js b/admin-web/src/components/Product/ProductAttrSelectFormItem.js
new file mode 100644
index 000000000..3c019d940
--- /dev/null
+++ b/admin-web/src/components/Product/ProductAttrSelectFormItem.js
@@ -0,0 +1,101 @@
+import React, {PureComponent} from "react";
+import {Select} from "antd";
+
+const Option = Select.Option;
+
+export default class ProductAttrSelectFormItem extends PureComponent {
+
+ handleSelectAttr = (value, option) => {
+ // console.log(value);
+ // console.log(option);
+ // debugger;
+ const { dispatch, index } = 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: index,
+ attr: {
+ id: option.props.value,
+ name: option.props.children,
+ values: []
+ }
+ },
+ });
+ }
+
+ handleSelectAttrValue = (values, options) => {
+ let attrValues = [];
+ const { dispatch, index } = 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('-'));
+ 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: index,
+ attrValues: attrValues,
+ },
+ });
+ // debugger;
+
+ // console.log(value);
+ }
+
+ render() {
+ const {attr, allAttrTree, selectedAttrIds, index} = this.props;
+ // console.log('i: ' + i);
+ // 1. 规格
+ let attrOptions = [];
+ // allAttrTree.unshift(attr);
+ // debugger;
+ for (let j in allAttrTree) {
+ let allAttr = allAttrTree[j];
+ if (selectedAttrIds.has(allAttr.id) && allAttr.id !== attr.id) {
+ continue;
+ }
+ attrOptions.push();
+ }
+ // 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(); // + '' 的原因是,多选必须是字符串
+ }
+ }
+ // 3. 拼装最终,添加到 attrTreeHTML 中
+ return
+
+
+
;
+ }
+
+}
\ No newline at end of file
diff --git a/admin-web/src/components/Product/ProductSkuAddOrUpdateTable.js b/admin-web/src/components/Product/ProductSkuAddOrUpdateTable.js
new file mode 100644
index 000000000..8e9452b92
--- /dev/null
+++ b/admin-web/src/components/Product/ProductSkuAddOrUpdateTable.js
@@ -0,0 +1,85 @@
+import React, {PureComponent} from "react";
+import {InputNumber, Select, Table} from "antd";
+import Input from "antd/es/input";
+
+const Option = Select.Option;
+
+class SkuInputNumber extends PureComponent {
+
+ handleChange = value => {
+ // debugger;
+ const { dispatch, index, dataIndex } = this.props;
+ if (dataIndex === 'price') {
+ dispatch({
+ type: 'productSpuAddOrUpdate/inputSkuPrice',
+ payload: {
+ index: index,
+ price: value
+ },
+ });
+ } else if (dataIndex === 'quantity') {
+ dispatch({
+ type: 'productSpuAddOrUpdate/inputSkuQuantity',
+ payload: {
+ index: index,
+ quantity: value
+ },
+ });
+ }
+ }
+
+ render() {
+ return
+ }
+
+}
+
+export default class ProductSkuAddOrUpdateTable extends PureComponent {
+
+ render() {
+ let that = this;
+ // debugger;
+ // console.log('ProductSkuAddOrUpdateTable');
+ const {attrTree, skus, dispatch} = this.props;
+ let columns = [];
+ for (let i in attrTree) {
+ let attr = attrTree[i];
+ columns.push({
+ title: attr.name,
+ dataIndex: 'attrs[i]',
+ render(value, record) {
+ return record.attrs[i].name;
+ }
+ })
+ }
+ columns.push({
+ title: '价格',
+ dataIndex: 'price',
+ render(value, record, index) {
+ let props = {
+ record: record,
+ index: index,
+ dispatch: dispatch,
+ dataIndex: 'price'
+ };
+ return ;
+ }
+ });
+ columns.push({
+ title: '库存',
+ dataIndex: 'quantity',
+ render(value, record, index) {
+ let props = {
+ record: record,
+ index: index,
+ dispatch: dispatch,
+ dataIndex: 'quantity'
+ };
+ return ;
+ }
+ });
+ return ;
+ // return ;
+ }
+
+}
\ No newline at end of file
diff --git a/admin-web/src/locales/zh-CN/menu.js b/admin-web/src/locales/zh-CN/menu.js
index be480f4df..44bdf9a3f 100644
--- a/admin-web/src/locales/zh-CN/menu.js
+++ b/admin-web/src/locales/zh-CN/menu.js
@@ -44,4 +44,9 @@ export default {
'menu.account.settings': '个人设置',
'menu.account.trigger': '触发报错',
'menu.account.logout': '退出登录',
+ // 商品相关
+ 'menu.product': '商品管理',
+ 'menu.product.product-spu-list': '商品管理',
+ 'menu.product.product-spu-add': '商品添加',
+ 'menu.product.product-category-list': '商品分类',
};
diff --git a/admin-web/src/models/product/productSpuAddOrUpdate.js b/admin-web/src/models/product/productSpuAddOrUpdate.js
index f457e333c..da9f0afc8 100644
--- a/admin-web/src/models/product/productSpuAddOrUpdate.js
+++ b/admin-web/src/models/product/productSpuAddOrUpdate.js
@@ -1,5 +1,5 @@
import { message } from 'antd';
-import { productCategoryTree, productCategoryAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
+import { productCategoryTree, productSpuAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
export default {
namespace: 'productSpuAddOrUpdate',
@@ -15,6 +15,16 @@ export default {
// name: //
// }]
// }
+ ],
+ skus: [
+ // {
+ // attrs: [{
+ // id: // 规格值编号
+ // name: // 规格值名
+ // }],
+ // price: // 价格
+ // quantity: // 数量
+ // }
]
},
@@ -63,7 +73,7 @@ export default {
*addAttr({ payload }, { call, put }) {
// const { queryParams } = payload;
// const response = yield call(productCategoryTree, queryParams);
- message.info('调试:添加规格成功!');
+ // message.info('调试:添加规格成功!');
yield put({
type: 'addAttrSuccess',
payload: {
@@ -74,12 +84,46 @@ export default {
*selectAttr({ payload }, { call, put }) {
// const { queryParams } = payload;
// const response = yield call(productCategoryTree, queryParams);
- message.info('调试:添加规格成功!');
+ // message.info('调试:选择规格成功!');
yield put({
type: 'selectAttrSuccess',
payload: payload,
});
},
+ *selectAttrValues({ payload }, { call, put }) {
+ // const { queryParams } = payload;
+ // const response = yield call(productCategoryTree, queryParams);
+ // message.info('调试:选择规格值成功!');
+ yield put({
+ type: 'selectAttrValueSuccess',
+ payload: payload,
+ });
+ },
+ *inputSkuPrice({ payload }, { call, put }) {
+ // debugger;
+ yield put({
+ type: 'inputSkuPriceSuccess',
+ payload: payload,
+ });
+ },
+ *inputSkuQuantity({ payload }, { call, put }) {
+ // debugger;
+ yield put({
+ type: 'inputSkuQuantitySuccess',
+ payload: payload,
+ });
+ },
+ *add({ payload }, { call, put }) {
+ const { callback, body } = payload;
+ const response = yield call(productSpuAdd, body);
+ if (callback) {
+ callback(response);
+ }
+ yield put({
+ type: 'tree',
+ payload: {},
+ });
+ },
},
reducers: {
@@ -99,6 +143,62 @@ export default {
...state
}
},
+ selectAttrValueSuccess(state, {payload}) {
+ // debugger;
+ // console.log(state);
+ state.attrTree[payload.attrIndex].values = payload.attrValues;
+ // 生成 skus 值
+ let skus = [];
+ let skuSize = 1;
+ for (let i in state.attrTree) { // 先计算 sku 数量
+ let attr = state.attrTree[i];
+ skuSize = skuSize * attr.values.length;
+ }
+ // console.log('skuSize: ' + skuSize);
+ for (let i = 0; i < skuSize; i++) { // 初始化 sku 格子
+ skus.push({
+ attrs: [],
+ price: undefined,
+ quantity: undefined,
+ });
+ }
+ for (let i = 0; i < state.attrTree.length; i++) { // 初始化 sku 格子里的 attrs
+ for (let j = 0; j < skuSize; j++) {
+ // let values = state.attrTree[i].values;
+ // let attr = values[j % values.length];
+ // skus[i].attrs.push({
+ // id: attr.id,
+ // name: attr.name,
+ // });
+ let values = state.attrTree[i].values;
+ let attr = values[j % values.length];
+ skus[j].attrs.push({
+ id: attr.id,
+ name: attr.name,
+ });
+ }
+ }
+ state.skus = skus;
+ // debugger;
+ // console.l og('skus: ' + skus);
+ return {
+ ...state
+ }
+ },
+ inputSkuPriceSuccess(state, {payload}) {
+ // debugger;
+ state.skus[payload.index].price = payload.price;
+ return {
+ ...state
+ }
+ },
+ inputSkuQuantitySuccess(state, {payload}) {
+ // debugger;
+ state.skus[payload.index].quantity = payload.quantity;
+ return {
+ ...state
+ }
+ },
treeSuccess(state, { payload }) {
return {
...state,
diff --git a/admin-web/src/pages/Product/ProductSpuAddOrUpdate.js b/admin-web/src/pages/Product/ProductSpuAddOrUpdate.js
index 7308e0474..2aa0d6660 100644
--- a/admin-web/src/pages/Product/ProductSpuAddOrUpdate.js
+++ b/admin-web/src/pages/Product/ProductSpuAddOrUpdate.js
@@ -7,6 +7,8 @@ import {Card, Form, Input, Radio, Button, Table, Select} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './ProductSpuAddOrUpdate.less';
+import ProductAttrSelectFormItem from "../../components/Product/ProductAttrSelectFormItem";
+import ProductSkuAddOrUpdateTable from "../../components/Product/ProductSkuAddOrUpdateTable";
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
@@ -19,7 +21,8 @@ const Option = Select.Option;
productAttrList,
productSpuAddOrUpdate,
allAttrTree: productAttrList.tree,
- attrTree: productSpuAddOrUpdate.attrTree
+ attrTree: productSpuAddOrUpdate.attrTree,
+ skus: productSpuAddOrUpdate.skus,
}))
@Form.create()
@@ -44,18 +47,18 @@ class ProductSpuAddOrUpdate extends Component {
});
}
- handleSubmit = e => {
- const { dispatch, form } = this.props;
- e.preventDefault();
- form.validateFieldsAndScroll((err, values) => {
- if (!err) {
- dispatch({
- type: 'form/submitRegularForm',
- payload: values,
- });
- }
- });
- }
+ // handleSubmit = e => {
+ // const { dispatch, form } = this.props;
+ // e.preventDefault();
+ // form.validateFieldsAndScroll((err, values) => {
+ // if (!err) {
+ // dispatch({
+ // type: 'form/submitRegularForm',
+ // payload: values,
+ // });
+ // }
+ // });
+ // }
handleAddAttr = e => {
// alert('你猜');
@@ -67,74 +70,171 @@ class ProductSpuAddOrUpdate extends Component {
});
}
- handleSelectAttr = (value, option) => {
- console.log(value);
- console.log(option);
+ handleSubmit = e => {
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,
- }
- },
+ e.preventDefault();
+ const { skus, dispatch } = this.props;
+ // 生成 skuStr 格式
+ let skuStr = []; // 因为提交是字符串格式
+ for (let i in skus) {
+ let sku = skus[i];
+ if (!sku.price || !sku.quantity) {
+ continue;
+ }
+ let newAttr = {
+ attrs: [],
+ price: sku.price,
+ quantity: sku.quantity,
+ }
+ for (let j in sku.attrs) {
+ newAttr.attrs.push(sku.attrs[j].id);
+ }
+ skuStr.push(newAttr);
+ }
+ if (skuStr.length === 0) {
+ alert('请设置商品规格!');
+ return;
+ }
+ this.props.form.validateFields((err, values) => {
+ if (!err) {
+ dispatch({
+ type: 'productSpuAddOrUpdate/add',
+ payload: {
+ body: {
+ ...values,
+ skuStr: JSON.stringify(skuStr)
+ }
+ },
+ });
+ }
});
+ // 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() {
// debugger;
- const { form, data, attrTree, allAttrTree } = this.props;
- const that = this;
-
- // 规格明细
- const columns = [
- {
- title: '颜色',
- dataIndex: 'price'
- },
- {
- title: '价格',
- dataIndex: 'price',
- render(val) {
- return {status[val]};
- },
- },
- {
- title: '库存',
- dataIndex: 'quantity',
- }
- ];
+ const { form, skus, attrTree, allAttrTree, dispatch } = this.props;
+ // const that = this;
// 添加规格
// debugger;
let attrTreeHTML = [];
if (attrTree && attrTree.length > 0) {
+ // 已选择的的规格集合
+ let selectedAttrIds = new Set();
for (let i in attrTree) {
let attr = attrTree[i];
- // console.log('i: ' + i);
- // 1. 规格
- let options = [];
- for (let j in allAttrTree) {
- let attr = allAttrTree[j];
- options.push();
- }
- // 2. 规格值
-
- // 3. 拼装最终,添加到 attrTreeHTML 中
- attr =
-
-
;
- attrTreeHTML.push(attr);
+ selectedAttrIds.add(attr.id);
+ }
+ // 创建每个规格下拉框的 HTML
+ for (let i in attrTree) {
+ let attr = attrTree[i];
+ let itemProps = {
+ attr: attr,
+ allAttrTree: allAttrTree,
+ dispatch: dispatch,
+ selectedAttrIds: selectedAttrIds,
+ index: i // 位置。不然无法正确修改 Model 指定位置的数据
+ };
+ attrTreeHTML.push();
}
}
+ // 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();
+ // }
+ // // 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(); // + '' 的原因是,多选必须是字符串
+ // }
+ // }
+ // // 3. 拼装最终,添加到 attrTreeHTML 中
+ // attr =
+ //
+ //
+ //
;
+ // attrTreeHTML.push(attr);
+ // }
+ // }
+ // 规格明细
+ let productSkuProps = {
+ attrTree: attrTree,
+ skus: skus,
+ dispatch: dispatch,
+ };
+ // console.log(productSkuProps);
return (
@@ -189,13 +289,12 @@ class ProductSpuAddOrUpdate extends Component {
)}
- {/**/}
- {/*{form.getFieldDecorator('visible', {*/}
- {/*initialValue: 1, // TODO 修改*/}
- {/*})(*/}
- {/**/}
- {/*)}*/}
- {/**/}
+
+ {/**/}
+
+
+
+
diff --git a/admin-web/src/services/product.js b/admin-web/src/services/product.js
index c0a51274e..f8181bf02 100644
--- a/admin-web/src/services/product.js
+++ b/admin-web/src/services/product.js
@@ -44,6 +44,13 @@ export async function productSpuPage(params) {
});
}
+export async function productSpuAdd(params) {
+ return request(`/product-api/admins/spu/add?${stringify(params)}`, {
+ method: 'POST',
+ body: {},
+ });
+}
+
// product attr + attr value
export async function productAttrTree(params) {
diff --git a/product/product-application/src/main/java/cn/iocoder/mall/product/application/controller/admins/AdminsProductSpuController.java b/product/product-application/src/main/java/cn/iocoder/mall/product/application/controller/admins/AdminsProductSpuController.java
index 0c64f6dab..d598b03dd 100644
--- a/product/product-application/src/main/java/cn/iocoder/mall/product/application/controller/admins/AdminsProductSpuController.java
+++ b/product/product-application/src/main/java/cn/iocoder/mall/product/application/controller/admins/AdminsProductSpuController.java
@@ -52,7 +52,7 @@ public class AdminsProductSpuController {
@RequestParam("sellPoint") String sellPoint,
@RequestParam("description") String description,
@RequestParam("cid") Integer cid,
- @RequestParam("picURLs") List picUrls,
+ @RequestParam("picUrls") List picUrls,
@RequestParam("visible") Boolean visible,
@RequestParam("skuStr") String skuStr) { // TODO 芋艿,因为考虑不使用 json 接受参数,所以这里手动转。
// 创建 ProductSpuAddDTO 对象