Files
ai_dianshang/server/internal/handler/frontend.go

755 lines
23 KiB
Go
Raw Normal View History

2025-11-17 14:11:46 +08:00
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
}