diff --git a/controller/product/inventory.go b/controller/product/inventory.go new file mode 100644 index 00000000..35475064 --- /dev/null +++ b/controller/product/inventory.go @@ -0,0 +1,175 @@ +package product + +import ( + "fmt" + "strings" + "strconv" + "unicode/utf8" + "wemall/model" + "wemall/config" + "gopkg.in/kataras/iris.v6" +) + +func combinationPropValue(productID uint, properties []model.Property) []model.Inventory { + var inventories []model.Inventory + if len(properties) == 1 { + for i := 0; i < len(properties[0].PropertyValues); i++ { + var inventory = model.Inventory{ + ProductID : productID, + PropertyValues : []model.PropertyValue{ + properties[0].PropertyValues[i], + }, + } + inventories = append(inventories, inventory) + } + } else { + theInventories := combinationPropValue(productID, properties[1:]) + property := properties[0] + for i := len(theInventories) - 1; i >= 0; i-- { + for j := 0; j < len(property.PropertyValues); j++ { + var inventory = model.Inventory{ + ProductID : productID, + PropertyValues : theInventories[i].PropertyValues, + } + inventory.PropertyValues = append(inventory.PropertyValues, property.PropertyValues[j]) + inventories = append(inventories, inventory) + } + theInventories = append(theInventories[:i], theInventories[i + 1:]...) + } + } + return inventories +} + +// test 添加商品属性值 +func test(ctx *iris.Context) { + var productID uint + var propertyValue model.PropertyValue + + if err := ctx.ReadJSON(&propertyValue); err != nil { + fmt.Println(err.Error()); + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "参数无效", + "data" : iris.Map{}, + }) + return + } + + productID = propertyValue.ProductID + propertyValue.Name = strings.TrimSpace(propertyValue.Name) + + var isErr bool + var errMsg = "" + + if productID <= 0 { + isErr = true + errMsg = "无效的商品ID" + } else if utf8.RuneCountInString(propertyValue.Name) > config.ServerConfig.MaxNameLen { + isErr = true + errMsg = "名称不能超过" + strconv.Itoa(config.ServerConfig.MaxNameLen) + "个字符" + } else if utf8.RuneCountInString(propertyValue.Name) <= 0 { + isErr = true + errMsg = "名称不能为空" + } + + if isErr { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : errMsg, + "data" : iris.Map{}, + }) + return + } + + var product model.Product + + if err := model.DB.First(&product, productID).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.NotFound, + "msg" : "错误的商品id", + "data" : iris.Map{}, + }) + return + } + + if err := model.DB.Model(&product).Related(&product.Properties).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + var properties = product.Properties + var index = -1 //属性(新添加的属性值属于的属性)在属性数组中的索引 + for i := 0; i < len(properties); i++ { + fmt.Println(123, properties[i].ID, propertyValue.PropertyID) + if properties[i].ID == propertyValue.PropertyID { + index = i + break; + } + } + + if index < 0 { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "错误的propertyID", + "data" : iris.Map{}, + }) + return + } + + for i := 0; i < len(properties); i++ { + property := properties[i] + if err := model.DB.Model(&property).Related(&property.PropertyValues).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + properties[i] = property + } + + if err := model.DB.Create(&propertyValue).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + var inventories []model.Inventory + if len(properties) == 1 { + var inventory = model.Inventory{ + ProductID : productID, + PropertyValues : append([]model.PropertyValue{}, propertyValue), + } + inventories = append(inventories, inventory) + } else if len(properties) >= 2 { + properties = append(properties[:index], properties[index + 1:]...) + inventories = combinationPropValue(productID, properties) + for i := 0; i < len(inventories); i++ { + inventories[i].PropertyValues = append(inventories[i].PropertyValues, propertyValue) + } + } + + for i := 0; i < len(inventories); i++ { + if err := model.DB.Create(&inventories[i]).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + } +} \ No newline at end of file diff --git a/controller/product/property.go b/controller/product/property.go new file mode 100644 index 00000000..6fb3ccc9 --- /dev/null +++ b/controller/product/property.go @@ -0,0 +1,182 @@ +package product + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" + "wemall/model" + "wemall/config" + "gopkg.in/kataras/iris.v6" +) + +// AddProperty 添加商品属性 +func AddProperty(ctx *iris.Context) { + var property model.Property + + if err := ctx.ReadJSON(&property); err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "参数无效", + "data" : iris.Map{}, + }) + return + } + + property.Name = strings.TrimSpace(property.Name) + + var isErr bool + var errMsg = "" + + if property.ProductID <= 0 { + isErr = true + errMsg = "无效的商品ID" + } else if utf8.RuneCountInString(property.Name) > config.ServerConfig.MaxNameLen { + isErr = true + errMsg = "属性名称不能超过" + strconv.Itoa(config.ServerConfig.MaxNameLen) + "个字符" + } else if utf8.RuneCountInString(property.Name) <= 0 { + isErr = true + errMsg = "属性名称不能为空" + } + + if isErr { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : errMsg, + "data" : iris.Map{}, + }) + return + } + + var product model.Product + + if err := model.DB.First(&product, property.ProductID).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.NotFound, + "msg" : "错误的商品id", + "data" : iris.Map{}, + }) + return + } + + if err := model.DB.Create(&property).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + property.PropertyValues = []model.PropertyValue{} + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.SUCCESS, + "msg" : "success", + "data" : iris.Map{ + "property": property, + }, + }) +} + +// AddPropertyValue 添加商品属性值 +func AddPropertyValue(ctx *iris.Context) { + var productID uint + var propertyValue model.PropertyValue + + if err := ctx.ReadJSON(&propertyValue); err != nil { + fmt.Println(err.Error()); + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "参数无效", + "data" : iris.Map{}, + }) + return + } + + productID = propertyValue.ProductID + propertyValue.Name = strings.TrimSpace(propertyValue.Name) + + var isErr bool + var errMsg = "" + + if productID <= 0 { + isErr = true + errMsg = "无效的商品ID" + } else if utf8.RuneCountInString(propertyValue.Name) > config.ServerConfig.MaxNameLen { + isErr = true + errMsg = "名称不能超过" + strconv.Itoa(config.ServerConfig.MaxNameLen) + "个字符" + } else if utf8.RuneCountInString(propertyValue.Name) <= 0 { + isErr = true + errMsg = "名称不能为空" + } + + if isErr { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : errMsg, + "data" : iris.Map{}, + }) + return + } + + var product model.Product + + if err := model.DB.First(&product, productID).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.NotFound, + "msg" : "错误的商品id", + "data" : iris.Map{}, + }) + return + } + + if err := model.DB.Model(&product).Related(&product.Properties).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + var properties = product.Properties + var index = -1 //属性(新添加的属性值属于的属性)在属性数组中的索引 + for i := 0; i < len(properties); i++ { + if properties[i].ID == propertyValue.PropertyID { + index = i + break; + } + } + + if index < 0 { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "错误的propertyID", + "data" : iris.Map{}, + }) + return + } + + if err := model.DB.Create(&propertyValue).Error; err != nil { + fmt.Println(err.Error()) + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.SUCCESS, + "msg" : "success", + "data" : iris.Map{ + "propertyValue": propertyValue, + }, + }) +} \ No newline at end of file diff --git a/main.go b/main.go index 176653b1..9b4b3e0f 100644 --- a/main.go +++ b/main.go @@ -69,4 +69,7 @@ func main() { }) app.Listen(":" + strconv.Itoa(config.ServerConfig.Port)) -} \ No newline at end of file +} + + + diff --git a/model/inventory.go b/model/inventory.go new file mode 100644 index 00000000..4b551a46 --- /dev/null +++ b/model/inventory.go @@ -0,0 +1,14 @@ +package model + +import "time" + +// Inventory 商品库存 +type Inventory struct { + ID uint `gorm:"primary_key" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `sql:"index" json:"deletedAt"` + ProductID uint `json:"productID"` + Count uint `json:"count"` + PropertyValues []PropertyValue `gorm:"many2many:inventory_property_value;ForeignKey:ID;AssociationForeignKey:ID" json:"propertyValues"` +} \ No newline at end of file diff --git a/model/product.go b/model/product.go index d3740091..765b46e3 100644 --- a/model/product.go +++ b/model/product.go @@ -27,39 +27,6 @@ type Product struct { Categories []Category `gorm:"many2many:product_category;ForeignKey:ID;AssociationForeignKey:ID" json:"categories"` } -// Property 商品属性 -type Property struct { - ID uint `gorm:"primary_key" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `sql:"index" json:"deletedAt"` - Name string `json:"name"` - ProductID uint `json:"productID"` - PropertyValues []PropertyValue `json:"values"` -} - -// PropertyValue 商品属性值 -type PropertyValue struct { - ID uint `gorm:"primary_key" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `sql:"index" json:"deletedAt"` - Name string `json:"name"` - Note string `json:"note"` - PropertyID uint `json:"propertyID"` -} - -// Inventory 商品库存 -type Inventory struct { - ID uint `gorm:"primary_key" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `sql:"index" json:"deletedAt"` - ProductID uint `json:"productID"` - Count uint `json:"count"` - PropertyValues []PropertyValue `gorm:"many2many:inventory_property;ForeignKey:ID;AssociationForeignKey:ID" json:"propertyValues"` -} - const ( // ProductUpShelf 商品已上架 ProductUpShelf = 1 diff --git a/model/property.go b/model/property.go new file mode 100644 index 00000000..64a6a8c3 --- /dev/null +++ b/model/property.go @@ -0,0 +1,26 @@ +package model + +import "time" + +// Property 商品属性 +type Property struct { + ID uint `gorm:"primary_key" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `sql:"index" json:"deletedAt"` + Name string `json:"name"` + ProductID uint `json:"productID"` + PropertyValues []PropertyValue `json:"values"` +} + +// PropertyValue 商品属性值 +type PropertyValue struct { + ID uint `gorm:"primary_key" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `sql:"index" json:"deletedAt"` + Name string `json:"name"` + Note string `json:"note"` + ProductID uint `json:"productID"` + PropertyID uint `json:"propertyID"` +} \ No newline at end of file diff --git a/nodejs/static/javascripts/admin/actions/product/requestSaveProperty.js b/nodejs/static/javascripts/admin/actions/product/requestSaveProperty.js new file mode 100644 index 00000000..86a074b4 --- /dev/null +++ b/nodejs/static/javascripts/admin/actions/product/requestSaveProperty.js @@ -0,0 +1,32 @@ +import { + REQUEST_SAVE_PRODUCT_PROP, + REQUEST_SAVE_PRODUCT_PROP_SUCCESS, +} from '../../constants'; + +function receive(data) { + return { + type : REQUEST_SAVE_PRODUCT_PROP_SUCCESS, + property : data.property + }; +} + +export default function(data) { + return dispatch => { + var url = pageConfig.apiPath + '/admin/product/property/create'; + var reqData = { + productID : data.productID, + name : data.name + }; + return fetch(url, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(reqData) + }) + .then(response => response.json()) + .then(json => dispatch(receive(json.data))); + }; +}; + diff --git a/nodejs/static/javascripts/admin/actions/product/requestSavePropertyValue.js b/nodejs/static/javascripts/admin/actions/product/requestSavePropertyValue.js new file mode 100644 index 00000000..abf6b0e5 --- /dev/null +++ b/nodejs/static/javascripts/admin/actions/product/requestSavePropertyValue.js @@ -0,0 +1,33 @@ +import { + REQUEST_SAVE_PRODUCT_PROP_VALUE, + REQUEST_SAVE_PRODUCT_PROP_VALUE_SUCCESS, +} from '../../constants'; + +function receive(data) { + return { + type : REQUEST_SAVE_PRODUCT_PROP_VALUE_SUCCESS, + propertyValue : data.propertyValue + }; +} + +export default function(data) { + return dispatch => { + var url = pageConfig.apiPath + '/admin/product/property/saveval'; + var reqData = { + productID : data.productID, + name : data.name, + propertyID : data.propertyID + }; + return fetch(url, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(reqData) + }) + .then(response => response.json()) + .then(json => dispatch(receive(json.data))); + }; +}; + diff --git a/nodejs/static/javascripts/admin/constants/index.js b/nodejs/static/javascripts/admin/constants/index.js index e37dda74..b3a8e412 100644 --- a/nodejs/static/javascripts/admin/constants/index.js +++ b/nodejs/static/javascripts/admin/constants/index.js @@ -24,6 +24,14 @@ export const REQUEST_SAVE_PRODUCT = 'requestSaveProduct'; export const REQUEST_SAVE_PRODUCT_SUCCESS = 'requestSaveProductSuccess'; +export const REQUEST_SAVE_PRODUCT_PROP = 'requestSaveProductProp'; + +export const REQUEST_SAVE_PRODUCT_PROP_SUCCESS = 'requestSaveProductPropSuccess'; + +export const REQUEST_SAVE_PRODUCT_PROP_VALUE = 'requestSaveProductPropValue'; + +export const REQUEST_SAVE_PRODUCT_PROP_VALUE_SUCCESS = 'requestSaveProductPropValueSuccess'; + export const CHANGE_PRODUCT_STATUS = 'changeProductStatus'; export const REQUEST_CATEGORY_LIST = 'requestCategoryList'; diff --git a/nodejs/static/javascripts/admin/containers/product/EditProduct.js b/nodejs/static/javascripts/admin/containers/product/EditProduct.js index 7ed9a95c..532f3917 100644 --- a/nodejs/static/javascripts/admin/containers/product/EditProduct.js +++ b/nodejs/static/javascripts/admin/containers/product/EditProduct.js @@ -21,6 +21,8 @@ import { import requestProduct from '../../actions/product/requestProduct'; import requestSaveProduct from '../../actions/product/requestSaveProduct'; import requestCategoryList from '../../actions/category/requestCategoryList'; +import requestSaveProperty from '../../actions/product/requestSaveProperty'; +import requestSavePropertyValue from '../../actions/product/requestSavePropertyValue'; import Software from '../Software'; import utils from '../../utils'; import analyze from '../../../sdk/analyze'; @@ -48,6 +50,10 @@ class EditProduct extends Component { this.onContentImageChange = this.onContentImageChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.onPropValueVisibleChange = this.onPropValueVisibleChange.bind(this); + this.onPropVisibleChange = this.onPropVisibleChange.bind(this); + this.onPropInput = this.onPropInput.bind(this); + this.addProp = this.addProp.bind(this); + this.cancelAddProp = this.cancelAddProp.bind(this); this.state = { productId : this.props.routeParams.id, @@ -69,6 +75,8 @@ class EditProduct extends Component { inventories : [], propValueVisibleMap : {}, propValueTemp : '', + propPopupVisible : false, + propTemp : '', isLoading : true }; } @@ -114,6 +122,7 @@ class EditProduct extends Component { if (allCategories && allCategories.length > 0) { if (this.state.productId) { if (product) { + console.log(product); var categories = []; for (var i = 0; i < product.categories.length; i++) { var parentId = product.categories[i].parentId; @@ -306,7 +315,12 @@ class EditProduct extends Component { propValueTemp : '' }); - message.success(propValueTemp + '添加成功! 请完善库存。'); + const { dispatch } = this.props; + dispatch(requestSavePropertyValue({ + productID : this.state.productId, + propertyID : propId, + name : propValueTemp + })); } cancelAddPropValue(propId) { var propValueVisibleMap = this.state.propValueVisibleMap; @@ -316,6 +330,34 @@ class EditProduct extends Component { propValueTemp : '' }); } + onPropVisibleChange(visible) { + this.setState({ + propPopupVisible: visible + }); + } + onPropInput(event) { + this.setState({ + propTemp: event.target.value + }); + } + addProp() { + var propTemp = this.state.propTemp; + this.setState({ + propTemp : '', + propPopupVisible : false + }); + const { dispatch } = this.props; + dispatch(requestSaveProperty({ + productID : this.state.productId, + name : propTemp + })); + } + cancelAddProp() { + this.setState({ + propTemp : '', + propPopupVisible : false + }); + } render() { let self = this; let { data } = this.props; @@ -337,6 +379,8 @@ class EditProduct extends Component { let inventories = this.state.inventories; let propValueVisibleMap = this.state.propValueVisibleMap; let propValueTemp = this.state.propValueTemp; + let propPopupVisible = this.state.propPopupVisible; + let propTemp = this.state.propTemp; let TabPane = Tabs.TabPane; @@ -514,6 +558,19 @@ class EditProduct extends Component { ); }) } + + + + + + } + onVisibleChange={self.onPropVisibleChange} + visible={propPopupVisible} + title={'添加属性'} trigger="click" > + + + { inventories.map(function(inv) { diff --git a/nodejs/static/javascripts/admin/reducers/product.js b/nodejs/static/javascripts/admin/reducers/product.js index 7703d460..57dc3c20 100644 --- a/nodejs/static/javascripts/admin/reducers/product.js +++ b/nodejs/static/javascripts/admin/reducers/product.js @@ -6,7 +6,9 @@ import { REQUEST_PRODUCT_SUCCESS, REQUEST_CATEGORY_LIST, REQUEST_CATEGORY_LIST_SUCCESS, - REQUEST_SAVE_PRODUCT_SUCCESS + REQUEST_SAVE_PRODUCT_SUCCESS, + REQUEST_SAVE_PRODUCT_PROP_SUCCESS, + REQUEST_SAVE_PRODUCT_PROP_VALUE_SUCCESS } from '../constants'; let initState = { @@ -73,6 +75,29 @@ export default (state = initState, action) => { categories: action.categories }; } + case REQUEST_SAVE_PRODUCT_PROP_SUCCESS: { + let product = state.product; + let properties = product.properties.concat(action.property); + product.properties = properties; + return { + ...state, + product: product + }; + } + case REQUEST_SAVE_PRODUCT_PROP_VALUE_SUCCESS: { + let product = state.product; + let properties = product.properties; + for (let i = 0; i < properties.length; i++) { + if (properties[i].id == action.propertyValue.propertyID) { + properties[i].values.push(action.propertyValue); + break; + } + } + return { + ...state, + product: product + }; + } default: { return state } diff --git a/nodejs/static/styles/admin/product/editProduct.css b/nodejs/static/styles/admin/product/editProduct.css index 3e7d5e0a..1c779ee6 100644 --- a/nodejs/static/styles/admin/product/editProduct.css +++ b/nodejs/static/styles/admin/product/editProduct.css @@ -56,6 +56,10 @@ color: #666; } +.product-prop-add label { + font-size: 0; +} + .product-prop-value { margin-right: 12px; } @@ -65,12 +69,12 @@ cursor: pointer; } -.product-prop-value-add-input { +.product-prop-add-input, .product-prop-value-add-input { width: 120px; margin-right: 12px; } -.product-prop-value-add-confirm { +.product-prop-add-confirm, .product-prop-value-add-confirm { margin-right: 12px; } diff --git a/route/route.go b/route/route.go index 3dc887c7..5e3f3231 100644 --- a/route/route.go +++ b/route/route.go @@ -39,11 +39,13 @@ func Route(app *iris.Framework) { adminRouter.Post("/category/update", category.Update) adminRouter.Post("/category/status/update", category.UpdateStatus) - adminRouter.Get("/products", product.AdminList) - adminRouter.Get("/product/:id", product.Info) - adminRouter.Post("/product/create", product.Create) - adminRouter.Post("/product/update", product.Update) - adminRouter.Post("/product/status/update", product.UpdateStatus) + adminRouter.Get("/products", product.AdminList) + adminRouter.Get("/product/:id", product.Info) + adminRouter.Post("/product/create", product.Create) + adminRouter.Post("/product/update", product.Update) + adminRouter.Post("/product/status/update", product.UpdateStatus) + adminRouter.Post("/product/property/saveval", product.AddPropertyValue) + adminRouter.Post("/product/property/create", product.AddProperty) adminRouter.Get("/order/analyze", order.Analyze) adminRouter.Get("/order/todaycount", order.TodayCount)