package handler import ( "dianshang/internal/model" "dianshang/internal/service" "dianshang/pkg/response" "dianshang/pkg/utils" "fmt" "strconv" "github.com/gin-gonic/gin" ) // FrontendHandler 前端接口处理器 type FrontendHandler struct { productService *service.ProductService userService *service.UserService orderService *service.OrderService cartService *service.CartService bannerService *service.BannerService couponService *service.CouponService } // NewFrontendHandler 创建前端接口处理器 func NewFrontendHandler( productService *service.ProductService, userService *service.UserService, orderService *service.OrderService, cartService *service.CartService, bannerService *service.BannerService, couponService *service.CouponService, ) *FrontendHandler { return &FrontendHandler{ productService: productService, userService: userService, orderService: orderService, cartService: cartService, bannerService: bannerService, couponService: couponService, } } // GetHomeData 获取首页数据 func (h *FrontendHandler) GetHomeData(c *gin.Context) { // 获取轮播图 banners, err := h.bannerService.GetActiveBanners() if err != nil { response.ErrorWithMessage(c, response.ERROR, "获取轮播图失败: "+err.Error()) return } // 获取分类 categories, err := h.productService.GetCategories() if err != nil { response.ErrorWithMessage(c, response.ERROR, "获取分类失败: "+err.Error()) return } // 获取推荐商品 recommendProducts, _, err := h.productService.GetProductList(1, 10, 0, "", 0, 0, "default", "desc") if err != nil { response.ErrorWithMessage(c, response.ERROR, "获取推荐商品失败: "+err.Error()) return } // 获取热门商品 hotProducts, _, err := h.productService.GetProductList(1, 10, 0, "", 0, 0, "sales", "desc") if err != nil { response.ErrorWithMessage(c, response.ERROR, "获取热门商品失败: "+err.Error()) return } // 转换为前端格式 frontendBanners := h.convertBannersToFrontend(banners) frontendCategories := h.convertCategoriesToFrontend(categories) frontendRecommendProducts := h.convertProductsToFrontend(recommendProducts) frontendHotProducts := h.convertProductsToFrontend(hotProducts) homeData := gin.H{ "banners": frontendBanners, "categories": frontendCategories, "recommendProducts": frontendRecommendProducts, "hotProducts": frontendHotProducts, } response.Success(c, homeData) } // GetProductsRecommend 获取推荐商品 func (h *FrontendHandler) GetProductsRecommend(c *gin.Context) { page := utils.StringToInt(c.DefaultQuery("page", "1")) pageSize := utils.StringToInt(c.DefaultQuery("page_size", "10")) products, pagination, err := h.productService.GetProductList(page, pageSize, 0, "", 0, 0, "default", "desc") if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendProducts := h.convertProductsToFrontend(products) response.Page(c, frontendProducts, pagination.Total, pagination.Page, pagination.PageSize) } // GetProductsHot 获取热门商品 func (h *FrontendHandler) GetProductsHot(c *gin.Context) { page := utils.StringToInt(c.DefaultQuery("page", "1")) pageSize := utils.StringToInt(c.DefaultQuery("page_size", "10")) products, pagination, err := h.productService.GetProductList(page, pageSize, 0, "", 0, 0, "sales", "desc") if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendProducts := h.convertProductsToFrontend(products) response.Page(c, frontendProducts, pagination.Total, pagination.Page, pagination.PageSize) } // GetProductDetail 获取商品详情 func (h *FrontendHandler) GetProductDetail(c *gin.Context) { spuID := c.Param("spuId") if spuID == "" { response.BadRequest(c, "商品ID不能为空") return } // 这里简化处理,实际应该根据spuID查询 id := utils.StringToUint(spuID) if id == 0 { response.BadRequest(c, "无效的商品ID") return } product, err := h.productService.GetProductDetail(id) if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendProduct := h.convertProductToFrontend(product) response.Success(c, frontendProduct) } // GetUserCenter 获取用户中心数据 func (h *FrontendHandler) GetUserCenter(c *gin.Context) { userID := c.GetUint("user_id") if userID == 0 { response.Unauthorized(c) return } user, err := h.userService.GetUserByID(userID) if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } // 获取用户优惠券数量 couponCount := 0 if userCoupons, err := h.couponService.GetUserCoupons(userID, 1); err == nil { // 状态1表示未使用 couponCount = len(userCoupons) } // 获取用户订单统计 orderStats := map[int]int{ 1: 0, // 待付款 (对应前端status=1) 3: 0, // 待发货 (对应前端status=3) 5: 0, // 待收货 (对应前端status=5) 6: 0, // 待评价 (对应前端status=6) } // 获取用户订单列表来统计各状态订单数量 if orders, _, err := h.orderService.GetUserOrders(userID, 0, 1, 1000); err == nil { for _, order := range orders { switch order.Status { case 1: // 数据库中的待付款状态 orderStats[1]++ // 映射到前端status=1 case 2: // 数据库中的待发货状态 orderStats[3]++ // 映射到前端status=3 case 3: // 数据库中的待收货状态 orderStats[5]++ // 映射到前端status=5 case 4: // 数据库中的待评价状态 orderStats[6]++ // 映射到前端status=6 } } } // 构造用户中心数据 userCenter := model.FrontendUserCenter{ UserInfo: model.FrontendUserInfo{ AvatarURL: user.Avatar, NickName: user.Nickname, PhoneNumber: user.Phone, Gender: int(user.Gender), }, CountsData: []model.FrontendCountData{ {Num: couponCount, Name: "优惠券", Type: "coupon"}, {Num: 0, Name: "收藏", Type: "favorite"}, }, OrderTagInfos: []model.FrontendOrderTagInfo{ {OrderNum: orderStats[1], TabType: 1}, // 待付款 {OrderNum: orderStats[3], TabType: 3}, // 待发货 {OrderNum: orderStats[5], TabType: 5}, // 待收货 {OrderNum: orderStats[6], TabType: 6}, // 待评价 }, CustomerServiceInfo: model.FrontendCustomerServiceInfo{ ServicePhone: "400-123-4567", ServiceTimeDuration: "9:00-18:00", }, } response.Success(c, userCenter) } // GetCartData 获取购物车数据 func (h *FrontendHandler) GetCartData(c *gin.Context) { userID := c.GetUint("user_id") if userID == 0 { response.Unauthorized(c) return } cartItems, err := h.cartService.GetCart(userID) if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendCartData := h.convertCartToFrontend(cartItems) response.Success(c, frontendCartData) } // GetOrderList 获取订单列表 func (h *FrontendHandler) GetOrderList(c *gin.Context) { userID := c.GetUint("user_id") if userID == 0 { response.Unauthorized(c) return } page := utils.StringToInt(c.DefaultQuery("page", "1")) pageSize := utils.StringToInt(c.DefaultQuery("page_size", "10")) statusStr := c.Query("status") status := 0 if statusStr != "" { frontendStatus := utils.StringToInt(statusStr) // 将前端状态映射到数据库状态 // 前端和数据库状态保持一致: // 1=未付款, 2=已付款/待发货, 3=待发货, 4=已发货, 5=待收货, 6=已完成, 7=已取消, 8=退货中, 9=已退款 status = frontendStatus } orders, pagination, err := h.orderService.GetUserOrders(userID, status, page, pageSize) if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendOrders := h.convertOrdersToFrontend(orders) orderList := model.FrontendOrderList{ PageNum: pagination.Page, PageSize: pagination.PageSize, TotalCount: int(pagination.Total), Orders: frontendOrders, } response.Success(c, orderList) } // GetAddressList 获取地址列表 func (h *FrontendHandler) GetAddressList(c *gin.Context) { userID := c.GetUint("user_id") if userID == 0 { response.Unauthorized(c) return } addresses, err := h.userService.GetUserAddresses(userID) if err != nil { response.ErrorWithMessage(c, response.ERROR, err.Error()) return } frontendAddresses := h.convertAddressesToFrontend(addresses) response.Success(c, frontendAddresses) } // 转换函数 func (h *FrontendHandler) convertBannersToFrontend(banners []model.Banner) []gin.H { result := make([]gin.H, len(banners)) for i, banner := range banners { result[i] = gin.H{ "img": banner.Image, "text": banner.Title, "url": banner.LinkValue, } } return result } func (h *FrontendHandler) convertCategoriesToFrontend(categories []model.Category) []model.FrontendCategory { result := make([]model.FrontendCategory, len(categories)) for i, category := range categories { frontendCategory := model.FrontendCategory{ GroupID: strconv.Itoa(int(category.ID)), Name: category.Name, Thumbnail: category.Icon, } // 处理子分类 if len(category.Children) > 0 { frontendCategory.Children = h.convertCategoriesToFrontend(category.Children) } result[i] = frontendCategory } return result } func (h *FrontendHandler) convertProductsToFrontend(products []model.Product) []model.FrontendProduct { result := make([]model.FrontendProduct, len(products)) for i, product := range products { result[i] = h.convertProductToFrontend(&product) } return result } func (h *FrontendHandler) convertProductToFrontend(product *model.Product) model.FrontendProduct { // 获取商品图片 images := make([]string, 0) if len(product.Images) > 0 { for _, img := range product.Images { images = append(images, img) } } // 若无图片则回退使用主图,确保前端轮播可显示 if len(images) == 0 && product.MainImage != "" { images = append(images, product.MainImage) } // 生成详情描述,优先使用详情图片数组,其次文本描述 desc := make([]string, 0) if len(product.DetailImages) > 0 { for _, d := range product.DetailImages { desc = append(desc, d) } } else if product.Description != "" { desc = append(desc, product.Description) } // 获取商品规格和SKU specList := make([]model.FrontendProductSpec, 0) skuList := make([]model.FrontendProductSKU, 0) // 用于收集所有规格信息 specMap := make(map[string]map[string]bool) // specName -> specValue -> exists if len(product.SKUs) > 0 { // 首先遍历所有SKU,收集规格信息 for _, sku := range product.SKUs { for specName, specValue := range sku.SpecValues { if specMap[specName] == nil { specMap[specName] = make(map[string]bool) } if specValueStr, ok := specValue.(string); ok { specMap[specName][specValueStr] = true } } } // 生成specList和规格值ID映射 specIndex := 1 specNameToID := make(map[string]string) specValueToID := make(map[string]map[string]string) // specName -> specValue -> specValueID for specName, specValues := range specMap { specID := strconv.Itoa(specIndex) specNameToID[specName] = specID specValueToID[specName] = make(map[string]string) specValueList := make([]model.FrontendProductSpecValue, 0) valueIndex := 1 for specValue := range specValues { specValueID := fmt.Sprintf("%s_%d", specID, valueIndex) specValueToID[specName][specValue] = specValueID specValueList = append(specValueList, model.FrontendProductSpecValue{ SpecValueID: specValueID, SpecID: specID, SaasID: "default", SpecValue: specValue, Image: "", }) valueIndex++ } specList = append(specList, model.FrontendProductSpec{ SpecID: specID, Title: specName, SpecValueList: specValueList, }) specIndex++ } // 生成skuList for _, sku := range product.SKUs { specInfo := make([]model.FrontendSKUSpecInfo, 0) for specName, specValue := range sku.SpecValues { if specValueStr, ok := specValue.(string); ok { specValueID := "" if specValueMap, exists := specValueToID[specName]; exists { if valueID, exists := specValueMap[specValueStr]; exists { specValueID = valueID } } specInfo = append(specInfo, model.FrontendSKUSpecInfo{ SpecID: specNameToID[specName], SpecTitle: specName, SpecValueID: specValueID, SpecValue: specValueStr, }) } } frontendSKU := model.FrontendProductSKU{ SkuID: strconv.Itoa(int(sku.ID)), SkuImage: sku.Image, SpecInfo: specInfo, PriceInfo: []model.FrontendSKUPriceInfo{ { PriceType: 1, Price: fmt.Sprintf("%.0f", sku.Price), PriceTypeName: "销售价", }, }, StockInfo: model.FrontendSKUStockInfo{ StockQuantity: sku.Stock, SafeStockQuantity: 10, SoldQuantity: 0, }, } skuList = append(skuList, frontendSKU) } } // 获取商品标签 spuTagList := make([]model.FrontendProductTag, 0) if len(product.Tags) > 0 { for _, tag := range product.Tags { spuTagList = append(spuTagList, model.FrontendProductTag{ ID: strconv.Itoa(int(tag.ID)), Title: tag.Name, Image: "", }) } } return model.FrontendProduct{ SaasID: "default", StoreID: strconv.Itoa(int(product.StoreID)), SpuID: strconv.Itoa(int(product.ID)), Title: product.Name, PrimaryImage: product.MainImage, Images: images, Available: 1, MinSalePrice: fmt.Sprintf("%.0f", product.Price), MinLinePrice: fmt.Sprintf("%.0f", product.Price), MaxSalePrice: fmt.Sprintf("%.0f", product.Price), MaxLinePrice: fmt.Sprintf("%.0f", product.Price), SpuStockQuantity: product.Stock, SoldNum: product.Sales, IsPutOnSale: 1, CategoryIds: []string{strconv.Itoa(int(product.CategoryID))}, SpecList: specList, SkuList: skuList, SpuTagList: spuTagList, Desc: desc, Video: func() *string { if product.VideoURL != "" { v := product.VideoURL; return &v }; return nil }(), Etitle: product.Name, PromotionList: nil, MinProfitPrice: nil, } } func (h *FrontendHandler) convertCartToFrontend(cartItems []model.Cart) model.FrontendCartData { if len(cartItems) == 0 { return model.FrontendCartData{ IsNotEmpty: false, StoreGoods: []model.FrontendStoreGoods{}, IsAllSelected: false, SelectedGoodsCount: 0, TotalAmount: "0.00", TotalDiscountAmount: "0.00", } } // 按店铺分组 storeMap := make(map[uint][]model.Cart) for _, item := range cartItems { storeID := item.Product.StoreID storeMap[storeID] = append(storeMap[storeID], item) } storeGoods := make([]model.FrontendStoreGoods, 0) totalAmount := 0.0 selectedCount := 0 for storeID, items := range storeMap { goodsList := make([]model.FrontendCartGoods, 0) storeTotal := 0.0 for _, item := range items { price := (item.Product.Price / 100) * float64(item.Quantity) storeTotal += price if item.Selected { selectedCount += item.Quantity totalAmount += price } // 构建SKU规格信息 specInfo := make([]model.FrontendSpecification, 0) if item.SKUID != nil { // 获取对应的SKU信息 for _, sku := range item.Product.SKUs { if sku.ID == *item.SKUID { for specName, specValue := range sku.SpecValues { if specValueStr, ok := specValue.(string); ok { specInfo = append(specInfo, model.FrontendSpecification{ SpecTitle: specName, SpecValue: specValueStr, }) } } break } } } goodsList = append(goodsList, model.FrontendCartGoods{ UID: strconv.Itoa(int(item.ID)), SaasID: "default", StoreID: strconv.Itoa(int(storeID)), SpuID: strconv.Itoa(int(item.ProductID)), SkuID: func() string { if item.SKUID != nil { return strconv.Itoa(int(*item.SKUID)) } return "0" }(), IsSelected: utils.BoolToInt(item.Selected), Thumb: item.Product.MainImage, Title: item.Product.Name, PrimaryImage: item.Product.MainImage, SpecInfo: specInfo, Quantity: item.Quantity, StockStatus: item.Product.Stock > 0, StockQuantity: item.Product.Stock, Price: fmt.Sprintf("%.0f", item.Product.Price), OriginPrice: fmt.Sprintf("%.0f", item.Product.Price), JoinCartTime: item.CreatedAt.Format("2006-01-02 15:04:05"), Available: 1, PutOnSale: 1, }) } // 动态获取店铺名称 storeName := "默认店铺" // 默认店铺名 if storeID > 0 { store, err := h.productService.GetStoreByID(storeID) if err == nil && store != nil { storeName = store.Name } } storeGoods = append(storeGoods, model.FrontendStoreGoods{ StoreID: strconv.Itoa(int(storeID)), StoreName: storeName, StoreStatus: 1, TotalDiscountSalePrice: fmt.Sprintf("%.0f", storeTotal), GoodsList: goodsList, }) } return model.FrontendCartData{ IsNotEmpty: len(cartItems) > 0, StoreGoods: storeGoods, IsAllSelected: selectedCount == len(cartItems), SelectedGoodsCount: selectedCount, TotalAmount: fmt.Sprintf("%.0f", totalAmount), TotalDiscountAmount: "0", } } func (h *FrontendHandler) convertOrdersToFrontend(orders []model.Order) []model.FrontendOrder { result := make([]model.FrontendOrder, len(orders)) for i, order := range orders { orderItems := make([]model.FrontendOrderItem, len(order.OrderItems)) for j, item := range order.OrderItems { // 处理规格信息 specifications := []model.FrontendSpecification{} // 优先从SKU获取规格信息 if item.SKUID != nil && item.SKU.ID > 0 && len(item.SKU.SpecValues) > 0 { for specName, specValue := range item.SKU.SpecValues { if specValueStr, ok := specValue.(string); ok { specifications = append(specifications, model.FrontendSpecification{ SpecTitle: specName, SpecValue: specValueStr, }) } } } else if len(item.SpecInfo) > 0 { // 如果SKU没有规格信息,从订单项的SpecInfo获取 for specName, specValue := range item.SpecInfo { if specValueStr, ok := specValue.(string); ok { specifications = append(specifications, model.FrontendSpecification{ SpecTitle: specName, SpecValue: specValueStr, }) } } } else if item.SpecName != "" && item.SpecValue != "" { // 从单独的规格字段获取 specifications = append(specifications, model.FrontendSpecification{ SpecTitle: item.SpecName, SpecValue: item.SpecValue, }) } else { // 如果订单项没有规格信息,尝试从产品的第一个SKU获取默认规格 if item.Product.ID > 0 && len(item.Product.SKUs) > 0 { firstSKU := item.Product.SKUs[0] for specName, specValue := range firstSKU.SpecValues { if specValueStr, ok := specValue.(string); ok { specifications = append(specifications, model.FrontendSpecification{ SpecTitle: specName, SpecValue: specValueStr, // 使用实际的规格值 }) } } } // 如果还是没有规格信息,提供一个默认的规格 if len(specifications) == 0 { specifications = append(specifications, model.FrontendSpecification{ SpecTitle: "规格", SpecValue: "标准", }) } } orderItems[j] = model.FrontendOrderItem{ ID: strconv.Itoa(int(item.ID)), SpuID: strconv.Itoa(int(item.ProductID)), SkuID: func() string { if item.SKUID != nil { return strconv.Itoa(int(*item.SKUID)) } return "0" }(), GoodsMainType: 1, GoodsViceType: 1, GoodsName: item.ProductName, Specifications: specifications, GoodsPictureURL: item.ProductImage, OriginPrice: fmt.Sprintf("%.0f", item.Price), ActualPrice: fmt.Sprintf("%.0f", item.Price), BuyQuantity: item.Quantity, ItemTotalAmount: fmt.Sprintf("%.0f", item.Price*float64(item.Quantity)), ItemDiscountAmount: "0.00", ItemPaymentAmount: fmt.Sprintf("%.0f", item.Price*float64(item.Quantity)), GoodsPaymentPrice: fmt.Sprintf("%.0f", item.Price), } } // 动态获取店铺名称 storeName := "默认店铺" // 默认店铺名 if order.StoreID > 0 { store, err := h.productService.GetStoreByID(order.StoreID) if err == nil && store != nil { storeName = store.Name } } result[i] = model.FrontendOrder{ SaasID: "default", StoreID: strconv.Itoa(int(order.StoreID)), StoreName: storeName, UID: strconv.Itoa(int(order.UserID)), ParentOrderNo: order.OrderNo, OrderID: strconv.Itoa(int(order.ID)), OrderNo: order.OrderNo, OrderType: 1, OrderSubType: 1, OrderStatus: int(order.Status), TotalAmount: fmt.Sprintf("%.0f", order.TotalAmount), GoodsAmount: fmt.Sprintf("%.0f", order.TotalAmount), GoodsAmountApp: fmt.Sprintf("%.0f", order.TotalAmount), PaymentAmount: fmt.Sprintf("%.0f", order.PayAmount), FreightFee: fmt.Sprintf("%.0f", order.ShippingFee), PackageFee: "0", DiscountAmount: fmt.Sprintf("%.0f", order.DiscountAmount), ChannelType: 1, ChannelSource: "miniprogram", ChannelIdentity: "wechat", Remark: order.Remark, CreateTime: order.CreatedAt.Format("2006-01-02 15:04:05"), OrderItemVOs: orderItems, LogisticsVO: model.FrontendLogistics{ LogisticsType: 1, LogisticsNo: order.LogisticsNo, LogisticsCompanyCode: "SF", LogisticsCompanyName: "顺丰速运", ReceiverName: order.ReceiverName, ReceiverPhone: order.ReceiverPhone, ReceiverAddress: order.ReceiverAddress, }, PaymentVO: model.FrontendPayment{ PayStatus: int(order.PayStatus), Amount: fmt.Sprintf("%.2f", order.PayAmount), }, } } return result } func (h *FrontendHandler) convertAddressesToFrontend(addresses []model.UserAddress) []model.FrontendAddress { result := make([]model.FrontendAddress, len(addresses)) for i, addr := range addresses { result[i] = model.FrontendAddress{ SaasID: "default", UID: strconv.Itoa(int(addr.UserID)), ID: strconv.Itoa(int(addr.ID)), AddressID: strconv.Itoa(int(addr.ID)), Phone: addr.Phone, Name: addr.Name, CountryName: "中国", CountryCode: "CN", ProvinceName: addr.ProvinceName, ProvinceCode: "000000", // UserAddress模型中没有ProvinceCode字段,使用默认值 CityName: addr.CityName, CityCode: "000000", // UserAddress模型中没有CityCode字段,使用默认值 DistrictName: addr.DistrictName, DistrictCode: "000000", // UserAddress模型中没有DistrictCode字段,使用默认值 DetailAddress: addr.DetailAddress, IsDefault: utils.BoolToInt(addr.IsDefault), AddressTag: addr.AddressTag, Latitude: fmt.Sprintf("%.6f", addr.Latitude), Longitude: fmt.Sprintf("%.6f", addr.Longitude), } } return result }