Files
ai_dianshang/server/internal/handler/frontend.go
2025-11-28 15:18:10 +08:00

761 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, nil, "default", "desc")
if err != nil {
response.ErrorWithMessage(c, response.ERROR, "获取推荐商品失败: "+err.Error())
return
}
// 获取热门商品
hotProducts, _, err := h.productService.GetProductList(1, 10, 0, "", 0, 0, nil, "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, nil, "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, nil, "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: func() []string {
ids := make([]string, len(product.CategoryID))
for i, id := range product.CategoryID {
ids[i] = strconv.Itoa(int(id))
}
return ids
}(),
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
}