feat: expand superadmin user detail views
This commit is contained in:
118
backend/app/http/super/v1/dto/super_user.go
Normal file
118
backend/app/http/super/v1/dto/super_user.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/pkg/consts"
|
||||
)
|
||||
|
||||
// SuperUserNotificationListFilter 超管用户通知列表过滤条件。
|
||||
type SuperUserNotificationListFilter struct {
|
||||
requests.Pagination
|
||||
// TenantID 租户ID过滤(为空表示全部)。
|
||||
TenantID *int64 `query:"tenant_id"`
|
||||
// Type 通知类型过滤(system/order/interaction)。
|
||||
Type *string `query:"type"`
|
||||
// Read 是否已读过滤。
|
||||
Read *bool `query:"read"`
|
||||
// CreatedAtFrom 创建时间起始(RFC3339)。
|
||||
CreatedAtFrom *string `query:"created_at_from"`
|
||||
// CreatedAtTo 创建时间结束(RFC3339)。
|
||||
CreatedAtTo *string `query:"created_at_to"`
|
||||
}
|
||||
|
||||
// SuperUserNotificationItem 超管用户通知列表项。
|
||||
type SuperUserNotificationItem struct {
|
||||
// ID 通知ID。
|
||||
ID int64 `json:"id"`
|
||||
// TenantID 通知所属租户ID。
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
// TenantCode 通知所属租户编码。
|
||||
TenantCode string `json:"tenant_code"`
|
||||
// TenantName 通知所属租户名称。
|
||||
TenantName string `json:"tenant_name"`
|
||||
// Type 通知类型。
|
||||
Type string `json:"type"`
|
||||
// Title 通知标题。
|
||||
Title string `json:"title"`
|
||||
// Content 通知内容。
|
||||
Content string `json:"content"`
|
||||
// Read 是否已读。
|
||||
Read bool `json:"read"`
|
||||
// CreatedAt 发送时间(RFC3339)。
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// SuperUserCouponListFilter 超管用户优惠券列表过滤条件。
|
||||
type SuperUserCouponListFilter struct {
|
||||
requests.Pagination
|
||||
// TenantID 租户ID过滤(为空表示全部)。
|
||||
TenantID *int64 `query:"tenant_id"`
|
||||
// TenantCode 租户编码(模糊匹配)。
|
||||
TenantCode *string `query:"tenant_code"`
|
||||
// TenantName 租户名称(模糊匹配)。
|
||||
TenantName *string `query:"tenant_name"`
|
||||
// Status 用户券状态过滤(unused/used/expired)。
|
||||
Status *consts.UserCouponStatus `query:"status"`
|
||||
// Type 券模板类型过滤(fix_amount/discount)。
|
||||
Type *consts.CouponType `query:"type"`
|
||||
// Keyword 标题或描述关键词(模糊匹配)。
|
||||
Keyword *string `query:"keyword"`
|
||||
// CreatedAtFrom 领取时间起始(RFC3339)。
|
||||
CreatedAtFrom *string `query:"created_at_from"`
|
||||
// CreatedAtTo 领取时间结束(RFC3339)。
|
||||
CreatedAtTo *string `query:"created_at_to"`
|
||||
}
|
||||
|
||||
// SuperUserCouponItem 超管用户优惠券列表项。
|
||||
type SuperUserCouponItem struct {
|
||||
// ID 用户券ID。
|
||||
ID int64 `json:"id"`
|
||||
// CouponID 券模板ID。
|
||||
CouponID int64 `json:"coupon_id"`
|
||||
// TenantID 券所属租户ID。
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
// TenantCode 券所属租户编码。
|
||||
TenantCode string `json:"tenant_code"`
|
||||
// TenantName 券所属租户名称。
|
||||
TenantName string `json:"tenant_name"`
|
||||
// Title 券标题。
|
||||
Title string `json:"title"`
|
||||
// Description 券描述。
|
||||
Description string `json:"description"`
|
||||
// Type 券类型。
|
||||
Type consts.CouponType `json:"type"`
|
||||
// TypeDescription 券类型描述(用于展示)。
|
||||
TypeDescription string `json:"type_description"`
|
||||
// Value 券面值/折扣值。
|
||||
Value int64 `json:"value"`
|
||||
// MinOrderAmount 使用门槛金额(分)。
|
||||
MinOrderAmount int64 `json:"min_order_amount"`
|
||||
// MaxDiscount 折扣券最高抵扣金额(分)。
|
||||
MaxDiscount int64 `json:"max_discount"`
|
||||
// StartAt 生效时间(RFC3339)。
|
||||
StartAt string `json:"start_at"`
|
||||
// EndAt 过期时间(RFC3339)。
|
||||
EndAt string `json:"end_at"`
|
||||
// Status 用户券状态。
|
||||
Status consts.UserCouponStatus `json:"status"`
|
||||
// StatusDescription 用户券状态描述(用于展示)。
|
||||
StatusDescription string `json:"status_description"`
|
||||
// OrderID 使用订单ID(未使用为0)。
|
||||
OrderID int64 `json:"order_id"`
|
||||
// UsedAt 使用时间(RFC3339)。
|
||||
UsedAt string `json:"used_at"`
|
||||
// CreatedAt 领取时间(RFC3339)。
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// SuperUserRealNameResponse 超管实名认证详情。
|
||||
type SuperUserRealNameResponse struct {
|
||||
// IsRealNameVerified 是否已实名认证。
|
||||
IsRealNameVerified bool `json:"is_real_name_verified"`
|
||||
// VerifiedAt 实名认证时间(RFC3339)。
|
||||
VerifiedAt string `json:"verified_at"`
|
||||
// RealName 真实姓名(来自用户元数据)。
|
||||
RealName string `json:"real_name"`
|
||||
// IDCardMasked 身份证号脱敏展示。
|
||||
IDCardMasked string `json:"id_card_masked"`
|
||||
}
|
||||
@@ -226,6 +226,23 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.users.Get,
|
||||
PathParam[int64]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:id<int>/coupons -> users.ListCoupons")
|
||||
router.Get("/super/v1/users/:id<int>/coupons"[len(r.Path()):], DataFunc2(
|
||||
r.users.ListCoupons,
|
||||
PathParam[int64]("id"),
|
||||
Query[dto.SuperUserCouponListFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:id<int>/notifications -> users.ListNotifications")
|
||||
router.Get("/super/v1/users/:id<int>/notifications"[len(r.Path()):], DataFunc2(
|
||||
r.users.ListNotifications,
|
||||
PathParam[int64]("id"),
|
||||
Query[dto.SuperUserNotificationListFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:id<int>/realname -> users.RealName")
|
||||
router.Get("/super/v1/users/:id<int>/realname"[len(r.Path()):], DataFunc1(
|
||||
r.users.RealName,
|
||||
PathParam[int64]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:id<int>/tenants -> users.ListTenants")
|
||||
router.Get("/super/v1/users/:id<int>/tenants"[len(r.Path()):], DataFunc2(
|
||||
r.users.ListTenants,
|
||||
|
||||
@@ -58,6 +58,57 @@ func (c *users) Wallet(ctx fiber.Ctx, id int64) (*dto.SuperWalletResponse, error
|
||||
return services.Super.GetUserWallet(ctx, id)
|
||||
}
|
||||
|
||||
// List user notifications
|
||||
//
|
||||
// @Router /super/v1/users/:id<int>/notifications [get]
|
||||
// @Summary List user notifications
|
||||
// @Description List notifications of a user
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int64 true "User ID"
|
||||
// @Param page query int false "Page number"
|
||||
// @Param limit query int false "Page size"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.SuperUserNotificationItem}
|
||||
// @Bind id path
|
||||
// @Bind filter query
|
||||
func (c *users) ListNotifications(ctx fiber.Ctx, id int64, filter *dto.SuperUserNotificationListFilter) (*requests.Pager, error) {
|
||||
return services.Super.ListUserNotifications(ctx, id, filter)
|
||||
}
|
||||
|
||||
// List user coupons
|
||||
//
|
||||
// @Router /super/v1/users/:id<int>/coupons [get]
|
||||
// @Summary List user coupons
|
||||
// @Description List coupons of a user
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int64 true "User ID"
|
||||
// @Param page query int false "Page number"
|
||||
// @Param limit query int false "Page size"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.SuperUserCouponItem}
|
||||
// @Bind id path
|
||||
// @Bind filter query
|
||||
func (c *users) ListCoupons(ctx fiber.Ctx, id int64, filter *dto.SuperUserCouponListFilter) (*requests.Pager, error) {
|
||||
return services.Super.ListUserCoupons(ctx, id, filter)
|
||||
}
|
||||
|
||||
// Get user real-name verification detail
|
||||
//
|
||||
// @Router /super/v1/users/:id<int>/realname [get]
|
||||
// @Summary Get user real-name verification detail
|
||||
// @Description Get real-name verification detail of a user
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int64 true "User ID"
|
||||
// @Success 200 {object} dto.SuperUserRealNameResponse
|
||||
// @Bind id path
|
||||
func (c *users) RealName(ctx fiber.Ctx, id int64) (*dto.SuperUserRealNameResponse, error) {
|
||||
return services.Super.GetUserRealName(ctx, id)
|
||||
}
|
||||
|
||||
// List user tenants
|
||||
//
|
||||
// @Router /super/v1/users/:id<int>/tenants [get]
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -286,6 +287,7 @@ func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, err
|
||||
Roles: u.Roles,
|
||||
Status: u.Status,
|
||||
StatusDescription: u.Status.Description(),
|
||||
VerifiedAt: s.formatTime(u.VerifiedAt),
|
||||
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
||||
},
|
||||
@@ -382,6 +384,301 @@ func (s *super) GetUserWallet(ctx context.Context, userID int64) (*super_dto.Sup
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) GetUserRealName(ctx context.Context, userID int64) (*super_dto.SuperUserRealNameResponse, error) {
|
||||
if userID == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("用户ID不能为空")
|
||||
}
|
||||
|
||||
tbl, q := models.UserQuery.QueryContext(ctx)
|
||||
u, err := q.Where(tbl.ID.Eq(userID)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 从用户元数据中读取实名字段,便于超管展示。
|
||||
meta := make(map[string]interface{})
|
||||
if len(u.Metas) > 0 {
|
||||
if err := json.Unmarshal(u.Metas, &meta); err != nil {
|
||||
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("解析实名认证信息失败")
|
||||
}
|
||||
}
|
||||
|
||||
realName := ""
|
||||
if value, ok := meta["real_name"].(string); ok {
|
||||
realName = value
|
||||
}
|
||||
idCard := ""
|
||||
if value, ok := meta["id_card"].(string); ok {
|
||||
idCard = value
|
||||
}
|
||||
|
||||
return &super_dto.SuperUserRealNameResponse{
|
||||
IsRealNameVerified: u.IsRealNameVerified,
|
||||
VerifiedAt: s.formatTime(u.VerifiedAt),
|
||||
RealName: realName,
|
||||
IDCardMasked: s.maskIDCard(idCard),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) ListUserNotifications(ctx context.Context, userID int64, filter *super_dto.SuperUserNotificationListFilter) (*requests.Pager, error) {
|
||||
if userID == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("用户ID不能为空")
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &super_dto.SuperUserNotificationListFilter{}
|
||||
}
|
||||
|
||||
tbl, q := models.NotificationQuery.QueryContext(ctx)
|
||||
q = q.Where(tbl.UserID.Eq(userID))
|
||||
if filter.TenantID != nil && *filter.TenantID > 0 {
|
||||
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
|
||||
}
|
||||
if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
|
||||
q = q.Where(tbl.Type.Eq(strings.TrimSpace(*filter.Type)))
|
||||
}
|
||||
if filter.Read != nil {
|
||||
q = q.Where(tbl.IsRead.Is(*filter.Read))
|
||||
}
|
||||
if filter.CreatedAtFrom != nil {
|
||||
from, err := s.parseFilterTime(filter.CreatedAtFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from != nil {
|
||||
q = q.Where(tbl.CreatedAt.Gte(*from))
|
||||
}
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
to, err := s.parseFilterTime(filter.CreatedAtTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where(tbl.CreatedAt.Lte(*to))
|
||||
}
|
||||
}
|
||||
|
||||
filter.Pagination.Format()
|
||||
total, err := q.Count()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
list, err := q.Order(tbl.CreatedAt.Desc()).
|
||||
Offset(int(filter.Pagination.Offset())).
|
||||
Limit(int(filter.Pagination.Limit)).
|
||||
Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 补齐租户信息,便于跨租户展示。
|
||||
tenantIDs := make([]int64, 0, len(list))
|
||||
tenantSet := make(map[int64]struct{})
|
||||
for _, n := range list {
|
||||
if n.TenantID > 0 {
|
||||
if _, ok := tenantSet[n.TenantID]; !ok {
|
||||
tenantSet[n.TenantID] = struct{}{}
|
||||
tenantIDs = append(tenantIDs, n.TenantID)
|
||||
}
|
||||
}
|
||||
}
|
||||
tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
|
||||
if len(tenantIDs) > 0 {
|
||||
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
|
||||
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
for _, tenant := range tenants {
|
||||
tenantMap[tenant.ID] = tenant
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]super_dto.SuperUserNotificationItem, 0, len(list))
|
||||
for _, n := range list {
|
||||
tenant := tenantMap[n.TenantID]
|
||||
tenantCode := ""
|
||||
tenantName := ""
|
||||
if tenant != nil {
|
||||
tenantCode = tenant.Code
|
||||
tenantName = tenant.Name
|
||||
}
|
||||
items = append(items, super_dto.SuperUserNotificationItem{
|
||||
ID: n.ID,
|
||||
TenantID: n.TenantID,
|
||||
TenantCode: tenantCode,
|
||||
TenantName: tenantName,
|
||||
Type: n.Type,
|
||||
Title: n.Title,
|
||||
Content: n.Content,
|
||||
Read: n.IsRead,
|
||||
CreatedAt: s.formatTime(n.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) ListUserCoupons(ctx context.Context, userID int64, filter *super_dto.SuperUserCouponListFilter) (*requests.Pager, error) {
|
||||
if userID == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("用户ID不能为空")
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &super_dto.SuperUserCouponListFilter{}
|
||||
}
|
||||
|
||||
couponIDs, couponFilter, err := s.filterCouponIDs(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbl, q := models.UserCouponQuery.QueryContext(ctx)
|
||||
q = q.Where(tbl.UserID.Eq(userID))
|
||||
if filter.Status != nil && *filter.Status != "" {
|
||||
q = q.Where(tbl.Status.Eq(*filter.Status))
|
||||
}
|
||||
if filter.CreatedAtFrom != nil {
|
||||
from, err := s.parseFilterTime(filter.CreatedAtFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from != nil {
|
||||
q = q.Where(tbl.CreatedAt.Gte(*from))
|
||||
}
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
to, err := s.parseFilterTime(filter.CreatedAtTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where(tbl.CreatedAt.Lte(*to))
|
||||
}
|
||||
}
|
||||
if couponFilter {
|
||||
if len(couponIDs) == 0 {
|
||||
filter.Pagination.Format()
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: 0,
|
||||
Items: []super_dto.SuperUserCouponItem{},
|
||||
}, nil
|
||||
}
|
||||
q = q.Where(tbl.CouponID.In(couponIDs...))
|
||||
}
|
||||
|
||||
filter.Pagination.Format()
|
||||
total, err := q.Count()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
list, err := q.Order(tbl.CreatedAt.Desc()).
|
||||
Offset(int(filter.Pagination.Offset())).
|
||||
Limit(int(filter.Pagination.Limit)).
|
||||
Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: []super_dto.SuperUserCouponItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 读取券模板与租户信息,便于聚合展示。
|
||||
couponIDSet := make(map[int64]struct{})
|
||||
couponIDs = make([]int64, 0, len(list))
|
||||
for _, uc := range list {
|
||||
if uc.CouponID > 0 {
|
||||
if _, ok := couponIDSet[uc.CouponID]; !ok {
|
||||
couponIDSet[uc.CouponID] = struct{}{}
|
||||
couponIDs = append(couponIDs, uc.CouponID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
couponMap := make(map[int64]*models.Coupon, len(couponIDs))
|
||||
tenantIDs := make([]int64, 0)
|
||||
tenantSet := make(map[int64]struct{})
|
||||
if len(couponIDs) > 0 {
|
||||
couponTbl, couponQuery := models.CouponQuery.QueryContext(ctx)
|
||||
coupons, err := couponQuery.Where(couponTbl.ID.In(couponIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
for _, coupon := range coupons {
|
||||
couponMap[coupon.ID] = coupon
|
||||
if coupon.TenantID > 0 {
|
||||
if _, ok := tenantSet[coupon.TenantID]; !ok {
|
||||
tenantSet[coupon.TenantID] = struct{}{}
|
||||
tenantIDs = append(tenantIDs, coupon.TenantID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
|
||||
if len(tenantIDs) > 0 {
|
||||
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
|
||||
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
for _, tenant := range tenants {
|
||||
tenantMap[tenant.ID] = tenant
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]super_dto.SuperUserCouponItem, 0, len(list))
|
||||
for _, uc := range list {
|
||||
item := super_dto.SuperUserCouponItem{
|
||||
ID: uc.ID,
|
||||
CouponID: uc.CouponID,
|
||||
Status: uc.Status,
|
||||
StatusDescription: uc.Status.Description(),
|
||||
OrderID: uc.OrderID,
|
||||
UsedAt: s.formatTime(uc.UsedAt),
|
||||
CreatedAt: s.formatTime(uc.CreatedAt),
|
||||
}
|
||||
|
||||
coupon := couponMap[uc.CouponID]
|
||||
if coupon != nil {
|
||||
item.TenantID = coupon.TenantID
|
||||
item.Title = coupon.Title
|
||||
item.Description = coupon.Description
|
||||
item.Type = coupon.Type
|
||||
item.TypeDescription = coupon.Type.Description()
|
||||
item.Value = coupon.Value
|
||||
item.MinOrderAmount = coupon.MinOrderAmount
|
||||
item.MaxDiscount = coupon.MaxDiscount
|
||||
item.StartAt = s.formatTime(coupon.StartAt)
|
||||
item.EndAt = s.formatTime(coupon.EndAt)
|
||||
|
||||
if tenant := tenantMap[coupon.TenantID]; tenant != nil {
|
||||
item.TenantCode = tenant.Code
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) UpdateUserStatus(ctx context.Context, id int64, form *super_dto.UserStatusUpdateForm) error {
|
||||
tbl, q := models.UserQuery.QueryContext(ctx)
|
||||
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.UserStatus(form.Status))
|
||||
@@ -3400,6 +3697,75 @@ func (s *super) formatTime(t time.Time) string {
|
||||
return t.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (s *super) maskIDCard(raw string) string {
|
||||
text := strings.TrimSpace(raw)
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
text = strings.TrimPrefix(text, "ENC:")
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
length := len(text)
|
||||
if length <= 4 {
|
||||
return strings.Repeat("*", length)
|
||||
}
|
||||
if length <= 8 {
|
||||
return text[:2] + strings.Repeat("*", length-4) + text[length-2:]
|
||||
}
|
||||
return text[:3] + strings.Repeat("*", length-7) + text[length-4:]
|
||||
}
|
||||
|
||||
func (s *super) filterCouponIDs(ctx context.Context, filter *super_dto.SuperUserCouponListFilter) ([]int64, bool, error) {
|
||||
if filter == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
couponTbl, couponQuery := models.CouponQuery.QueryContext(ctx)
|
||||
applied := false
|
||||
|
||||
if filter.TenantID != nil && *filter.TenantID > 0 {
|
||||
applied = true
|
||||
couponQuery = couponQuery.Where(couponTbl.TenantID.Eq(*filter.TenantID))
|
||||
} else {
|
||||
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if tenantFilter {
|
||||
applied = true
|
||||
if len(tenantIDs) == 0 {
|
||||
return []int64{}, true, nil
|
||||
}
|
||||
couponQuery = couponQuery.Where(couponTbl.TenantID.In(tenantIDs...))
|
||||
}
|
||||
}
|
||||
|
||||
if filter.Type != nil && *filter.Type != "" {
|
||||
applied = true
|
||||
couponQuery = couponQuery.Where(couponTbl.Type.Eq(*filter.Type))
|
||||
}
|
||||
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
|
||||
applied = true
|
||||
keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
|
||||
couponQuery = couponQuery.Where(field.Or(couponTbl.Title.Like(keyword), couponTbl.Description.Like(keyword)))
|
||||
}
|
||||
|
||||
if !applied {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
coupons, err := couponQuery.Select(couponTbl.ID).Find()
|
||||
if err != nil {
|
||||
return nil, true, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
ids := make([]int64, 0, len(coupons))
|
||||
for _, coupon := range coupons {
|
||||
ids = append(ids, coupon.ID)
|
||||
}
|
||||
return ids, true, nil
|
||||
}
|
||||
|
||||
func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reason string) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
|
||||
@@ -520,6 +520,100 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenant-join-requests": {
|
||||
"get": {
|
||||
"description": "List tenant join requests across tenants",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "List tenant join requests",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperTenantJoinRequestItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenant-join-requests/{id}/review": {
|
||||
"post": {
|
||||
"description": "Approve or reject a tenant join request",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "Review tenant join request",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Join request ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Review form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantJoinReviewForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Reviewed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants": {
|
||||
"get": {
|
||||
"description": "List tenants",
|
||||
@@ -913,6 +1007,229 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons": {
|
||||
"post": {
|
||||
"description": "Create coupon for tenant",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Create coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Create form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponCreateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons/{id}": {
|
||||
"get": {
|
||||
"description": "Get coupon detail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Get coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update coupon for tenant",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Update coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Update form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponUpdateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons/{id}/grant": {
|
||||
"post": {
|
||||
"description": "Grant coupon to users",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Grant coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Grant form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponGrantForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SuperCouponGrantResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/invites": {
|
||||
"post": {
|
||||
"description": "Create tenant invite code",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "Create tenant invite",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Invite form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantInviteCreateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantInviteItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/users": {
|
||||
"get": {
|
||||
"description": "List tenant users",
|
||||
@@ -1116,6 +1433,159 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/coupons": {
|
||||
"get": {
|
||||
"description": "List coupons of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "List user coupons",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperUserCouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/notifications": {
|
||||
"get": {
|
||||
"description": "List notifications of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "List user notifications",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperUserNotificationItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/realname": {
|
||||
"get": {
|
||||
"description": "Get real-name verification detail of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Get user real-name verification detail",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SuperUserRealNameResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/roles": {
|
||||
"patch": {
|
||||
"description": "Update user roles",
|
||||
@@ -4279,6 +4749,19 @@ const docTemplate = `{
|
||||
"TenantUserRoleTenantAdmin"
|
||||
]
|
||||
},
|
||||
"consts.UserCouponStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unused",
|
||||
"used",
|
||||
"expired"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UserCouponStatusUnused",
|
||||
"UserCouponStatusUsed",
|
||||
"UserCouponStatusExpired"
|
||||
]
|
||||
},
|
||||
"consts.UserStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -5635,6 +6118,15 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperCouponGrantResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"granted": {
|
||||
"description": "Granted 实际发放数量。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperCouponItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5922,6 +6414,67 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperTenantJoinRequestItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"description": "CreatedAt 申请时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"decided_at": {
|
||||
"description": "DecidedAt 审核时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"decided_operator_user_id": {
|
||||
"description": "DecidedOperatorUserID 审核操作者ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"decided_reason": {
|
||||
"description": "DecidedReason 审核备注/原因。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 申请记录ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason 申请说明。",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 申请状态。",
|
||||
"type": "string"
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 状态描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "UpdatedAt 更新时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "UserID 申请用户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 申请用户名称。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperTenantUserItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5943,6 +6496,95 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserCouponItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"coupon_id": {
|
||||
"description": "CouponID 券模板ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt 领取时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description 券描述。",
|
||||
"type": "string"
|
||||
},
|
||||
"end_at": {
|
||||
"description": "EndAt 过期时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 用户券ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_discount": {
|
||||
"description": "MaxDiscount 折扣券最高抵扣金额(分)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"min_order_amount": {
|
||||
"description": "MinOrderAmount 使用门槛金额(分)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"order_id": {
|
||||
"description": "OrderID 使用订单ID(未使用为0)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"start_at": {
|
||||
"description": "StartAt 生效时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 用户券状态。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserCouponStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 用户券状态描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 券所属租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 券所属租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 券所属租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title 券标题。",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type 券类型。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.CouponType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_description": {
|
||||
"description": "TypeDescription 券类型描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"used_at": {
|
||||
"description": "UsedAt 使用时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value 券面值/折扣值。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5987,6 +6629,68 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserNotificationItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content 通知内容。",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt 发送时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 通知ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"read": {
|
||||
"description": "Read 是否已读。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 通知所属租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 通知所属租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 通知所属租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title 通知标题。",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type 通知类型。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserRealNameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id_card_masked": {
|
||||
"description": "IDCardMasked 身份证号脱敏展示。",
|
||||
"type": "string"
|
||||
},
|
||||
"is_real_name_verified": {
|
||||
"description": "IsRealNameVerified 是否已实名认证。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"real_name": {
|
||||
"description": "RealName 真实姓名(来自用户元数据)。",
|
||||
"type": "string"
|
||||
},
|
||||
"verified_at": {
|
||||
"description": "VerifiedAt 实名认证时间(RFC3339)。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperWalletResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -514,6 +514,100 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenant-join-requests": {
|
||||
"get": {
|
||||
"description": "List tenant join requests across tenants",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "List tenant join requests",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperTenantJoinRequestItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenant-join-requests/{id}/review": {
|
||||
"post": {
|
||||
"description": "Approve or reject a tenant join request",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "Review tenant join request",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Join request ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Review form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantJoinReviewForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Reviewed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants": {
|
||||
"get": {
|
||||
"description": "List tenants",
|
||||
@@ -907,6 +1001,229 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons": {
|
||||
"post": {
|
||||
"description": "Create coupon for tenant",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Create coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Create form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponCreateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons/{id}": {
|
||||
"get": {
|
||||
"description": "Get coupon detail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Get coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update coupon for tenant",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Update coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Update form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponUpdateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/coupons/{id}/grant": {
|
||||
"post": {
|
||||
"description": "Grant coupon to users",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Coupon"
|
||||
],
|
||||
"summary": "Grant coupon",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Coupon ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Grant form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CouponGrantForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SuperCouponGrantResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/invites": {
|
||||
"post": {
|
||||
"description": "Create tenant invite code",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "Create tenant invite",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Tenant ID",
|
||||
"name": "tenantID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Invite form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantInviteCreateForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.TenantInviteItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/tenants/{tenantID}/users": {
|
||||
"get": {
|
||||
"description": "List tenant users",
|
||||
@@ -1110,6 +1427,159 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/coupons": {
|
||||
"get": {
|
||||
"description": "List coupons of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "List user coupons",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperUserCouponItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/notifications": {
|
||||
"get": {
|
||||
"description": "List notifications of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "List user notifications",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SuperUserNotificationItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/realname": {
|
||||
"get": {
|
||||
"description": "Get real-name verification detail of a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Get user real-name verification detail",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SuperUserRealNameResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/super/v1/users/{id}/roles": {
|
||||
"patch": {
|
||||
"description": "Update user roles",
|
||||
@@ -4273,6 +4743,19 @@
|
||||
"TenantUserRoleTenantAdmin"
|
||||
]
|
||||
},
|
||||
"consts.UserCouponStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unused",
|
||||
"used",
|
||||
"expired"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UserCouponStatusUnused",
|
||||
"UserCouponStatusUsed",
|
||||
"UserCouponStatusExpired"
|
||||
]
|
||||
},
|
||||
"consts.UserStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -5629,6 +6112,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperCouponGrantResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"granted": {
|
||||
"description": "Granted 实际发放数量。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperCouponItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5916,6 +6408,67 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperTenantJoinRequestItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"description": "CreatedAt 申请时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"decided_at": {
|
||||
"description": "DecidedAt 审核时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"decided_operator_user_id": {
|
||||
"description": "DecidedOperatorUserID 审核操作者ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"decided_reason": {
|
||||
"description": "DecidedReason 审核备注/原因。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 申请记录ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason 申请说明。",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 申请状态。",
|
||||
"type": "string"
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 状态描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "UpdatedAt 更新时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "UserID 申请用户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 申请用户名称。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperTenantUserItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5937,6 +6490,95 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserCouponItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"coupon_id": {
|
||||
"description": "CouponID 券模板ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt 领取时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description 券描述。",
|
||||
"type": "string"
|
||||
},
|
||||
"end_at": {
|
||||
"description": "EndAt 过期时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 用户券ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_discount": {
|
||||
"description": "MaxDiscount 折扣券最高抵扣金额(分)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"min_order_amount": {
|
||||
"description": "MinOrderAmount 使用门槛金额(分)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"order_id": {
|
||||
"description": "OrderID 使用订单ID(未使用为0)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"start_at": {
|
||||
"description": "StartAt 生效时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 用户券状态。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserCouponStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 用户券状态描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 券所属租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 券所属租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 券所属租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title 券标题。",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type 券类型。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.CouponType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_description": {
|
||||
"description": "TypeDescription 券类型描述(用于展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"used_at": {
|
||||
"description": "UsedAt 使用时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value 券面值/折扣值。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5981,6 +6623,68 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserNotificationItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content 通知内容。",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt 发送时间(RFC3339)。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 通知ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"read": {
|
||||
"description": "Read 是否已读。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 通知所属租户编码。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 通知所属租户ID。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 通知所属租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title 通知标题。",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type 通知类型。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperUserRealNameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id_card_masked": {
|
||||
"description": "IDCardMasked 身份证号脱敏展示。",
|
||||
"type": "string"
|
||||
},
|
||||
"is_real_name_verified": {
|
||||
"description": "IsRealNameVerified 是否已实名认证。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"real_name": {
|
||||
"description": "RealName 真实姓名(来自用户元数据)。",
|
||||
"type": "string"
|
||||
},
|
||||
"verified_at": {
|
||||
"description": "VerifiedAt 实名认证时间(RFC3339)。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SuperWalletResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -92,6 +92,16 @@ definitions:
|
||||
x-enum-varnames:
|
||||
- TenantUserRoleMember
|
||||
- TenantUserRoleTenantAdmin
|
||||
consts.UserCouponStatus:
|
||||
enum:
|
||||
- unused
|
||||
- used
|
||||
- expired
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- UserCouponStatusUnused
|
||||
- UserCouponStatusUsed
|
||||
- UserCouponStatusExpired
|
||||
consts.UserStatus:
|
||||
enum:
|
||||
- active
|
||||
@@ -1050,6 +1060,12 @@ definitions:
|
||||
description: Name 租户名称。
|
||||
type: string
|
||||
type: object
|
||||
dto.SuperCouponGrantResponse:
|
||||
properties:
|
||||
granted:
|
||||
description: Granted 实际发放数量。
|
||||
type: integer
|
||||
type: object
|
||||
dto.SuperCouponItem:
|
||||
properties:
|
||||
created_at:
|
||||
@@ -1239,6 +1255,51 @@ definitions:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
dto.SuperTenantJoinRequestItem:
|
||||
properties:
|
||||
created_at:
|
||||
description: CreatedAt 申请时间(RFC3339)。
|
||||
type: string
|
||||
decided_at:
|
||||
description: DecidedAt 审核时间(RFC3339)。
|
||||
type: string
|
||||
decided_operator_user_id:
|
||||
description: DecidedOperatorUserID 审核操作者ID。
|
||||
type: integer
|
||||
decided_reason:
|
||||
description: DecidedReason 审核备注/原因。
|
||||
type: string
|
||||
id:
|
||||
description: ID 申请记录ID。
|
||||
type: integer
|
||||
reason:
|
||||
description: Reason 申请说明。
|
||||
type: string
|
||||
status:
|
||||
description: Status 申请状态。
|
||||
type: string
|
||||
status_description:
|
||||
description: StatusDescription 状态描述(用于展示)。
|
||||
type: string
|
||||
tenant_code:
|
||||
description: TenantCode 租户编码。
|
||||
type: string
|
||||
tenant_id:
|
||||
description: TenantID 租户ID。
|
||||
type: integer
|
||||
tenant_name:
|
||||
description: TenantName 租户名称。
|
||||
type: string
|
||||
updated_at:
|
||||
description: UpdatedAt 更新时间(RFC3339)。
|
||||
type: string
|
||||
user_id:
|
||||
description: UserID 申请用户ID。
|
||||
type: integer
|
||||
username:
|
||||
description: Username 申请用户名称。
|
||||
type: string
|
||||
type: object
|
||||
dto.SuperTenantUserItem:
|
||||
properties:
|
||||
tenant_user:
|
||||
@@ -1250,6 +1311,68 @@ definitions:
|
||||
- $ref: '#/definitions/dto.SuperUserLite'
|
||||
description: User 用户信息。
|
||||
type: object
|
||||
dto.SuperUserCouponItem:
|
||||
properties:
|
||||
coupon_id:
|
||||
description: CouponID 券模板ID。
|
||||
type: integer
|
||||
created_at:
|
||||
description: CreatedAt 领取时间(RFC3339)。
|
||||
type: string
|
||||
description:
|
||||
description: Description 券描述。
|
||||
type: string
|
||||
end_at:
|
||||
description: EndAt 过期时间(RFC3339)。
|
||||
type: string
|
||||
id:
|
||||
description: ID 用户券ID。
|
||||
type: integer
|
||||
max_discount:
|
||||
description: MaxDiscount 折扣券最高抵扣金额(分)。
|
||||
type: integer
|
||||
min_order_amount:
|
||||
description: MinOrderAmount 使用门槛金额(分)。
|
||||
type: integer
|
||||
order_id:
|
||||
description: OrderID 使用订单ID(未使用为0)。
|
||||
type: integer
|
||||
start_at:
|
||||
description: StartAt 生效时间(RFC3339)。
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.UserCouponStatus'
|
||||
description: Status 用户券状态。
|
||||
status_description:
|
||||
description: StatusDescription 用户券状态描述(用于展示)。
|
||||
type: string
|
||||
tenant_code:
|
||||
description: TenantCode 券所属租户编码。
|
||||
type: string
|
||||
tenant_id:
|
||||
description: TenantID 券所属租户ID。
|
||||
type: integer
|
||||
tenant_name:
|
||||
description: TenantName 券所属租户名称。
|
||||
type: string
|
||||
title:
|
||||
description: Title 券标题。
|
||||
type: string
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.CouponType'
|
||||
description: Type 券类型。
|
||||
type_description:
|
||||
description: TypeDescription 券类型描述(用于展示)。
|
||||
type: string
|
||||
used_at:
|
||||
description: UsedAt 使用时间(RFC3339)。
|
||||
type: string
|
||||
value:
|
||||
description: Value 券面值/折扣值。
|
||||
type: integer
|
||||
type: object
|
||||
dto.SuperUserLite:
|
||||
properties:
|
||||
created_at:
|
||||
@@ -1280,6 +1403,51 @@ definitions:
|
||||
description: VerifiedAt 实名认证时间(RFC3339)。
|
||||
type: string
|
||||
type: object
|
||||
dto.SuperUserNotificationItem:
|
||||
properties:
|
||||
content:
|
||||
description: Content 通知内容。
|
||||
type: string
|
||||
created_at:
|
||||
description: CreatedAt 发送时间(RFC3339)。
|
||||
type: string
|
||||
id:
|
||||
description: ID 通知ID。
|
||||
type: integer
|
||||
read:
|
||||
description: Read 是否已读。
|
||||
type: boolean
|
||||
tenant_code:
|
||||
description: TenantCode 通知所属租户编码。
|
||||
type: string
|
||||
tenant_id:
|
||||
description: TenantID 通知所属租户ID。
|
||||
type: integer
|
||||
tenant_name:
|
||||
description: TenantName 通知所属租户名称。
|
||||
type: string
|
||||
title:
|
||||
description: Title 通知标题。
|
||||
type: string
|
||||
type:
|
||||
description: Type 通知类型。
|
||||
type: string
|
||||
type: object
|
||||
dto.SuperUserRealNameResponse:
|
||||
properties:
|
||||
id_card_masked:
|
||||
description: IDCardMasked 身份证号脱敏展示。
|
||||
type: string
|
||||
is_real_name_verified:
|
||||
description: IsRealNameVerified 是否已实名认证。
|
||||
type: boolean
|
||||
real_name:
|
||||
description: RealName 真实姓名(来自用户元数据)。
|
||||
type: string
|
||||
verified_at:
|
||||
description: VerifiedAt 实名认证时间(RFC3339)。
|
||||
type: string
|
||||
type: object
|
||||
dto.SuperWalletResponse:
|
||||
properties:
|
||||
balance:
|
||||
@@ -2421,6 +2589,65 @@ paths:
|
||||
summary: Report overview
|
||||
tags:
|
||||
- Report
|
||||
/super/v1/tenant-join-requests:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: List tenant join requests across tenants
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/requests.Pager'
|
||||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/dto.SuperTenantJoinRequestItem'
|
||||
type: array
|
||||
type: object
|
||||
summary: List tenant join requests
|
||||
tags:
|
||||
- Tenant
|
||||
/super/v1/tenant-join-requests/{id}/review:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Approve or reject a tenant join request
|
||||
parameters:
|
||||
- description: Join request ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Review form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.TenantJoinReviewForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Reviewed
|
||||
schema:
|
||||
type: string
|
||||
summary: Review tenant join request
|
||||
tags:
|
||||
- Tenant
|
||||
/super/v1/tenants:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2625,6 +2852,157 @@ paths:
|
||||
summary: Update content status
|
||||
tags:
|
||||
- Content
|
||||
/super/v1/tenants/{tenantID}/coupons:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create coupon for tenant
|
||||
parameters:
|
||||
- description: Tenant ID
|
||||
format: int64
|
||||
in: path
|
||||
name: tenantID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Create form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponCreateForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponItem'
|
||||
summary: Create coupon
|
||||
tags:
|
||||
- Coupon
|
||||
/super/v1/tenants/{tenantID}/coupons/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get coupon detail
|
||||
parameters:
|
||||
- description: Tenant ID
|
||||
format: int64
|
||||
in: path
|
||||
name: tenantID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Coupon ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponItem'
|
||||
summary: Get coupon
|
||||
tags:
|
||||
- Coupon
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update coupon for tenant
|
||||
parameters:
|
||||
- description: Tenant ID
|
||||
format: int64
|
||||
in: path
|
||||
name: tenantID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Coupon ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Update form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponUpdateForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponItem'
|
||||
summary: Update coupon
|
||||
tags:
|
||||
- Coupon
|
||||
/super/v1/tenants/{tenantID}/coupons/{id}/grant:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Grant coupon to users
|
||||
parameters:
|
||||
- description: Tenant ID
|
||||
format: int64
|
||||
in: path
|
||||
name: tenantID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Coupon ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Grant form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CouponGrantForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SuperCouponGrantResponse'
|
||||
summary: Grant coupon
|
||||
tags:
|
||||
- Coupon
|
||||
/super/v1/tenants/{tenantID}/invites:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create tenant invite code
|
||||
parameters:
|
||||
- description: Tenant ID
|
||||
format: int64
|
||||
in: path
|
||||
name: tenantID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Invite form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.TenantInviteCreateForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.TenantInviteItem'
|
||||
summary: Create tenant invite
|
||||
tags:
|
||||
- Tenant
|
||||
/super/v1/tenants/{tenantID}/users:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2767,6 +3145,102 @@ paths:
|
||||
summary: Get user
|
||||
tags:
|
||||
- User
|
||||
/super/v1/users/{id}/coupons:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: List coupons of a user
|
||||
parameters:
|
||||
- description: User ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/requests.Pager'
|
||||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/dto.SuperUserCouponItem'
|
||||
type: array
|
||||
type: object
|
||||
summary: List user coupons
|
||||
tags:
|
||||
- User
|
||||
/super/v1/users/{id}/notifications:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: List notifications of a user
|
||||
parameters:
|
||||
- description: User ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/requests.Pager'
|
||||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/dto.SuperUserNotificationItem'
|
||||
type: array
|
||||
type: object
|
||||
summary: List user notifications
|
||||
tags:
|
||||
- User
|
||||
/super/v1/users/{id}/realname:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get real-name verification detail of a user
|
||||
parameters:
|
||||
- description: User ID
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SuperUserRealNameResponse'
|
||||
summary: Get user real-name verification detail
|
||||
tags:
|
||||
- User
|
||||
/super/v1/users/{id}/roles:
|
||||
patch:
|
||||
consumes:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 1) 总体结论
|
||||
|
||||
- **已落地**:登录、租户/用户/订单/内容的基础管理、内容审核(含批量)、提现审核、报表概览与导出、用户钱包查看、创作者成员审核/邀请、优惠券创建/编辑/发放。
|
||||
- **已落地**:登录、租户/用户/订单/内容的基础管理、内容审核(含批量)、提现审核、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录视图、创作者成员审核/邀请、优惠券创建/编辑/发放。
|
||||
- **部分落地**:平台概览(缺少内容统计与运营趋势)、租户详情(缺财务/报表聚合)、用户管理/详情(缺通知/优惠券/实名/充值/收藏等明细)、创作者/优惠券(缺结算账户审核/发放记录/冻结等深度治理)。
|
||||
- **未落地**:资产治理、通知中心、审计与系统配置类能力。
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
- 缺口:租户级财务与报表聚合入口(成员审核/邀请由超管入口完成)。
|
||||
|
||||
### 2.5 用户管理 `/superadmin/users`
|
||||
- 状态:**部分完成**
|
||||
- 已有:用户列表、角色/状态变更、统计。
|
||||
- 缺口:实名认证详情、通知/优惠券明细、充值记录等超管视图接口。
|
||||
- 状态:**已完成**
|
||||
- 已有:用户列表、角色/状态变更、统计、余额与实名认证概览。
|
||||
- 缺口:无显著功能缺口。
|
||||
|
||||
### 2.6 用户详情 `/superadmin/users/:userID`
|
||||
- 状态:**部分完成**
|
||||
- 已有:用户资料、租户关系、订单查询、钱包余额与流水。
|
||||
- 缺口:通知、优惠券、收藏/点赞、实名认证详情等明细。
|
||||
- 已有:用户资料、租户关系、订单查询、钱包余额与流水、充值记录、通知、优惠券、实名认证详情。
|
||||
- 缺口:收藏/点赞、关注、内容消费明细等用户互动视图。
|
||||
|
||||
### 2.7 内容治理 `/superadmin/contents`
|
||||
- 状态:**部分完成**
|
||||
@@ -82,12 +82,12 @@
|
||||
|
||||
## 3) `/super/v1` 接口覆盖度概览
|
||||
|
||||
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包)、Contents、Orders、Withdrawals、Reports、Coupons(列表/创建/编辑/发放)、Creators(列表)。
|
||||
- **缺失/待补**:资产治理、通知中心、用户优惠券/通知明细、创作者申请/结算账户审核、优惠券冻结与发放记录。
|
||||
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名)、Contents、Orders、Withdrawals、Reports、Coupons(列表/创建/编辑/发放)、Creators(列表)。
|
||||
- **缺失/待补**:资产治理、通知中心、用户互动明细(收藏/点赞/关注)、创作者申请/结算账户审核、优惠券冻结与发放记录。
|
||||
|
||||
## 4) 建议的下一步(按优先级)
|
||||
|
||||
1. **完善用户与通知/优惠券明细**:补齐用户通知、优惠券、实名详情、充值记录等超管视图接口与页面。
|
||||
2. **创作者/优惠券深度治理**:补齐创作者申请/结算账户审核、优惠券冻结/发放记录。
|
||||
3. **平台概览增强**:补齐内容总量与趋势、退款率、订单漏斗等核心指标。
|
||||
4. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。
|
||||
1. **创作者/优惠券深度治理**:补齐创作者申请/结算账户审核、优惠券冻结/发放记录。
|
||||
2. **平台概览增强**:补齐内容总量与趋势、退款率、订单漏斗等核心指标。
|
||||
3. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。
|
||||
4. **用户互动明细**:补齐收藏/点赞/关注等互动明细视图与聚合能力。
|
||||
|
||||
@@ -117,5 +117,68 @@ export const UserService = {
|
||||
total: data?.total ?? 0,
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async getUserRealName(userID) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
return requestJson(`/super/v1/users/${userID}/realname`);
|
||||
},
|
||||
async listUserNotifications(userID, { page, limit, tenant_id, type, read, created_at_from, created_at_to } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
const iso = (d) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
if (Number.isNaN(date.getTime())) return undefined;
|
||||
return date.toISOString();
|
||||
};
|
||||
|
||||
const query = {
|
||||
page,
|
||||
limit,
|
||||
tenant_id,
|
||||
type,
|
||||
read,
|
||||
created_at_from: iso(created_at_from),
|
||||
created_at_to: iso(created_at_to)
|
||||
};
|
||||
|
||||
const data = await requestJson(`/super/v1/users/${userID}/notifications`, { query });
|
||||
return {
|
||||
page: data?.page ?? page ?? 1,
|
||||
limit: data?.limit ?? limit ?? 10,
|
||||
total: data?.total ?? 0,
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async listUserCoupons(userID, { page, limit, tenant_id, tenant_code, tenant_name, status, type, keyword, created_at_from, created_at_to } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
const iso = (d) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
if (Number.isNaN(date.getTime())) return undefined;
|
||||
return date.toISOString();
|
||||
};
|
||||
|
||||
const query = {
|
||||
page,
|
||||
limit,
|
||||
tenant_id,
|
||||
tenant_code,
|
||||
tenant_name,
|
||||
status,
|
||||
type,
|
||||
keyword,
|
||||
created_at_from: iso(created_at_from),
|
||||
created_at_to: iso(created_at_to)
|
||||
};
|
||||
|
||||
const data = await requestJson(`/super/v1/users/${userID}/coupons`, { query });
|
||||
return {
|
||||
page: data?.page ?? page ?? 1,
|
||||
limit: data?.limit ?? limit ?? 10,
|
||||
total: data?.total ?? 0,
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import SearchField from '@/components/SearchField.vue';
|
||||
import SearchPanel from '@/components/SearchPanel.vue';
|
||||
import { OrderService } from '@/service/OrderService';
|
||||
import { TenantService } from '@/service/TenantService';
|
||||
import { UserService } from '@/service/UserService';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
@@ -18,6 +19,27 @@ const user = ref(null);
|
||||
const wallet = ref(null);
|
||||
const walletLoading = ref(false);
|
||||
|
||||
const realName = ref(null);
|
||||
const realNameLoading = ref(false);
|
||||
|
||||
const notifications = ref([]);
|
||||
const notificationsLoading = ref(false);
|
||||
const notificationsTotal = ref(0);
|
||||
const notificationsPage = ref(1);
|
||||
const notificationsRows = ref(10);
|
||||
|
||||
const coupons = ref([]);
|
||||
const couponsLoading = ref(false);
|
||||
const couponsTotal = ref(0);
|
||||
const couponsPage = ref(1);
|
||||
const couponsRows = ref(10);
|
||||
|
||||
const rechargeOrders = ref([]);
|
||||
const rechargeOrdersLoading = ref(false);
|
||||
const rechargeOrdersTotal = ref(0);
|
||||
const rechargeOrdersPage = ref(1);
|
||||
const rechargeOrdersRows = ref(10);
|
||||
|
||||
const tabValue = ref('owned');
|
||||
|
||||
function formatDate(value) {
|
||||
@@ -69,6 +91,42 @@ function getStatusSeverity(status) {
|
||||
}
|
||||
}
|
||||
|
||||
function getOrderStatusSeverity(value) {
|
||||
switch (value) {
|
||||
case 'paid':
|
||||
return 'success';
|
||||
case 'created':
|
||||
case 'refunding':
|
||||
return 'warn';
|
||||
case 'failed':
|
||||
case 'canceled':
|
||||
return 'danger';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function getNotificationReadSeverity(value) {
|
||||
return value ? 'secondary' : 'warn';
|
||||
}
|
||||
|
||||
function getUserCouponSeverity(value) {
|
||||
switch (value) {
|
||||
case 'unused':
|
||||
return 'success';
|
||||
case 'used':
|
||||
return 'secondary';
|
||||
case 'expired':
|
||||
return 'danger';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function getRealNameSeverity(value) {
|
||||
return value ? 'success' : 'secondary';
|
||||
}
|
||||
|
||||
function hasRole(role) {
|
||||
const roles = user.value?.roles || [];
|
||||
return Array.isArray(roles) && roles.includes(role);
|
||||
@@ -100,6 +158,77 @@ async function loadWallet() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRealName() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
realNameLoading.value = true;
|
||||
try {
|
||||
realName.value = await UserService.getUserRealName(id);
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载实名认证信息', life: 4000 });
|
||||
} finally {
|
||||
realNameLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNotifications() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
notificationsLoading.value = true;
|
||||
try {
|
||||
const result = await UserService.listUserNotifications(id, {
|
||||
page: notificationsPage.value,
|
||||
limit: notificationsRows.value
|
||||
});
|
||||
notifications.value = result.items;
|
||||
notificationsTotal.value = result.total;
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载通知列表', life: 4000 });
|
||||
} finally {
|
||||
notificationsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCoupons() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
couponsLoading.value = true;
|
||||
try {
|
||||
const result = await UserService.listUserCoupons(id, {
|
||||
page: couponsPage.value,
|
||||
limit: couponsRows.value
|
||||
});
|
||||
coupons.value = result.items;
|
||||
couponsTotal.value = result.total;
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载优惠券列表', life: 4000 });
|
||||
} finally {
|
||||
couponsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRechargeOrders() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
rechargeOrdersLoading.value = true;
|
||||
try {
|
||||
const result = await OrderService.listOrders({
|
||||
page: rechargeOrdersPage.value,
|
||||
limit: rechargeOrdersRows.value,
|
||||
user_id: id,
|
||||
type: 'recharge',
|
||||
sortField: 'created_at',
|
||||
sortOrder: -1
|
||||
});
|
||||
rechargeOrders.value = result.items;
|
||||
rechargeOrdersTotal.value = result.total;
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载充值记录', life: 4000 });
|
||||
} finally {
|
||||
rechargeOrdersLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const statusDialogVisible = ref(false);
|
||||
const statusLoading = ref(false);
|
||||
const statusOptionsLoading = ref(false);
|
||||
@@ -266,15 +395,40 @@ function onJoinedTenantsPage(event) {
|
||||
loadJoinedTenants();
|
||||
}
|
||||
|
||||
function onNotificationsPage(event) {
|
||||
notificationsPage.value = (event.page ?? 0) + 1;
|
||||
notificationsRows.value = event.rows ?? notificationsRows.value;
|
||||
loadNotifications();
|
||||
}
|
||||
|
||||
function onCouponsPage(event) {
|
||||
couponsPage.value = (event.page ?? 0) + 1;
|
||||
couponsRows.value = event.rows ?? couponsRows.value;
|
||||
loadCoupons();
|
||||
}
|
||||
|
||||
function onRechargeOrdersPage(event) {
|
||||
rechargeOrdersPage.value = (event.page ?? 0) + 1;
|
||||
rechargeOrdersRows.value = event.rows ?? rechargeOrdersRows.value;
|
||||
loadRechargeOrders();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => userID.value,
|
||||
() => {
|
||||
ownedTenantsPage.value = 1;
|
||||
joinedTenantsPage.value = 1;
|
||||
notificationsPage.value = 1;
|
||||
couponsPage.value = 1;
|
||||
rechargeOrdersPage.value = 1;
|
||||
loadUser();
|
||||
loadWallet();
|
||||
loadRealName();
|
||||
loadOwnedTenants();
|
||||
loadJoinedTenants();
|
||||
loadNotifications();
|
||||
loadCoupons();
|
||||
loadRechargeOrders();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
@@ -343,6 +497,10 @@ onMounted(() => {
|
||||
<Tab value="owned">拥有的租户</Tab>
|
||||
<Tab value="joined">加入的租户</Tab>
|
||||
<Tab value="wallet">钱包</Tab>
|
||||
<Tab value="recharge">充值记录</Tab>
|
||||
<Tab value="coupons">优惠券</Tab>
|
||||
<Tab value="notifications">通知</Tab>
|
||||
<Tab value="realname">实名认证</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel value="owned">
|
||||
@@ -538,6 +696,213 @@ onMounted(() => {
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="recharge">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-color">共 {{ rechargeOrdersTotal }} 条</span>
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadRechargeOrders" :disabled="rechargeOrdersLoading" />
|
||||
</div>
|
||||
<DataTable
|
||||
:value="rechargeOrders"
|
||||
dataKey="id"
|
||||
:loading="rechargeOrdersLoading"
|
||||
lazy
|
||||
:paginator="true"
|
||||
:rows="rechargeOrdersRows"
|
||||
:totalRecords="rechargeOrdersTotal"
|
||||
:first="(rechargeOrdersPage - 1) * rechargeOrdersRows"
|
||||
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||
@page="onRechargeOrdersPage"
|
||||
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||
scrollable
|
||||
scrollHeight="420px"
|
||||
responsiveLayout="scroll"
|
||||
>
|
||||
<Column field="id" header="订单ID" style="min-width: 8rem" />
|
||||
<Column field="status" header="状态" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data?.status_description || data?.status || '-'" :severity="getOrderStatusSeverity(data?.status)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="amount_paid" header="实付金额" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatCny(data.amount_paid) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="租户" style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ data?.tenant?.name || '平台/系统' }}</span>
|
||||
<span class="text-xs text-muted-color">Code: {{ data?.tenant?.code || '-' }} / ID: {{ data?.tenant?.id ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="paid_at" header="支付时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.paid_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="created_at" header="创建时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.created_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="coupons">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-color">共 {{ couponsTotal }} 条</span>
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadCoupons" :disabled="couponsLoading" />
|
||||
</div>
|
||||
<DataTable
|
||||
:value="coupons"
|
||||
dataKey="id"
|
||||
:loading="couponsLoading"
|
||||
lazy
|
||||
:paginator="true"
|
||||
:rows="couponsRows"
|
||||
:totalRecords="couponsTotal"
|
||||
:first="(couponsPage - 1) * couponsRows"
|
||||
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||
@page="onCouponsPage"
|
||||
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||
scrollable
|
||||
scrollHeight="420px"
|
||||
responsiveLayout="scroll"
|
||||
>
|
||||
<Column field="id" header="用户券ID" style="min-width: 8rem" />
|
||||
<Column field="coupon_id" header="券ID" style="min-width: 8rem" />
|
||||
<Column header="租户" style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ data.tenant_name || '-' }}</span>
|
||||
<span class="text-xs text-muted-color">Code: {{ data.tenant_code || '-' }} / ID: {{ data.tenant_id ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="title" header="标题" style="min-width: 16rem" />
|
||||
<Column header="类型" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
{{ data.type_description || data.type || '-' }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="value" header="面额/折扣" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<span v-if="data.type === 'discount'">{{ data.value ?? '-' }}%</span>
|
||||
<span v-else>{{ formatCny(data.value) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="status" header="状态" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data.status_description || data.status || '-'" :severity="getUserCouponSeverity(data.status)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="order_id" header="使用订单" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
{{ data.order_id ? data.order_id : '-' }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="used_at" header="使用时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.used_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="created_at" header="领取时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.created_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="notifications">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-color">共 {{ notificationsTotal }} 条</span>
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadNotifications" :disabled="notificationsLoading" />
|
||||
</div>
|
||||
<DataTable
|
||||
:value="notifications"
|
||||
dataKey="id"
|
||||
:loading="notificationsLoading"
|
||||
lazy
|
||||
:paginator="true"
|
||||
:rows="notificationsRows"
|
||||
:totalRecords="notificationsTotal"
|
||||
:first="(notificationsPage - 1) * notificationsRows"
|
||||
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||
@page="onNotificationsPage"
|
||||
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||
scrollable
|
||||
scrollHeight="420px"
|
||||
responsiveLayout="scroll"
|
||||
>
|
||||
<Column field="id" header="通知ID" style="min-width: 8rem" />
|
||||
<Column header="租户" style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ data.tenant_name || '平台/系统' }}</span>
|
||||
<span class="text-xs text-muted-color">Code: {{ data.tenant_code || '-' }} / ID: {{ data.tenant_id ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="type" header="类型" style="min-width: 10rem" />
|
||||
<Column field="title" header="标题" style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<span class="block max-w-[240px] truncate">{{ data.title || '-' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="content" header="内容" style="min-width: 20rem">
|
||||
<template #body="{ data }">
|
||||
<span class="block max-w-[320px] truncate">{{ data.content || '-' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="read" header="状态" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data.read ? '已读' : '未读'" :severity="getNotificationReadSeverity(data.read)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="created_at" header="发送时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.created_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="realname">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Tag :value="realName?.is_real_name_verified ? '已认证' : '未认证'" :severity="getRealNameSeverity(realName?.is_real_name_verified)" />
|
||||
<span class="text-muted-color">实名认证信息</span>
|
||||
</div>
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadRealName" :disabled="realNameLoading" />
|
||||
</div>
|
||||
<div v-if="realNameLoading" class="flex items-center justify-center py-10">
|
||||
<ProgressSpinner style="width: 36px; height: 36px" strokeWidth="6" />
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-12 gap-3">
|
||||
<div class="col-span-12 md:col-span-4">
|
||||
<div class="text-sm text-muted-color">真实姓名</div>
|
||||
<div class="font-medium">{{ realName?.real_name || '-' }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4">
|
||||
<div class="text-sm text-muted-color">身份证号</div>
|
||||
<div class="font-medium">{{ realName?.id_card_masked || '-' }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4">
|
||||
<div class="text-sm text-muted-color">认证时间</div>
|
||||
<div class="font-medium">{{ formatDate(realName?.verified_at) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user