feat: extend superadmin navigation
This commit is contained in:
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: 'Users', icon: 'pi pi-fw pi-users', to: '/superadmin/users' },
|
||||
{ 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',
|
||||
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',
|
||||
name: 'superadmin-order-detail',
|
||||
|
||||
@@ -67,23 +67,7 @@ export const ContentService = {
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async listTenantContents(
|
||||
tenantID,
|
||||
{
|
||||
page,
|
||||
limit,
|
||||
keyword,
|
||||
status,
|
||||
visibility,
|
||||
user_id,
|
||||
published_at_from,
|
||||
published_at_to,
|
||||
created_at_from,
|
||||
created_at_to,
|
||||
sortField,
|
||||
sortOrder
|
||||
} = {}
|
||||
) {
|
||||
async listTenantContents(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');
|
||||
|
||||
const iso = (d) => {
|
||||
@@ -117,8 +101,7 @@ export const ContentService = {
|
||||
total: data?.total ?? 0,
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
}
|
||||
,
|
||||
},
|
||||
async updateTenantContentStatus(tenantID, contentID, { status } = {}) {
|
||||
if (!tenantID) throw new Error('tenantID is required');
|
||||
if (!contentID) throw new Error('contentID is required');
|
||||
|
||||
@@ -7,21 +7,7 @@ function normalizeItems(items) {
|
||||
}
|
||||
|
||||
export const TenantService = {
|
||||
async listTenants({
|
||||
page,
|
||||
limit,
|
||||
id,
|
||||
user_id,
|
||||
name,
|
||||
code,
|
||||
status,
|
||||
expired_at_from,
|
||||
expired_at_to,
|
||||
created_at_from,
|
||||
created_at_to,
|
||||
sortField,
|
||||
sortOrder
|
||||
} = {}) {
|
||||
async listTenants({ 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) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
|
||||
@@ -7,21 +7,7 @@ function normalizeItems(items) {
|
||||
}
|
||||
|
||||
export const UserService = {
|
||||
async listUsers({
|
||||
page,
|
||||
limit,
|
||||
id,
|
||||
tenant_id,
|
||||
username,
|
||||
status,
|
||||
role,
|
||||
created_at_from,
|
||||
created_at_to,
|
||||
verified_at_from,
|
||||
verified_at_to,
|
||||
sortField,
|
||||
sortOrder
|
||||
} = {}) {
|
||||
async listUsers({ 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) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
@@ -98,10 +84,7 @@ export const UserService = {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
return requestJson(`/super/v1/users/${userID}`);
|
||||
},
|
||||
async listUserTenants(
|
||||
userID,
|
||||
{ page, limit, tenant_id, code, name, role, status, created_at_from, created_at_to } = {}
|
||||
) {
|
||||
async listUserTenants(userID, { page, limit, tenant_id, code, name, role, status, created_at_from, created_at_to } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
const iso = (d) => {
|
||||
|
||||
@@ -39,4 +39,3 @@ export async function refreshSuperToken() {
|
||||
if (token) setSuperAuthToken(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 header="租户" sortable sortField="tenant_id" style="min-width: 18rem">
|
||||
<template #body="{ data }">
|
||||
<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}`"
|
||||
>
|
||||
<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}`">
|
||||
<span class="truncate max-w-[220px]">{{ data?.tenant?.name ?? data?.tenant?.code ?? '-' }}</span>
|
||||
<i class="pi pi-external-link text-xs" />
|
||||
</router-link>
|
||||
@@ -309,11 +305,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="Owner" sortable sortField="user_id" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
<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}`"
|
||||
>
|
||||
<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}`">
|
||||
<span class="truncate max-w-[200px]">{{ data?.owner?.username ?? `ID:${data?.content?.user_id ?? '-'}` }}</span>
|
||||
<i class="pi pi-external-link text-xs" />
|
||||
</router-link>
|
||||
@@ -353,16 +345,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
v-if="data?.content?.status === 'published'"
|
||||
label="下架"
|
||||
icon="pi pi-ban"
|
||||
severity="danger"
|
||||
text
|
||||
size="small"
|
||||
class="p-0"
|
||||
@click="openUnpublishDialog(data)"
|
||||
/>
|
||||
<Button 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>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-sm text-muted-color">
|
||||
该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。
|
||||
</div>
|
||||
<div class="text-sm text-muted-color">该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
||||
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
||||
@@ -194,4 +192,3 @@ watch(
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -328,15 +328,7 @@ loadOrders();
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
label="退款"
|
||||
icon="pi pi-replay"
|
||||
text
|
||||
size="small"
|
||||
class="p-0"
|
||||
:disabled="data?.status !== 'paid'"
|
||||
@click="openRefundDialog(data)"
|
||||
/>
|
||||
<Button label="退款" icon="pi pi-replay" text size="small" class="p-0" :disabled="data?.status !== 'paid'" @click="openRefundDialog(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
@@ -352,9 +344,7 @@ loadOrders();
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-sm text-muted-color">
|
||||
该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。
|
||||
</div>
|
||||
<div class="text-sm text-muted-color">该操作会将订单从 <span class="font-medium">paid</span> 推进到 <span class="font-medium">refunding</span> 并提交异步退款任务。</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox v-model="refundForce" inputId="refundForce" binary />
|
||||
<label for="refundForce" class="cursor-pointer">强制退款(绕过默认时间窗)</label>
|
||||
@@ -366,14 +356,7 @@ loadOrders();
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="refundDialogVisible = false" :disabled="refundLoading" />
|
||||
<Button
|
||||
label="确认退款"
|
||||
icon="pi pi-check"
|
||||
severity="danger"
|
||||
@click="confirmRefund"
|
||||
:loading="refundLoading"
|
||||
:disabled="refundOrder?.status !== 'paid'"
|
||||
/>
|
||||
<Button label="确认退款" icon="pi pi-check" severity="danger" @click="confirmRefund" :loading="refundLoading" :disabled="refundOrder?.status !== 'paid'" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</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;
|
||||
try {
|
||||
const list = await TenantService.getTenantStatuses();
|
||||
statusOptions.value = (list || [])
|
||||
.map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' }))
|
||||
.filter((item) => item.value);
|
||||
statusOptions.value = (list || []).map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' })).filter((item) => item.value);
|
||||
} finally {
|
||||
statusOptionsLoading.value = false;
|
||||
}
|
||||
@@ -642,14 +640,7 @@ onMounted(() => {
|
||||
<Select v-model="contentsStatus" :options="contentStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="可见性">
|
||||
<Select
|
||||
v-model="contentsVisibility"
|
||||
:options="contentVisibilityOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="请选择"
|
||||
class="w-full"
|
||||
/>
|
||||
<Select v-model="contentsVisibility" :options="contentVisibilityOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="OwnerUserID">
|
||||
<InputNumber v-model="contentsOwnerUserID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||
@@ -717,10 +708,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="可见性" sortable sortField="visibility" style="min-width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<Tag
|
||||
:value="data?.visibility_description || data?.content?.visibility || '-'"
|
||||
:severity="getContentVisibilitySeverity(data?.content?.visibility)"
|
||||
/>
|
||||
<Tag :value="data?.visibility_description || data?.content?.visibility || '-'" :severity="getContentVisibilitySeverity(data?.content?.visibility)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="价格" style="min-width: 10rem">
|
||||
@@ -743,16 +731,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
v-if="data?.content?.status === 'published'"
|
||||
label="下架"
|
||||
icon="pi pi-ban"
|
||||
severity="danger"
|
||||
text
|
||||
size="small"
|
||||
class="p-0"
|
||||
@click="openUnpublishDialog(data)"
|
||||
/>
|
||||
<Button 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>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
@@ -448,15 +448,7 @@ onMounted(() => {
|
||||
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||
<Column field="name" header="名称" sortable 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}`"
|
||||
/>
|
||||
<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 field="user_id" header="Owner" sortable style="min-width: 12rem">
|
||||
@@ -480,14 +472,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column field="user_count" header="用户数" sortable style="min-width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
:label="String(data.user_count ?? 0)"
|
||||
text
|
||||
size="small"
|
||||
icon="pi pi-users"
|
||||
class="p-0"
|
||||
@click="openTenantUsersDialog(data)"
|
||||
/>
|
||||
<Button :label="String(data.user_count ?? 0)" text size="small" icon="pi pi-users" class="p-0" @click="openTenantUsersDialog(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="income_amount_paid_sum" header="累计收入" sortable style="min-width: 10rem">
|
||||
|
||||
@@ -78,9 +78,7 @@ async function ensureStatusOptionsLoaded() {
|
||||
statusOptionsLoading.value = true;
|
||||
try {
|
||||
const list = await UserService.getUserStatuses();
|
||||
statusOptions.value = (list || [])
|
||||
.map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' }))
|
||||
.filter((item) => item.value);
|
||||
statusOptions.value = (list || []).map((kv) => ({ label: kv?.value ?? kv?.key ?? '-', value: kv?.key ?? '' })).filter((item) => item.value);
|
||||
} finally {
|
||||
statusOptionsLoading.value = false;
|
||||
}
|
||||
@@ -336,15 +334,7 @@ onMounted(() => {
|
||||
<Column field="code" header="Code" style="min-width: 10rem" />
|
||||
<Column field="name" header="名称" style="min-width: 14rem">
|
||||
<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}`"
|
||||
/>
|
||||
<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 field="status_description" header="状态" style="min-width: 10rem" />
|
||||
@@ -393,15 +383,7 @@ onMounted(() => {
|
||||
/>
|
||||
</SearchField>
|
||||
<SearchField label="成员状态">
|
||||
<Select
|
||||
v-model="joinedTenantsStatus"
|
||||
:options="statusFilterOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="请选择"
|
||||
:loading="statusOptionsLoading"
|
||||
class="w-full"
|
||||
/>
|
||||
<Select v-model="joinedTenantsStatus" :options="statusFilterOptions" optionLabel="label" optionValue="value" placeholder="请选择" :loading="statusOptionsLoading" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="加入时间 From">
|
||||
<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="name" header="名称" style="min-width: 14rem">
|
||||
<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.tenant_id}`"
|
||||
/>
|
||||
<Button :label="data.name || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/tenants/${data.tenant_id}`" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="Owner" style="min-width: 12rem">
|
||||
|
||||
@@ -475,15 +475,7 @@ onMounted(() => {
|
||||
<Column field="id" header="ID" sortable style="min-width: 6rem" />
|
||||
<Column field="username" header="用户名" sortable style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
:label="data.username || '-'"
|
||||
icon="pi pi-external-link"
|
||||
text
|
||||
size="small"
|
||||
class="p-0"
|
||||
as="router-link"
|
||||
:to="`/superadmin/users/${data.id}`"
|
||||
/>
|
||||
<Button :label="data.username || '-'" icon="pi pi-external-link" text size="small" class="p-0" as="router-link" :to="`/superadmin/users/${data.id}`" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="status" header="状态" sortable style="min-width: 10rem">
|
||||
@@ -501,14 +493,7 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="超管" style="min-width: 9rem">
|
||||
<template #body="{ data }">
|
||||
<Button
|
||||
:label="hasRole(data, 'super_admin') ? '是' : '否'"
|
||||
icon="pi pi-user-edit"
|
||||
text
|
||||
size="small"
|
||||
class="p-0"
|
||||
@click="openRolesDialog(data)"
|
||||
/>
|
||||
<Button :label="hasRole(data, 'super_admin') ? '是' : '否'" icon="pi pi-user-edit" text size="small" class="p-0" @click="openRolesDialog(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="balance" header="余额" sortable style="min-width: 10rem">
|
||||
@@ -523,28 +508,12 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="拥有租户" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<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)"
|
||||
/>
|
||||
<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)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="加入租户" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<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)"
|
||||
/>
|
||||
<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)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="verified_at" header="认证时间" sortable style="min-width: 14rem">
|
||||
|
||||
Reference in New Issue
Block a user