feat: 新增店铺联级查询接口
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
- 新增 GET /api/admin/shops/cascade 接口,支持按店铺名模糊查询和上级ID过滤
- Store 层新增 ListForCascade(支持数据权限过滤)和 GetParentIDsWithChildren 方法
- Service 层新增 ListCascade,批量判断 has_children 避免 N+1 查询
- 返回格式:[{id, shop_name, has_children}]
💘 Generated with Crush
Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
This commit is contained in:
@@ -6134,6 +6134,19 @@ components:
|
||||
- card_no
|
||||
- ssid
|
||||
type: object
|
||||
DtoShopCascadeItem:
|
||||
properties:
|
||||
has_children:
|
||||
description: 是否有下级店铺
|
||||
type: boolean
|
||||
id:
|
||||
description: 店铺ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
shop_name:
|
||||
description: 店铺名称
|
||||
type: string
|
||||
type: object
|
||||
DtoShopCommissionRecordItem:
|
||||
properties:
|
||||
amount:
|
||||
@@ -20811,6 +20824,82 @@ paths:
|
||||
summary: 代理商提现记录
|
||||
tags:
|
||||
- 代理商佣金管理
|
||||
/api/admin/shops/cascade:
|
||||
get:
|
||||
parameters:
|
||||
- description: 店铺名称(模糊查询)
|
||||
in: query
|
||||
name: shop_name
|
||||
schema:
|
||||
description: 店铺名称(模糊查询)
|
||||
maxLength: 100
|
||||
type: string
|
||||
- description: 上级店铺ID(不传则查询顶级店铺)
|
||||
in: query
|
||||
name: parent_id
|
||||
schema:
|
||||
description: 上级店铺ID(不传则查询顶级店铺)
|
||||
minimum: 1
|
||||
nullable: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
code:
|
||||
description: 响应码
|
||||
example: 0
|
||||
type: integer
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/DtoShopCascadeItem'
|
||||
type: array
|
||||
msg:
|
||||
description: 响应消息
|
||||
example: success
|
||||
type: string
|
||||
timestamp:
|
||||
description: 时间戳
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- code
|
||||
- msg
|
||||
- data
|
||||
- timestamp
|
||||
type: object
|
||||
description: 成功
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 店铺联级查询
|
||||
tags:
|
||||
- 店铺管理
|
||||
/api/admin/shops/commission-summary:
|
||||
get:
|
||||
parameters:
|
||||
|
||||
@@ -78,3 +78,19 @@ func (h *ShopHandler) Delete(c *fiber.Ctx) error {
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Cascade 店铺联级查询
|
||||
// GET /api/admin/shops/cascade
|
||||
func (h *ShopHandler) Cascade(c *fiber.Ctx) error {
|
||||
var req dto.ShopCascadeRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
items, err := h.service.ListCascade(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, items)
|
||||
}
|
||||
|
||||
@@ -69,3 +69,16 @@ type UpdateShopParams struct {
|
||||
IDReq
|
||||
UpdateShopRequest
|
||||
}
|
||||
|
||||
// ShopCascadeRequest 店铺联级查询请求
|
||||
type ShopCascadeRequest struct {
|
||||
ShopName string `json:"shop_name" query:"shop_name" validate:"omitempty,max=100" maxLength:"100" description:"店铺名称(模糊查询)"`
|
||||
ParentID *uint `json:"parent_id" query:"parent_id" validate:"omitempty,min=1" minimum:"1" description:"上级店铺ID(不传则查询顶级店铺)"`
|
||||
}
|
||||
|
||||
// ShopCascadeItem 联级查询结果项
|
||||
type ShopCascadeItem struct {
|
||||
ID uint `json:"id" description:"店铺ID"`
|
||||
ShopName string `json:"shop_name" description:"店铺名称"`
|
||||
HasChildren bool `json:"has_children" description:"是否有下级店铺"`
|
||||
}
|
||||
|
||||
@@ -43,6 +43,14 @@ func registerShopRoutes(router fiber.Router, handler *admin.ShopHandler, doc *op
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(shops, doc, groupPath, "GET", "/cascade", handler.Cascade, RouteSpec{
|
||||
Summary: "店铺联级查询",
|
||||
Tags: []string{"店铺管理"},
|
||||
Input: new(dto.ShopCascadeRequest),
|
||||
Output: new([]dto.ShopCascadeItem),
|
||||
Auth: true,
|
||||
})
|
||||
}
|
||||
|
||||
func registerShopRoleRoutes(router fiber.Router, handler *admin.ShopRoleHandler, doc *openapi.Generator, basePath string) {
|
||||
|
||||
@@ -350,6 +350,44 @@ func (s *Service) List(ctx context.Context, opts *store.QueryOptions, filters ma
|
||||
return s.shopStore.List(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// ListCascade 联级查询店铺,用于新增店铺或其他场景选择上级店铺
|
||||
func (s *Service) ListCascade(ctx context.Context, req *dto.ShopCascadeRequest) ([]*dto.ShopCascadeItem, error) {
|
||||
shops, err := s.shopStore.ListForCascade(ctx, req.ShopName, req.ParentID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询店铺失败")
|
||||
}
|
||||
|
||||
if len(shops) == 0 {
|
||||
return []*dto.ShopCascadeItem{}, nil
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(shops))
|
||||
for _, shop := range shops {
|
||||
ids = append(ids, shop.ID)
|
||||
}
|
||||
|
||||
childParentIDs, err := s.shopStore.GetParentIDsWithChildren(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询下级店铺失败")
|
||||
}
|
||||
|
||||
hasChildrenSet := make(map[uint]bool, len(childParentIDs))
|
||||
for _, pid := range childParentIDs {
|
||||
hasChildrenSet[pid] = true
|
||||
}
|
||||
|
||||
result := make([]*dto.ShopCascadeItem, 0, len(shops))
|
||||
for _, shop := range shops {
|
||||
result = append(result, &dto.ShopCascadeItem{
|
||||
ID: shop.ID,
|
||||
ShopName: shop.ShopName,
|
||||
HasChildren: hasChildrenSet[shop.ID],
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
|
||||
@@ -228,3 +228,40 @@ func (s *ShopStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Shop, er
|
||||
}
|
||||
return shops, nil
|
||||
}
|
||||
|
||||
// ListForCascade 联级查询店铺(支持按名称模糊查询和按上级ID过滤)
|
||||
func (s *ShopStore) ListForCascade(ctx context.Context, shopName string, parentID *uint) ([]*model.Shop, error) {
|
||||
query := s.db.WithContext(ctx).Model(&model.Shop{})
|
||||
query = middleware.ApplyShopIDFilter(ctx, query)
|
||||
|
||||
if parentID != nil {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
} else {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
}
|
||||
|
||||
if shopName != "" {
|
||||
query = query.Where("shop_name LIKE ?", "%"+shopName+"%")
|
||||
}
|
||||
|
||||
var shops []*model.Shop
|
||||
if err := query.Order("created_at DESC").Find(&shops).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shops, nil
|
||||
}
|
||||
|
||||
// GetParentIDsWithChildren 查询指定 ID 列表中哪些店铺有下级(返回作为上级的店铺ID集合)
|
||||
func (s *ShopStore) GetParentIDsWithChildren(ctx context.Context, ids []uint) ([]uint, error) {
|
||||
if len(ids) == 0 {
|
||||
return []uint{}, nil
|
||||
}
|
||||
var parentIDs []uint
|
||||
if err := s.db.WithContext(ctx).Model(&model.Shop{}).
|
||||
Distinct("parent_id").
|
||||
Where("parent_id IN ?", ids).
|
||||
Pluck("parent_id", &parentIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parentIDs, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user