diff --git a/backend/internal/api/handlers/order.go b/backend/internal/api/handlers/order.go index d6a854f..e57e945 100644 --- a/backend/internal/api/handlers/order.go +++ b/backend/internal/api/handlers/order.go @@ -300,14 +300,80 @@ func (h *OrderHandler) Create(c *gin.Context) { break } - var shippingFeeFirstWeight, shippingFeePerGram, serviceFeeRate, taxRate, channelFeeRate float64 + // 获取收货地址省份 + var shippingAddress models.Address + var province string + if err := utils.DB.First(&shippingAddress, req.ShippingAddressID).Error; err == nil { + province = shippingAddress.Province + } + + // 使用运费模板计算运费 + shippingFee := 0.0 + + // 按运费模板分组计算 + type TemplateGroup struct { + TemplateID *uint + Weight float64 + Quantity int + Subtotal float64 + } + templateGroups := make(map[uint]*TemplateGroup) + noTemplateGroup := &TemplateGroup{Subtotal: 0, Weight: 0, Quantity: 0} + + for _, cart := range carts { + var templateID uint + if cart.Product.ShippingTemplateID != nil { + templateID = *cart.Product.ShippingTemplateID + } + + if templateID == 0 { + // 没有运费模板,累加到无模板组 + noTemplateGroup.Weight += cart.Product.Weight * float64(cart.Quantity) + noTemplateGroup.Quantity += cart.Quantity + noTemplateGroup.Subtotal += cart.Product.Price * float64(cart.Quantity) + } else { + if _, ok := templateGroups[templateID]; !ok { + templateGroups[templateID] = &TemplateGroup{ + TemplateID: &templateID, + Weight: 0, + Quantity: 0, + Subtotal: 0, + } + } + templateGroups[templateID].Weight += cart.Product.Weight * float64(cart.Quantity) + templateGroups[templateID].Quantity += cart.Quantity + templateGroups[templateID].Subtotal += cart.Product.Price * float64(cart.Quantity) + } + } + + // 计算有模板的运费 + for _, group := range templateGroups { + fee, _ := CalculateShippingFee(group.TemplateID, group.Weight, group.Quantity, group.Subtotal, province) + shippingFee += fee + } + + // 计算无模板的运费(使用旧的系统设置) + if noTemplateGroup.Subtotal > 0 { + var shippingFeeFirstWeight, shippingFeePerGram float64 + var setting models.SystemSetting + if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil { + shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64) + } + if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil { + shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64) + } + if noTemplateGroup.Subtotal < 99 && shippingFeeFirstWeight > 0 { + fee := shippingFeeFirstWeight + if noTemplateGroup.Weight > 500 { + fee += shippingFeePerGram * (noTemplateGroup.Weight - 500) + } + shippingFee += fee + } + } + + // 获取其他费率 + var serviceFeeRate, taxRate, channelFeeRate float64 var setting models.SystemSetting - if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil { - shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64) - } - if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil { - shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64) - } if err := utils.DB.Where("`key` = ?", "service_fee_rate").First(&setting).Error; err == nil { serviceFeeRate, _ = strconv.ParseFloat(setting.Value, 64) } @@ -318,15 +384,6 @@ func (h *OrderHandler) Create(c *gin.Context) { channelFeeRate, _ = strconv.ParseFloat(setting.Value, 64) } - // 根据商品重量计算运费 - shippingFee := 0.0 - if subtotal < 99 && shippingFeeFirstWeight > 0 { - shippingFee = shippingFeeFirstWeight - if totalWeight > 500 { - shippingFee += shippingFeePerGram * (totalWeight - 500) - } - } - serviceFee := subtotal * serviceFeeRate / 100 tax := subtotal * taxRate / 100 channelFee := subtotal * channelFeeRate / 100 diff --git a/backend/internal/api/handlers/product.go b/backend/internal/api/handlers/product.go index 65ea7e8..37ffca5 100644 --- a/backend/internal/api/handlers/product.go +++ b/backend/internal/api/handlers/product.go @@ -225,7 +225,7 @@ func (h *ProductHandler) GetByID(c *gin.Context) { func (h *ProductHandler) AdminList(c *gin.Context) { var products []models.Product - utils.DB.Preload("Categories").Preload("Brand").Order("created_at DESC").Find(&products) + utils.DB.Preload("Categories").Preload("Brand").Preload("ShippingTemplate").Order("created_at DESC").Find(&products) c.JSON(http.StatusOK, gin.H{"data": products}) } @@ -237,22 +237,23 @@ func (h *ProductHandler) Create(c *gin.Context) { } product := models.Product{ - Name: req.Name, - Description: req.Description, - Price: req.Price, - Weight: req.Weight, - MinPurchase: req.MinPurchase, - MaxPurchase: req.MaxPurchase, - MinWeight: req.MinWeight, - MaxWeight: req.MaxWeight, - MinAmount: req.MinAmount, - MaxAmount: req.MaxAmount, - RequireCredit: req.RequireCredit, - CreditCost: req.CreditCost, - CreditReward: req.CreditReward, - Images: req.Images, - BrandID: req.BrandID, - IsActive: true, + Name: req.Name, + Description: req.Description, + Price: req.Price, + Weight: req.Weight, + MinPurchase: req.MinPurchase, + MaxPurchase: req.MaxPurchase, + MinWeight: req.MinWeight, + MaxWeight: req.MaxWeight, + MinAmount: req.MinAmount, + MaxAmount: req.MaxAmount, + RequireCredit: req.RequireCredit, + CreditCost: req.CreditCost, + CreditReward: req.CreditReward, + Images: req.Images, + BrandID: req.BrandID, + ShippingTemplateID: req.ShippingTemplateID, + IsActive: true, } if err := utils.DB.Create(&product).Error; err != nil { @@ -276,7 +277,7 @@ func (h *ProductHandler) Create(c *gin.Context) { } } - utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID) + utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").Preload("ShippingTemplate").First(&product, product.ID) c.JSON(http.StatusCreated, gin.H{"data": product}) } @@ -343,6 +344,9 @@ func (h *ProductHandler) Update(c *gin.Context) { if req.BrandID != nil { updates["brand_id"] = *req.BrandID } + if req.ShippingTemplateID != nil { + updates["shipping_template_id"] = *req.ShippingTemplateID + } utils.DB.Model(&product).Updates(updates) @@ -363,7 +367,7 @@ func (h *ProductHandler) Update(c *gin.Context) { } } - utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID) + utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").Preload("ShippingTemplate").First(&product, product.ID) c.JSON(http.StatusOK, gin.H{"data": product}) } diff --git a/backend/internal/api/handlers/shipping_template.go b/backend/internal/api/handlers/shipping_template.go new file mode 100644 index 0000000..87a264f --- /dev/null +++ b/backend/internal/api/handlers/shipping_template.go @@ -0,0 +1,260 @@ +package handlers + +import ( + "encoding/json" + "math" + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type ShippingTemplateHandler struct{} + +func NewShippingTemplateHandler() *ShippingTemplateHandler { + return &ShippingTemplateHandler{} +} + +// List 获取运费模板列表 +func (h *ShippingTemplateHandler) List(c *gin.Context) { + var templates []models.ShippingTemplate + utils.DB.Order("sort_order ASC, id ASC").Find(&templates) + + // 解析 provinces JSON 为数组 + type TemplateResponse struct { + models.ShippingTemplate + ProvincesList []string `json:"provinces_list"` + } + + var response []TemplateResponse + for _, t := range templates { + var provinces []string + if t.Provinces != "" { + json.Unmarshal([]byte(t.Provinces), &provinces) + } + response = append(response, TemplateResponse{ + ShippingTemplate: t, + ProvincesList: provinces, + }) + } + + c.JSON(http.StatusOK, gin.H{"data": response}) +} + +// GetByID 获取单个运费模板 +func (h *ShippingTemplateHandler) GetByID(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var template models.ShippingTemplate + if err := utils.DB.First(&template, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"}) + return + } + + var provinces []string + if template.Provinces != "" { + json.Unmarshal([]byte(template.Provinces), &provinces) + } + + c.JSON(http.StatusOK, gin.H{ + "data": gin.H{ + "ShippingTemplate": template, + "provinces_list": provinces, + }, + }) +} + +// Create 创建运费模板 +func (h *ShippingTemplateHandler) Create(c *gin.Context) { + var req schemas.CreateShippingTemplateRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // 如果设为默认,先取消其他默认模板 + if req.IsDefault { + utils.DB.Model(&models.ShippingTemplate{}).Where("is_default = ?", true).Update("is_default", false) + } + + // 序列化省份列表 + var provincesJSON string + if len(req.Provinces) > 0 { + data, _ := json.Marshal(req.Provinces) + provincesJSON = string(data) + } + + template := models.ShippingTemplate{ + Name: req.Name, + CalcType: req.CalcType, + FirstUnit: req.FirstUnit, + FirstFee: req.FirstFee, + AdditionalUnit: req.AdditionalUnit, + AdditionalFee: req.AdditionalFee, + FreeAmount: req.FreeAmount, + Provinces: provincesJSON, + IsDefault: req.IsDefault, + SortOrder: req.SortOrder, + } + + if err := utils.DB.Create(&template).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "创建运费模板失败"}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": template}) +} + +// Update 更新运费模板 +func (h *ShippingTemplateHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var template models.ShippingTemplate + if err := utils.DB.First(&template, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"}) + return + } + + var req schemas.UpdateShippingTemplateRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + + if req.Name != nil { + updates["name"] = *req.Name + } + if req.CalcType != nil { + updates["calc_type"] = *req.CalcType + } + if req.FirstUnit != nil { + updates["first_unit"] = *req.FirstUnit + } + if req.FirstFee != nil { + updates["first_fee"] = *req.FirstFee + } + if req.AdditionalUnit != nil { + updates["additional_unit"] = *req.AdditionalUnit + } + if req.AdditionalFee != nil { + updates["additional_fee"] = *req.AdditionalFee + } + if req.FreeAmount != nil { + updates["free_amount"] = *req.FreeAmount + } + if req.Provinces != nil { + if len(req.Provinces) > 0 { + data, _ := json.Marshal(req.Provinces) + updates["provinces"] = string(data) + } else { + updates["provinces"] = "" + } + } + if req.IsDefault != nil { + if *req.IsDefault { + utils.DB.Model(&models.ShippingTemplate{}).Where("is_default = ? AND id != ?", true, id).Update("is_default", false) + } + updates["is_default"] = *req.IsDefault + } + if req.SortOrder != nil { + updates["sort_order"] = *req.SortOrder + } + + utils.DB.Model(&template).Updates(updates) + utils.DB.First(&template, id) + + c.JSON(http.StatusOK, gin.H{"data": template}) +} + +// Delete 删除运费模板 +func (h *ShippingTemplateHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var template models.ShippingTemplate + if err := utils.DB.First(&template, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"}) + return + } + + // 检查是否有关联的商品 + var count int64 + utils.DB.Model(&models.Product{}).Where("shipping_template_id = ?", id).Count(&count) + if count > 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "该运费模板已被商品关联,无法删除"}) + return + } + + if err := utils.DB.Delete(&template).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "删除运费模板失败"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) +} + +// CalculateShippingFee 计算运费(供内部调用) +func CalculateShippingFee(templateID *uint, weight float64, quantity int, subtotal float64, province string) (float64, error) { + var template models.ShippingTemplate + + // 如果没有指定模板,使用默认模板 + if templateID == nil || *templateID == 0 { + if err := utils.DB.Where("is_default = ?", true).First(&template).Error; err != nil { + // 没有默认模板,运费为0 + return 0, nil + } + } else { + if err := utils.DB.First(&template, *templateID).Error; err != nil { + // 模板不存在,使用默认模板 + if err := utils.DB.Where("is_default = ?", true).First(&template).Error; err != nil { + return 0, nil + } + } + } + + // 检查适用地区 + if template.Provinces != "" && province != "" { + var provinces []string + json.Unmarshal([]byte(template.Provinces), &provinces) + matched := false + for _, p := range provinces { + if p == province { + matched = true + break + } + } + if !matched { + // 地址不在适用范围内,使用默认模板 + var defaultTemplate models.ShippingTemplate + if err := utils.DB.Where("is_default = ?", true).First(&defaultTemplate).Error; err == nil { + template = defaultTemplate + } + } + } + + // 检查是否满足包邮条件 + if template.FreeAmount > 0 && subtotal >= template.FreeAmount { + return 0, nil + } + + // 根据计费方式计算运费 + var totalUnit float64 + if template.CalcType == models.CalcTypeWeight { + totalUnit = weight // 重量(克) + } else { + totalUnit = float64(quantity) // 件数 + } + + // 计算运费 + if totalUnit <= template.FirstUnit { + return template.FirstFee, nil + } + + remainingUnit := totalUnit - template.FirstUnit + additionalCount := math.Ceil(remainingUnit / template.AdditionalUnit) + + return template.FirstFee + additionalCount*template.AdditionalFee, nil +} diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index d79c651..96f4be5 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -27,6 +27,7 @@ func SetupRoutes(r *gin.Engine) { paymentHandler := handlers.NewPaymentHandler() logHandler := handlers.NewLogHandler() purchaseGroupHandler := handlers.NewPurchaseGroupHandler() + shippingTemplateHandler := handlers.NewShippingTemplateHandler() r.Use(middlewares.CORSMiddleware()) @@ -250,6 +251,15 @@ func SetupRoutes(r *gin.Engine) { admin.GET("/logs", logHandler.GetLogs) admin.DELETE("/logs", logHandler.ClearLogs) admin.GET("/logs/info", logHandler.GetLogInfo) + + adminShippingTemplates := admin.Group("/shipping-templates") + { + adminShippingTemplates.GET("", shippingTemplateHandler.List) + adminShippingTemplates.GET("/:id", shippingTemplateHandler.GetByID) + adminShippingTemplates.POST("", shippingTemplateHandler.Create) + adminShippingTemplates.PUT("/:id", shippingTemplateHandler.Update) + adminShippingTemplates.DELETE("/:id", shippingTemplateHandler.Delete) + } } } } diff --git a/backend/internal/models/product.go b/backend/internal/models/product.go index 2f57333..6bfae6b 100644 --- a/backend/internal/models/product.go +++ b/backend/internal/models/product.go @@ -7,29 +7,31 @@ import ( ) type Product struct { - ID uint `gorm:"primaryKey" json:"id"` - Name string `gorm:"size:255;not null" json:"name"` - Description string `json:"description"` - Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` - Weight float64 `gorm:"type:decimal(10,2);default:0" json:"weight"` // 商品克重(克) - MinPurchase int `gorm:"default:1" json:"min_purchase"` - MaxPurchase *int `json:"max_purchase"` - MinWeight *float64 `json:"min_weight"` - MaxWeight *float64 `json:"max_weight"` - MinAmount *float64 `json:"min_amount"` - MaxAmount *float64 `json:"max_amount"` - RequireCredit bool `gorm:"default:false" json:"require_credit"` - CreditCost int `gorm:"default:0" json:"credit_cost"` - CreditReward int `gorm:"default:0" json:"credit_reward"` - Images string `gorm:"type:text" json:"images"` - IsActive bool `gorm:"default:true" json:"is_active"` - BrandID *uint `json:"brand_id"` - Brand *Brand `json:"brand,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - Categories []Category `gorm:"many2many:product_categories;" json:"categories,omitempty"` - CustomFields []ProductCustomField `json:"custom_fields,omitempty"` + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:255;not null" json:"name"` + Description string `json:"description"` + Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` + Weight float64 `gorm:"type:decimal(10,2);default:0" json:"weight"` // 商品克重(克) + MinPurchase int `gorm:"default:1" json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit bool `gorm:"default:false" json:"require_credit"` + CreditCost int `gorm:"default:0" json:"credit_cost"` + CreditReward int `gorm:"default:0" json:"credit_reward"` + Images string `gorm:"type:text" json:"images"` + IsActive bool `gorm:"default:true" json:"is_active"` + BrandID *uint `json:"brand_id"` + Brand *Brand `json:"brand,omitempty"` + ShippingTemplateID *uint `json:"shipping_template_id"` + ShippingTemplate *ShippingTemplate `json:"shipping_template,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Categories []Category `gorm:"many2many:product_categories;" json:"categories,omitempty"` + CustomFields []ProductCustomField `json:"custom_fields,omitempty"` } func (Product) TableName() string { diff --git a/backend/internal/models/shipping_template.go b/backend/internal/models/shipping_template.go new file mode 100644 index 0000000..4d140a5 --- /dev/null +++ b/backend/internal/models/shipping_template.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// ShippingTemplate 运费模板 +type ShippingTemplate struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:255;not null" json:"name"` // 模板名称(如"江浙沪包邮"、"偏远地区") + CalcType string `gorm:"size:20;not null;default:'weight'" json:"calc_type"` // 计费方式:weight=按重量, piece=按件数 + FirstUnit float64 `gorm:"type:decimal(10,2);not null" json:"first_unit"` // 首重/首件数量 + FirstFee float64 `gorm:"type:decimal(10,2);not null" json:"first_fee"` // 首重/首件价格 + AdditionalUnit float64 `gorm:"type:decimal(10,2);not null" json:"additional_unit"` // 续重/续件数量 + AdditionalFee float64 `gorm:"type:decimal(10,2);not null" json:"additional_fee"` // 续重/续件价格 + FreeAmount float64 `gorm:"type:decimal(10,2);default:0" json:"free_amount"` // 包邮金额(0表示不包邮) + Provinces string `gorm:"type:text" json:"provinces"` // 适用省份列表(JSON数组,为空表示全国) + IsDefault bool `gorm:"default:false" json:"is_default"` // 是否默认模板 + SortOrder int `gorm:"default:0" json:"sort_order"` // 排序 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (ShippingTemplate) TableName() string { + return "shipping_templates" +} + +// 计费方式常量 +const ( + CalcTypeWeight = "weight" // 按重量计费 + CalcTypePiece = "piece" // 按件数计费 +) diff --git a/backend/internal/schemas/product.go b/backend/internal/schemas/product.go index 4783da4..84a7663 100644 --- a/backend/internal/schemas/product.go +++ b/backend/internal/schemas/product.go @@ -41,44 +41,46 @@ type UpdatePurchaseGroupRequest struct { } type CreateProductRequest struct { - Name string `json:"name" binding:"required"` - Description string `json:"description"` - Price float64 `json:"price" binding:"gte=0"` - Weight float64 `json:"weight"` - MinPurchase int `json:"min_purchase"` - MaxPurchase *int `json:"max_purchase"` - MinWeight *float64 `json:"min_weight"` - MaxWeight *float64 `json:"max_weight"` - MinAmount *float64 `json:"min_amount"` - MaxAmount *float64 `json:"max_amount"` - RequireCredit bool `json:"require_credit"` - CreditCost int `json:"credit_cost"` - CreditReward int `json:"credit_reward"` - Images string `json:"images"` - BrandID *uint `json:"brand_id"` - CategoryIDs []uint `json:"category_ids"` - CustomFields []CustomFieldRequest `json:"custom_fields"` + Name string `json:"name" binding:"required"` + Description string `json:"description"` + Price float64 `json:"price" binding:"gte=0"` + Weight float64 `json:"weight"` + MinPurchase int `json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit bool `json:"require_credit"` + CreditCost int `json:"credit_cost"` + CreditReward int `json:"credit_reward"` + Images string `json:"images"` + BrandID *uint `json:"brand_id"` + ShippingTemplateID *uint `json:"shipping_template_id"` + CategoryIDs []uint `json:"category_ids"` + CustomFields []CustomFieldRequest `json:"custom_fields"` } type UpdateProductRequest struct { - Name *string `json:"name"` - Description *string `json:"description"` - Price *float64 `json:"price"` - Weight *float64 `json:"weight"` - MinPurchase *int `json:"min_purchase"` - MaxPurchase *int `json:"max_purchase"` - MinWeight *float64 `json:"min_weight"` - MaxWeight *float64 `json:"max_weight"` - MinAmount *float64 `json:"min_amount"` - MaxAmount *float64 `json:"max_amount"` - RequireCredit *bool `json:"require_credit"` - CreditCost *int `json:"credit_cost"` - CreditReward *int `json:"credit_reward"` - Images *string `json:"images"` - IsActive *bool `json:"is_active"` - BrandID *uint `json:"brand_id"` - CategoryIDs []uint `json:"category_ids"` - CustomFields []CustomFieldRequest `json:"custom_fields"` + Name *string `json:"name"` + Description *string `json:"description"` + Price *float64 `json:"price"` + Weight *float64 `json:"weight"` + MinPurchase *int `json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit *bool `json:"require_credit"` + CreditCost *int `json:"credit_cost"` + CreditReward *int `json:"credit_reward"` + Images *string `json:"images"` + IsActive *bool `json:"is_active"` + BrandID *uint `json:"brand_id"` + ShippingTemplateID *uint `json:"shipping_template_id"` + CategoryIDs []uint `json:"category_ids"` + CustomFields []CustomFieldRequest `json:"custom_fields"` } type CustomFieldRequest struct { diff --git a/backend/internal/schemas/shipping_template.go b/backend/internal/schemas/shipping_template.go new file mode 100644 index 0000000..33c107c --- /dev/null +++ b/backend/internal/schemas/shipping_template.go @@ -0,0 +1,29 @@ +package schemas + +// CreateShippingTemplateRequest 创建运费模板请求 +type CreateShippingTemplateRequest struct { + Name string `json:"name" binding:"required"` + CalcType string `json:"calc_type" binding:"required,oneof=weight piece"` + FirstUnit float64 `json:"first_unit" binding:"required,gt=0"` + FirstFee float64 `json:"first_fee" binding:"gte=0"` + AdditionalUnit float64 `json:"additional_unit" binding:"required,gt=0"` + AdditionalFee float64 `json:"additional_fee" binding:"gte=0"` + FreeAmount float64 `json:"free_amount"` + Provinces []string `json:"provinces"` // 省份列表 + IsDefault bool `json:"is_default"` + SortOrder int `json:"sort_order"` +} + +// UpdateShippingTemplateRequest 更新运费模板请求 +type UpdateShippingTemplateRequest struct { + Name *string `json:"name"` + CalcType *string `json:"calc_type" binding:"omitempty,oneof=weight piece"` + FirstUnit *float64 `json:"first_unit" binding:"omitempty,gt=0"` + FirstFee *float64 `json:"first_fee" binding:"omitempty,gte=0"` + AdditionalUnit *float64 `json:"additional_unit" binding:"omitempty,gt=0"` + AdditionalFee *float64 `json:"additional_fee" binding:"omitempty,gte=0"` + FreeAmount *float64 `json:"free_amount"` + Provinces []string `json:"provinces"` + IsDefault *bool `json:"is_default"` + SortOrder *int `json:"sort_order"` +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index b8bc810..f3c3d0f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@
- +