feat: extend superadmin navigation
This commit is contained in:
@@ -1,148 +1,100 @@
|
|||||||
# 超级管理员后台功能规划(按页面拆解)
|
# 超级管理员后台功能规划(与当前系统功能对齐)
|
||||||
|
|
||||||
> 目标:基于现有 `/super/v1/*` 能力,补齐平台级“管理 + 统计”闭环。以下按页面拆分,分别给出管理动作、统计指标与接口对照。
|
> 目标:在不脱离现有后端能力的前提下,确保“系统已有功能超管可介入管理”,并通过统一入口、跨租户筛选与批量操作提升运营效率。
|
||||||
|
|
||||||
## 0) 全局约定
|
## 0) 设计原则
|
||||||
|
|
||||||
- **鉴权**:`Authorization: Bearer <token>`;登录后本地持久化 token。
|
- **全量可管**:覆盖租户、用户、内容、订单、优惠券、钱包、提现、上传、通知等现有能力。
|
||||||
- **路由基座**:`/super/`(前端),API 基座 `/super/v1`。
|
- **便捷优先**:跨租户统一筛选、全局搜索、批量处理、审核队列,减少重复跳转。
|
||||||
- **分页**:统一 `page/limit`,响应为 `requests.Pager`。
|
- **风险可控**:默认只读,危险操作需二次确认,动作全量记录。
|
||||||
- **枚举**:优先取 `/super/v1/tenants/statuses`、`/super/v1/users/statuses`。
|
- **接口对齐**:优先复用 `/super/v1/*` 现有能力,其它模块按“超管包装层”补齐。
|
||||||
|
|
||||||
## 1) 登录 `/auth/login`
|
## 1) 当前系统能力地图(基于现有路由)
|
||||||
|
|
||||||
- 管理功能:账号登录、token 写入、自动续期。
|
- **租户与成员**:租户列表/详情、创建、状态变更、续期、健康检查、成员关注/加入/邀请。
|
||||||
- 统计功能:可选记录登录失败次数、IP、设备指纹(审计)。
|
- **用户与认证**:登录/OTP、资料更新、实名、钱包/充值、订单、收藏/点赞、通知、优惠券。
|
||||||
- 现有接口:
|
- **内容与互动**:内容列表/详情、话题、评论、点赞/收藏。
|
||||||
- `POST /super/v1/auth/login`(需补齐实现)
|
- **创作者与运营**:申请、内容 CRUD、优惠券 CRUD/发放、成员审核/邀请、订单与退款、报表、提现、结算账户、设置。
|
||||||
- `GET /super/v1/auth/token`(token 校验/续期)
|
- **交易与支付**:订单创建/支付/状态、支付回调。
|
||||||
|
- **资产与存储**:分片/直传上传、哈希检查、上传完成、媒体资产删除、存储签名上传/下载。
|
||||||
|
|
||||||
## 2) 概览 Dashboard `/`
|
## 2) 超管平台页面规划(按业务域)
|
||||||
|
|
||||||
- 管理功能:快捷入口(租户/用户/订单/内容)。
|
### 2.1 登录与权限 `/auth/login`
|
||||||
- 统计指标(建议):
|
- **功能**:登录、token 续期、权限校验。
|
||||||
- 租户总数/活跃数/过期数
|
- **接口**:`POST /super/v1/auth/login`,`GET /super/v1/auth/token`。
|
||||||
- 用户总数/活跃数(按状态拆分)
|
|
||||||
- 订单数/成交额/退款额(按日、按状态)
|
|
||||||
- 内容总数/新增内容/被封禁内容
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/users/statistics`(需补齐实现)
|
|
||||||
- `GET /super/v1/orders/statistics`(需补齐实现)
|
|
||||||
- `GET /super/v1/tenants?limit=1&page=1`(可取 total)
|
|
||||||
- `GET /super/v1/contents?limit=1&page=1`(可取 total)
|
|
||||||
|
|
||||||
## 3) 租户管理 `/superadmin/tenants`
|
### 2.2 平台概览 `/`
|
||||||
|
- **功能**:核心指标、异常概览、快捷入口。
|
||||||
|
- **接口**:`GET /super/v1/users/statistics`,`GET /super/v1/orders/statistics`,`GET /super/v1/tenants`(count),`GET /super/v1/contents`(count)。
|
||||||
|
- **缺口**:平台级内容/订单趋势、退款率、提现统计。
|
||||||
|
|
||||||
- 管理功能:
|
### 2.3 租户管理 `/superadmin/tenants`
|
||||||
- 新建租户(绑定管理员)
|
- **功能**:租户列表、创建、状态变更、续期、健康检查。
|
||||||
- 更新租户状态(正常/禁用)
|
- **接口**:`GET/POST /super/v1/tenants`,`PATCH /super/v1/tenants/:id/status`,`PATCH /super/v1/tenants/:id`,`GET /super/v1/tenants/health`,`GET /super/v1/tenants/statuses`。
|
||||||
- 续期/变更过期时间
|
|
||||||
- 统计指标:
|
|
||||||
- 状态分布(待审核/正常/禁用)
|
|
||||||
- 即将过期租户数(7/30 天)
|
|
||||||
- 租户 GMV Top N(需补接口)
|
|
||||||
- 现有接口:
|
|
||||||
- `POST /super/v1/tenants`
|
|
||||||
- `GET /super/v1/tenants`
|
|
||||||
- `PATCH /super/v1/tenants/{tenantID}/status`
|
|
||||||
- `PATCH /super/v1/tenants/{tenantID}`(续期)
|
|
||||||
- `GET /super/v1/tenants/statuses`
|
|
||||||
|
|
||||||
## 4) 租户详情 `/superadmin/tenants/:tenantID`
|
### 2.4 租户详情 `/superadmin/tenants/:tenantID`
|
||||||
|
- **功能**:租户信息、成员、内容、订单、资金与运营概览。
|
||||||
|
- **接口**:`GET /super/v1/tenants/:id`,`GET /super/v1/tenants/:tenantID/users`,`GET /super/v1/tenants/:tenantID/contents`,`GET /super/v1/orders`(按 tenant_id 过滤)。
|
||||||
|
- **缺口**:租户成员邀请/审核、租户级财务/报表聚合。
|
||||||
|
|
||||||
- 管理功能(建议):
|
### 2.5 用户管理 `/superadmin/users`
|
||||||
- 基本信息/状态/过期时间编辑
|
- **功能**:用户列表、状态/角色、关联租户、账户概要(余额/冻结/实名认证)。
|
||||||
- 管理员与成员列表(角色管理)
|
- **接口**:`GET /super/v1/users`,`PATCH /super/v1/users/:id/status`,`PATCH /super/v1/users/:id/roles`,`GET /super/v1/users/statistics`,`GET /super/v1/users/statuses`。
|
||||||
- 内容列表、订单列表、资金汇总
|
- **缺口**:实名信息、钱包明细、通知、优惠券、充值记录等超管视图接口。
|
||||||
- 统计指标(建议):
|
|
||||||
- 租户用户数、内容数、订单数、GMV
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/tenants/{tenantID}`(已有)
|
|
||||||
- 建议补充接口:
|
|
||||||
- `GET /super/v1/tenants/{tenantID}/users`
|
|
||||||
- `GET /super/v1/tenants/{tenantID}/contents`
|
|
||||||
- `GET /super/v1/tenants/{tenantID}/orders`
|
|
||||||
- `GET /super/v1/tenants/{tenantID}/statistics`
|
|
||||||
|
|
||||||
## 5) 用户管理 `/superadmin/users`
|
### 2.6 用户详情 `/superadmin/users/:userID`
|
||||||
|
- **功能**:资料、角色、加入/拥有租户、订单与内容消费、收藏/点赞/关注、优惠券与通知。
|
||||||
|
- **接口**:`GET /super/v1/users/:id`,`GET /super/v1/users/:id/tenants`,`GET /super/v1/orders`(按 user_id 过滤)。
|
||||||
|
- **缺口**:用户钱包/通知/优惠券明细需要新接口。
|
||||||
|
|
||||||
- 管理功能:
|
### 2.7 内容治理 `/superadmin/contents`
|
||||||
- 用户列表筛选(用户名/状态/角色/所属租户)
|
- **功能**:跨租户内容列表、审核、状态变更、违规处置。
|
||||||
- 状态变更、角色授予
|
- **接口**:`GET /super/v1/contents`,`POST /super/v1/contents/:id/review`,`PATCH /super/v1/tenants/:tenantID/contents/:contentID/status`。
|
||||||
- 统计指标:
|
- **缺口**:评论治理、内容举报、内容资产明细(若需要)。
|
||||||
- 用户状态统计(已提供)
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/users`
|
|
||||||
- `PATCH /super/v1/users/{userID}/status`
|
|
||||||
- `PATCH /super/v1/users/{userID}/roles`
|
|
||||||
- `GET /super/v1/users/statistics`
|
|
||||||
- `GET /super/v1/users/statuses`
|
|
||||||
|
|
||||||
## 6) 用户详情 `/superadmin/users/:userID`
|
### 2.8 订单与退款 `/superadmin/orders`
|
||||||
|
- **功能**:订单列表、退款/强制退款、问题订单标记、支付状态核对。
|
||||||
|
- **接口**:`GET /super/v1/orders`,`GET /super/v1/orders/:id`,`POST /super/v1/orders/:id/refund`。
|
||||||
|
|
||||||
- 管理功能(建议):
|
### 2.9 创作者与成员审核 `/superadmin/creators`
|
||||||
- 用户资料、角色、状态
|
- **功能**:创作者申请审核、成员加入审核/邀请、创作者设置查看、提现与结算账户审核。
|
||||||
- 用户所属/拥有租户列表
|
- **接口**:租户级 `POST /t/:tenantCode/v1/creator/apply`、`POST /t/:tenantCode/v1/creator/members/:id/review`、`POST /t/:tenantCode/v1/creator/members/invite`、`GET /t/:tenantCode/v1/creator/payout-accounts`、`POST /t/:tenantCode/v1/creator/withdraw`。
|
||||||
- 用户订单与内容购买记录
|
- **缺口**:超管跨租户管理接口(建议新增 `/super/v1/creators/*`)。
|
||||||
- 统计指标(建议):
|
|
||||||
- 用户消费总额、退款次数
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/users/{userID}`(已有)
|
|
||||||
- 建议补充接口:
|
|
||||||
- `GET /super/v1/users/{userID}/tenants`
|
|
||||||
- `GET /super/v1/users/{userID}/orders`
|
|
||||||
- `GET /super/v1/users/{userID}/contents`
|
|
||||||
|
|
||||||
## 7) 订单管理 `/superadmin/orders`
|
### 2.10 优惠券 `/superadmin/coupons`
|
||||||
|
- **功能**:跨租户优惠券列表、状态变更、发放记录、异常核查。
|
||||||
|
- **接口**:租户级 `GET/POST/PUT /t/:tenantCode/v1/creator/coupons`、`POST /t/:tenantCode/v1/creator/coupons/:id/grant`、用户侧 `/t/:tenantCode/v1/me/coupons*`。
|
||||||
|
- **缺口**:超管聚合查询与冻结/归档接口。
|
||||||
|
|
||||||
- 管理功能:
|
### 2.11 财务与钱包 `/superadmin/finance`
|
||||||
- 订单列表(按租户/用户/状态/时间过滤)
|
- **功能**:提现审核、用户钱包概况、异常充值/退款排查。
|
||||||
- 退款操作(平台侧)
|
- **接口**:租户级 `POST /t/:tenantCode/v1/creator/withdraw`、用户侧 `GET/POST /t/:tenantCode/v1/me/wallet*`。
|
||||||
- 统计指标:
|
- **缺口**:超管提现列表与审批接口、钱包流水接口。
|
||||||
- 订单状态分布、GMV、退款额
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/orders`
|
|
||||||
- `POST /super/v1/orders/{orderID}/refund`(需补齐实现)
|
|
||||||
- `GET /super/v1/orders/statistics`(需补齐实现)
|
|
||||||
|
|
||||||
## 8) 订单详情 `/superadmin/orders/:orderID`
|
### 2.12 报表与导出 `/superadmin/reports`
|
||||||
|
- **功能**:跨租户报表、导出运营数据(订单、内容、退款、提现)。
|
||||||
|
- **接口**:租户级 `GET /t/:tenantCode/v1/creator/reports/overview`、`POST /t/:tenantCode/v1/creator/reports/export`。
|
||||||
|
- **缺口**:超管聚合与导出接口。
|
||||||
|
|
||||||
- 管理功能:
|
### 2.13 资产与上传 `/superadmin/assets`
|
||||||
- 查看订单快照、支付信息、退款信息
|
- **功能**:上传记录、媒体资产清理、异常上传排查、存储用量。
|
||||||
- 退款/强制关闭
|
- **接口**:租户级 `POST /t/:tenantCode/v1/upload/*`、`DELETE /t/:tenantCode/v1/media-assets/:id`、`/t/:tenantCode/v1/storage/*`。
|
||||||
- 现有接口:
|
- **缺口**:资产列表/用量统计/跨租户查询接口。
|
||||||
- `GET /super/v1/orders/{orderID}`(需补齐实现)
|
|
||||||
|
|
||||||
## 9) 内容管理 `/superadmin/contents`
|
### 2.14 通知与消息 `/superadmin/notifications`
|
||||||
|
- **功能**:通知查看、批量触达、模板管理。
|
||||||
|
- **接口**:用户侧 `GET /t/:tenantCode/v1/me/notifications`。
|
||||||
|
- **缺口**:超管发送与批量触达接口、通知模板管理接口。
|
||||||
|
|
||||||
- 管理功能:
|
## 3) 导航与便捷性设计(建议)
|
||||||
- 跨租户内容列表
|
|
||||||
- 内容状态更新(封禁/下架)
|
|
||||||
- 统计指标:
|
|
||||||
- 内容状态分布、热门内容 Top N
|
|
||||||
- 现有接口:
|
|
||||||
- `GET /super/v1/contents`
|
|
||||||
- `PATCH /super/v1/tenants/{tenantID}/contents/{contentID}/status`
|
|
||||||
|
|
||||||
## 10) 财务/提现(可选)
|
- 统一筛选器:租户/用户/时间/状态为默认筛选维度。
|
||||||
|
- 快捷入口:租户详情可直接跳转到用户/内容/订单/提现。
|
||||||
|
- 审核队列:内容审核、退款、创作者申请、提现审核统一入口。
|
||||||
|
|
||||||
- 管理功能:
|
## 4) 推进顺序(与现有接口匹配)
|
||||||
- 提现订单审核(通过/驳回)
|
|
||||||
- 记录操作原因
|
|
||||||
- 统计指标:
|
|
||||||
- 提现订单数、金额、失败率
|
|
||||||
- 现有接口:无(服务层有 `ListWithdrawals/Approve/Reject`,需补 controller + route)
|
|
||||||
|
|
||||||
## 11) 审计日志 / 操作记录(建议)
|
|
||||||
|
|
||||||
- 管理功能:
|
|
||||||
- 展示后台操作日志(操作人、对象、动作、时间)
|
|
||||||
- 支持导出
|
|
||||||
- 现有接口:无(可基于 `services.Audit` 扩展)
|
|
||||||
|
|
||||||
## 12) 系统配置 / 平台策略(建议)
|
|
||||||
|
|
||||||
- 管理功能:
|
|
||||||
- 平台佣金比例、内容审核策略、默认到期策略
|
|
||||||
- 现有接口:无(需新增配置表与接口)
|
|
||||||
|
|
||||||
|
- **P0(已有接口即可落地)**:登录、概览、租户管理、用户管理、内容治理、订单退款。
|
||||||
|
- **P1(需补超管接口)**:创作者审核、优惠券、提现/钱包、报表导出。
|
||||||
|
- **P2(扩展增强)**:资产/上传、通知中心、审计与系统配置。
|
||||||
|
|||||||
36
frontend/superadmin/src/components/PendingPanel.vue
Normal file
36
frontend/superadmin/src/components/PendingPanel.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: String, required: true },
|
||||||
|
description: { type: String, default: '' },
|
||||||
|
badge: { type: String, default: 'Pending' },
|
||||||
|
endpoints: { type: Array, default: () => [] },
|
||||||
|
notes: { type: Array, default: () => [] }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h4 class="m-0">{{ props.title }}</h4>
|
||||||
|
<Tag severity="warning" :value="props.badge" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Message v-if="props.description" severity="warn" icon="pi pi-exclamation-triangle">
|
||||||
|
{{ props.description }}
|
||||||
|
</Message>
|
||||||
|
|
||||||
|
<div v-if="props.endpoints.length" class="mt-4">
|
||||||
|
<div class="font-medium mb-2">Suggested APIs</div>
|
||||||
|
<ul class="list-disc pl-5 text-sm text-muted-color">
|
||||||
|
<li v-for="item in props.endpoints" :key="item">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="props.notes.length" class="mt-4">
|
||||||
|
<div class="font-medium mb-2">Notes</div>
|
||||||
|
<ul class="list-disc pl-5 text-sm text-muted-color">
|
||||||
|
<li v-for="item in props.notes" :key="item">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -14,7 +14,13 @@ const model = ref([
|
|||||||
{ label: 'Tenants', icon: 'pi pi-fw pi-building', to: '/superadmin/tenants' },
|
{ label: 'Tenants', icon: 'pi pi-fw pi-building', to: '/superadmin/tenants' },
|
||||||
{ label: 'Users', icon: 'pi pi-fw pi-users', to: '/superadmin/users' },
|
{ label: 'Users', icon: 'pi pi-fw pi-users', to: '/superadmin/users' },
|
||||||
{ label: 'Orders', icon: 'pi pi-fw pi-shopping-cart', to: '/superadmin/orders' },
|
{ label: 'Orders', icon: 'pi pi-fw pi-shopping-cart', to: '/superadmin/orders' },
|
||||||
{ label: 'Contents', icon: 'pi pi-fw pi-file', to: '/superadmin/contents' }
|
{ label: 'Contents', icon: 'pi pi-fw pi-file', to: '/superadmin/contents' },
|
||||||
|
{ label: 'Creators', icon: 'pi pi-fw pi-star', to: '/superadmin/creators' },
|
||||||
|
{ label: 'Coupons', icon: 'pi pi-fw pi-ticket', to: '/superadmin/coupons' },
|
||||||
|
{ label: 'Finance', icon: 'pi pi-fw pi-wallet', to: '/superadmin/finance' },
|
||||||
|
{ label: 'Reports', icon: 'pi pi-fw pi-chart-line', to: '/superadmin/reports' },
|
||||||
|
{ label: 'Assets', icon: 'pi pi-fw pi-folder', to: '/superadmin/assets' },
|
||||||
|
{ label: 'Notifications', icon: 'pi pi-fw pi-bell', to: '/superadmin/notifications' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -144,6 +144,36 @@ const router = createRouter({
|
|||||||
name: 'superadmin-contents',
|
name: 'superadmin-contents',
|
||||||
component: () => import('@/views/superadmin/Contents.vue')
|
component: () => import('@/views/superadmin/Contents.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/creators',
|
||||||
|
name: 'superadmin-creators',
|
||||||
|
component: () => import('@/views/superadmin/Creators.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/coupons',
|
||||||
|
name: 'superadmin-coupons',
|
||||||
|
component: () => import('@/views/superadmin/Coupons.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/finance',
|
||||||
|
name: 'superadmin-finance',
|
||||||
|
component: () => import('@/views/superadmin/Finance.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/reports',
|
||||||
|
name: 'superadmin-reports',
|
||||||
|
component: () => import('@/views/superadmin/Reports.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/assets',
|
||||||
|
name: 'superadmin-assets',
|
||||||
|
component: () => import('@/views/superadmin/Assets.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/superadmin/notifications',
|
||||||
|
name: 'superadmin-notifications',
|
||||||
|
component: () => import('@/views/superadmin/Notifications.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/superadmin/orders/:orderID',
|
path: '/superadmin/orders/:orderID',
|
||||||
name: 'superadmin-order-detail',
|
name: 'superadmin-order-detail',
|
||||||
|
|||||||
@@ -67,23 +67,7 @@ export const ContentService = {
|
|||||||
items: normalizeItems(data?.items)
|
items: normalizeItems(data?.items)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async listTenantContents(
|
async listTenantContents(tenantID, { page, limit, keyword, status, visibility, user_id, published_at_from, published_at_to, created_at_from, created_at_to, sortField, sortOrder } = {}) {
|
||||||
tenantID,
|
|
||||||
{
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
keyword,
|
|
||||||
status,
|
|
||||||
visibility,
|
|
||||||
user_id,
|
|
||||||
published_at_from,
|
|
||||||
published_at_to,
|
|
||||||
created_at_from,
|
|
||||||
created_at_to,
|
|
||||||
sortField,
|
|
||||||
sortOrder
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
if (!tenantID) throw new Error('tenantID is required');
|
if (!tenantID) throw new Error('tenantID is required');
|
||||||
|
|
||||||
const iso = (d) => {
|
const iso = (d) => {
|
||||||
@@ -117,8 +101,7 @@ export const ContentService = {
|
|||||||
total: data?.total ?? 0,
|
total: data?.total ?? 0,
|
||||||
items: normalizeItems(data?.items)
|
items: normalizeItems(data?.items)
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
,
|
|
||||||
async updateTenantContentStatus(tenantID, contentID, { status } = {}) {
|
async updateTenantContentStatus(tenantID, contentID, { status } = {}) {
|
||||||
if (!tenantID) throw new Error('tenantID is required');
|
if (!tenantID) throw new Error('tenantID is required');
|
||||||
if (!contentID) throw new Error('contentID is required');
|
if (!contentID) throw new Error('contentID is required');
|
||||||
|
|||||||
@@ -7,21 +7,7 @@ function normalizeItems(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TenantService = {
|
export const TenantService = {
|
||||||
async listTenants({
|
async listTenants({ page, limit, id, user_id, name, code, status, expired_at_from, expired_at_to, created_at_from, created_at_to, sortField, sortOrder } = {}) {
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
name,
|
|
||||||
code,
|
|
||||||
status,
|
|
||||||
expired_at_from,
|
|
||||||
expired_at_to,
|
|
||||||
created_at_from,
|
|
||||||
created_at_to,
|
|
||||||
sortField,
|
|
||||||
sortOrder
|
|
||||||
} = {}) {
|
|
||||||
const iso = (d) => {
|
const iso = (d) => {
|
||||||
if (!d) return undefined;
|
if (!d) return undefined;
|
||||||
const date = d instanceof Date ? d : new Date(d);
|
const date = d instanceof Date ? d : new Date(d);
|
||||||
|
|||||||
@@ -7,21 +7,7 @@ function normalizeItems(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const UserService = {
|
export const UserService = {
|
||||||
async listUsers({
|
async listUsers({ page, limit, id, tenant_id, username, status, role, created_at_from, created_at_to, verified_at_from, verified_at_to, sortField, sortOrder } = {}) {
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
id,
|
|
||||||
tenant_id,
|
|
||||||
username,
|
|
||||||
status,
|
|
||||||
role,
|
|
||||||
created_at_from,
|
|
||||||
created_at_to,
|
|
||||||
verified_at_from,
|
|
||||||
verified_at_to,
|
|
||||||
sortField,
|
|
||||||
sortOrder
|
|
||||||
} = {}) {
|
|
||||||
const iso = (d) => {
|
const iso = (d) => {
|
||||||
if (!d) return undefined;
|
if (!d) return undefined;
|
||||||
const date = d instanceof Date ? d : new Date(d);
|
const date = d instanceof Date ? d : new Date(d);
|
||||||
@@ -98,10 +84,7 @@ export const UserService = {
|
|||||||
if (!userID) throw new Error('userID is required');
|
if (!userID) throw new Error('userID is required');
|
||||||
return requestJson(`/super/v1/users/${userID}`);
|
return requestJson(`/super/v1/users/${userID}`);
|
||||||
},
|
},
|
||||||
async listUserTenants(
|
async listUserTenants(userID, { page, limit, tenant_id, code, name, role, status, created_at_from, created_at_to } = {}) {
|
||||||
userID,
|
|
||||||
{ page, limit, tenant_id, code, name, role, status, created_at_from, created_at_to } = {}
|
|
||||||
) {
|
|
||||||
if (!userID) throw new Error('userID is required');
|
if (!userID) throw new Error('userID is required');
|
||||||
|
|
||||||
const iso = (d) => {
|
const iso = (d) => {
|
||||||
|
|||||||
@@ -39,4 +39,3 @@ export async function refreshSuperToken() {
|
|||||||
if (token) setSuperAuthToken(token);
|
if (token) setSuperAuthToken(token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
frontend/superadmin/src/views/superadmin/Assets.vue
Normal file
11
frontend/superadmin/src/views/superadmin/Assets.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = ['GET /super/v1/assets', 'DELETE /super/v1/assets/:id', 'GET /super/v1/assets/usage'];
|
||||||
|
|
||||||
|
const notes = ['Upload and storage endpoints are tenant-scoped today.', 'Add asset inventory before enabling cleanup actions.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Assets" description="Asset governance requires a super admin inventory API." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
@@ -292,11 +292,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="租户" sortable sortField="tenant_id" style="min-width: 18rem">
|
<Column header="租户" sortable sortField="tenant_id" style="min-width: 18rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<router-link
|
<router-link v-if="data?.tenant?.id" class="inline-flex items-center gap-1 font-medium text-primary hover:underline" :to="`/superadmin/tenants/${data.tenant.id}`">
|
||||||
v-if="data?.tenant?.id"
|
|
||||||
class="inline-flex items-center gap-1 font-medium text-primary hover:underline"
|
|
||||||
:to="`/superadmin/tenants/${data.tenant.id}`"
|
|
||||||
>
|
|
||||||
<span class="truncate max-w-[220px]">{{ data?.tenant?.name ?? data?.tenant?.code ?? '-' }}</span>
|
<span class="truncate max-w-[220px]">{{ data?.tenant?.name ?? data?.tenant?.code ?? '-' }}</span>
|
||||||
<i class="pi pi-external-link text-xs" />
|
<i class="pi pi-external-link text-xs" />
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -309,11 +305,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="Owner" sortable sortField="user_id" style="min-width: 14rem">
|
<Column header="Owner" sortable sortField="user_id" style="min-width: 14rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<router-link
|
<router-link v-if="(data?.owner?.id ?? data?.content?.user_id) > 0" class="inline-flex items-center gap-1 font-medium text-primary hover:underline" :to="`/superadmin/users/${data?.owner?.id ?? data?.content?.user_id}`">
|
||||||
v-if="(data?.owner?.id ?? data?.content?.user_id) > 0"
|
|
||||||
class="inline-flex items-center gap-1 font-medium text-primary hover:underline"
|
|
||||||
:to="`/superadmin/users/${data?.owner?.id ?? data?.content?.user_id}`"
|
|
||||||
>
|
|
||||||
<span class="truncate max-w-[200px]">{{ data?.owner?.username ?? `ID:${data?.content?.user_id ?? '-'}` }}</span>
|
<span class="truncate max-w-[200px]">{{ data?.owner?.username ?? `ID:${data?.content?.user_id ?? '-'}` }}</span>
|
||||||
<i class="pi pi-external-link text-xs" />
|
<i class="pi pi-external-link text-xs" />
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -353,16 +345,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="操作" style="min-width: 10rem">
|
<Column header="操作" style="min-width: 10rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button v-if="data?.content?.status === 'published'" label="下架" icon="pi pi-ban" severity="danger" text size="small" class="p-0" @click="openUnpublishDialog(data)" />
|
||||||
v-if="data?.content?.status === 'published'"
|
|
||||||
label="下架"
|
|
||||||
icon="pi pi-ban"
|
|
||||||
severity="danger"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
@click="openUnpublishDialog(data)"
|
|
||||||
/>
|
|
||||||
<span v-else class="text-muted-color">-</span>
|
<span v-else class="text-muted-color">-</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
11
frontend/superadmin/src/views/superadmin/Coupons.vue
Normal file
11
frontend/superadmin/src/views/superadmin/Coupons.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = ['GET /super/v1/coupons', 'PATCH /super/v1/coupons/:id/status', 'GET /super/v1/coupon-grants'];
|
||||||
|
|
||||||
|
const notes = ['Current coupon CRUD endpoints are tenant-scoped and tied to creator ownership.', 'Expose cross-tenant coupon listing before adding bulk actions.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Coupons" description="Coupon management needs a super admin aggregation layer." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
18
frontend/superadmin/src/views/superadmin/Creators.vue
Normal file
18
frontend/superadmin/src/views/superadmin/Creators.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = [
|
||||||
|
'GET /super/v1/creators',
|
||||||
|
'GET /super/v1/creator-applications',
|
||||||
|
'POST /super/v1/creator-applications/:id/review',
|
||||||
|
'GET /super/v1/creator-members',
|
||||||
|
'POST /super/v1/creator-members/:id/review',
|
||||||
|
'POST /super/v1/creator-members/invite'
|
||||||
|
];
|
||||||
|
|
||||||
|
const notes = ['Tenant-level creator endpoints require the tenant owner and are not usable from super admin today.', 'Keep creator approvals in the tenant admin portal until super admin APIs are added.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Creators" description="Super admin creator operations require cross-tenant APIs." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
11
frontend/superadmin/src/views/superadmin/Finance.vue
Normal file
11
frontend/superadmin/src/views/superadmin/Finance.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = ['GET /super/v1/withdrawals', 'POST /super/v1/withdrawals/:id/approve', 'POST /super/v1/withdrawals/:id/reject', 'GET /super/v1/wallet-ledgers'];
|
||||||
|
|
||||||
|
const notes = ['Withdrawals currently exist only in tenant creator APIs.', 'Add a super admin ledger view before exposing approvals.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Finance" description="Withdrawals and wallet visibility require super admin endpoints." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
11
frontend/superadmin/src/views/superadmin/Notifications.vue
Normal file
11
frontend/superadmin/src/views/superadmin/Notifications.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = ['GET /super/v1/notifications', 'POST /super/v1/notifications/broadcast', 'POST /super/v1/notifications/templates', 'GET /super/v1/notifications/templates'];
|
||||||
|
|
||||||
|
const notes = ['The current notification API is user-scoped only.', 'Add super admin send and template endpoints before enabling operations.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Notifications" description="Notification management is pending super admin APIs." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
@@ -176,9 +176,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="text-sm text-muted-color">
|
<div class="text-sm text-muted-color">该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。</div>
|
||||||
该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
||||||
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
||||||
@@ -194,4 +192,3 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -328,15 +328,7 @@ loadOrders();
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="操作" style="min-width: 10rem">
|
<Column header="操作" style="min-width: 10rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button label="退款" icon="pi pi-replay" text size="small" class="p-0" :disabled="data?.status !== 'paid'" @click="openRefundDialog(data)" />
|
||||||
label="退款"
|
|
||||||
icon="pi pi-replay"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
:disabled="data?.status !== 'paid'"
|
|
||||||
@click="openRefundDialog(data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
@@ -352,9 +344,7 @@ loadOrders();
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="text-sm text-muted-color">
|
<div class="text-sm text-muted-color">该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。</div>
|
||||||
该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
||||||
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
||||||
@@ -366,14 +356,7 @@ loadOrders();
|
|||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Button label="取消" icon="pi pi-times" text @click="refundDialogVisible = false" :disabled="refundLoading" />
|
<Button label="取消" icon="pi pi-times" text @click="refundDialogVisible = false" :disabled="refundLoading" />
|
||||||
<Button
|
<Button label="确认退款" icon="pi pi-check" severity="danger" @click="confirmRefund" :loading="refundLoading" :disabled="refundOrder?.status !== 'paid'" />
|
||||||
label="确认退款"
|
|
||||||
icon="pi pi-check"
|
|
||||||
severity="danger"
|
|
||||||
@click="confirmRefund"
|
|
||||||
:loading="refundLoading"
|
|
||||||
:disabled="refundOrder?.status !== 'paid'"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
11
frontend/superadmin/src/views/superadmin/Reports.vue
Normal file
11
frontend/superadmin/src/views/superadmin/Reports.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import PendingPanel from '@/components/PendingPanel.vue';
|
||||||
|
|
||||||
|
const endpoints = ['GET /super/v1/reports/overview', 'GET /super/v1/reports/series', 'POST /super/v1/reports/export'];
|
||||||
|
|
||||||
|
const notes = ['Current report APIs are scoped to creators in tenant context.', 'Add cross-tenant aggregation before wiring charts and exports.'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PendingPanel title="Reports" description="Platform reporting needs aggregated super admin APIs." :endpoints="endpoints" :notes="notes" />
|
||||||
|
</template>
|
||||||
@@ -111,9 +111,7 @@ async function ensureStatusOptionsLoaded() {
|
|||||||
statusOptionsLoading.value = true;
|
statusOptionsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const list = await TenantService.getTenantStatuses();
|
const list = await TenantService.getTenantStatuses();
|
||||||
statusOptions.value = (list || [])
|
statusOptions.value = (list || []).map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' })).filter((item) => item.value);
|
||||||
.map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' }))
|
|
||||||
.filter((item) => item.value);
|
|
||||||
} finally {
|
} finally {
|
||||||
statusOptionsLoading.value = false;
|
statusOptionsLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -642,14 +640,7 @@ onMounted(() => {
|
|||||||
<Select v-model="contentsStatus" :options="contentStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
<Select v-model="contentsStatus" :options="contentStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||||
</SearchField>
|
</SearchField>
|
||||||
<SearchField label="可见性">
|
<SearchField label="可见性">
|
||||||
<Select
|
<Select v-model="contentsVisibility" :options="contentVisibilityOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||||
v-model="contentsVisibility"
|
|
||||||
:options="contentVisibilityOptions"
|
|
||||||
optionLabel="label"
|
|
||||||
optionValue="value"
|
|
||||||
placeholder="请选择"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</SearchField>
|
</SearchField>
|
||||||
<SearchField label="OwnerUserID">
|
<SearchField label="OwnerUserID">
|
||||||
<InputNumber v-model="contentsOwnerUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
<InputNumber v-model="contentsOwnerUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||||
@@ -717,10 +708,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="可见性" sortable sortField="visibility" style="min-width: 12rem">
|
<Column header="可见性" sortable sortField="visibility" style="min-width: 12rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Tag
|
<Tag :value="data?.visibility_description || data?.content?.visibility || '-'" :severity="getContentVisibilitySeverity(data?.content?.visibility)" />
|
||||||
:value="data?.visibility_description || data?.content?.visibility || '-'"
|
|
||||||
:severity="getContentVisibilitySeverity(data?.content?.visibility)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column header="价格" style="min-width: 10rem">
|
<Column header="价格" style="min-width: 10rem">
|
||||||
@@ -743,16 +731,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="操作" style="min-width: 10rem">
|
<Column header="操作" style="min-width: 10rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button v-if="data?.content?.status === 'published'" label="下架" icon="pi pi-ban" severity="danger" text size="small" class="p-0" @click="openUnpublishDialog(data)" />
|
||||||
v-if="data?.content?.status === 'published'"
|
|
||||||
label="下架"
|
|
||||||
icon="pi pi-ban"
|
|
||||||
severity="danger"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
@click="openUnpublishDialog(data)"
|
|
||||||
/>
|
|
||||||
<span v-else class="text-muted-color">-</span>
|
<span v-else class="text-muted-color">-</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -448,15 +448,7 @@ onMounted(() => {
|
|||||||
<Column field="code" header="Code" style="min-width: 10rem" />
|
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||||
<Column field="name" header="名称" sortable style="min-width: 16rem">
|
<Column field="name" header="名称" sortable style="min-width: 16rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="data.name || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/tenants/${data.id}`" />
|
||||||
:label="data.name || '-'"
|
|
||||||
icon="pi pi-external-link"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
as="router-link"
|
|
||||||
:to="`/superadmin/tenants/${data.id}`"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="user_id" header="Owner" sortable style="min-width: 12rem">
|
<Column field="user_id" header="Owner" sortable style="min-width: 12rem">
|
||||||
@@ -480,14 +472,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column field="user_count" header="用户数" sortable style="min-width: 8rem">
|
<Column field="user_count" header="用户数" sortable style="min-width: 8rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="String(data.user_count ?? 0)" text size="small" icon="pi pi-users" class="p-0" @click="openTenantUsersDialog(data)" />
|
||||||
:label="String(data.user_count ?? 0)"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
icon="pi pi-users"
|
|
||||||
class="p-0"
|
|
||||||
@click="openTenantUsersDialog(data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="income_amount_paid_sum" header="累计收入" sortable style="min-width: 10rem">
|
<Column field="income_amount_paid_sum" header="累计收入" sortable style="min-width: 10rem">
|
||||||
|
|||||||
@@ -78,9 +78,7 @@ async function ensureStatusOptionsLoaded() {
|
|||||||
statusOptionsLoading.value = true;
|
statusOptionsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const list = await UserService.getUserStatuses();
|
const list = await UserService.getUserStatuses();
|
||||||
statusOptions.value = (list || [])
|
statusOptions.value = (list || []).map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' })).filter((item) => item.value);
|
||||||
.map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' }))
|
|
||||||
.filter((item) => item.value);
|
|
||||||
} finally {
|
} finally {
|
||||||
statusOptionsLoading.value = false;
|
statusOptionsLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -336,15 +334,7 @@ onMounted(() => {
|
|||||||
<Column field="code" header="Code" style="min-width: 10rem" />
|
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||||
<Column field="name" header="名称" style="min-width: 14rem">
|
<Column field="name" header="名称" style="min-width: 14rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="data.name || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/tenants/${data.id}`" />
|
||||||
:label="data.name || '-'"
|
|
||||||
icon="pi pi-external-link"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
as="router-link"
|
|
||||||
:to="`/superadmin/tenants/${data.id}`"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="status_description" header="状态" style="min-width: 10rem" />
|
<Column field="status_description" header="状态" style="min-width: 10rem" />
|
||||||
@@ -393,15 +383,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</SearchField>
|
</SearchField>
|
||||||
<SearchField label="成员状态">
|
<SearchField label="成员状态">
|
||||||
<Select
|
<Select v-model="joinedTenantsStatus" :options="statusFilterOptions" optionLabel="label" optionValue="value" placeholder="请选择" :loading="statusOptionsLoading" class="w-full" />
|
||||||
v-model="joinedTenantsStatus"
|
|
||||||
:options="statusFilterOptions"
|
|
||||||
optionLabel="label"
|
|
||||||
optionValue="value"
|
|
||||||
placeholder="请选择"
|
|
||||||
:loading="statusOptionsLoading"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</SearchField>
|
</SearchField>
|
||||||
<SearchField label="加入时间 From">
|
<SearchField label="加入时间 From">
|
||||||
<DatePicker v-model="joinedTenantsJoinedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
<DatePicker v-model="joinedTenantsJoinedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||||
@@ -432,15 +414,7 @@ onMounted(() => {
|
|||||||
<Column field="code" header="Code" style="min-width: 10rem" />
|
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||||
<Column field="name" header="名称" style="min-width: 14rem">
|
<Column field="name" header="名称" style="min-width: 14rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="data.name || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/tenants/${data.tenant_id}`" />
|
||||||
:label="data.name || '-'"
|
|
||||||
icon="pi pi-external-link"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
as="router-link"
|
|
||||||
:to="`/superadmin/tenants/${data.tenant_id}`"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column header="Owner" style="min-width: 12rem">
|
<Column header="Owner" style="min-width: 12rem">
|
||||||
|
|||||||
@@ -475,15 +475,7 @@ onMounted(() => {
|
|||||||
<Column field="id" header="ID" sortable style="min-width: 6rem" />
|
<Column field="id" header="ID" sortable style="min-width: 6rem" />
|
||||||
<Column field="username" header="用户名" sortable style="min-width: 16rem">
|
<Column field="username" header="用户名" sortable style="min-width: 16rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="data.username || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/users/${data.id}`" />
|
||||||
:label="data.username || '-'"
|
|
||||||
icon="pi pi-external-link"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
as="router-link"
|
|
||||||
:to="`/superadmin/users/${data.id}`"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="status" header="状态" sortable style="min-width: 10rem">
|
<Column field="status" header="状态" sortable style="min-width: 10rem">
|
||||||
@@ -501,14 +493,7 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="超管" style="min-width: 9rem">
|
<Column header="超管" style="min-width: 9rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="hasRole(data, 'super_admin') ? '是' : '否'" icon="pi pi-user-edit" text size="small" class="p-0" @click="openRolesDialog(data)" />
|
||||||
:label="hasRole(data, 'super_admin') ? '是' : '否'"
|
|
||||||
icon="pi pi-user-edit"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
@click="openRolesDialog(data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="balance" header="余额" sortable style="min-width: 10rem">
|
<Column field="balance" header="余额" sortable style="min-width: 10rem">
|
||||||
@@ -523,28 +508,12 @@ onMounted(() => {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column header="拥有租户" style="min-width: 10rem">
|
<Column header="拥有租户" style="min-width: 10rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="String(data.owned_tenant_count ?? 0)" icon="pi pi-building" text size="small" class="p-0" :disabled="(data.owned_tenant_count ?? 0) === 0" @click="openOwnedTenantsDialog(data)" />
|
||||||
:label="String(data.owned_tenant_count ?? 0)"
|
|
||||||
icon="pi pi-building"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
:disabled="(data.owned_tenant_count ?? 0) === 0"
|
|
||||||
@click="openOwnedTenantsDialog(data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column header="加入租户" style="min-width: 10rem">
|
<Column header="加入租户" style="min-width: 10rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button :label="String(data.joined_tenant_count ?? 0)" icon="pi pi-users" text size="small" class="p-0" :disabled="(data.joined_tenant_count ?? 0) === 0" @click="openJoinedTenantsDialog(data)" />
|
||||||
:label="String(data.joined_tenant_count ?? 0)"
|
|
||||||
icon="pi pi-users"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
class="p-0"
|
|
||||||
:disabled="(data.joined_tenant_count ?? 0) === 0"
|
|
||||||
@click="openJoinedTenantsDialog(data)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="verified_at" header="认证时间" sortable style="min-width: 14rem">
|
<Column field="verified_at" header="认证时间" sortable style="min-width: 14rem">
|
||||||
|
|||||||
Reference in New Issue
Block a user