feat: add TenantDetail and UserDetail views with comprehensive functionality

- Implemented TenantDetail.vue to display tenant information, manage tenant status, and handle tenant renewals.
- Added user management features in UserDetail.vue, including user status updates and role management.
- Integrated data loading for tenant users and orders in TenantDetail.vue.
- Included search and pagination functionalities for owned and joined tenants in UserDetail.vue.
- Enhanced user experience with toast notifications for success and error messages.
This commit is contained in:
2025-12-24 15:10:49 +08:00
parent 40776b78e2
commit 8fa321dbf6
18 changed files with 4106 additions and 190 deletions

View File

@@ -82,6 +82,11 @@ func (r *Routes) Register(router fiber.Router) {
r.tenant.list,
Query[dto.TenantFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID<int> -> tenant.detail")
router.Get("/super/v1/tenants/:tenantID<int>"[len(r.Path()):], DataFunc1(
r.tenant.detail,
PathParam[int64]("tenantID"),
))
r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID<int>/users -> tenant.users")
router.Get("/super/v1/tenants/:tenantID<int>/users"[len(r.Path()):], DataFunc2(
r.tenant.users,
@@ -115,6 +120,11 @@ func (r *Routes) Register(router fiber.Router) {
r.user.list,
Query[dto.UserPageFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/users/:userID<int> -> user.detail")
router.Get("/super/v1/users/:userID<int>"[len(r.Path()):], DataFunc1(
r.user.detail,
PathParam[int64]("userID"),
))
r.log.Debugf("Registering route: Get /super/v1/users/:userID<int>/tenants -> user.tenants")
router.Get("/super/v1/users/:userID<int>/tenants"[len(r.Path()):], DataFunc2(
r.user.tenants,

View File

@@ -15,6 +15,21 @@ import (
// @provider
type tenant struct{}
// detail
//
// @Summary 租户详情
// @Tags Super
// @Accept json
// @Produce json
// @Param tenantID path int64 true "TenantID"
// @Success 200 {object} dto.TenantItem
//
// @Router /super/v1/tenants/:tenantID<int> [get]
// @Bind tenantID path
func (*tenant) detail(ctx fiber.Ctx, tenantID int64) (*dto.TenantItem, error) {
return services.Tenant.SuperDetail(ctx, tenantID)
}
// list
//
// @Summary 租户列表

View File

@@ -13,6 +13,21 @@ import (
// @provider
type user struct{}
// detail
//
// @Summary 用户详情
// @Tags Super
// @Accept json
// @Produce json
// @Param userID path int64 true "UserID"
// @Success 200 {object} dto.UserItem
//
// @Router /super/v1/users/:userID<int> [get]
// @Bind userID path
func (*user) detail(ctx fiber.Ctx, userID int64) (*dto.UserItem, error) {
return services.User.Detail(ctx, userID)
}
// list
//
// @Summary 用户列表

View File

@@ -26,6 +26,49 @@ import (
// @provider
type tenant struct{}
// SuperDetail 查询单个租户详情(平台侧)。
func (t *tenant) SuperDetail(ctx context.Context, tenantID int64) (*superdto.TenantItem, error) {
if tenantID <= 0 {
return nil, errors.New("tenant_id must be > 0")
}
tbl, query := models.TenantQuery.QueryContext(ctx)
m, err := query.Where(tbl.ID.Eq(tenantID)).First()
if err != nil {
return nil, err
}
userCountMapping, err := t.TenantUserCountMapping(ctx, []int64{m.ID})
if err != nil {
return nil, err
}
incomeMapping, err := t.TenantIncomePaidMapping(ctx, []int64{m.ID})
if err != nil {
return nil, err
}
item := &superdto.TenantItem{
Tenant: m,
UserCount: lo.ValueOr(userCountMapping, m.ID, 0),
IncomeAmountPaidSum: lo.ValueOr(incomeMapping, m.ID, 0),
StatusDescription: m.Status.Description(),
}
ownerMapping, err := t.TenantOwnerUserMapping(ctx, []*models.Tenant{m})
if err != nil {
return nil, err
}
item.Owner = ownerMapping[m.ID]
adminMapping, err := t.TenantAdminUsersMapping(ctx, []int64{m.ID})
if err != nil {
return nil, err
}
item.AdminUsers = adminMapping[m.ID]
return item, nil
}
// SuperCreateTenant 超级管理员创建租户,并将指定用户设为租户管理员。
func (t *tenant) SuperCreateTenant(ctx context.Context, form *superdto.TenantCreateForm) (*models.Tenant, error) {
if form == nil {

View File

@@ -41,6 +41,42 @@ func (t *user) FindByUsername(ctx context.Context, username string) (*models.Use
return model, nil
}
// Detail 查询用户详情(超级管理员侧,返回脱敏后的 DTO
func (t *user) Detail(ctx context.Context, userID int64) (*dto.UserItem, error) {
if userID <= 0 {
return nil, errors.New("user_id must be > 0")
}
model, err := t.FindByID(ctx, userID)
if err != nil {
return nil, err
}
ownedTenantCounts, err := t.UserOwnedTenantCountMapping(ctx, []int64{model.ID})
if err != nil {
return nil, err
}
joinedTenantCounts, err := t.UserJoinedTenantCountMapping(ctx, []int64{model.ID})
if err != nil {
return nil, err
}
return &dto.UserItem{
ID: model.ID,
Username: model.Username,
Roles: model.Roles,
Status: model.Status,
StatusDescription: model.Status.Description(),
Balance: model.Balance,
BalanceFrozen: model.BalanceFrozen,
VerifiedAt: model.VerifiedAt,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
OwnedTenantCount: ownedTenantCounts[model.ID],
JoinedTenantCount: joinedTenantCounts[model.ID],
}, nil
}
func (t *user) Create(ctx context.Context, user *models.User) (*models.User, error) {
if err := user.Create(ctx); err != nil {
return nil, errors.Wrapf(err, "Create user failed, %s", user.Username)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -585,6 +585,44 @@ definitions:
description: TypeDescription 流水类型中文说明(用于前端展示)。
type: string
type: object
dto.OrderBuyerLite:
properties:
id:
type: integer
username:
type: string
type: object
dto.OrderStatisticsResponse:
properties:
by_status:
items:
$ref: '#/definitions/dto.OrderStatisticsRow'
type: array
total_amount_paid_sum:
type: integer
total_count:
type: integer
type: object
dto.OrderStatisticsRow:
properties:
amount_paid_sum:
type: integer
count:
type: integer
status:
$ref: '#/definitions/consts.OrderStatus'
status_description:
type: string
type: object
dto.OrderTenantLite:
properties:
code:
type: string
id:
type: integer
name:
type: string
type: object
dto.PurchaseContentForm:
properties:
idempotency_key:
@@ -613,6 +651,119 @@ definitions:
description: Order is the created or existing order record (may be nil for
owner/free-path without order).
type: object
dto.SuperOrderDetail:
properties:
buyer:
$ref: '#/definitions/dto.OrderBuyerLite'
order:
$ref: '#/definitions/models.Order'
tenant:
$ref: '#/definitions/dto.OrderTenantLite'
type: object
dto.SuperOrderItem:
properties:
amount_discount:
type: integer
amount_original:
type: integer
amount_paid:
type: integer
buyer:
$ref: '#/definitions/dto.OrderBuyerLite'
created_at:
type: string
currency:
$ref: '#/definitions/consts.Currency'
id:
type: integer
paid_at:
type: string
refunded_at:
type: string
status:
$ref: '#/definitions/consts.OrderStatus'
status_description:
type: string
tenant:
$ref: '#/definitions/dto.OrderTenantLite'
type:
$ref: '#/definitions/consts.OrderType'
updated_at:
type: string
type: object
dto.SuperOrderRefundForm:
properties:
force:
description: Force indicates bypassing the default refund window check (paid_at
+ 24h).
type: boolean
idempotency_key:
description: IdempotencyKey ensures refund request is processed at most once.
type: string
reason:
description: Reason is the human-readable refund reason used for audit.
type: string
type: object
dto.SuperTenantUserItem:
properties:
tenant_user:
$ref: '#/definitions/models.TenantUser'
user:
$ref: '#/definitions/dto.SuperUserLite'
type: object
dto.SuperUserLite:
properties:
created_at:
type: string
id:
type: integer
roles:
items:
$ref: '#/definitions/consts.Role'
type: array
status:
$ref: '#/definitions/consts.UserStatus'
status_description:
type: string
updated_at:
type: string
username:
type: string
verified_at:
type: string
type: object
dto.TenantAdminUserLite:
properties:
id:
type: integer
username:
type: string
type: object
dto.TenantCreateForm:
properties:
admin_user_id:
type: integer
code:
maxLength: 64
type: string
duration:
description: Duration 租户有效期(天),从“创建时刻”起算;与续期接口保持一致。
enum:
- 7
- 30
- 90
- 180
- 365
type: integer
name:
maxLength: 128
type: string
required:
- admin_user_id
- code
- duration
- name
type: object
dto.TenantExpireUpdateForm:
properties:
duration:
@@ -628,6 +779,10 @@ definitions:
type: object
dto.TenantItem:
properties:
admin_users:
items:
$ref: '#/definitions/dto.TenantAdminUserLite'
type: array
code:
type: string
config:
@@ -640,16 +795,19 @@ definitions:
type: string
id:
type: integer
income_amount_paid_sum:
description: IncomeAmountPaidSum 累计收入金额单位CNY按 orders 聚合得到的已支付净收入(不含退款中/已退款订单)。
type: integer
name:
type: string
owner:
$ref: '#/definitions/dto.TenantOwnerUserLite'
status:
$ref: '#/definitions/consts.TenantStatus'
status_description:
type: string
updated_at:
type: string
user_balance:
type: integer
user_count:
type: integer
user_id:
@@ -661,6 +819,13 @@ definitions:
uuid:
type: string
type: object
dto.TenantOwnerUserLite:
properties:
id:
type: integer
username:
type: string
type: object
dto.TenantStatusUpdateForm:
properties:
status:
@@ -675,25 +840,17 @@ definitions:
dto.UserItem:
properties:
balance:
description: 全局可用余额:分/最小货币单位;用户在所有已加入租户内共享该余额;默认 0
type: integer
balance_frozen:
description: 全局冻结余额:分/最小货币单位;用于下单冻结等;默认 0
type: integer
created_at:
type: string
deleted_at:
$ref: '#/definitions/gorm.DeletedAt'
id:
type: integer
metas:
items:
type: integer
type: array
owned:
$ref: '#/definitions/models.Tenant'
password:
type: string
joined_tenant_count:
type: integer
owned_tenant_count:
type: integer
roles:
items:
$ref: '#/definitions/consts.Role'
@@ -702,10 +859,6 @@ definitions:
$ref: '#/definitions/consts.UserStatus'
status_description:
type: string
tenants:
items:
$ref: '#/definitions/models.Tenant'
type: array
updated_at:
type: string
username:
@@ -713,6 +866,16 @@ definitions:
verified_at:
type: string
type: object
dto.UserRolesUpdateForm:
properties:
roles:
items:
$ref: '#/definitions/consts.Role'
minItems: 1
type: array
required:
- roles
type: object
dto.UserStatistics:
properties:
count:
@@ -733,6 +896,33 @@ definitions:
required:
- status
type: object
dto.UserTenantItem:
properties:
code:
type: string
expired_at:
type: string
joined_at:
type: string
member_status:
$ref: '#/definitions/consts.UserStatus'
member_status_description:
type: string
name:
type: string
owner:
$ref: '#/definitions/dto.TenantOwnerUserLite'
role:
items:
$ref: '#/definitions/consts.TenantUserRole'
type: array
tenant_id:
type: integer
tenant_status:
$ref: '#/definitions/consts.TenantStatus'
tenant_status_description:
type: string
type: object
gorm.DeletedAt:
properties:
time:
@@ -1342,6 +1532,173 @@ paths:
$ref: '#/definitions/dto.LoginResponse'
tags:
- Super
/super/v1/orders:
get:
consumes:
- application/json
parameters:
- in: query
name: amount_paid_max
type: integer
- in: query
name: amount_paid_min
type: integer
- description: Asc specifies comma-separated field names to sort ascending by.
in: query
name: asc
type: string
- in: query
name: content_id
type: integer
- in: query
name: content_title
type: string
- in: query
name: created_at_from
type: string
- in: query
name: created_at_to
type: string
- description: Desc specifies comma-separated field names to sort descending
by.
in: query
name: desc
type: string
- in: query
name: id
type: integer
- description: Limit is page size; only values in {10,20,50,100} are accepted
(otherwise defaults to 10).
in: query
name: limit
type: integer
- description: Page is 1-based page index; values <= 0 are normalized to 1.
in: query
name: page
type: integer
- in: query
name: paid_at_from
type: string
- in: query
name: paid_at_to
type: string
- enum:
- created
- paid
- refunding
- refunded
- canceled
- failed
in: query
name: status
type: string
x-enum-varnames:
- OrderStatusCreated
- OrderStatusPaid
- OrderStatusRefunding
- OrderStatusRefunded
- OrderStatusCanceled
- OrderStatusFailed
- in: query
name: tenant_code
type: string
- in: query
name: tenant_id
type: integer
- in: query
name: tenant_name
type: string
- enum:
- content_purchase
in: query
name: type
type: string
x-enum-varnames:
- OrderTypeContentPurchase
- in: query
name: user_id
type: integer
- in: query
name: username
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
$ref: '#/definitions/dto.SuperOrderItem'
type: object
summary: 订单列表
tags:
- Super
/super/v1/orders/{orderID}:
get:
consumes:
- application/json
parameters:
- description: OrderID
format: int64
in: path
name: orderID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SuperOrderDetail'
summary: 订单详情
tags:
- Super
/super/v1/orders/{orderID}/refund:
post:
consumes:
- application/json
description: 该接口只负责将订单从 paid 推进到 refunding并提交异步退款任务退款入账与权益回收由 worker 异步完成。
parameters:
- description: OrderID
format: int64
in: path
name: orderID
required: true
type: integer
- description: Form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperOrderRefundForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.Order'
summary: 订单退款(平台)
tags:
- Super
/super/v1/orders/statistics:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.OrderStatisticsResponse'
summary: 订单统计信息
tags:
- Super
/super/v1/tenants:
get:
consumes:
@@ -1351,11 +1708,29 @@ paths:
in: query
name: asc
type: string
- in: query
name: code
type: string
- in: query
name: created_at_from
type: string
- in: query
name: created_at_to
type: string
- description: Desc specifies comma-separated field names to sort descending
by.
in: query
name: desc
type: string
- in: query
name: expired_at_from
type: string
- in: query
name: expired_at_to
type: string
- in: query
name: id
type: integer
- description: Limit is page size; only values in {10,20,50,100} are accepted
(otherwise defaults to 10).
in: query
@@ -1379,6 +1754,9 @@ paths:
- TenantStatusPendingVerify
- TenantStatusVerified
- TenantStatusBanned
- in: query
name: user_id
type: integer
produces:
- application/json
responses:
@@ -1394,7 +1772,47 @@ paths:
summary: 租户列表
tags:
- Super
post:
consumes:
- application/json
parameters:
- description: Form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.TenantCreateForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.Tenant'
summary: 创建租户并设置租户管理员
tags:
- Super
/super/v1/tenants/{tenantID}:
get:
consumes:
- application/json
parameters:
- description: TenantID
format: int64
in: path
name: tenantID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.TenantItem'
summary: 租户详情
tags:
- Super
patch:
consumes:
- application/json
@@ -1440,6 +1858,71 @@ paths:
summary: 更新租户状态
tags:
- Super
/super/v1/tenants/{tenantID}/users:
get:
consumes:
- application/json
parameters:
- description: TenantID
format: int64
in: path
name: tenantID
required: true
type: integer
- description: Limit is page size; only values in {10,20,50,100} are accepted
(otherwise defaults to 10).
in: query
name: limit
type: integer
- description: Page is 1-based page index; values <= 0 are normalized to 1.
in: query
name: page
type: integer
- description: Role 按角色过滤可选member/tenant_admin。
enum:
- member
- tenant_admin
in: query
name: role
type: string
x-enum-varnames:
- TenantUserRoleMember
- TenantUserRoleTenantAdmin
- description: Status 按成员状态过滤可选pending_verify/verified/banned。
enum:
- pending_verify
- verified
- banned
in: query
name: status
type: string
x-enum-varnames:
- UserStatusPendingVerify
- UserStatusVerified
- UserStatusBanned
- description: UserID 按用户ID过滤可选
in: query
name: user_id
type: integer
- description: Username 按用户名模糊查询(可选,支持包含匹配)。
in: query
name: username
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
$ref: '#/definitions/dto.SuperTenantUserItem'
type: object
summary: 租户成员列表(平台侧)
tags:
- Super
/super/v1/tenants/statuses:
get:
consumes:
@@ -1465,11 +1948,20 @@ paths:
in: query
name: asc
type: string
- in: query
name: created_at_from
type: string
- in: query
name: created_at_to
type: string
- description: Desc specifies comma-separated field names to sort descending
by.
in: query
name: desc
type: string
- in: query
name: id
type: integer
- description: Limit is page size; only values in {10,20,50,100} are accepted
(otherwise defaults to 10).
in: query
@@ -1479,6 +1971,16 @@ paths:
in: query
name: page
type: integer
- description: Role filters users containing a role (user/super_admin).
enum:
- user
- super_admin
in: query
name: role
type: string
x-enum-varnames:
- RoleUser
- RoleSuperAdmin
- enum:
- pending_verify
- verified
@@ -1490,12 +1992,19 @@ paths:
- UserStatusPendingVerify
- UserStatusVerified
- UserStatusBanned
- in: query
name: tenantID
- description: TenantID filters users by membership in the given tenant.
in: query
name: tenant_id
type: integer
- in: query
name: username
type: string
- in: query
name: verified_at_from
type: string
- in: query
name: verified_at_to
type: string
produces:
- application/json
responses:
@@ -1508,7 +2017,51 @@ paths:
items:
$ref: '#/definitions/dto.UserItem'
type: object
summary: 户列表
summary: 户列表
tags:
- Super
/super/v1/users/{userID}:
get:
consumes:
- application/json
parameters:
- description: UserID
format: int64
in: path
name: userID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.UserItem'
summary: 用户详情
tags:
- Super
/super/v1/users/{userID}/roles:
patch:
consumes:
- application/json
parameters:
- description: UserID
format: int64
in: path
name: userID
required: true
type: integer
- description: Form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.UserRolesUpdateForm'
produces:
- application/json
responses: {}
summary: 更新用户角色
tags:
- Super
/super/v1/users/{userID}/status:
@@ -1534,6 +2087,78 @@ paths:
summary: 更新用户状态
tags:
- Super
/super/v1/users/{userID}/tenants:
get:
consumes:
- application/json
parameters:
- description: UserID
format: int64
in: path
name: userID
required: true
type: integer
- in: query
name: code
type: string
- in: query
name: created_at_from
type: string
- in: query
name: created_at_to
type: string
- description: Limit is page size; only values in {10,20,50,100} are accepted
(otherwise defaults to 10).
in: query
name: limit
type: integer
- in: query
name: name
type: string
- description: Page is 1-based page index; values <= 0 are normalized to 1.
in: query
name: page
type: integer
- description: Role filters tenant_users.role containing a role (tenant_admin/member).
enum:
- member
- tenant_admin
in: query
name: role
type: string
x-enum-varnames:
- TenantUserRoleMember
- TenantUserRoleTenantAdmin
- description: Status filters tenant_users.status.
enum:
- pending_verify
- verified
- banned
in: query
name: status
type: string
x-enum-varnames:
- UserStatusPendingVerify
- UserStatusVerified
- UserStatusBanned
- in: query
name: tenant_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
$ref: '#/definitions/dto.UserTenantItem'
type: object
summary: 用户加入的租户列表
tags:
- Super
/super/v1/users/statistics:
get:
consumes: