feat: add superadmin creator review and coupon governance
This commit is contained in:
@@ -29,6 +29,40 @@ func (c *coupons) List(ctx fiber.Ctx, filter *dto.SuperCouponListFilter) (*reque
|
|||||||
return services.Super.ListCoupons(ctx, filter)
|
return services.Super.ListCoupons(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List coupon grants
|
||||||
|
//
|
||||||
|
// @Router /super/v1/coupon-grants [get]
|
||||||
|
// @Summary List coupon grants
|
||||||
|
// @Description List coupon grant records across tenants
|
||||||
|
// @Tags Coupon
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "Page number"
|
||||||
|
// @Param limit query int false "Page size"
|
||||||
|
// @Success 200 {object} requests.Pager{items=[]dto.SuperCouponGrantItem}
|
||||||
|
// @Bind filter query
|
||||||
|
func (c *coupons) ListGrants(ctx fiber.Ctx, filter *dto.SuperCouponGrantListFilter) (*requests.Pager, error) {
|
||||||
|
return services.Super.ListCouponGrants(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update coupon status
|
||||||
|
//
|
||||||
|
// @Router /super/v1/coupons/:id<int>/status [patch]
|
||||||
|
// @Summary Update coupon status
|
||||||
|
// @Description Update coupon status across tenants
|
||||||
|
// @Tags Coupon
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int64 true "Coupon ID"
|
||||||
|
// @Param form body dto.SuperCouponStatusUpdateForm true "Update form"
|
||||||
|
// @Success 200 {string} string "Updated"
|
||||||
|
// @Bind user local key(__ctx_user)
|
||||||
|
// @Bind id path
|
||||||
|
// @Bind form body
|
||||||
|
func (c *coupons) UpdateStatus(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperCouponStatusUpdateForm) error {
|
||||||
|
return services.Super.UpdateCouponStatus(ctx, user.ID, id, form)
|
||||||
|
}
|
||||||
|
|
||||||
// Create coupon
|
// Create coupon
|
||||||
//
|
//
|
||||||
// @Router /super/v1/tenants/:tenantID<int>/coupons [post]
|
// @Router /super/v1/tenants/:tenantID<int>/coupons [post]
|
||||||
|
|||||||
47
backend/app/http/super/v1/creator_applications.go
Normal file
47
backend/app/http/super/v1/creator_applications.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
dto "quyun/v2/app/http/super/v1/dto"
|
||||||
|
"quyun/v2/app/requests"
|
||||||
|
"quyun/v2/app/services"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @provider
|
||||||
|
type creatorApplications struct{}
|
||||||
|
|
||||||
|
// List creator applications
|
||||||
|
//
|
||||||
|
// @Router /super/v1/creator-applications [get]
|
||||||
|
// @Summary List creator applications
|
||||||
|
// @Description List creator applications across tenants
|
||||||
|
// @Tags Creator
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "Page number"
|
||||||
|
// @Param limit query int false "Page size"
|
||||||
|
// @Success 200 {object} requests.Pager{items=[]dto.TenantItem}
|
||||||
|
// @Bind filter query
|
||||||
|
func (c *creatorApplications) List(ctx fiber.Ctx, filter *dto.TenantListFilter) (*requests.Pager, error) {
|
||||||
|
return services.Super.ListCreatorApplications(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Review creator application
|
||||||
|
//
|
||||||
|
// @Router /super/v1/creator-applications/:id<int>/review [post]
|
||||||
|
// @Summary Review creator application
|
||||||
|
// @Description Approve or reject creator application
|
||||||
|
// @Tags Creator
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int64 true "Tenant ID"
|
||||||
|
// @Param form body dto.SuperCreatorApplicationReviewForm true "Review form"
|
||||||
|
// @Success 200 {string} string "Reviewed"
|
||||||
|
// @Bind user local key(__ctx_user)
|
||||||
|
// @Bind id path
|
||||||
|
// @Bind form body
|
||||||
|
func (c *creatorApplications) Review(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperCreatorApplicationReviewForm) error {
|
||||||
|
return services.Super.ReviewCreatorApplication(ctx, user.ID, id, form)
|
||||||
|
}
|
||||||
@@ -79,3 +79,66 @@ type SuperCouponGrantResponse struct {
|
|||||||
// Granted 实际发放数量。
|
// Granted 实际发放数量。
|
||||||
Granted int `json:"granted"`
|
Granted int `json:"granted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuperCouponStatusUpdateForm 超管优惠券状态更新表单。
|
||||||
|
type SuperCouponStatusUpdateForm struct {
|
||||||
|
// Status 目标状态(frozen)。
|
||||||
|
Status string `json:"status" validate:"required,oneof=frozen"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperCouponGrantListFilter 超管优惠券发放记录过滤条件。
|
||||||
|
type SuperCouponGrantListFilter struct {
|
||||||
|
requests.Pagination
|
||||||
|
// CouponID 优惠券ID过滤(精确匹配)。
|
||||||
|
CouponID *int64 `query:"coupon_id"`
|
||||||
|
// TenantID 租户ID过滤(精确匹配)。
|
||||||
|
TenantID *int64 `query:"tenant_id"`
|
||||||
|
// TenantCode 租户编码过滤(模糊匹配)。
|
||||||
|
TenantCode *string `query:"tenant_code"`
|
||||||
|
// TenantName 租户名称过滤(模糊匹配)。
|
||||||
|
TenantName *string `query:"tenant_name"`
|
||||||
|
// UserID 用户ID过滤(精确匹配)。
|
||||||
|
UserID *int64 `query:"user_id"`
|
||||||
|
// Username 用户名过滤(模糊匹配)。
|
||||||
|
Username *string `query:"username"`
|
||||||
|
// Status 用户券状态过滤(unused/used/expired)。
|
||||||
|
Status *consts.UserCouponStatus `query:"status"`
|
||||||
|
// CreatedAtFrom 领取时间起始(RFC3339)。
|
||||||
|
CreatedAtFrom *string `query:"created_at_from"`
|
||||||
|
// CreatedAtTo 领取时间结束(RFC3339)。
|
||||||
|
CreatedAtTo *string `query:"created_at_to"`
|
||||||
|
// UsedAtFrom 使用时间起始(RFC3339)。
|
||||||
|
UsedAtFrom *string `query:"used_at_from"`
|
||||||
|
// UsedAtTo 使用时间结束(RFC3339)。
|
||||||
|
UsedAtTo *string `query:"used_at_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperCouponGrantItem 超管优惠券发放记录项。
|
||||||
|
type SuperCouponGrantItem struct {
|
||||||
|
// ID 用户券ID。
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
// CouponID 优惠券ID。
|
||||||
|
CouponID int64 `json:"coupon_id"`
|
||||||
|
// CouponTitle 优惠券标题。
|
||||||
|
CouponTitle string `json:"coupon_title"`
|
||||||
|
// TenantID 租户ID。
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
// TenantCode 租户编码。
|
||||||
|
TenantCode string `json:"tenant_code"`
|
||||||
|
// TenantName 租户名称。
|
||||||
|
TenantName string `json:"tenant_name"`
|
||||||
|
// UserID 用户ID。
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
// Username 用户名。
|
||||||
|
Username string `json:"username"`
|
||||||
|
// Status 用户券状态。
|
||||||
|
Status consts.UserCouponStatus `json:"status"`
|
||||||
|
// StatusDescription 状态描述(用于展示)。
|
||||||
|
StatusDescription string `json:"status_description"`
|
||||||
|
// OrderID 使用订单ID。
|
||||||
|
OrderID int64 `json:"order_id"`
|
||||||
|
// UsedAt 使用时间(RFC3339)。
|
||||||
|
UsedAt string `json:"used_at"`
|
||||||
|
// CreatedAt 领取时间(RFC3339)。
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|||||||
9
backend/app/http/super/v1/dto/super_creator.go
Normal file
9
backend/app/http/super/v1/dto/super_creator.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
// SuperCreatorApplicationReviewForm 超管创作者申请审核表单。
|
||||||
|
type SuperCreatorApplicationReviewForm struct {
|
||||||
|
// Action 审核动作(approve/reject)。
|
||||||
|
Action string `json:"action" validate:"required,oneof=approve reject"`
|
||||||
|
// Reason 审核说明(可选,驳回时填写)。
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
52
backend/app/http/super/v1/dto/super_payout.go
Normal file
52
backend/app/http/super/v1/dto/super_payout.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "quyun/v2/app/requests"
|
||||||
|
|
||||||
|
// SuperPayoutAccountListFilter 超管结算账户列表过滤条件。
|
||||||
|
type SuperPayoutAccountListFilter struct {
|
||||||
|
requests.Pagination
|
||||||
|
// TenantID 租户ID过滤(精确匹配)。
|
||||||
|
TenantID *int64 `query:"tenant_id"`
|
||||||
|
// TenantCode 租户编码过滤(模糊匹配)。
|
||||||
|
TenantCode *string `query:"tenant_code"`
|
||||||
|
// TenantName 租户名称过滤(模糊匹配)。
|
||||||
|
TenantName *string `query:"tenant_name"`
|
||||||
|
// UserID 用户ID过滤(精确匹配)。
|
||||||
|
UserID *int64 `query:"user_id"`
|
||||||
|
// Username 用户名过滤(模糊匹配)。
|
||||||
|
Username *string `query:"username"`
|
||||||
|
// Type 账户类型过滤(bank/alipay)。
|
||||||
|
Type *string `query:"type"`
|
||||||
|
// CreatedAtFrom 创建时间起始(RFC3339)。
|
||||||
|
CreatedAtFrom *string `query:"created_at_from"`
|
||||||
|
// CreatedAtTo 创建时间结束(RFC3339)。
|
||||||
|
CreatedAtTo *string `query:"created_at_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperPayoutAccountItem 超管结算账户列表项。
|
||||||
|
type SuperPayoutAccountItem 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"`
|
||||||
|
// UserID 用户ID。
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
// Username 用户名。
|
||||||
|
Username string `json:"username"`
|
||||||
|
// Type 账户类型。
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Name 账户名称/开户行。
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Account 收款账号。
|
||||||
|
Account string `json:"account"`
|
||||||
|
// Realname 收款人姓名。
|
||||||
|
Realname string `json:"realname"`
|
||||||
|
// CreatedAt 创建时间(RFC3339)。
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
// UpdatedAt 更新时间(RFC3339)。
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
45
backend/app/http/super/v1/payout_accounts.go
Normal file
45
backend/app/http/super/v1/payout_accounts.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
dto "quyun/v2/app/http/super/v1/dto"
|
||||||
|
"quyun/v2/app/requests"
|
||||||
|
"quyun/v2/app/services"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @provider
|
||||||
|
type payoutAccounts struct{}
|
||||||
|
|
||||||
|
// List payout accounts
|
||||||
|
//
|
||||||
|
// @Router /super/v1/payout-accounts [get]
|
||||||
|
// @Summary List payout accounts
|
||||||
|
// @Description List payout accounts across tenants
|
||||||
|
// @Tags Finance
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "Page number"
|
||||||
|
// @Param limit query int false "Page size"
|
||||||
|
// @Success 200 {object} requests.Pager{items=[]dto.SuperPayoutAccountItem}
|
||||||
|
// @Bind filter query
|
||||||
|
func (c *payoutAccounts) List(ctx fiber.Ctx, filter *dto.SuperPayoutAccountListFilter) (*requests.Pager, error) {
|
||||||
|
return services.Super.ListPayoutAccounts(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove payout account
|
||||||
|
//
|
||||||
|
// @Router /super/v1/payout-accounts/:id<int> [delete]
|
||||||
|
// @Summary Remove payout account
|
||||||
|
// @Description Remove payout account across tenants
|
||||||
|
// @Tags Finance
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int64 true "Payout account ID"
|
||||||
|
// @Success 200 {string} string "Removed"
|
||||||
|
// @Bind user local key(__ctx_user)
|
||||||
|
// @Bind id path
|
||||||
|
func (c *payoutAccounts) Remove(ctx fiber.Ctx, user *models.User, id int64) error {
|
||||||
|
return services.Super.RemovePayoutAccount(ctx, user.ID, id)
|
||||||
|
}
|
||||||
@@ -24,6 +24,13 @@ func Provide(opts ...opt.Option) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := container.Container.Provide(func() (*creatorApplications, error) {
|
||||||
|
obj := &creatorApplications{}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := container.Container.Provide(func() (*creators, error) {
|
if err := container.Container.Provide(func() (*creators, error) {
|
||||||
obj := &creators{}
|
obj := &creators{}
|
||||||
|
|
||||||
@@ -38,6 +45,13 @@ func Provide(opts ...opt.Option) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := container.Container.Provide(func() (*payoutAccounts, error) {
|
||||||
|
obj := &payoutAccounts{}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := container.Container.Provide(func() (*reports, error) {
|
if err := container.Container.Provide(func() (*reports, error) {
|
||||||
obj := &reports{}
|
obj := &reports{}
|
||||||
|
|
||||||
@@ -48,9 +62,11 @@ func Provide(opts ...opt.Option) error {
|
|||||||
if err := container.Container.Provide(func(
|
if err := container.Container.Provide(func(
|
||||||
contents *contents,
|
contents *contents,
|
||||||
coupons *coupons,
|
coupons *coupons,
|
||||||
|
creatorApplications *creatorApplications,
|
||||||
creators *creators,
|
creators *creators,
|
||||||
middlewares *middlewares.Middlewares,
|
middlewares *middlewares.Middlewares,
|
||||||
orders *orders,
|
orders *orders,
|
||||||
|
payoutAccounts *payoutAccounts,
|
||||||
reports *reports,
|
reports *reports,
|
||||||
tenants *tenants,
|
tenants *tenants,
|
||||||
users *users,
|
users *users,
|
||||||
@@ -59,9 +75,11 @@ func Provide(opts ...opt.Option) error {
|
|||||||
obj := &Routes{
|
obj := &Routes{
|
||||||
contents: contents,
|
contents: contents,
|
||||||
coupons: coupons,
|
coupons: coupons,
|
||||||
|
creatorApplications: creatorApplications,
|
||||||
creators: creators,
|
creators: creators,
|
||||||
middlewares: middlewares,
|
middlewares: middlewares,
|
||||||
orders: orders,
|
orders: orders,
|
||||||
|
payoutAccounts: payoutAccounts,
|
||||||
reports: reports,
|
reports: reports,
|
||||||
tenants: tenants,
|
tenants: tenants,
|
||||||
users: users,
|
users: users,
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ type Routes struct {
|
|||||||
// Controller instances
|
// Controller instances
|
||||||
contents *contents
|
contents *contents
|
||||||
coupons *coupons
|
coupons *coupons
|
||||||
|
creatorApplications *creatorApplications
|
||||||
creators *creators
|
creators *creators
|
||||||
orders *orders
|
orders *orders
|
||||||
|
payoutAccounts *payoutAccounts
|
||||||
reports *reports
|
reports *reports
|
||||||
tenants *tenants
|
tenants *tenants
|
||||||
users *users
|
users *users
|
||||||
@@ -83,6 +85,11 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
Body[dto.SuperContentBatchReviewForm]("form"),
|
Body[dto.SuperContentBatchReviewForm]("form"),
|
||||||
))
|
))
|
||||||
// Register routes for controller: coupons
|
// Register routes for controller: coupons
|
||||||
|
r.log.Debugf("Registering route: Get /super/v1/coupon-grants -> coupons.ListGrants")
|
||||||
|
router.Get("/super/v1/coupon-grants"[len(r.Path()):], DataFunc1(
|
||||||
|
r.coupons.ListGrants,
|
||||||
|
Query[dto.SuperCouponGrantListFilter]("filter"),
|
||||||
|
))
|
||||||
r.log.Debugf("Registering route: Get /super/v1/coupons -> coupons.List")
|
r.log.Debugf("Registering route: Get /super/v1/coupons -> coupons.List")
|
||||||
router.Get("/super/v1/coupons"[len(r.Path()):], DataFunc1(
|
router.Get("/super/v1/coupons"[len(r.Path()):], DataFunc1(
|
||||||
r.coupons.List,
|
r.coupons.List,
|
||||||
@@ -94,6 +101,13 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
PathParam[int64]("tenantID"),
|
PathParam[int64]("tenantID"),
|
||||||
PathParam[int64]("id"),
|
PathParam[int64]("id"),
|
||||||
))
|
))
|
||||||
|
r.log.Debugf("Registering route: Patch /super/v1/coupons/:id<int>/status -> coupons.UpdateStatus")
|
||||||
|
router.Patch("/super/v1/coupons/:id<int>/status"[len(r.Path()):], Func3(
|
||||||
|
r.coupons.UpdateStatus,
|
||||||
|
Local[*models.User]("__ctx_user"),
|
||||||
|
PathParam[int64]("id"),
|
||||||
|
Body[dto.SuperCouponStatusUpdateForm]("form"),
|
||||||
|
))
|
||||||
r.log.Debugf("Registering route: Post /super/v1/tenants/:tenantID<int>/coupons -> coupons.Create")
|
r.log.Debugf("Registering route: Post /super/v1/tenants/:tenantID<int>/coupons -> coupons.Create")
|
||||||
router.Post("/super/v1/tenants/:tenantID<int>/coupons"[len(r.Path()):], DataFunc3(
|
router.Post("/super/v1/tenants/:tenantID<int>/coupons"[len(r.Path()):], DataFunc3(
|
||||||
r.coupons.Create,
|
r.coupons.Create,
|
||||||
@@ -116,6 +130,19 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
PathParam[int64]("id"),
|
PathParam[int64]("id"),
|
||||||
Body[v1_dto.CouponUpdateForm]("form"),
|
Body[v1_dto.CouponUpdateForm]("form"),
|
||||||
))
|
))
|
||||||
|
// Register routes for controller: creatorApplications
|
||||||
|
r.log.Debugf("Registering route: Get /super/v1/creator-applications -> creatorApplications.List")
|
||||||
|
router.Get("/super/v1/creator-applications"[len(r.Path()):], DataFunc1(
|
||||||
|
r.creatorApplications.List,
|
||||||
|
Query[dto.TenantListFilter]("filter"),
|
||||||
|
))
|
||||||
|
r.log.Debugf("Registering route: Post /super/v1/creator-applications/:id<int>/review -> creatorApplications.Review")
|
||||||
|
router.Post("/super/v1/creator-applications/:id<int>/review"[len(r.Path()):], Func3(
|
||||||
|
r.creatorApplications.Review,
|
||||||
|
Local[*models.User]("__ctx_user"),
|
||||||
|
PathParam[int64]("id"),
|
||||||
|
Body[dto.SuperCreatorApplicationReviewForm]("form"),
|
||||||
|
))
|
||||||
// Register routes for controller: creators
|
// Register routes for controller: creators
|
||||||
r.log.Debugf("Registering route: Get /super/v1/creators -> creators.List")
|
r.log.Debugf("Registering route: Get /super/v1/creators -> creators.List")
|
||||||
router.Get("/super/v1/creators"[len(r.Path()):], DataFunc1(
|
router.Get("/super/v1/creators"[len(r.Path()):], DataFunc1(
|
||||||
@@ -143,6 +170,18 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
PathParam[int64]("id"),
|
PathParam[int64]("id"),
|
||||||
Body[dto.SuperOrderRefundForm]("form"),
|
Body[dto.SuperOrderRefundForm]("form"),
|
||||||
))
|
))
|
||||||
|
// Register routes for controller: payoutAccounts
|
||||||
|
r.log.Debugf("Registering route: Delete /super/v1/payout-accounts/:id<int> -> payoutAccounts.Remove")
|
||||||
|
router.Delete("/super/v1/payout-accounts/:id<int>"[len(r.Path()):], Func2(
|
||||||
|
r.payoutAccounts.Remove,
|
||||||
|
Local[*models.User]("__ctx_user"),
|
||||||
|
PathParam[int64]("id"),
|
||||||
|
))
|
||||||
|
r.log.Debugf("Registering route: Get /super/v1/payout-accounts -> payoutAccounts.List")
|
||||||
|
router.Get("/super/v1/payout-accounts"[len(r.Path()):], DataFunc1(
|
||||||
|
r.payoutAccounts.List,
|
||||||
|
Query[dto.SuperPayoutAccountListFilter]("filter"),
|
||||||
|
))
|
||||||
// Register routes for controller: reports
|
// Register routes for controller: reports
|
||||||
r.log.Debugf("Registering route: Get /super/v1/reports/overview -> reports.Overview")
|
r.log.Debugf("Registering route: Get /super/v1/reports/overview -> reports.Overview")
|
||||||
router.Get("/super/v1/reports/overview"[len(r.Path()):], DataFunc1(
|
router.Get("/super/v1/reports/overview"[len(r.Path()):], DataFunc1(
|
||||||
|
|||||||
@@ -953,6 +953,76 @@ func (s *super) TenantHealth(ctx context.Context, filter *super_dto.TenantListFi
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *super) ListCreatorApplications(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
|
||||||
|
if filter == nil {
|
||||||
|
filter = &super_dto.TenantListFilter{}
|
||||||
|
}
|
||||||
|
if filter.Status == nil || *filter.Status == "" {
|
||||||
|
status := consts.TenantStatusPendingVerify
|
||||||
|
filter.Status = &status
|
||||||
|
}
|
||||||
|
return s.ListTenants(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) ReviewCreatorApplication(ctx context.Context, operatorID, tenantID int64, form *super_dto.SuperCreatorApplicationReviewForm) error {
|
||||||
|
if operatorID == 0 {
|
||||||
|
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||||
|
}
|
||||||
|
if tenantID == 0 || form == nil {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("审核参数无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
action := strings.ToLower(strings.TrimSpace(form.Action))
|
||||||
|
if action != "approve" && action != "reject" {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("审核动作无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
|
tenant, err := q.Where(tbl.ID.Eq(tenantID)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errorx.ErrRecordNotFound.WithMsg("创作者申请不存在")
|
||||||
|
}
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
if tenant.Status != consts.TenantStatusPendingVerify {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("创作者申请已处理")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStatus := consts.TenantStatusVerified
|
||||||
|
if action == "reject" {
|
||||||
|
nextStatus = consts.TenantStatusBanned
|
||||||
|
}
|
||||||
|
_, err = q.Where(tbl.ID.Eq(tenant.ID), tbl.Status.Eq(consts.TenantStatusPendingVerify)).Update(tbl.Status, nextStatus)
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Notification != nil {
|
||||||
|
title := "创作者申请审核结果"
|
||||||
|
detail := "您的创作者申请已通过"
|
||||||
|
if nextStatus == consts.TenantStatusBanned {
|
||||||
|
detail = "您的创作者申请已驳回"
|
||||||
|
if strings.TrimSpace(form.Reason) != "" {
|
||||||
|
detail += ",原因:" + strings.TrimSpace(form.Reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = Notification.Send(ctx, tenant.ID, tenant.UserID, string(consts.NotificationTypeAudit), title, detail)
|
||||||
|
}
|
||||||
|
if Audit != nil {
|
||||||
|
detail := "approve"
|
||||||
|
if nextStatus == consts.TenantStatusBanned {
|
||||||
|
detail = "reject"
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(form.Reason) != "" {
|
||||||
|
detail += ",原因:" + strings.TrimSpace(form.Reason)
|
||||||
|
}
|
||||||
|
Audit.Log(ctx, operatorID, "review_creator_application", cast.ToString(tenant.ID), detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
||||||
uid := form.AdminUserID
|
uid := form.AdminUserID
|
||||||
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
|
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
|
||||||
@@ -1065,6 +1135,191 @@ func (s *super) ListTenantUsers(ctx context.Context, tenantID int64, filter *sup
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *super) ListPayoutAccounts(ctx context.Context, filter *super_dto.SuperPayoutAccountListFilter) (*requests.Pager, error) {
|
||||||
|
if filter == nil {
|
||||||
|
filter = &super_dto.SuperPayoutAccountListFilter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.PayoutAccountQuery.QueryContext(ctx)
|
||||||
|
if filter.TenantID != nil && *filter.TenantID > 0 {
|
||||||
|
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
|
||||||
|
}
|
||||||
|
if filter.UserID != nil && *filter.UserID > 0 {
|
||||||
|
q = q.Where(tbl.UserID.Eq(*filter.UserID))
|
||||||
|
}
|
||||||
|
if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
|
||||||
|
q = q.Where(tbl.Type.Eq(strings.TrimSpace(*filter.Type)))
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tenantFilter {
|
||||||
|
if len(tenantIDs) == 0 {
|
||||||
|
q = q.Where(tbl.ID.Eq(-1))
|
||||||
|
} else {
|
||||||
|
q = q.Where(tbl.TenantID.In(tenantIDs...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userFilter {
|
||||||
|
if len(userIDs) == 0 {
|
||||||
|
q = q.Where(tbl.ID.Eq(-1))
|
||||||
|
} else {
|
||||||
|
q = q.Where(tbl.UserID.In(userIDs...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantMap := make(map[int64]*models.Tenant)
|
||||||
|
userMap := make(map[int64]*models.User)
|
||||||
|
if len(list) > 0 {
|
||||||
|
tenantIDSet := make(map[int64]struct{})
|
||||||
|
userIDSet := make(map[int64]struct{})
|
||||||
|
for _, pa := range list {
|
||||||
|
if pa.TenantID > 0 {
|
||||||
|
tenantIDSet[pa.TenantID] = struct{}{}
|
||||||
|
}
|
||||||
|
if pa.UserID > 0 {
|
||||||
|
userIDSet[pa.UserID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantIDs = tenantIDs[:0]
|
||||||
|
for id := range tenantIDSet {
|
||||||
|
tenantIDs = append(tenantIDs, id)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDs = userIDs[:0]
|
||||||
|
for id := range userIDSet {
|
||||||
|
userIDs = append(userIDs, id)
|
||||||
|
}
|
||||||
|
if len(userIDs) > 0 {
|
||||||
|
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
|
||||||
|
users, err := userQuery.Where(userTbl.ID.In(userIDs...)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
userMap[user.ID] = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]super_dto.SuperPayoutAccountItem, 0, len(list))
|
||||||
|
for _, pa := range list {
|
||||||
|
tenant := tenantMap[pa.TenantID]
|
||||||
|
user := userMap[pa.UserID]
|
||||||
|
tenantCode := ""
|
||||||
|
tenantName := ""
|
||||||
|
if tenant != nil {
|
||||||
|
tenantCode = tenant.Code
|
||||||
|
tenantName = tenant.Name
|
||||||
|
}
|
||||||
|
username := ""
|
||||||
|
if user != nil {
|
||||||
|
username = user.Username
|
||||||
|
}
|
||||||
|
if username == "" && pa.UserID > 0 {
|
||||||
|
username = "ID:" + strconv.FormatInt(pa.UserID, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, super_dto.SuperPayoutAccountItem{
|
||||||
|
ID: pa.ID,
|
||||||
|
TenantID: pa.TenantID,
|
||||||
|
TenantCode: tenantCode,
|
||||||
|
TenantName: tenantName,
|
||||||
|
UserID: pa.UserID,
|
||||||
|
Username: username,
|
||||||
|
Type: pa.Type,
|
||||||
|
Name: pa.Name,
|
||||||
|
Account: pa.Account,
|
||||||
|
Realname: pa.Realname,
|
||||||
|
CreatedAt: s.formatTime(pa.CreatedAt),
|
||||||
|
UpdatedAt: s.formatTime(pa.UpdatedAt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: filter.Pagination,
|
||||||
|
Total: total,
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) error {
|
||||||
|
if operatorID == 0 {
|
||||||
|
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||||
|
}
|
||||||
|
if id == 0 {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("结算账户ID不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.PayoutAccountQuery.QueryContext(ctx)
|
||||||
|
account, err := q.Where(tbl.ID.Eq(id)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
|
||||||
|
}
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := q.Where(tbl.ID.Eq(account.ID)).Delete(); err != nil {
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Audit != nil {
|
||||||
|
Audit.Log(ctx, operatorID, "remove_payout_account", cast.ToString(account.ID), "Removed payout account")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *super) ListTenantJoinRequests(ctx context.Context, filter *super_dto.SuperTenantJoinRequestListFilter) (*requests.Pager, error) {
|
func (s *super) ListTenantJoinRequests(ctx context.Context, filter *super_dto.SuperTenantJoinRequestListFilter) (*requests.Pager, error) {
|
||||||
if filter == nil {
|
if filter == nil {
|
||||||
filter = &super_dto.SuperTenantJoinRequestListFilter{}
|
filter = &super_dto.SuperTenantJoinRequestListFilter{}
|
||||||
@@ -3214,6 +3469,241 @@ func (s *super) ListCoupons(ctx context.Context, filter *super_dto.SuperCouponLi
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *super) ListCouponGrants(ctx context.Context, filter *super_dto.SuperCouponGrantListFilter) (*requests.Pager, error) {
|
||||||
|
if filter == nil {
|
||||||
|
filter = &super_dto.SuperCouponGrantListFilter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.UserCouponQuery.QueryContext(ctx)
|
||||||
|
if filter.UserID != nil && *filter.UserID > 0 {
|
||||||
|
q = q.Where(tbl.UserID.Eq(*filter.UserID))
|
||||||
|
}
|
||||||
|
if filter.Status != nil && *filter.Status != "" {
|
||||||
|
q = q.Where(tbl.Status.Eq(*filter.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userFilter {
|
||||||
|
if len(userIDs) == 0 {
|
||||||
|
q = q.Where(tbl.ID.Eq(-1))
|
||||||
|
} else {
|
||||||
|
q = q.Where(tbl.UserID.In(userIDs...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
couponIDs, couponFilter, err := s.filterCouponGrantCouponIDs(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if couponFilter {
|
||||||
|
if len(couponIDs) == 0 {
|
||||||
|
filter.Pagination.Format()
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: filter.Pagination,
|
||||||
|
Total: 0,
|
||||||
|
Items: []super_dto.SuperCouponGrantItem{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
q = q.Where(tbl.CouponID.In(couponIDs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 filter.UsedAtFrom != nil {
|
||||||
|
from, err := s.parseFilterTime(filter.UsedAtFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if from != nil {
|
||||||
|
q = q.Where(tbl.UsedAt.Gte(*from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filter.UsedAtTo != nil {
|
||||||
|
to, err := s.parseFilterTime(filter.UsedAtTo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if to != nil {
|
||||||
|
q = q.Where(tbl.UsedAt.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)
|
||||||
|
}
|
||||||
|
if len(list) == 0 {
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: filter.Pagination,
|
||||||
|
Total: total,
|
||||||
|
Items: []super_dto.SuperCouponGrantItem{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
couponIDSet := make(map[int64]struct{})
|
||||||
|
userIDSet := make(map[int64]struct{})
|
||||||
|
for _, uc := range list {
|
||||||
|
if uc.CouponID > 0 {
|
||||||
|
couponIDSet[uc.CouponID] = struct{}{}
|
||||||
|
}
|
||||||
|
if uc.UserID > 0 {
|
||||||
|
userIDSet[uc.UserID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
couponIDs = couponIDs[:0]
|
||||||
|
for id := range couponIDSet {
|
||||||
|
couponIDs = append(couponIDs, id)
|
||||||
|
}
|
||||||
|
userIDs = userIDs[:0]
|
||||||
|
for id := range userIDSet {
|
||||||
|
userIDs = append(userIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
couponMap := make(map[int64]*models.Coupon, len(couponIDs))
|
||||||
|
tenantMap := make(map[int64]*models.Tenant)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
tenantSet := make(map[int64]struct{})
|
||||||
|
for _, coupon := range coupons {
|
||||||
|
couponMap[coupon.ID] = coupon
|
||||||
|
if coupon.TenantID > 0 {
|
||||||
|
tenantSet[coupon.TenantID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tenantIDs := make([]int64, 0, len(tenantSet))
|
||||||
|
for id := range tenantSet {
|
||||||
|
tenantIDs = append(tenantIDs, id)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userMap := make(map[int64]*models.User, len(userIDs))
|
||||||
|
if len(userIDs) > 0 {
|
||||||
|
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
|
||||||
|
users, err := userQuery.Where(userTbl.ID.In(userIDs...)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
userMap[user.ID] = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]super_dto.SuperCouponGrantItem, 0, len(list))
|
||||||
|
for _, uc := range list {
|
||||||
|
item := super_dto.SuperCouponGrantItem{
|
||||||
|
ID: uc.ID,
|
||||||
|
CouponID: uc.CouponID,
|
||||||
|
UserID: uc.UserID,
|
||||||
|
Status: uc.Status,
|
||||||
|
StatusDescription: uc.Status.Description(),
|
||||||
|
OrderID: uc.OrderID,
|
||||||
|
UsedAt: s.formatTime(uc.UsedAt),
|
||||||
|
CreatedAt: s.formatTime(uc.CreatedAt),
|
||||||
|
}
|
||||||
|
if user := userMap[uc.UserID]; user != nil {
|
||||||
|
item.Username = user.Username
|
||||||
|
} else if uc.UserID > 0 {
|
||||||
|
item.Username = "ID:" + strconv.FormatInt(uc.UserID, 10)
|
||||||
|
}
|
||||||
|
if coupon := couponMap[uc.CouponID]; coupon != nil {
|
||||||
|
item.CouponTitle = coupon.Title
|
||||||
|
item.TenantID = coupon.TenantID
|
||||||
|
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) UpdateCouponStatus(ctx context.Context, operatorID, couponID int64, form *super_dto.SuperCouponStatusUpdateForm) error {
|
||||||
|
if operatorID == 0 {
|
||||||
|
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||||
|
}
|
||||||
|
if couponID == 0 || form == nil {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("参数无效")
|
||||||
|
}
|
||||||
|
status := strings.ToLower(strings.TrimSpace(form.Status))
|
||||||
|
if status != "frozen" {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("仅支持冻结操作")
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.CouponQuery.QueryContext(ctx)
|
||||||
|
coupon, err := q.Where(tbl.ID.Eq(couponID)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
|
||||||
|
}
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if !coupon.EndAt.IsZero() && now.After(coupon.EndAt) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = q.Where(tbl.ID.Eq(coupon.ID)).UpdateSimple(
|
||||||
|
tbl.EndAt.Value(now),
|
||||||
|
tbl.UpdatedAt.Value(now),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Audit != nil {
|
||||||
|
Audit.Log(ctx, operatorID, "freeze_coupon", cast.ToString(coupon.ID), "Freeze coupon")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
|
func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
|
||||||
// 统一统计时间范围与粒度。
|
// 统一统计时间范围与粒度。
|
||||||
rg, err := s.normalizeReportRange(filter)
|
rg, err := s.normalizeReportRange(filter)
|
||||||
@@ -3766,6 +4256,50 @@ func (s *super) filterCouponIDs(ctx context.Context, filter *super_dto.SuperUser
|
|||||||
return ids, true, nil
|
return ids, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *super) filterCouponGrantCouponIDs(ctx context.Context, filter *super_dto.SuperCouponGrantListFilter) ([]int64, bool, error) {
|
||||||
|
if filter == nil {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.CouponID != nil && *filter.CouponID > 0 {
|
||||||
|
return []int64{*filter.CouponID}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantIDs := make([]int64, 0)
|
||||||
|
applied := false
|
||||||
|
if filter.TenantID != nil && *filter.TenantID > 0 {
|
||||||
|
applied = true
|
||||||
|
tenantIDs = append(tenantIDs, *filter.TenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
if tenantFilter {
|
||||||
|
applied = true
|
||||||
|
tenantIDs = append(tenantIDs, lookupIDs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !applied {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
if len(tenantIDs) == 0 {
|
||||||
|
return []int64{}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
couponTbl, couponQuery := models.CouponQuery.QueryContext(ctx)
|
||||||
|
coupons, err := couponQuery.Where(couponTbl.TenantID.In(tenantIDs...)).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 {
|
func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reason string) error {
|
||||||
if operatorID == 0 {
|
if operatorID == 0 {
|
||||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||||
|
|||||||
@@ -209,6 +209,58 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/coupon-grants": {
|
||||||
|
"get": {
|
||||||
|
"description": "List coupon grant records across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Coupon"
|
||||||
|
],
|
||||||
|
"summary": "List coupon grants",
|
||||||
|
"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.SuperCouponGrantItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/coupons": {
|
"/super/v1/coupons": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List coupon templates across tenants",
|
"description": "List coupon templates across tenants",
|
||||||
@@ -261,6 +313,142 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/coupons/{id}/status": {
|
||||||
|
"patch": {
|
||||||
|
"description": "Update coupon status across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Coupon"
|
||||||
|
],
|
||||||
|
"summary": "Update coupon status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"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.SuperCouponStatusUpdateForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updated",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/creator-applications": {
|
||||||
|
"get": {
|
||||||
|
"description": "List creator applications across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Creator"
|
||||||
|
],
|
||||||
|
"summary": "List creator applications",
|
||||||
|
"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.TenantItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/creator-applications/{id}/review": {
|
||||||
|
"post": {
|
||||||
|
"description": "Approve or reject creator application",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Creator"
|
||||||
|
],
|
||||||
|
"summary": "Review creator application",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Tenant ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Review form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SuperCreatorApplicationReviewForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Reviewed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/creators": {
|
"/super/v1/creators": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List creator tenants (channels) across the platform",
|
"description": "List creator tenants (channels) across the platform",
|
||||||
@@ -463,6 +651,91 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/payout-accounts": {
|
||||||
|
"get": {
|
||||||
|
"description": "List payout accounts across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"summary": "List payout accounts",
|
||||||
|
"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.SuperPayoutAccountItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/payout-accounts/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Remove payout account across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"summary": "Remove payout account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Payout account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Removed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/reports/export": {
|
"/super/v1/reports/export": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Export platform report data",
|
"description": "Export platform report data",
|
||||||
@@ -6118,6 +6391,67 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperCouponGrantItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"coupon_id": {
|
||||||
|
"description": "CouponID 优惠券ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"coupon_title": {
|
||||||
|
"description": "CouponTitle 优惠券标题。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"description": "CreatedAt 领取时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "ID 用户券ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"order_id": {
|
||||||
|
"description": "OrderID 使用订单ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"used_at": {
|
||||||
|
"description": "UsedAt 使用时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"description": "UserID 用户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"description": "Username 用户名。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperCouponGrantResponse": {
|
"dto.SuperCouponGrantResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6212,6 +6546,41 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperCouponStatusUpdateForm": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"description": "Status 目标状态(frozen)。",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"frozen"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.SuperCreatorApplicationReviewForm": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"action"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"description": "Action 审核动作(approve/reject)。",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"approve",
|
||||||
|
"reject"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"description": "Reason 审核说明(可选,驳回时填写)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperOrderDetail": {
|
"dto.SuperOrderDetail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6369,6 +6738,59 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperPayoutAccountItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account": {
|
||||||
|
"description": "Account 收款账号。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"description": "CreatedAt 创建时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "ID 结算账户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Name 账户名称/开户行。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"realname": {
|
||||||
|
"description": "Realname 收款人姓名。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_code": {
|
||||||
|
"description": "TenantCode 租户编码。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 租户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_name": {
|
||||||
|
"description": "TenantName 租户名称。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Type 账户类型。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"description": "UpdatedAt 更新时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"description": "UserID 用户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"description": "Username 用户名。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperReportExportForm": {
|
"dto.SuperReportExportForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -203,6 +203,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/coupon-grants": {
|
||||||
|
"get": {
|
||||||
|
"description": "List coupon grant records across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Coupon"
|
||||||
|
],
|
||||||
|
"summary": "List coupon grants",
|
||||||
|
"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.SuperCouponGrantItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/coupons": {
|
"/super/v1/coupons": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List coupon templates across tenants",
|
"description": "List coupon templates across tenants",
|
||||||
@@ -255,6 +307,142 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/coupons/{id}/status": {
|
||||||
|
"patch": {
|
||||||
|
"description": "Update coupon status across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Coupon"
|
||||||
|
],
|
||||||
|
"summary": "Update coupon status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"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.SuperCouponStatusUpdateForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updated",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/creator-applications": {
|
||||||
|
"get": {
|
||||||
|
"description": "List creator applications across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Creator"
|
||||||
|
],
|
||||||
|
"summary": "List creator applications",
|
||||||
|
"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.TenantItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/creator-applications/{id}/review": {
|
||||||
|
"post": {
|
||||||
|
"description": "Approve or reject creator application",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Creator"
|
||||||
|
],
|
||||||
|
"summary": "Review creator application",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Tenant ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Review form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SuperCreatorApplicationReviewForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Reviewed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/creators": {
|
"/super/v1/creators": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List creator tenants (channels) across the platform",
|
"description": "List creator tenants (channels) across the platform",
|
||||||
@@ -457,6 +645,91 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/super/v1/payout-accounts": {
|
||||||
|
"get": {
|
||||||
|
"description": "List payout accounts across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"summary": "List payout accounts",
|
||||||
|
"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.SuperPayoutAccountItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/super/v1/payout-accounts/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Remove payout account across tenants",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"summary": "Remove payout account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Payout account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Removed",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/super/v1/reports/export": {
|
"/super/v1/reports/export": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Export platform report data",
|
"description": "Export platform report data",
|
||||||
@@ -6112,6 +6385,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperCouponGrantItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"coupon_id": {
|
||||||
|
"description": "CouponID 优惠券ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"coupon_title": {
|
||||||
|
"description": "CouponTitle 优惠券标题。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"description": "CreatedAt 领取时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "ID 用户券ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"order_id": {
|
||||||
|
"description": "OrderID 使用订单ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"used_at": {
|
||||||
|
"description": "UsedAt 使用时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"description": "UserID 用户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"description": "Username 用户名。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperCouponGrantResponse": {
|
"dto.SuperCouponGrantResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6206,6 +6540,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperCouponStatusUpdateForm": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"description": "Status 目标状态(frozen)。",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"frozen"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.SuperCreatorApplicationReviewForm": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"action"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"description": "Action 审核动作(approve/reject)。",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"approve",
|
||||||
|
"reject"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"description": "Reason 审核说明(可选,驳回时填写)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperOrderDetail": {
|
"dto.SuperOrderDetail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6363,6 +6732,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SuperPayoutAccountItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account": {
|
||||||
|
"description": "Account 收款账号。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"description": "CreatedAt 创建时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "ID 结算账户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Name 账户名称/开户行。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"realname": {
|
||||||
|
"description": "Realname 收款人姓名。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_code": {
|
||||||
|
"description": "TenantCode 租户编码。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"description": "TenantID 租户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tenant_name": {
|
||||||
|
"description": "TenantName 租户名称。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Type 账户类型。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"description": "UpdatedAt 更新时间(RFC3339)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"description": "UserID 用户ID。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"description": "Username 用户名。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SuperReportExportForm": {
|
"dto.SuperReportExportForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1060,6 +1060,49 @@ definitions:
|
|||||||
description: Name 租户名称。
|
description: Name 租户名称。
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.SuperCouponGrantItem:
|
||||||
|
properties:
|
||||||
|
coupon_id:
|
||||||
|
description: CouponID 优惠券ID。
|
||||||
|
type: integer
|
||||||
|
coupon_title:
|
||||||
|
description: CouponTitle 优惠券标题。
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
description: CreatedAt 领取时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
description: ID 用户券ID。
|
||||||
|
type: integer
|
||||||
|
order_id:
|
||||||
|
description: OrderID 使用订单ID。
|
||||||
|
type: integer
|
||||||
|
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
|
||||||
|
used_at:
|
||||||
|
description: UsedAt 使用时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
description: UserID 用户ID。
|
||||||
|
type: integer
|
||||||
|
username:
|
||||||
|
description: Username 用户名。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.SuperCouponGrantResponse:
|
dto.SuperCouponGrantResponse:
|
||||||
properties:
|
properties:
|
||||||
granted:
|
granted:
|
||||||
@@ -1127,6 +1170,30 @@ definitions:
|
|||||||
description: Value 优惠券面额/折扣值。
|
description: Value 优惠券面额/折扣值。
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
dto.SuperCouponStatusUpdateForm:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
description: Status 目标状态(frozen)。
|
||||||
|
enum:
|
||||||
|
- frozen
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
dto.SuperCreatorApplicationReviewForm:
|
||||||
|
properties:
|
||||||
|
action:
|
||||||
|
description: Action 审核动作(approve/reject)。
|
||||||
|
enum:
|
||||||
|
- approve
|
||||||
|
- reject
|
||||||
|
type: string
|
||||||
|
reason:
|
||||||
|
description: Reason 审核说明(可选,驳回时填写)。
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- action
|
||||||
|
type: object
|
||||||
dto.SuperOrderDetail:
|
dto.SuperOrderDetail:
|
||||||
properties:
|
properties:
|
||||||
buyer:
|
buyer:
|
||||||
@@ -1225,6 +1292,45 @@ definitions:
|
|||||||
description: Reason 退款原因说明。
|
description: Reason 退款原因说明。
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.SuperPayoutAccountItem:
|
||||||
|
properties:
|
||||||
|
account:
|
||||||
|
description: Account 收款账号。
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
description: CreatedAt 创建时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
description: ID 结算账户ID。
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
description: Name 账户名称/开户行。
|
||||||
|
type: string
|
||||||
|
realname:
|
||||||
|
description: Realname 收款人姓名。
|
||||||
|
type: string
|
||||||
|
tenant_code:
|
||||||
|
description: TenantCode 租户编码。
|
||||||
|
type: string
|
||||||
|
tenant_id:
|
||||||
|
description: TenantID 租户ID。
|
||||||
|
type: integer
|
||||||
|
tenant_name:
|
||||||
|
description: TenantName 租户名称。
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: Type 账户类型。
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
description: UpdatedAt 更新时间(RFC3339)。
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
description: UserID 用户ID。
|
||||||
|
type: integer
|
||||||
|
username:
|
||||||
|
description: Username 用户名。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.SuperReportExportForm:
|
dto.SuperReportExportForm:
|
||||||
properties:
|
properties:
|
||||||
end_at:
|
end_at:
|
||||||
@@ -2394,6 +2500,37 @@ paths:
|
|||||||
summary: Batch review contents
|
summary: Batch review contents
|
||||||
tags:
|
tags:
|
||||||
- Content
|
- Content
|
||||||
|
/super/v1/coupon-grants:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: List coupon grant records 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.SuperCouponGrantItem'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: List coupon grants
|
||||||
|
tags:
|
||||||
|
- Coupon
|
||||||
/super/v1/coupons:
|
/super/v1/coupons:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -2425,6 +2562,93 @@ paths:
|
|||||||
summary: List coupons
|
summary: List coupons
|
||||||
tags:
|
tags:
|
||||||
- Coupon
|
- Coupon
|
||||||
|
/super/v1/coupons/{id}/status:
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update coupon status across tenants
|
||||||
|
parameters:
|
||||||
|
- 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.SuperCouponStatusUpdateForm'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Updated
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Update coupon status
|
||||||
|
tags:
|
||||||
|
- Coupon
|
||||||
|
/super/v1/creator-applications:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: List creator applications 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.TenantItem'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: List creator applications
|
||||||
|
tags:
|
||||||
|
- Creator
|
||||||
|
/super/v1/creator-applications/{id}/review:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Approve or reject creator application
|
||||||
|
parameters:
|
||||||
|
- description: Tenant ID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Review form
|
||||||
|
in: body
|
||||||
|
name: form
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.SuperCreatorApplicationReviewForm'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Reviewed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Review creator application
|
||||||
|
tags:
|
||||||
|
- Creator
|
||||||
/super/v1/creators:
|
/super/v1/creators:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -2552,6 +2776,59 @@ paths:
|
|||||||
summary: Order statistics
|
summary: Order statistics
|
||||||
tags:
|
tags:
|
||||||
- Order
|
- Order
|
||||||
|
/super/v1/payout-accounts:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: List payout accounts 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.SuperPayoutAccountItem'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: List payout accounts
|
||||||
|
tags:
|
||||||
|
- Finance
|
||||||
|
/super/v1/payout-accounts/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Remove payout account across tenants
|
||||||
|
parameters:
|
||||||
|
- description: Payout account ID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Removed
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Remove payout account
|
||||||
|
tags:
|
||||||
|
- Finance
|
||||||
/super/v1/reports/export:
|
/super/v1/reports/export:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -52,13 +52,13 @@
|
|||||||
|
|
||||||
### 2.9 创作者与成员审核 `/superadmin/creators`
|
### 2.9 创作者与成员审核 `/superadmin/creators`
|
||||||
- 状态:**部分完成**
|
- 状态:**部分完成**
|
||||||
- 已有:创作者(租户)列表、状态更新、成员申请列表/审核、成员邀请创建。
|
- 已有:创作者(租户)列表、状态更新、创作者申请审核、成员申请列表/审核、成员邀请创建、结算账户列表与删除。
|
||||||
- 缺口:跨租户创作者申请审核、结算账户审核、提现审核入口(与财务联动)。
|
- 缺口:提现审核入口(与财务联动)、结算账户审批流(若需要区分通过/驳回状态)。
|
||||||
|
|
||||||
### 2.10 优惠券 `/superadmin/coupons`
|
### 2.10 优惠券 `/superadmin/coupons`
|
||||||
- 状态:**部分完成**
|
- 状态:**部分完成**
|
||||||
- 已有:跨租户优惠券列表、创建/编辑、发放。
|
- 已有:跨租户优惠券列表、创建/编辑、发放、冻结、发放记录查询。
|
||||||
- 缺口:冻结/归档、发放记录与异常核查。
|
- 缺口:异常核查与自动告警策略。
|
||||||
|
|
||||||
### 2.11 财务与钱包 `/superadmin/finance`
|
### 2.11 财务与钱包 `/superadmin/finance`
|
||||||
- 状态:**部分完成**
|
- 状态:**部分完成**
|
||||||
@@ -83,11 +83,11 @@
|
|||||||
## 3) `/super/v1` 接口覆盖度概览
|
## 3) `/super/v1` 接口覆盖度概览
|
||||||
|
|
||||||
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名)、Contents、Orders、Withdrawals、Reports、Coupons(列表/创建/编辑/发放)、Creators(列表)。
|
- **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名)、Contents、Orders、Withdrawals、Reports、Coupons(列表/创建/编辑/发放)、Creators(列表)。
|
||||||
- **缺失/待补**:资产治理、通知中心、用户互动明细(收藏/点赞/关注)、创作者申请/结算账户审核、优惠券冻结与发放记录。
|
- **缺失/待补**:资产治理、通知中心、用户互动明细(收藏/点赞/关注)、创作者提现审核、优惠券异常核查与风控。
|
||||||
|
|
||||||
## 4) 建议的下一步(按优先级)
|
## 4) 建议的下一步(按优先级)
|
||||||
|
|
||||||
1. **创作者/优惠券深度治理**:补齐创作者申请/结算账户审核、优惠券冻结/发放记录。
|
1. **平台概览增强**:补齐内容总量与趋势、退款率、订单漏斗等核心指标。
|
||||||
2. **平台概览增强**:补齐内容总量与趋势、退款率、订单漏斗等核心指标。
|
2. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。
|
||||||
3. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。
|
3. **用户互动明细**:补齐收藏/点赞/关注等互动明细视图与聚合能力。
|
||||||
4. **用户互动明细**:补齐收藏/点赞/关注等互动明细视图与聚合能力。
|
4. **优惠券异常核查**:完善发放/核销异常监测与风控处理流程。
|
||||||
|
|||||||
@@ -69,6 +69,46 @@ export const CouponService = {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { user_ids: userIDs }
|
body: { user_ids: userIDs }
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
async updateCouponStatus(couponID, status) {
|
||||||
|
if (!couponID) throw new Error('couponID is required');
|
||||||
|
if (!status) throw new Error('status is required');
|
||||||
|
return requestJson(`/super/v1/coupons/${couponID}/status`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: { status }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async listCouponGrants({ page, limit, coupon_id, tenant_id, tenant_code, tenant_name, user_id, username, status, created_at_from, created_at_to, used_at_from, used_at_to } = {}) {
|
||||||
|
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,
|
||||||
|
coupon_id,
|
||||||
|
tenant_id,
|
||||||
|
tenant_code,
|
||||||
|
tenant_name,
|
||||||
|
user_id,
|
||||||
|
username,
|
||||||
|
status,
|
||||||
|
created_at_from: iso(created_at_from),
|
||||||
|
created_at_to: iso(created_at_to),
|
||||||
|
used_at_from: iso(used_at_from),
|
||||||
|
used_at_to: iso(used_at_to)
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await requestJson('/super/v1/coupon-grants', { query });
|
||||||
|
return {
|
||||||
|
page: data?.page ?? page ?? 1,
|
||||||
|
limit: data?.limit ?? limit ?? 10,
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
items: normalizeItems(data?.items)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,41 @@ export const CreatorService = {
|
|||||||
items: normalizeItems(data?.items)
|
items: normalizeItems(data?.items)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
async listCreatorApplications({ page, limit, id, user_id, name, code, status, created_at_from, created_at_to } = {}) {
|
||||||
|
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,
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
code,
|
||||||
|
status,
|
||||||
|
created_at_from: iso(created_at_from),
|
||||||
|
created_at_to: iso(created_at_to)
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await requestJson('/super/v1/creator-applications', { query });
|
||||||
|
return {
|
||||||
|
page: data?.page ?? page ?? 1,
|
||||||
|
limit: data?.limit ?? limit ?? 10,
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
items: normalizeItems(data?.items)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async reviewCreatorApplication(tenantID, { action, reason } = {}) {
|
||||||
|
if (!tenantID) throw new Error('tenantID is required');
|
||||||
|
return requestJson(`/super/v1/creator-applications/${tenantID}/review`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: { action, reason }
|
||||||
|
});
|
||||||
|
},
|
||||||
async reviewJoinRequest(requestID, { action, reason } = {}) {
|
async reviewJoinRequest(requestID, { action, reason } = {}) {
|
||||||
if (!requestID) throw new Error('requestID is required');
|
if (!requestID) throw new Error('requestID is required');
|
||||||
return requestJson(`/super/v1/tenant-join-requests/${requestID}/review`, {
|
return requestJson(`/super/v1/tenant-join-requests/${requestID}/review`, {
|
||||||
@@ -75,6 +110,39 @@ export const CreatorService = {
|
|||||||
body: { action, reason }
|
body: { action, reason }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async listPayoutAccounts({ page, limit, tenant_id, tenant_code, tenant_name, user_id, username, type, created_at_from, created_at_to } = {}) {
|
||||||
|
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,
|
||||||
|
user_id,
|
||||||
|
username,
|
||||||
|
type,
|
||||||
|
created_at_from: iso(created_at_from),
|
||||||
|
created_at_to: iso(created_at_to)
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await requestJson('/super/v1/payout-accounts', { query });
|
||||||
|
return {
|
||||||
|
page: data?.page ?? page ?? 1,
|
||||||
|
limit: data?.limit ?? limit ?? 10,
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
items: normalizeItems(data?.items)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async removePayoutAccount(accountID) {
|
||||||
|
if (!accountID) throw new Error('accountID is required');
|
||||||
|
return requestJson(`/super/v1/payout-accounts/${accountID}`, { method: 'DELETE' });
|
||||||
|
},
|
||||||
async createInvite(tenantID, { max_uses, expires_at, remark } = {}) {
|
async createInvite(tenantID, { max_uses, expires_at, remark } = {}) {
|
||||||
if (!tenantID) throw new Error('tenantID is required');
|
if (!tenantID) throw new Error('tenantID is required');
|
||||||
return requestJson(`/super/v1/tenants/${tenantID}/invites`, {
|
return requestJson(`/super/v1/tenants/${tenantID}/invites`, {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { onMounted, ref } from 'vue';
|
|||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
const tabValue = ref('coupons');
|
||||||
|
|
||||||
const coupons = ref([]);
|
const coupons = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
@@ -26,6 +28,23 @@ const createdAtTo = ref(null);
|
|||||||
const sortField = ref('created_at');
|
const sortField = ref('created_at');
|
||||||
const sortOrder = ref(-1);
|
const sortOrder = ref(-1);
|
||||||
|
|
||||||
|
const grants = ref([]);
|
||||||
|
const grantsLoading = ref(false);
|
||||||
|
const grantsTotal = ref(0);
|
||||||
|
const grantsPage = ref(1);
|
||||||
|
const grantsRows = ref(10);
|
||||||
|
const grantCouponIDFilter = ref(null);
|
||||||
|
const grantTenantID = ref(null);
|
||||||
|
const grantTenantCode = ref('');
|
||||||
|
const grantTenantName = ref('');
|
||||||
|
const grantUserID = ref(null);
|
||||||
|
const grantUsername = ref('');
|
||||||
|
const grantStatus = ref('');
|
||||||
|
const grantCreatedAtFrom = ref(null);
|
||||||
|
const grantCreatedAtTo = ref(null);
|
||||||
|
const grantUsedAtFrom = ref(null);
|
||||||
|
const grantUsedAtTo = ref(null);
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '固定金额', value: 'fix_amount' },
|
{ label: '固定金额', value: 'fix_amount' },
|
||||||
@@ -39,6 +58,13 @@ const statusOptions = [
|
|||||||
{ label: '已过期', value: 'expired' }
|
{ label: '已过期', value: 'expired' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const grantStatusOptions = [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
{ label: '未使用', value: 'unused' },
|
||||||
|
{ label: '已使用', value: 'used' },
|
||||||
|
{ label: '已过期', value: 'expired' }
|
||||||
|
];
|
||||||
|
|
||||||
const editDialogVisible = ref(false);
|
const editDialogVisible = ref(false);
|
||||||
const couponSubmitting = ref(false);
|
const couponSubmitting = ref(false);
|
||||||
const editingCoupon = ref(null);
|
const editingCoupon = ref(null);
|
||||||
@@ -58,6 +84,10 @@ const grantSubmitting = ref(false);
|
|||||||
const grantCoupon = ref(null);
|
const grantCoupon = ref(null);
|
||||||
const grantUserIDsText = ref('');
|
const grantUserIDsText = ref('');
|
||||||
|
|
||||||
|
const freezeDialogVisible = ref(false);
|
||||||
|
const freezeSubmitting = ref(false);
|
||||||
|
const freezeTarget = ref(null);
|
||||||
|
|
||||||
function formatDate(value) {
|
function formatDate(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
if (String(value).startsWith('0001-01-01')) return '-';
|
if (String(value).startsWith('0001-01-01')) return '-';
|
||||||
@@ -92,6 +122,19 @@ function getStatusSeverity(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGrantStatusSeverity(value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'unused':
|
||||||
|
return 'success';
|
||||||
|
case 'used':
|
||||||
|
return 'secondary';
|
||||||
|
case 'expired':
|
||||||
|
return 'danger';
|
||||||
|
default:
|
||||||
|
return 'secondary';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetCouponForm() {
|
function resetCouponForm() {
|
||||||
formTenantID.value = null;
|
formTenantID.value = null;
|
||||||
formTitle.value = '';
|
formTitle.value = '';
|
||||||
@@ -197,6 +240,27 @@ async function confirmGrant() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFreezeDialog(row) {
|
||||||
|
freezeTarget.value = row;
|
||||||
|
freezeDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmFreeze() {
|
||||||
|
const couponIDValue = freezeTarget.value?.id;
|
||||||
|
if (!couponIDValue) return;
|
||||||
|
freezeSubmitting.value = true;
|
||||||
|
try {
|
||||||
|
await CouponService.updateCouponStatus(couponIDValue, 'frozen');
|
||||||
|
toast.add({ severity: 'success', summary: '已冻结', detail: `CouponID: ${couponIDValue}`, life: 3000 });
|
||||||
|
freezeDialogVisible.value = false;
|
||||||
|
await loadCoupons();
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '冻结失败', detail: error?.message || '无法冻结优惠券', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
freezeSubmitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadCoupons() {
|
async function loadCoupons() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -224,11 +288,43 @@ async function loadCoupons() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadGrants() {
|
||||||
|
grantsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await CouponService.listCouponGrants({
|
||||||
|
page: grantsPage.value,
|
||||||
|
limit: grantsRows.value,
|
||||||
|
coupon_id: grantCouponIDFilter.value || undefined,
|
||||||
|
tenant_id: grantTenantID.value || undefined,
|
||||||
|
tenant_code: grantTenantCode.value,
|
||||||
|
tenant_name: grantTenantName.value,
|
||||||
|
user_id: grantUserID.value || undefined,
|
||||||
|
username: grantUsername.value,
|
||||||
|
status: grantStatus.value || undefined,
|
||||||
|
created_at_from: grantCreatedAtFrom.value || undefined,
|
||||||
|
created_at_to: grantCreatedAtTo.value || undefined,
|
||||||
|
used_at_from: grantUsedAtFrom.value || undefined,
|
||||||
|
used_at_to: grantUsedAtTo.value || undefined
|
||||||
|
});
|
||||||
|
grants.value = result.items;
|
||||||
|
grantsTotal.value = result.total;
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载发放记录', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
grantsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onSearch() {
|
function onSearch() {
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
loadCoupons();
|
loadCoupons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGrantSearch() {
|
||||||
|
grantsPage.value = 1;
|
||||||
|
loadGrants();
|
||||||
|
}
|
||||||
|
|
||||||
function onReset() {
|
function onReset() {
|
||||||
couponID.value = null;
|
couponID.value = null;
|
||||||
tenantID.value = null;
|
tenantID.value = null;
|
||||||
@@ -246,12 +342,35 @@ function onReset() {
|
|||||||
loadCoupons();
|
loadCoupons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGrantReset() {
|
||||||
|
grantCouponIDFilter.value = null;
|
||||||
|
grantTenantID.value = null;
|
||||||
|
grantTenantCode.value = '';
|
||||||
|
grantTenantName.value = '';
|
||||||
|
grantUserID.value = null;
|
||||||
|
grantUsername.value = '';
|
||||||
|
grantStatus.value = '';
|
||||||
|
grantCreatedAtFrom.value = null;
|
||||||
|
grantCreatedAtTo.value = null;
|
||||||
|
grantUsedAtFrom.value = null;
|
||||||
|
grantUsedAtTo.value = null;
|
||||||
|
grantsPage.value = 1;
|
||||||
|
grantsRows.value = 10;
|
||||||
|
loadGrants();
|
||||||
|
}
|
||||||
|
|
||||||
function onPage(event) {
|
function onPage(event) {
|
||||||
page.value = (event.page ?? 0) + 1;
|
page.value = (event.page ?? 0) + 1;
|
||||||
rows.value = event.rows ?? rows.value;
|
rows.value = event.rows ?? rows.value;
|
||||||
loadCoupons();
|
loadCoupons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGrantPage(event) {
|
||||||
|
grantsPage.value = (event.page ?? 0) + 1;
|
||||||
|
grantsRows.value = event.rows ?? grantsRows.value;
|
||||||
|
loadGrants();
|
||||||
|
}
|
||||||
|
|
||||||
function onSort(event) {
|
function onSort(event) {
|
||||||
sortField.value = event.sortField ?? sortField.value;
|
sortField.value = event.sortField ?? sortField.value;
|
||||||
sortOrder.value = event.sortOrder ?? sortOrder.value;
|
sortOrder.value = event.sortOrder ?? sortOrder.value;
|
||||||
@@ -260,11 +379,19 @@ function onSort(event) {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCoupons();
|
loadCoupons();
|
||||||
|
loadGrants();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
<Tabs v-model:value="tabValue" value="coupons">
|
||||||
|
<TabList>
|
||||||
|
<Tab value="coupons">券模板</Tab>
|
||||||
|
<Tab value="grants">发放记录</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel value="coupons">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h4 class="m-0">优惠券</h4>
|
<h4 class="m-0">优惠券</h4>
|
||||||
<Button label="新建优惠券" icon="pi pi-plus" @click="openCreateDialog" />
|
<Button label="新建优惠券" icon="pi pi-plus" @click="openCreateDialog" />
|
||||||
@@ -381,10 +508,125 @@ onMounted(() => {
|
|||||||
<Column header="操作" style="min-width: 12rem">
|
<Column header="操作" style="min-width: 12rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button label="编辑" icon="pi pi-pencil" text size="small" class="p-0 mr-3" @click="openEditDialog(data)" />
|
<Button label="编辑" icon="pi pi-pencil" text size="small" class="p-0 mr-3" @click="openEditDialog(data)" />
|
||||||
<Button label="发放" icon="pi pi-send" text size="small" class="p-0" @click="openGrantDialog(data)" />
|
<Button label="发放" icon="pi pi-send" text size="small" class="p-0 mr-3" @click="openGrantDialog(data)" />
|
||||||
|
<Button label="冻结" icon="pi pi-lock" text size="small" severity="danger" class="p-0" :disabled="data?.status === 'expired'" @click="openFreezeDialog(data)" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="grants">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h4 class="m-0">发放记录</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchPanel :loading="grantsLoading" @search="onGrantSearch" @reset="onGrantReset">
|
||||||
|
<SearchField label="CouponID">
|
||||||
|
<InputNumber v-model="grantCouponIDFilter" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="TenantID">
|
||||||
|
<InputNumber v-model="grantTenantID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Tenant Code">
|
||||||
|
<InputText v-model="grantTenantCode" placeholder="模糊匹配" class="w-full" @keyup.enter="onGrantSearch" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Tenant Name">
|
||||||
|
<InputText v-model="grantTenantName" placeholder="模糊匹配" class="w-full" @keyup.enter="onGrantSearch" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="UserID">
|
||||||
|
<InputNumber v-model="grantUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Username">
|
||||||
|
<IconField>
|
||||||
|
<InputIcon>
|
||||||
|
<i class="pi pi-search" />
|
||||||
|
</InputIcon>
|
||||||
|
<InputText v-model="grantUsername" placeholder="模糊匹配" class="w-full" @keyup.enter="onGrantSearch" />
|
||||||
|
</IconField>
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="状态">
|
||||||
|
<Select v-model="grantStatus" :options="grantStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="领取时间 From">
|
||||||
|
<DatePicker v-model="grantCreatedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="领取时间 To">
|
||||||
|
<DatePicker v-model="grantCreatedAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="使用时间 From">
|
||||||
|
<DatePicker v-model="grantUsedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="使用时间 To">
|
||||||
|
<DatePicker v-model="grantUsedAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
</SearchPanel>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:value="grants"
|
||||||
|
dataKey="id"
|
||||||
|
:loading="grantsLoading"
|
||||||
|
lazy
|
||||||
|
:paginator="true"
|
||||||
|
:rows="grantsRows"
|
||||||
|
:totalRecords="grantsTotal"
|
||||||
|
:first="(grantsPage - 1) * grantsRows"
|
||||||
|
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||||
|
@page="onGrantPage"
|
||||||
|
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||||
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||||
|
scrollable
|
||||||
|
scrollHeight="flex"
|
||||||
|
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.coupon_title || '-' }}</span>
|
||||||
|
<span class="text-xs text-muted-color">CouponID: {{ data.coupon_id ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</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 header="用户" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<router-link v-if="data.user_id" class="inline-flex items-center gap-1 font-medium text-primary hover:underline" :to="`/superadmin/users/${data.user_id}`">
|
||||||
|
<span class="truncate max-w-[200px]">{{ data.username || `ID:${data.user_id}` }}</span>
|
||||||
|
<i class="pi pi-external-link text-xs" />
|
||||||
|
</router-link>
|
||||||
|
<div class="text-xs text-muted-color">ID: {{ data.user_id ?? '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="status" header="状态" style="min-width: 10rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :value="data.status_description || data.status || '-'" :severity="getGrantStatusSeverity(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>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog v-model:visible="editDialogVisible" :modal="true" :style="{ width: '600px' }">
|
<Dialog v-model:visible="editDialogVisible" :modal="true" :style="{ width: '600px' }">
|
||||||
@@ -470,4 +712,24 @@ onMounted(() => {
|
|||||||
<Button label="确认发放" icon="pi pi-send" @click="confirmGrant" :loading="grantSubmitting" />
|
<Button label="确认发放" icon="pi pi-send" @click="confirmGrant" :loading="grantSubmitting" />
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog v-model:visible="freezeDialogVisible" :modal="true" :style="{ width: '420px' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-medium">冻结优惠券</span>
|
||||||
|
<span class="text-muted-color">ID: {{ freezeTarget?.id ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="text-sm text-muted-color">冻结后将停止该券的发放与使用,请确认。</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<span class="font-medium">{{ freezeTarget?.title || '-' }}</span>
|
||||||
|
<span class="text-muted-color">(TenantID: {{ freezeTarget?.tenant_id ?? '-' }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button label="取消" icon="pi pi-times" text @click="freezeDialogVisible = false" :disabled="freezeSubmitting" />
|
||||||
|
<Button label="确认冻结" icon="pi pi-lock" severity="danger" @click="confirmFreeze" :loading="freezeSubmitting" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import SearchPanel from '@/components/SearchPanel.vue';
|
|||||||
import { CreatorService } from '@/service/CreatorService';
|
import { CreatorService } from '@/service/CreatorService';
|
||||||
import { TenantService } from '@/service/TenantService';
|
import { TenantService } from '@/service/TenantService';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -34,6 +34,8 @@ const statusUpdating = ref(false);
|
|||||||
const statusTenant = ref(null);
|
const statusTenant = ref(null);
|
||||||
const statusValue = ref(null);
|
const statusValue = ref(null);
|
||||||
|
|
||||||
|
const applicationStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(statusOptions.value || [])]);
|
||||||
|
|
||||||
const joinRequests = ref([]);
|
const joinRequests = ref([]);
|
||||||
const joinRequestsLoading = ref(false);
|
const joinRequestsLoading = ref(false);
|
||||||
const joinRequestsTotal = ref(0);
|
const joinRequestsTotal = ref(0);
|
||||||
@@ -48,6 +50,33 @@ const joinStatus = ref('pending');
|
|||||||
const joinCreatedAtFrom = ref(null);
|
const joinCreatedAtFrom = ref(null);
|
||||||
const joinCreatedAtTo = ref(null);
|
const joinCreatedAtTo = ref(null);
|
||||||
|
|
||||||
|
const applications = ref([]);
|
||||||
|
const applicationsLoading = ref(false);
|
||||||
|
const applicationsTotal = ref(0);
|
||||||
|
const applicationsPage = ref(1);
|
||||||
|
const applicationsRows = ref(10);
|
||||||
|
const applicationTenantID = ref(null);
|
||||||
|
const applicationOwnerUserID = ref(null);
|
||||||
|
const applicationName = ref('');
|
||||||
|
const applicationCode = ref('');
|
||||||
|
const applicationStatus = ref('pending_verify');
|
||||||
|
const applicationCreatedAtFrom = ref(null);
|
||||||
|
const applicationCreatedAtTo = ref(null);
|
||||||
|
|
||||||
|
const payoutAccounts = ref([]);
|
||||||
|
const payoutAccountsLoading = ref(false);
|
||||||
|
const payoutAccountsTotal = ref(0);
|
||||||
|
const payoutAccountsPage = ref(1);
|
||||||
|
const payoutAccountsRows = ref(10);
|
||||||
|
const payoutTenantID = ref(null);
|
||||||
|
const payoutTenantCode = ref('');
|
||||||
|
const payoutTenantName = ref('');
|
||||||
|
const payoutUserID = ref(null);
|
||||||
|
const payoutUsername = ref('');
|
||||||
|
const payoutType = ref('');
|
||||||
|
const payoutCreatedAtFrom = ref(null);
|
||||||
|
const payoutCreatedAtTo = ref(null);
|
||||||
|
|
||||||
const joinStatusOptions = [
|
const joinStatusOptions = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '待审核', value: 'pending' },
|
{ label: '待审核', value: 'pending' },
|
||||||
@@ -61,6 +90,16 @@ const reviewAction = ref('approve');
|
|||||||
const reviewReason = ref('');
|
const reviewReason = ref('');
|
||||||
const reviewTarget = ref(null);
|
const reviewTarget = ref(null);
|
||||||
|
|
||||||
|
const applicationReviewDialogVisible = ref(false);
|
||||||
|
const applicationReviewSubmitting = ref(false);
|
||||||
|
const applicationReviewAction = ref('approve');
|
||||||
|
const applicationReviewReason = ref('');
|
||||||
|
const applicationReviewTarget = ref(null);
|
||||||
|
|
||||||
|
const payoutRemoveDialogVisible = ref(false);
|
||||||
|
const payoutRemoveSubmitting = ref(false);
|
||||||
|
const payoutRemoveTarget = ref(null);
|
||||||
|
|
||||||
const inviteDialogVisible = ref(false);
|
const inviteDialogVisible = ref(false);
|
||||||
const inviteSubmitting = ref(false);
|
const inviteSubmitting = ref(false);
|
||||||
const inviteTenantID = ref(null);
|
const inviteTenantID = ref(null);
|
||||||
@@ -166,6 +205,53 @@ async function loadJoinRequests() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadCreatorApplications() {
|
||||||
|
applicationsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await CreatorService.listCreatorApplications({
|
||||||
|
page: applicationsPage.value,
|
||||||
|
limit: applicationsRows.value,
|
||||||
|
id: applicationTenantID.value || undefined,
|
||||||
|
user_id: applicationOwnerUserID.value || undefined,
|
||||||
|
name: applicationName.value,
|
||||||
|
code: applicationCode.value,
|
||||||
|
status: applicationStatus.value || undefined,
|
||||||
|
created_at_from: applicationCreatedAtFrom.value || undefined,
|
||||||
|
created_at_to: applicationCreatedAtTo.value || undefined
|
||||||
|
});
|
||||||
|
applications.value = result.items;
|
||||||
|
applicationsTotal.value = result.total;
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载创作者申请', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
applicationsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPayoutAccounts() {
|
||||||
|
payoutAccountsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await CreatorService.listPayoutAccounts({
|
||||||
|
page: payoutAccountsPage.value,
|
||||||
|
limit: payoutAccountsRows.value,
|
||||||
|
tenant_id: payoutTenantID.value || undefined,
|
||||||
|
tenant_code: payoutTenantCode.value,
|
||||||
|
tenant_name: payoutTenantName.value,
|
||||||
|
user_id: payoutUserID.value || undefined,
|
||||||
|
username: payoutUsername.value,
|
||||||
|
type: payoutType.value || undefined,
|
||||||
|
created_at_from: payoutCreatedAtFrom.value || undefined,
|
||||||
|
created_at_to: payoutCreatedAtTo.value || undefined
|
||||||
|
});
|
||||||
|
payoutAccounts.value = result.items;
|
||||||
|
payoutAccountsTotal.value = result.total;
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载结算账户', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
payoutAccountsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onSearch() {
|
function onSearch() {
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
loadCreators();
|
loadCreators();
|
||||||
@@ -223,6 +309,55 @@ function onJoinPage(event) {
|
|||||||
loadJoinRequests();
|
loadJoinRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onApplicationSearch() {
|
||||||
|
applicationsPage.value = 1;
|
||||||
|
loadCreatorApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onApplicationReset() {
|
||||||
|
applicationTenantID.value = null;
|
||||||
|
applicationOwnerUserID.value = null;
|
||||||
|
applicationName.value = '';
|
||||||
|
applicationCode.value = '';
|
||||||
|
applicationStatus.value = 'pending_verify';
|
||||||
|
applicationCreatedAtFrom.value = null;
|
||||||
|
applicationCreatedAtTo.value = null;
|
||||||
|
applicationsPage.value = 1;
|
||||||
|
applicationsRows.value = 10;
|
||||||
|
loadCreatorApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onApplicationPage(event) {
|
||||||
|
applicationsPage.value = (event.page ?? 0) + 1;
|
||||||
|
applicationsRows.value = event.rows ?? applicationsRows.value;
|
||||||
|
loadCreatorApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPayoutSearch() {
|
||||||
|
payoutAccountsPage.value = 1;
|
||||||
|
loadPayoutAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPayoutReset() {
|
||||||
|
payoutTenantID.value = null;
|
||||||
|
payoutTenantCode.value = '';
|
||||||
|
payoutTenantName.value = '';
|
||||||
|
payoutUserID.value = null;
|
||||||
|
payoutUsername.value = '';
|
||||||
|
payoutType.value = '';
|
||||||
|
payoutCreatedAtFrom.value = null;
|
||||||
|
payoutCreatedAtTo.value = null;
|
||||||
|
payoutAccountsPage.value = 1;
|
||||||
|
payoutAccountsRows.value = 10;
|
||||||
|
loadPayoutAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPayoutPage(event) {
|
||||||
|
payoutAccountsPage.value = (event.page ?? 0) + 1;
|
||||||
|
payoutAccountsRows.value = event.rows ?? payoutAccountsRows.value;
|
||||||
|
loadPayoutAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
async function openStatusDialog(tenant) {
|
async function openStatusDialog(tenant) {
|
||||||
statusTenant.value = tenant;
|
statusTenant.value = tenant;
|
||||||
statusValue.value = tenant?.status ?? null;
|
statusValue.value = tenant?.status ?? null;
|
||||||
@@ -280,6 +415,57 @@ async function confirmReview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openApplicationReviewDialog(row, action) {
|
||||||
|
applicationReviewTarget.value = row;
|
||||||
|
applicationReviewAction.value = action || 'approve';
|
||||||
|
applicationReviewReason.value = '';
|
||||||
|
applicationReviewDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmApplicationReview() {
|
||||||
|
const targetID = applicationReviewTarget.value?.id;
|
||||||
|
if (!targetID) return;
|
||||||
|
const reason = applicationReviewReason.value.trim();
|
||||||
|
if (applicationReviewAction.value === 'reject' && !reason) {
|
||||||
|
toast.add({ severity: 'warn', summary: '请输入原因', detail: '驳回时需填写原因', life: 3000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationReviewSubmitting.value = true;
|
||||||
|
try {
|
||||||
|
await CreatorService.reviewCreatorApplication(targetID, { action: applicationReviewAction.value, reason });
|
||||||
|
toast.add({ severity: 'success', summary: '审核完成', detail: `TenantID: ${targetID}`, life: 3000 });
|
||||||
|
applicationReviewDialogVisible.value = false;
|
||||||
|
await loadCreatorApplications();
|
||||||
|
await loadCreators();
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '审核失败', detail: error?.message || '无法审核申请', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
applicationReviewSubmitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPayoutRemoveDialog(row) {
|
||||||
|
payoutRemoveTarget.value = row;
|
||||||
|
payoutRemoveDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmRemovePayoutAccount() {
|
||||||
|
const targetID = payoutRemoveTarget.value?.id;
|
||||||
|
if (!targetID) return;
|
||||||
|
payoutRemoveSubmitting.value = true;
|
||||||
|
try {
|
||||||
|
await CreatorService.removePayoutAccount(targetID);
|
||||||
|
toast.add({ severity: 'success', summary: '已删除', detail: `账户ID: ${targetID}`, life: 3000 });
|
||||||
|
payoutRemoveDialogVisible.value = false;
|
||||||
|
await loadPayoutAccounts();
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: '删除失败', detail: error?.message || '无法删除结算账户', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
payoutRemoveSubmitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openInviteDialog(row) {
|
function openInviteDialog(row) {
|
||||||
inviteTenantID.value = Number(row?.tenant_id ?? row?.tenant?.id ?? 0) || null;
|
inviteTenantID.value = Number(row?.tenant_id ?? row?.tenant?.id ?? 0) || null;
|
||||||
inviteMaxUses.value = 1;
|
inviteMaxUses.value = 1;
|
||||||
@@ -315,6 +501,8 @@ onMounted(() => {
|
|||||||
loadCreators();
|
loadCreators();
|
||||||
ensureStatusOptionsLoaded().catch(() => {});
|
ensureStatusOptionsLoaded().catch(() => {});
|
||||||
loadJoinRequests();
|
loadJoinRequests();
|
||||||
|
loadCreatorApplications();
|
||||||
|
loadPayoutAccounts();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -323,7 +511,9 @@ onMounted(() => {
|
|||||||
<Tabs v-model:value="tabValue" value="creators">
|
<Tabs v-model:value="tabValue" value="creators">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value="creators">创作者列表</Tab>
|
<Tab value="creators">创作者列表</Tab>
|
||||||
|
<Tab value="applications">申请审核</Tab>
|
||||||
<Tab value="members">成员审核</Tab>
|
<Tab value="members">成员审核</Tab>
|
||||||
|
<Tab value="payouts">结算账户</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel value="creators">
|
<TabPanel value="creators">
|
||||||
@@ -412,6 +602,96 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value="applications">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h4 class="m-0">创作者申请</h4>
|
||||||
|
<span class="text-muted-color">审核待开通创作者</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchPanel :loading="applicationsLoading" @search="onApplicationSearch" @reset="onApplicationReset">
|
||||||
|
<SearchField label="TenantID">
|
||||||
|
<InputNumber v-model="applicationTenantID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Owner UserID">
|
||||||
|
<InputNumber v-model="applicationOwnerUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="名称">
|
||||||
|
<IconField>
|
||||||
|
<InputIcon>
|
||||||
|
<i class="pi pi-search" />
|
||||||
|
</InputIcon>
|
||||||
|
<InputText v-model="applicationName" placeholder="模糊匹配" class="w-full" @keyup.enter="onApplicationSearch" />
|
||||||
|
</IconField>
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Code">
|
||||||
|
<IconField>
|
||||||
|
<InputIcon>
|
||||||
|
<i class="pi pi-search" />
|
||||||
|
</InputIcon>
|
||||||
|
<InputText v-model="applicationCode" placeholder="模糊匹配" class="w-full" @keyup.enter="onApplicationSearch" />
|
||||||
|
</IconField>
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="状态">
|
||||||
|
<Select v-model="applicationStatus" :options="applicationStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" :loading="statusOptionsLoading" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="申请时间 From">
|
||||||
|
<DatePicker v-model="applicationCreatedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="申请时间 To">
|
||||||
|
<DatePicker v-model="applicationCreatedAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
</SearchPanel>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:value="applications"
|
||||||
|
dataKey="id"
|
||||||
|
:loading="applicationsLoading"
|
||||||
|
lazy
|
||||||
|
:paginator="true"
|
||||||
|
:rows="applicationsRows"
|
||||||
|
:totalRecords="applicationsTotal"
|
||||||
|
:first="(applicationsPage - 1) * applicationsRows"
|
||||||
|
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||||
|
@page="onApplicationPage"
|
||||||
|
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||||
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||||
|
scrollable
|
||||||
|
scrollHeight="flex"
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
>
|
||||||
|
<Column field="id" header="ID" style="min-width: 6rem" />
|
||||||
|
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||||
|
<Column field="name" header="名称" style="min-width: 16rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button :label="data.name || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/tenants/${data.id}`" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="Owner" style="min-width: 12rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<span>{{ data?.owner?.username ?? '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="status" header="状态" style="min-width: 10rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :value="data.status_description || data.status || '-'" :severity="getStatusSeverity(data.status)" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="created_at" header="申请时间" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatDate(data.created_at) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="操作" style="min-width: 12rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button v-if="data.status === 'pending_verify'" label="通过" icon="pi pi-check" text size="small" class="p-0 mr-3" @click="openApplicationReviewDialog(data, 'approve')" />
|
||||||
|
<Button v-if="data.status === 'pending_verify'" label="驳回" icon="pi pi-times" severity="danger" text size="small" class="p-0" @click="openApplicationReviewDialog(data, 'reject')" />
|
||||||
|
<span v-if="data.status !== 'pending_verify'" class="text-muted-color">已处理</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</TabPanel>
|
||||||
<TabPanel value="members">
|
<TabPanel value="members">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@@ -514,6 +794,97 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value="payouts">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h4 class="m-0">结算账户</h4>
|
||||||
|
<span class="text-muted-color">跨租户结算账户审查</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchPanel :loading="payoutAccountsLoading" @search="onPayoutSearch" @reset="onPayoutReset">
|
||||||
|
<SearchField label="TenantID">
|
||||||
|
<InputNumber v-model="payoutTenantID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="TenantCode">
|
||||||
|
<InputText v-model="payoutTenantCode" placeholder="模糊匹配" class="w-full" @keyup.enter="onPayoutSearch" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="TenantName">
|
||||||
|
<InputText v-model="payoutTenantName" placeholder="模糊匹配" class="w-full" @keyup.enter="onPayoutSearch" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="UserID">
|
||||||
|
<InputNumber v-model="payoutUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="Username">
|
||||||
|
<IconField>
|
||||||
|
<InputIcon>
|
||||||
|
<i class="pi pi-search" />
|
||||||
|
</InputIcon>
|
||||||
|
<InputText v-model="payoutUsername" placeholder="模糊匹配" class="w-full" @keyup.enter="onPayoutSearch" />
|
||||||
|
</IconField>
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="类型">
|
||||||
|
<InputText v-model="payoutType" placeholder="bank/alipay" class="w-full" @keyup.enter="onPayoutSearch" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="创建时间 From">
|
||||||
|
<DatePicker v-model="payoutCreatedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
<SearchField label="创建时间 To">
|
||||||
|
<DatePicker v-model="payoutCreatedAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||||
|
</SearchField>
|
||||||
|
</SearchPanel>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:value="payoutAccounts"
|
||||||
|
dataKey="id"
|
||||||
|
:loading="payoutAccountsLoading"
|
||||||
|
lazy
|
||||||
|
:paginator="true"
|
||||||
|
:rows="payoutAccountsRows"
|
||||||
|
:totalRecords="payoutAccountsTotal"
|
||||||
|
:first="(payoutAccountsPage - 1) * payoutAccountsRows"
|
||||||
|
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||||
|
@page="onPayoutPage"
|
||||||
|
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: 6rem" />
|
||||||
|
<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 header="用户" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<router-link v-if="data.user_id" class="inline-flex items-center gap-1 font-medium text-primary hover:underline" :to="`/superadmin/users/${data.user_id}`">
|
||||||
|
<span class="truncate max-w-[200px]">{{ data.username || `ID:${data.user_id}` }}</span>
|
||||||
|
<i class="pi pi-external-link text-xs" />
|
||||||
|
</router-link>
|
||||||
|
<div class="text-xs text-muted-color">ID: {{ data.user_id ?? '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="type" header="类型" style="min-width: 10rem" />
|
||||||
|
<Column field="name" header="账户名称" style="min-width: 14rem" />
|
||||||
|
<Column field="account" header="账号" style="min-width: 14rem" />
|
||||||
|
<Column field="realname" header="收款人" style="min-width: 12rem" />
|
||||||
|
<Column field="created_at" header="创建时间" style="min-width: 14rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatDate(data.created_at) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="操作" style="min-width: 8rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button label="删除" icon="pi pi-trash" severity="danger" text size="small" class="p-0" @click="openPayoutRemoveDialog(data)" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -570,6 +941,66 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog v-model:visible="applicationReviewDialogVisible" :modal="true" :style="{ width: '520px' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-medium">创作者申请审核</span>
|
||||||
|
<span class="text-muted-color">TenantID: {{ applicationReviewTarget?.id ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="text-sm text-muted-color">审核创作者申请,请确认处理动作与备注。</div>
|
||||||
|
<div>
|
||||||
|
<label class="block font-medium mb-2">审核动作</label>
|
||||||
|
<Select
|
||||||
|
v-model="applicationReviewAction"
|
||||||
|
:options="[
|
||||||
|
{ label: '通过', value: 'approve' },
|
||||||
|
{ label: '驳回', value: 'reject' }
|
||||||
|
]"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block font-medium mb-2">审核说明</label>
|
||||||
|
<InputText v-model="applicationReviewReason" placeholder="驳回时建议填写原因" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button label="取消" icon="pi pi-times" text @click="applicationReviewDialogVisible = false" :disabled="applicationReviewSubmitting" />
|
||||||
|
<Button
|
||||||
|
label="确认审核"
|
||||||
|
icon="pi pi-check"
|
||||||
|
severity="success"
|
||||||
|
@click="confirmApplicationReview"
|
||||||
|
:loading="applicationReviewSubmitting"
|
||||||
|
:disabled="applicationReviewSubmitting || (applicationReviewAction === 'reject' && !applicationReviewReason.trim())"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog v-model:visible="payoutRemoveDialogVisible" :modal="true" :style="{ width: '420px' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-medium">删除结算账户</span>
|
||||||
|
<span class="text-muted-color">账户ID: {{ payoutRemoveTarget?.id ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="text-sm text-muted-color">该操作将移除结算账户信息,请确认。</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<span class="font-medium">{{ payoutRemoveTarget?.name || '-' }}</span>
|
||||||
|
<span class="text-muted-color">({{ payoutRemoveTarget?.account || '-' }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button label="取消" icon="pi pi-times" text @click="payoutRemoveDialogVisible = false" :disabled="payoutRemoveSubmitting" />
|
||||||
|
<Button label="确认删除" icon="pi pi-trash" severity="danger" @click="confirmRemovePayoutAccount" :loading="payoutRemoveSubmitting" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<Dialog v-model:visible="inviteDialogVisible" :modal="true" :style="{ width: '520px' }">
|
<Dialog v-model:visible="inviteDialogVisible" :modal="true" :style="{ width: '520px' }">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user