diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index 9391f44..86be448 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -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: diff --git a/internal/handler/admin/shop.go b/internal/handler/admin/shop.go index 6a06673..5fd2821 100644 --- a/internal/handler/admin/shop.go +++ b/internal/handler/admin/shop.go @@ -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) +} diff --git a/internal/model/dto/shop_dto.go b/internal/model/dto/shop_dto.go index 39052af..cd1a116 100644 --- a/internal/model/dto/shop_dto.go +++ b/internal/model/dto/shop_dto.go @@ -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:"是否有下级店铺"` +} diff --git a/internal/routes/shop.go b/internal/routes/shop.go index f3c2b56..68010d9 100644 --- a/internal/routes/shop.go +++ b/internal/routes/shop.go @@ -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) { diff --git a/internal/service/shop/service.go b/internal/service/shop/service.go index 356a799..bc1dcd4 100644 --- a/internal/service/shop/service.go +++ b/internal/service/shop/service.go @@ -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 { diff --git a/internal/store/postgres/shop_store.go b/internal/store/postgres/shop_store.go index d5e7bc0..165f438 100644 --- a/internal/store/postgres/shop_store.go +++ b/internal/store/postgres/shop_store.go @@ -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 +}