邹方成 642b3cf7dd
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 50s
build: 更新前端构建产物和资源文件
更新了前端构建产物包括JavaScript、CSS和HTML文件,主要涉及以下变更:

1. 新增了多个组件和工具函数,包括异常页面组件、iframe组件等
2. 更新了活动管理、产品管理、优惠券管理等业务模块
3. 优化了构建配置和依赖管理
4. 修复了一些样式和功能问题
5. 更新了测试相关文件

同时更新了部分后端服务接口和测试用例。这些变更主要是为了支持新功能和改进现有功能的用户体验。
2025-11-21 01:24:13 +08:00

190 lines
6.6 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 app
import (
"net/http"
"strconv"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/pkg/logger"
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
prodsvc "bindbox-game/internal/service/product"
)
type productHandler struct {
logger logger.CustomLogger
readDB *dao.Query
product prodsvc.Service
}
func NewProduct(logger logger.CustomLogger, db mysql.Repo) *productHandler {
return &productHandler{logger: logger, readDB: dao.Use(db.GetDbR()), product: prodsvc.New(logger, db)}
}
type listAppProductsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
CategoryID *int64 `form:"category_id"`
PriceMin *int64 `form:"price_min"`
PriceMax *int64 `form:"price_max"`
SalesMin *int64 `form:"sales_min"`
InStock *bool `form:"in_stock"`
SortBy string `form:"sort_by"`
Order string `form:"order"`
}
type listAppProductsItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
MainImage string `json:"main_image"`
Price int64 `json:"price"`
Sales int64 `json:"sales"`
InStock bool `json:"in_stock"`
}
type listAppProductsResponse struct {
Total int64 `json:"total"`
CurrentPage int `json:"currentPage"`
PageSize int `json:"pageSize"`
List []listAppProductsItem `json:"list"`
}
// ListProductsForApp 商品列表
// @Summary 商品列表
// @Description 分页查询商品列表,支持分类筛选,返回分页信息与商品数组
// @Tags APP端.商品
// @Accept json
// @Produce json
// @Security LoginVerifyToken
// @Param page query int false "页码默认1"
// @Param page_size query int false "每页数量默认20"
// @Param category_id query int false "分类ID"
// @Success 200 {object} listAppProductsResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/products [get]
func (h *productHandler) ListProductsForApp() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listAppProductsRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
items, total, err := h.product.ListForApp(ctx.RequestContext(), prodsvc.AppListInput{CategoryID: req.CategoryID, PriceMin: req.PriceMin, PriceMax: req.PriceMax, SalesMin: req.SalesMin, InStock: req.InStock, SortBy: req.SortBy, Order: req.Order, Page: req.Page, PageSize: req.PageSize})
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
return
}
rsp := &listAppProductsResponse{Total: total, CurrentPage: req.Page, PageSize: req.PageSize, List: make([]listAppProductsItem, len(items))}
for i, it := range items {
rsp.List[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, Sales: it.Sales, InStock: it.InStock}
}
ctx.Payload(rsp)
}
}
type getAppProductDetailResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Album []string `json:"album"`
Price int64 `json:"price"`
Sales int64 `json:"sales"`
Stock int64 `json:"stock"`
Description string `json:"description"`
Service []string `json:"service"`
Recommendations []listAppProductsItem `json:"recommendations"`
}
// GetProductDetailForApp 商品详情
// @Summary 商品详情
// @Description 根据商品ID返回完整商品信息含相册与同类推荐校验下架/缺货状态
// @Tags APP端.商品
// @Accept json
// @Produce json
// @Security LoginVerifyToken
// @Param id path int true "商品ID"
// @Success 200 {object} getAppProductDetailResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/products/{id} [get]
func (h *productHandler) GetProductDetailForApp() core.HandlerFunc {
return func(ctx core.Context) {
idStr := ctx.Param("id")
id, _ := strconv.ParseInt(idStr, 10, 64)
d, err := h.product.GetDetailForApp(ctx.RequestContext(), id)
if err != nil {
if err.Error() == "PRODUCT_OFFSHELF" {
ctx.AbortWithError(core.Error(http.StatusOK, 20001, "商品已下架"))
return
}
if err.Error() == "PRODUCT_OUT_OF_STOCK" {
ctx.AbortWithError(core.Error(http.StatusOK, 20002, "商品缺货"))
return
}
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err)))
return
}
rsp := &getAppProductDetailResponse{ID: d.ID, Name: d.Name, Album: d.Album, Price: d.Price, Sales: d.Sales, Stock: d.Stock, Description: d.Description, Service: d.Service, Recommendations: make([]listAppProductsItem, len(d.Recommendations))}
for i, it := range d.Recommendations {
rsp.Recommendations[i] = listAppProductsItem{ID: it.ID, Name: it.Name, MainImage: it.MainImage, Price: it.Price, Sales: it.Sales, InStock: it.InStock}
}
ctx.Payload(rsp)
}
}
func parseImages(s string) []string {
var arr []string
// lightweight JSON array parser without comments
if s == "" {
return arr
}
// naive parse: trim brackets and split by comma, remove quotes
b := []byte(s)
if len(b) < 2 {
return arr
}
start, end := 0, len(b)
for start < end && (b[start] == '[' || b[start] == ' ' || b[start] == '\n' || b[start] == '\t') {
start++
}
for end > start && (b[end-1] == ']' || b[end-1] == ' ' || b[end-1] == '\n' || b[end-1] == '\t') {
end--
}
if start >= end {
return arr
}
content := string(b[start:end])
parts := []rune(content)
cur := ""
inStr := false
for _, r := range parts {
if r == '"' {
if inStr {
arr = append(arr, cur)
cur = ""
inStr = false
} else {
inStr = true
}
continue
}
if inStr {
cur += string(r)
}
}
return arr
}
func parseFirstImage(s string) string {
imgs := parseImages(s)
if len(imgs) > 0 {
return imgs[0]
}
return ""
}