From 8fa321dbf6352d7fd8642f41a337d38b30ba5ac2 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 24 Dec 2025 15:10:49 +0800 Subject: [PATCH] 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. --- backend/app/http/super/routes.gen.go | 10 + backend/app/http/super/tenant.go | 15 + backend/app/http/super/user.go | 15 + backend/app/services/tenant.go | 43 + backend/app/services/user.go | 36 + backend/docs/docs.go | 1005 ++++++++++++++++- backend/docs/swagger.json | 1005 ++++++++++++++++- backend/docs/swagger.yaml | 667 ++++++++++- frontend/superadmin/dist/index.html | 4 +- frontend/superadmin/src/router/index.js | 15 + .../superadmin/src/service/TenantService.js | 4 + .../superadmin/src/service/UserService.js | 4 + .../src/views/superadmin/OrderDetail.vue | 197 ++++ .../src/views/superadmin/Orders.vue | 115 +- .../src/views/superadmin/TenantDetail.vue | 609 ++++++++++ .../src/views/superadmin/Tenants.vue | 14 +- .../src/views/superadmin/UserDetail.vue | 524 +++++++++ .../superadmin/src/views/superadmin/Users.vue | 14 +- 18 files changed, 4106 insertions(+), 190 deletions(-) create mode 100644 frontend/superadmin/src/views/superadmin/OrderDetail.vue create mode 100644 frontend/superadmin/src/views/superadmin/TenantDetail.vue create mode 100644 frontend/superadmin/src/views/superadmin/UserDetail.vue diff --git a/backend/app/http/super/routes.gen.go b/backend/app/http/super/routes.gen.go index 6ee4c36..ebd1ddd 100644 --- a/backend/app/http/super/routes.gen.go +++ b/backend/app/http/super/routes.gen.go @@ -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 -> tenant.detail") + router.Get("/super/v1/tenants/:tenantID"[len(r.Path()):], DataFunc1( + r.tenant.detail, + PathParam[int64]("tenantID"), + )) r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID/users -> tenant.users") router.Get("/super/v1/tenants/:tenantID/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 -> user.detail") + router.Get("/super/v1/users/:userID"[len(r.Path()):], DataFunc1( + r.user.detail, + PathParam[int64]("userID"), + )) r.log.Debugf("Registering route: Get /super/v1/users/:userID/tenants -> user.tenants") router.Get("/super/v1/users/:userID/tenants"[len(r.Path()):], DataFunc2( r.user.tenants, diff --git a/backend/app/http/super/tenant.go b/backend/app/http/super/tenant.go index 44a9bae..9110d25 100644 --- a/backend/app/http/super/tenant.go +++ b/backend/app/http/super/tenant.go @@ -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 [get] +// @Bind tenantID path +func (*tenant) detail(ctx fiber.Ctx, tenantID int64) (*dto.TenantItem, error) { + return services.Tenant.SuperDetail(ctx, tenantID) +} + // list // // @Summary 租户列表 diff --git a/backend/app/http/super/user.go b/backend/app/http/super/user.go index 573f60a..cb45448 100644 --- a/backend/app/http/super/user.go +++ b/backend/app/http/super/user.go @@ -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 [get] +// @Bind userID path +func (*user) detail(ctx fiber.Ctx, userID int64) (*dto.UserItem, error) { + return services.User.Detail(ctx, userID) +} + // list // // @Summary 用户列表 diff --git a/backend/app/services/tenant.go b/backend/app/services/tenant.go index c0bd841..063db3c 100644 --- a/backend/app/services/tenant.go +++ b/backend/app/services/tenant.go @@ -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 { diff --git a/backend/app/services/user.go b/backend/app/services/user.go index 1547c31..372129f 100644 --- a/backend/app/services/user.go +++ b/backend/app/services/user.go @@ -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) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 9e5bcd1..99ea164 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -77,6 +77,264 @@ const docTemplate = `{ } } }, + "/super/v1/orders": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单列表", + "parameters": [ + { + "type": "integer", + "name": "amount_paid_max", + "in": "query" + }, + { + "type": "integer", + "name": "amount_paid_min", + "in": "query" + }, + { + "type": "string", + "description": "Asc specifies comma-separated field names to sort ascending by.", + "name": "asc", + "in": "query" + }, + { + "type": "integer", + "name": "content_id", + "in": "query" + }, + { + "type": "string", + "name": "content_title", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Desc specifies comma-separated field names to sort descending by.", + "name": "desc", + "in": "query" + }, + { + "type": "integer", + "name": "id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "type": "string", + "name": "paid_at_from", + "in": "query" + }, + { + "type": "string", + "name": "paid_at_to", + "in": "query" + }, + { + "enum": [ + "created", + "paid", + "refunding", + "refunded", + "canceled", + "failed" + ], + "type": "string", + "x-enum-varnames": [ + "OrderStatusCreated", + "OrderStatusPaid", + "OrderStatusRefunding", + "OrderStatusRefunded", + "OrderStatusCanceled", + "OrderStatusFailed" + ], + "name": "status", + "in": "query" + }, + { + "type": "string", + "name": "tenant_code", + "in": "query" + }, + { + "type": "integer", + "name": "tenant_id", + "in": "query" + }, + { + "type": "string", + "name": "tenant_name", + "in": "query" + }, + { + "enum": [ + "content_purchase" + ], + "type": "string", + "x-enum-varnames": [ + "OrderTypeContentPurchase" + ], + "name": "type", + "in": "query" + }, + { + "type": "integer", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.SuperOrderItem" + } + } + } + ] + } + } + } + } + }, + "/super/v1/orders/statistics": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单统计信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OrderStatisticsResponse" + } + } + } + } + }, + "/super/v1/orders/{orderID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "OrderID", + "name": "orderID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SuperOrderDetail" + } + } + } + } + }, + "/super/v1/orders/{orderID}/refund": { + "post": { + "description": "该接口只负责将订单从 paid 推进到 refunding,并提交异步退款任务;退款入账与权益回收由 worker 异步完成。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单退款(平台)", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "OrderID", + "name": "orderID", + "in": "path", + "required": true + }, + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SuperOrderRefundForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Order" + } + } + } + } + }, "/super/v1/tenants": { "get": { "consumes": [ @@ -96,12 +354,42 @@ const docTemplate = `{ "name": "asc", "in": "query" }, + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, { "type": "string", "description": "Desc specifies comma-separated field names to sort descending by.", "name": "desc", "in": "query" }, + { + "type": "string", + "name": "expired_at_from", + "in": "query" + }, + { + "type": "string", + "name": "expired_at_to", + "in": "query" + }, + { + "type": "integer", + "name": "id", + "in": "query" + }, { "type": "integer", "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", @@ -133,6 +421,11 @@ const docTemplate = `{ ], "name": "status", "in": "query" + }, + { + "type": "integer", + "name": "user_id", + "in": "query" } ], "responses": { @@ -155,6 +448,37 @@ const docTemplate = `{ } } } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "创建租户并设置租户管理员", + "parameters": [ + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TenantCreateForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tenant" + } + } + } } }, "/super/v1/tenants/statuses": { @@ -183,6 +507,36 @@ const docTemplate = `{ } }, "/super/v1/tenants/{tenantID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "TenantID", + "name": "tenantID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.TenantItem" + } + } + } + }, "patch": { "consumes": [ "application/json" @@ -250,6 +604,104 @@ const docTemplate = `{ "responses": {} } }, + "/super/v1/tenants/{tenantID}/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户成员列表(平台侧)", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "TenantID", + "name": "tenantID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "enum": [ + "member", + "tenant_admin" + ], + "type": "string", + "x-enum-varnames": [ + "TenantUserRoleMember", + "TenantUserRoleTenantAdmin" + ], + "description": "Role 按角色过滤(可选):member/tenant_admin。", + "name": "role", + "in": "query" + }, + { + "enum": [ + "pending_verify", + "verified", + "banned" + ], + "type": "string", + "x-enum-varnames": [ + "UserStatusPendingVerify", + "UserStatusVerified", + "UserStatusBanned" + ], + "description": "Status 按成员状态过滤(可选):pending_verify/verified/banned。", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "UserID 按用户ID过滤(可选)。", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Username 按用户名模糊查询(可选,支持包含匹配)。", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.SuperTenantUserItem" + } + } + } + ] + } + } + } + } + }, "/super/v1/users": { "get": { "consumes": [ @@ -261,7 +713,7 @@ const docTemplate = `{ "tags": [ "Super" ], - "summary": "租户列表", + "summary": "用户列表", "parameters": [ { "type": "string", @@ -269,12 +721,27 @@ const docTemplate = `{ "name": "asc", "in": "query" }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, { "type": "string", "description": "Desc specifies comma-separated field names to sort descending by.", "name": "desc", "in": "query" }, + { + "type": "integer", + "name": "id", + "in": "query" + }, { "type": "integer", "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", @@ -287,6 +754,20 @@ const docTemplate = `{ "name": "page", "in": "query" }, + { + "enum": [ + "user", + "super_admin" + ], + "type": "string", + "x-enum-varnames": [ + "RoleUser", + "RoleSuperAdmin" + ], + "description": "Role filters users containing a role (user/super_admin).", + "name": "role", + "in": "query" + }, { "enum": [ "pending_verify", @@ -304,13 +785,24 @@ const docTemplate = `{ }, { "type": "integer", - "name": "tenantID", + "description": "TenantID filters users by membership in the given tenant.", + "name": "tenant_id", "in": "query" }, { "type": "string", "name": "username", "in": "query" + }, + { + "type": "string", + "name": "verified_at_from", + "in": "query" + }, + { + "type": "string", + "name": "verified_at_to", + "in": "query" } ], "responses": { @@ -385,6 +877,72 @@ const docTemplate = `{ } } }, + "/super/v1/users/{userID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "用户详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserItem" + } + } + } + } + }, + "/super/v1/users/{userID}/roles": { + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "更新用户角色", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + }, + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UserRolesUpdateForm" + } + } + ], + "responses": {} + } + }, "/super/v1/users/{userID}/status": { "patch": { "consumes": [ @@ -419,6 +977,117 @@ const docTemplate = `{ "responses": {} } }, + "/super/v1/users/{userID}/tenants": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "用户加入的租户列表", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "enum": [ + "member", + "tenant_admin" + ], + "type": "string", + "x-enum-varnames": [ + "TenantUserRoleMember", + "TenantUserRoleTenantAdmin" + ], + "description": "Role filters tenant_users.role containing a role (tenant_admin/member).", + "name": "role", + "in": "query" + }, + { + "enum": [ + "pending_verify", + "verified", + "banned" + ], + "type": "string", + "x-enum-varnames": [ + "UserStatusPendingVerify", + "UserStatusVerified", + "UserStatusBanned" + ], + "description": "Status filters tenant_users.status.", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "name": "tenant_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.UserTenantItem" + } + } + } + ] + } + } + } + } + }, "/t/{tenantCode}/v1/admin/contents": { "post": { "consumes": [ @@ -3516,6 +4185,65 @@ const docTemplate = `{ } } }, + "dto.OrderBuyerLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.OrderStatisticsResponse": { + "type": "object", + "properties": { + "by_status": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.OrderStatisticsRow" + } + }, + "total_amount_paid_sum": { + "type": "integer" + }, + "total_count": { + "type": "integer" + } + } + }, + "dto.OrderStatisticsRow": { + "type": "object", + "properties": { + "amount_paid_sum": { + "type": "integer" + }, + "count": { + "type": "integer" + }, + "status": { + "$ref": "#/definitions/consts.OrderStatus" + }, + "status_description": { + "type": "string" + } + } + }, + "dto.OrderTenantLite": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "dto.PurchaseContentForm": { "type": "object", "properties": { @@ -3558,6 +4286,171 @@ const docTemplate = `{ } } }, + "dto.SuperOrderDetail": { + "type": "object", + "properties": { + "buyer": { + "$ref": "#/definitions/dto.OrderBuyerLite" + }, + "order": { + "$ref": "#/definitions/models.Order" + }, + "tenant": { + "$ref": "#/definitions/dto.OrderTenantLite" + } + } + }, + "dto.SuperOrderItem": { + "type": "object", + "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" + } + } + }, + "dto.SuperOrderRefundForm": { + "type": "object", + "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" + } + } + }, + "dto.SuperTenantUserItem": { + "type": "object", + "properties": { + "tenant_user": { + "$ref": "#/definitions/models.TenantUser" + }, + "user": { + "$ref": "#/definitions/dto.SuperUserLite" + } + } + }, + "dto.SuperUserLite": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.Role" + } + }, + "status": { + "$ref": "#/definitions/consts.UserStatus" + }, + "status_description": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "verified_at": { + "type": "string" + } + } + }, + "dto.TenantAdminUserLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.TenantCreateForm": { + "type": "object", + "required": [ + "admin_user_id", + "code", + "duration", + "name" + ], + "properties": { + "admin_user_id": { + "type": "integer" + }, + "code": { + "type": "string", + "maxLength": 64 + }, + "duration": { + "description": "Duration 租户有效期(天),从“创建时刻”起算;与续期接口保持一致。", + "type": "integer", + "enum": [ + 7, + 30, + 90, + 180, + 365 + ] + }, + "name": { + "type": "string", + "maxLength": 128 + } + } + }, "dto.TenantExpireUpdateForm": { "type": "object", "required": [ @@ -3579,6 +4472,12 @@ const docTemplate = `{ "dto.TenantItem": { "type": "object", "properties": { + "admin_users": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TenantAdminUserLite" + } + }, "code": { "type": "string" }, @@ -3597,9 +4496,16 @@ const docTemplate = `{ "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" }, @@ -3609,9 +4515,6 @@ const docTemplate = `{ "updated_at": { "type": "string" }, - "user_balance": { - "type": "integer" - }, "user_count": { "type": "integer" }, @@ -3629,6 +4532,17 @@ const docTemplate = `{ } } }, + "dto.TenantOwnerUserLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, "dto.TenantStatusUpdateForm": { "type": "object", "required": [ @@ -3652,33 +4566,22 @@ const docTemplate = `{ "type": "object", "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": { - "type": "array", - "items": { - "type": "integer" - } + "joined_tenant_count": { + "type": "integer" }, - "owned": { - "$ref": "#/definitions/models.Tenant" - }, - "password": { - "type": "string" + "owned_tenant_count": { + "type": "integer" }, "roles": { "type": "array", @@ -3692,12 +4595,6 @@ const docTemplate = `{ "status_description": { "type": "string" }, - "tenants": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Tenant" - } - }, "updated_at": { "type": "string" }, @@ -3709,6 +4606,21 @@ const docTemplate = `{ } } }, + "dto.UserRolesUpdateForm": { + "type": "object", + "required": [ + "roles" + ], + "properties": { + "roles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/consts.Role" + } + } + } + }, "dto.UserStatistics": { "type": "object", "properties": { @@ -3742,6 +4654,47 @@ const docTemplate = `{ } } }, + "dto.UserTenantItem": { + "type": "object", + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.TenantUserRole" + } + }, + "tenant_id": { + "type": "integer" + }, + "tenant_status": { + "$ref": "#/definitions/consts.TenantStatus" + }, + "tenant_status_description": { + "type": "string" + } + } + }, "gorm.DeletedAt": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 2d51a29..92b1a80 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -71,6 +71,264 @@ } } }, + "/super/v1/orders": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单列表", + "parameters": [ + { + "type": "integer", + "name": "amount_paid_max", + "in": "query" + }, + { + "type": "integer", + "name": "amount_paid_min", + "in": "query" + }, + { + "type": "string", + "description": "Asc specifies comma-separated field names to sort ascending by.", + "name": "asc", + "in": "query" + }, + { + "type": "integer", + "name": "content_id", + "in": "query" + }, + { + "type": "string", + "name": "content_title", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Desc specifies comma-separated field names to sort descending by.", + "name": "desc", + "in": "query" + }, + { + "type": "integer", + "name": "id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "type": "string", + "name": "paid_at_from", + "in": "query" + }, + { + "type": "string", + "name": "paid_at_to", + "in": "query" + }, + { + "enum": [ + "created", + "paid", + "refunding", + "refunded", + "canceled", + "failed" + ], + "type": "string", + "x-enum-varnames": [ + "OrderStatusCreated", + "OrderStatusPaid", + "OrderStatusRefunding", + "OrderStatusRefunded", + "OrderStatusCanceled", + "OrderStatusFailed" + ], + "name": "status", + "in": "query" + }, + { + "type": "string", + "name": "tenant_code", + "in": "query" + }, + { + "type": "integer", + "name": "tenant_id", + "in": "query" + }, + { + "type": "string", + "name": "tenant_name", + "in": "query" + }, + { + "enum": [ + "content_purchase" + ], + "type": "string", + "x-enum-varnames": [ + "OrderTypeContentPurchase" + ], + "name": "type", + "in": "query" + }, + { + "type": "integer", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.SuperOrderItem" + } + } + } + ] + } + } + } + } + }, + "/super/v1/orders/statistics": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单统计信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OrderStatisticsResponse" + } + } + } + } + }, + "/super/v1/orders/{orderID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "OrderID", + "name": "orderID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SuperOrderDetail" + } + } + } + } + }, + "/super/v1/orders/{orderID}/refund": { + "post": { + "description": "该接口只负责将订单从 paid 推进到 refunding,并提交异步退款任务;退款入账与权益回收由 worker 异步完成。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "订单退款(平台)", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "OrderID", + "name": "orderID", + "in": "path", + "required": true + }, + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SuperOrderRefundForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Order" + } + } + } + } + }, "/super/v1/tenants": { "get": { "consumes": [ @@ -90,12 +348,42 @@ "name": "asc", "in": "query" }, + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, { "type": "string", "description": "Desc specifies comma-separated field names to sort descending by.", "name": "desc", "in": "query" }, + { + "type": "string", + "name": "expired_at_from", + "in": "query" + }, + { + "type": "string", + "name": "expired_at_to", + "in": "query" + }, + { + "type": "integer", + "name": "id", + "in": "query" + }, { "type": "integer", "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", @@ -127,6 +415,11 @@ ], "name": "status", "in": "query" + }, + { + "type": "integer", + "name": "user_id", + "in": "query" } ], "responses": { @@ -149,6 +442,37 @@ } } } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "创建租户并设置租户管理员", + "parameters": [ + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TenantCreateForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tenant" + } + } + } } }, "/super/v1/tenants/statuses": { @@ -177,6 +501,36 @@ } }, "/super/v1/tenants/{tenantID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "TenantID", + "name": "tenantID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.TenantItem" + } + } + } + }, "patch": { "consumes": [ "application/json" @@ -244,6 +598,104 @@ "responses": {} } }, + "/super/v1/tenants/{tenantID}/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户成员列表(平台侧)", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "TenantID", + "name": "tenantID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "enum": [ + "member", + "tenant_admin" + ], + "type": "string", + "x-enum-varnames": [ + "TenantUserRoleMember", + "TenantUserRoleTenantAdmin" + ], + "description": "Role 按角色过滤(可选):member/tenant_admin。", + "name": "role", + "in": "query" + }, + { + "enum": [ + "pending_verify", + "verified", + "banned" + ], + "type": "string", + "x-enum-varnames": [ + "UserStatusPendingVerify", + "UserStatusVerified", + "UserStatusBanned" + ], + "description": "Status 按成员状态过滤(可选):pending_verify/verified/banned。", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "UserID 按用户ID过滤(可选)。", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Username 按用户名模糊查询(可选,支持包含匹配)。", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.SuperTenantUserItem" + } + } + } + ] + } + } + } + } + }, "/super/v1/users": { "get": { "consumes": [ @@ -255,7 +707,7 @@ "tags": [ "Super" ], - "summary": "租户列表", + "summary": "用户列表", "parameters": [ { "type": "string", @@ -263,12 +715,27 @@ "name": "asc", "in": "query" }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, { "type": "string", "description": "Desc specifies comma-separated field names to sort descending by.", "name": "desc", "in": "query" }, + { + "type": "integer", + "name": "id", + "in": "query" + }, { "type": "integer", "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", @@ -281,6 +748,20 @@ "name": "page", "in": "query" }, + { + "enum": [ + "user", + "super_admin" + ], + "type": "string", + "x-enum-varnames": [ + "RoleUser", + "RoleSuperAdmin" + ], + "description": "Role filters users containing a role (user/super_admin).", + "name": "role", + "in": "query" + }, { "enum": [ "pending_verify", @@ -298,13 +779,24 @@ }, { "type": "integer", - "name": "tenantID", + "description": "TenantID filters users by membership in the given tenant.", + "name": "tenant_id", "in": "query" }, { "type": "string", "name": "username", "in": "query" + }, + { + "type": "string", + "name": "verified_at_from", + "in": "query" + }, + { + "type": "string", + "name": "verified_at_to", + "in": "query" } ], "responses": { @@ -379,6 +871,72 @@ } } }, + "/super/v1/users/{userID}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "用户详情", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserItem" + } + } + } + } + }, + "/super/v1/users/{userID}/roles": { + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "更新用户角色", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + }, + { + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UserRolesUpdateForm" + } + } + ], + "responses": {} + } + }, "/super/v1/users/{userID}/status": { "patch": { "consumes": [ @@ -413,6 +971,117 @@ "responses": {} } }, + "/super/v1/users/{userID}/tenants": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "用户加入的租户列表", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "UserID", + "name": "userID", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "name": "created_at_to", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "enum": [ + "member", + "tenant_admin" + ], + "type": "string", + "x-enum-varnames": [ + "TenantUserRoleMember", + "TenantUserRoleTenantAdmin" + ], + "description": "Role filters tenant_users.role containing a role (tenant_admin/member).", + "name": "role", + "in": "query" + }, + { + "enum": [ + "pending_verify", + "verified", + "banned" + ], + "type": "string", + "x-enum-varnames": [ + "UserStatusPendingVerify", + "UserStatusVerified", + "UserStatusBanned" + ], + "description": "Status filters tenant_users.status.", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "name": "tenant_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/requests.Pager" + }, + { + "type": "object", + "properties": { + "items": { + "$ref": "#/definitions/dto.UserTenantItem" + } + } + } + ] + } + } + } + } + }, "/t/{tenantCode}/v1/admin/contents": { "post": { "consumes": [ @@ -3510,6 +4179,65 @@ } } }, + "dto.OrderBuyerLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.OrderStatisticsResponse": { + "type": "object", + "properties": { + "by_status": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.OrderStatisticsRow" + } + }, + "total_amount_paid_sum": { + "type": "integer" + }, + "total_count": { + "type": "integer" + } + } + }, + "dto.OrderStatisticsRow": { + "type": "object", + "properties": { + "amount_paid_sum": { + "type": "integer" + }, + "count": { + "type": "integer" + }, + "status": { + "$ref": "#/definitions/consts.OrderStatus" + }, + "status_description": { + "type": "string" + } + } + }, + "dto.OrderTenantLite": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "dto.PurchaseContentForm": { "type": "object", "properties": { @@ -3552,6 +4280,171 @@ } } }, + "dto.SuperOrderDetail": { + "type": "object", + "properties": { + "buyer": { + "$ref": "#/definitions/dto.OrderBuyerLite" + }, + "order": { + "$ref": "#/definitions/models.Order" + }, + "tenant": { + "$ref": "#/definitions/dto.OrderTenantLite" + } + } + }, + "dto.SuperOrderItem": { + "type": "object", + "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" + } + } + }, + "dto.SuperOrderRefundForm": { + "type": "object", + "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" + } + } + }, + "dto.SuperTenantUserItem": { + "type": "object", + "properties": { + "tenant_user": { + "$ref": "#/definitions/models.TenantUser" + }, + "user": { + "$ref": "#/definitions/dto.SuperUserLite" + } + } + }, + "dto.SuperUserLite": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.Role" + } + }, + "status": { + "$ref": "#/definitions/consts.UserStatus" + }, + "status_description": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "verified_at": { + "type": "string" + } + } + }, + "dto.TenantAdminUserLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.TenantCreateForm": { + "type": "object", + "required": [ + "admin_user_id", + "code", + "duration", + "name" + ], + "properties": { + "admin_user_id": { + "type": "integer" + }, + "code": { + "type": "string", + "maxLength": 64 + }, + "duration": { + "description": "Duration 租户有效期(天),从“创建时刻”起算;与续期接口保持一致。", + "type": "integer", + "enum": [ + 7, + 30, + 90, + 180, + 365 + ] + }, + "name": { + "type": "string", + "maxLength": 128 + } + } + }, "dto.TenantExpireUpdateForm": { "type": "object", "required": [ @@ -3573,6 +4466,12 @@ "dto.TenantItem": { "type": "object", "properties": { + "admin_users": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TenantAdminUserLite" + } + }, "code": { "type": "string" }, @@ -3591,9 +4490,16 @@ "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" }, @@ -3603,9 +4509,6 @@ "updated_at": { "type": "string" }, - "user_balance": { - "type": "integer" - }, "user_count": { "type": "integer" }, @@ -3623,6 +4526,17 @@ } } }, + "dto.TenantOwnerUserLite": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, "dto.TenantStatusUpdateForm": { "type": "object", "required": [ @@ -3646,33 +4560,22 @@ "type": "object", "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": { - "type": "array", - "items": { - "type": "integer" - } + "joined_tenant_count": { + "type": "integer" }, - "owned": { - "$ref": "#/definitions/models.Tenant" - }, - "password": { - "type": "string" + "owned_tenant_count": { + "type": "integer" }, "roles": { "type": "array", @@ -3686,12 +4589,6 @@ "status_description": { "type": "string" }, - "tenants": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Tenant" - } - }, "updated_at": { "type": "string" }, @@ -3703,6 +4600,21 @@ } } }, + "dto.UserRolesUpdateForm": { + "type": "object", + "required": [ + "roles" + ], + "properties": { + "roles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/consts.Role" + } + } + } + }, "dto.UserStatistics": { "type": "object", "properties": { @@ -3736,6 +4648,47 @@ } } }, + "dto.UserTenantItem": { + "type": "object", + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.TenantUserRole" + } + }, + "tenant_id": { + "type": "integer" + }, + "tenant_status": { + "$ref": "#/definitions/consts.TenantStatus" + }, + "tenant_status_description": { + "type": "string" + } + } + }, "gorm.DeletedAt": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 33984c0..8e5a5b3 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -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: diff --git a/frontend/superadmin/dist/index.html b/frontend/superadmin/dist/index.html index 80d8810..df37dd5 100644 --- a/frontend/superadmin/dist/index.html +++ b/frontend/superadmin/dist/index.html @@ -7,8 +7,8 @@ Sakai Vue - - + + diff --git a/frontend/superadmin/src/router/index.js b/frontend/superadmin/src/router/index.js index 578e8a6..1ebea8a 100644 --- a/frontend/superadmin/src/router/index.js +++ b/frontend/superadmin/src/router/index.js @@ -119,15 +119,30 @@ const router = createRouter({ name: 'superadmin-tenants', component: () => import('@/views/superadmin/Tenants.vue') }, + { + path: '/superadmin/tenants/:tenantID', + name: 'superadmin-tenant-detail', + component: () => import('@/views/superadmin/TenantDetail.vue') + }, { path: '/superadmin/users', name: 'superadmin-users', component: () => import('@/views/superadmin/Users.vue') }, + { + path: '/superadmin/users/:userID', + name: 'superadmin-user-detail', + component: () => import('@/views/superadmin/UserDetail.vue') + }, { path: '/superadmin/orders', name: 'superadmin-orders', component: () => import('@/views/superadmin/Orders.vue') + }, + { + path: '/superadmin/orders/:orderID', + name: 'superadmin-order-detail', + component: () => import('@/views/superadmin/OrderDetail.vue') } ] }, diff --git a/frontend/superadmin/src/service/TenantService.js b/frontend/superadmin/src/service/TenantService.js index 4cfb8c9..615f82c 100644 --- a/frontend/superadmin/src/service/TenantService.js +++ b/frontend/superadmin/src/service/TenantService.js @@ -86,5 +86,9 @@ export const TenantService = { method: 'PATCH', body: { status } }); + }, + async getTenantDetail(tenantID) { + if (!tenantID) throw new Error('tenantID is required'); + return requestJson(`/super/v1/tenants/${tenantID}`); } }; diff --git a/frontend/superadmin/src/service/UserService.js b/frontend/superadmin/src/service/UserService.js index 97e05e3..2fa057e 100644 --- a/frontend/superadmin/src/service/UserService.js +++ b/frontend/superadmin/src/service/UserService.js @@ -94,6 +94,10 @@ export const UserService = { throw error; } }, + async getUserDetail(userID) { + if (!userID) throw new Error('userID is required'); + return requestJson(`/super/v1/users/${userID}`); + }, async listUserTenants( userID, { page, limit, tenant_id, code, name, role, status, created_at_from, created_at_to } = {} diff --git a/frontend/superadmin/src/views/superadmin/OrderDetail.vue b/frontend/superadmin/src/views/superadmin/OrderDetail.vue new file mode 100644 index 0000000..15a9d63 --- /dev/null +++ b/frontend/superadmin/src/views/superadmin/OrderDetail.vue @@ -0,0 +1,197 @@ + + + + diff --git a/frontend/superadmin/src/views/superadmin/Orders.vue b/frontend/superadmin/src/views/superadmin/Orders.vue index 6521e81..cd319aa 100644 --- a/frontend/superadmin/src/views/superadmin/Orders.vue +++ b/frontend/superadmin/src/views/superadmin/Orders.vue @@ -10,10 +10,6 @@ const toast = useToast(); const orders = ref([]); const loading = ref(false); -const detailDialogVisible = ref(false); -const detailLoading = ref(false); -const detail = ref(null); - const refundDialogVisible = ref(false); const refundLoading = ref(false); const refundOrder = ref(null); @@ -87,14 +83,6 @@ function getOrderStatusSeverity(value) { } } -function safeJson(value) { - try { - return JSON.stringify(value ?? null, null, 2); - } catch { - return String(value ?? ''); - } -} - async function loadOrders() { loading.value = true; try { @@ -129,24 +117,6 @@ async function loadOrders() { } } -async function openDetailDialog(order) { - const id = order?.id; - if (!id) return; - - detailDialogVisible.value = true; - detailLoading.value = true; - detail.value = null; - - try { - detail.value = await OrderService.getOrderDetail(id); - } catch (error) { - toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载订单详情', life: 4000 }); - detailDialogVisible.value = false; - } finally { - detailLoading.value = false; - } -} - function openRefundDialog(order) { refundOrder.value = order; refundDialogVisible.value = true; @@ -164,9 +134,6 @@ async function confirmRefund() { toast.add({ severity: 'success', summary: '已提交退款', detail: `订单ID: ${id}`, life: 3000 }); refundDialogVisible.value = false; await loadOrders(); - if (detailDialogVisible.value && detail.value?.order?.id === id) { - detail.value = await OrderService.getOrderDetail(id); - } } catch (error) { toast.add({ severity: 'error', summary: '退款失败', detail: error?.message || '无法发起退款', life: 4000 }); } finally { @@ -303,8 +270,8 @@ loadOrders(); > @@ -375,84 +342,6 @@ loadOrders(); - - -
- -
-
-
-
-
租户
-
{{ detail?.tenant?.name ?? '-' }}
-
{{ detail?.tenant?.code ?? '-' }} / {{ detail?.tenant?.id ?? '-' }}
-
-
-
买家
-
{{ detail?.buyer?.username ?? '-' }}
-
ID: {{ detail?.buyer?.id ?? '-' }}
-
-
-
状态
- -
-
-
实付
-
{{ formatCny(detail?.order?.amount_paid) }}
-
-
-
创建时间
-
{{ formatDate(detail?.order?.created_at) }}
-
-
-
支付时间
-
{{ formatDate(detail?.order?.paid_at) }}
-
-
- -
-
-
订单快照(snapshot)
-
{{ safeJson(detail?.order?.snapshot) }}
-
-
-
订单明细(items)
- - - - - - - - - - -
-
-
- -
-