forked from wangziqi/gongxue-base
feat: add platform staff management
This commit is contained in:
@@ -28,6 +28,7 @@ export default defineAppConfig({
|
||||
'pages/platform-admin/tenants/index',
|
||||
'pages/platform-admin/billing/index',
|
||||
'pages/platform-admin/question-banks/index',
|
||||
'pages/platform-admin/staff/index',
|
||||
],
|
||||
window: {
|
||||
backgroundTextStyle: 'light',
|
||||
|
||||
@@ -219,6 +219,74 @@
|
||||
color: #be123c;
|
||||
}
|
||||
|
||||
.platform-mini-button.active {
|
||||
border-color: #1d4ed8;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.platform-permission-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin: 8px 0 16px;
|
||||
padding: 18px;
|
||||
border: 1px solid #dbe4f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.platform-permission-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.platform-permission-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.platform-chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.platform-chip-list.compact {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.platform-chip {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: #334155;
|
||||
font-size: 20px;
|
||||
font-weight: 680;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.platform-chip.active {
|
||||
border-color: #1d4ed8;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.platform-chip.readonly {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
color: #475569;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.platform-error {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
|
||||
3
apps/taro/src/pages/platform-admin/staff/index.config.ts
Normal file
3
apps/taro/src/pages/platform-admin/staff/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '平台员工',
|
||||
});
|
||||
359
apps/taro/src/pages/platform-admin/staff/index.tsx
Normal file
359
apps/taro/src/pages/platform-admin/staff/index.tsx
Normal file
@@ -0,0 +1,359 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { Button, Input, Text, View } from '@tarojs/components';
|
||||
import {
|
||||
loadPlatformPermissions,
|
||||
loadPlatformStaff,
|
||||
updatePlatformStaffStatus,
|
||||
upsertPlatformStaff,
|
||||
type PlatformPermissionCatalogItem,
|
||||
type PlatformPermissionSummary,
|
||||
type PlatformStaffItem,
|
||||
} from '@/services/platformAdmin';
|
||||
import '../platform.css';
|
||||
|
||||
type StaffForm = {
|
||||
id: string;
|
||||
authUserId: string;
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
avatarUrl: string;
|
||||
status: string;
|
||||
platformPermissions: Record<string, true>;
|
||||
};
|
||||
|
||||
const emptyForm: StaffForm = {
|
||||
id: '',
|
||||
authUserId: '',
|
||||
username: '',
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
avatarUrl: '',
|
||||
status: 'active',
|
||||
platformPermissions: {},
|
||||
};
|
||||
|
||||
function dateText(value?: string | null) {
|
||||
return value ? String(value).slice(0, 19).replace('T', ' ') : '-';
|
||||
}
|
||||
|
||||
function permissionKeys(value?: Record<string, unknown> | null) {
|
||||
return Object.entries(value || {})
|
||||
.filter(([, enabled]) => enabled === true)
|
||||
.map(([key]) => key)
|
||||
.sort();
|
||||
}
|
||||
|
||||
function permissionLabel(item: PlatformPermissionCatalogItem) {
|
||||
return item.label || item.key;
|
||||
}
|
||||
|
||||
function groupLabel(group?: string | null) {
|
||||
const labels: Record<string, string> = {
|
||||
overview: '概览',
|
||||
staff: '员工',
|
||||
tenant: '租户',
|
||||
billing: '账务',
|
||||
usage: '用量',
|
||||
audit: '审计',
|
||||
question_bank: '公共题库',
|
||||
};
|
||||
return labels[group || ''] || group || '其他';
|
||||
}
|
||||
|
||||
function fromStaff(item: PlatformStaffItem): StaffForm {
|
||||
const platformPermissions: Record<string, true> = {};
|
||||
for (const key of permissionKeys(item.platformPermissions)) {
|
||||
platformPermissions[key] = true;
|
||||
}
|
||||
return {
|
||||
id: item.id || '',
|
||||
authUserId: item.authUserId || '',
|
||||
username: item.username || '',
|
||||
name: item.name || '',
|
||||
email: item.email || '',
|
||||
phone: item.phone || '',
|
||||
avatarUrl: item.avatarUrl || '',
|
||||
status: item.status || 'active',
|
||||
platformPermissions,
|
||||
};
|
||||
}
|
||||
|
||||
export default function PlatformStaffPage() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [staff, setStaff] = useState<PlatformStaffItem[]>([]);
|
||||
const [permissionSummary, setPermissionSummary] = useState<PlatformPermissionSummary | null>(null);
|
||||
const [form, setForm] = useState<StaffForm>(emptyForm);
|
||||
const [busy, setBusy] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const catalog = permissionSummary?.catalog || [];
|
||||
const effective = permissionSummary?.effective || {};
|
||||
const canWrite = effective['platform:staff:write'] === true || effective['*'] === true;
|
||||
const canChangeStatus = effective['platform:staff:status'] === true || effective['*'] === true;
|
||||
|
||||
const groupedCatalog = useMemo(() => {
|
||||
const groups: Array<{ group: string; items: PlatformPermissionCatalogItem[] }> = [];
|
||||
const byGroup = new Map<string, PlatformPermissionCatalogItem[]>();
|
||||
for (const item of catalog) {
|
||||
const group = item.group || 'other';
|
||||
byGroup.set(group, [...(byGroup.get(group) || []), item]);
|
||||
}
|
||||
for (const [group, items] of byGroup.entries()) {
|
||||
groups.push({ group, items });
|
||||
}
|
||||
return groups;
|
||||
}, [catalog]);
|
||||
|
||||
function reload(nextStatus = status, nextKeyword = keyword) {
|
||||
setError('');
|
||||
loadPlatformStaff({ q: nextKeyword || undefined, status: nextStatus || undefined, limit: 120 })
|
||||
.then(payload => setStaff(payload.items || []))
|
||||
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '平台员工加载失败'));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
loadPlatformPermissions().catch(() => ({ item: null })),
|
||||
loadPlatformStaff({ limit: 120 }).catch(() => ({ items: [] })),
|
||||
]).then(([permissionPayload, staffPayload]) => {
|
||||
setPermissionSummary(permissionPayload.item || null);
|
||||
setStaff(staffPayload.items || []);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '平台员工页面加载失败'));
|
||||
}, []);
|
||||
|
||||
function chooseStatus(nextStatus: string) {
|
||||
setStatus(nextStatus);
|
||||
reload(nextStatus, keyword);
|
||||
}
|
||||
|
||||
function updateForm(key: keyof StaffForm, value: string) {
|
||||
setForm(current => ({ ...current, [key]: value }));
|
||||
}
|
||||
|
||||
function togglePermission(key: string) {
|
||||
setForm(current => {
|
||||
const nextPermissions = { ...current.platformPermissions };
|
||||
if (nextPermissions[key]) {
|
||||
delete nextPermissions[key];
|
||||
} else {
|
||||
nextPermissions[key] = true;
|
||||
}
|
||||
return { ...current, platformPermissions: nextPermissions };
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSuperPermission() {
|
||||
togglePermission('*');
|
||||
}
|
||||
|
||||
function editStaff(item: PlatformStaffItem) {
|
||||
setForm(fromStaff(item));
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
setForm(emptyForm);
|
||||
}
|
||||
|
||||
async function confirm(title: string, content: string) {
|
||||
const result = await Taro.showModal({ title, content, confirmText: '确认', cancelText: '取消' });
|
||||
return result.confirm;
|
||||
}
|
||||
|
||||
async function submitStaff() {
|
||||
setError('');
|
||||
if (!canWrite) {
|
||||
setError('当前账号没有 platform:staff:write 权限。');
|
||||
return;
|
||||
}
|
||||
if (!form.name.trim()) {
|
||||
setError('平台员工必须填写姓名。');
|
||||
return;
|
||||
}
|
||||
if (!form.authUserId.trim()) {
|
||||
setError('平台员工必须绑定 Supabase Auth 用户 ID,生产环境不允许悬空账号。');
|
||||
return;
|
||||
}
|
||||
const keys = permissionKeys(form.platformPermissions);
|
||||
if (!keys.length) {
|
||||
setError('平台员工至少需要配置一个平台权限。');
|
||||
return;
|
||||
}
|
||||
const ok = await confirm(
|
||||
form.id ? '更新平台员工' : '创建平台员工',
|
||||
form.platformPermissions['*']
|
||||
? '该员工将拥有平台超级权限,请确认这是必要授权。'
|
||||
: `确认保存员工 ${form.name.trim()} 的 ${keys.length} 个权限点?`,
|
||||
);
|
||||
if (!ok) return;
|
||||
setBusy('save');
|
||||
try {
|
||||
const payload = await upsertPlatformStaff({
|
||||
id: form.id || undefined,
|
||||
authUserId: form.authUserId.trim(),
|
||||
username: form.username.trim() || undefined,
|
||||
name: form.name.trim(),
|
||||
email: form.email.trim() || undefined,
|
||||
phone: form.phone.trim() || undefined,
|
||||
avatarUrl: form.avatarUrl.trim() || undefined,
|
||||
status: form.status || 'active',
|
||||
platformPermissions: form.platformPermissions,
|
||||
});
|
||||
Taro.showToast({ title: '已保存', icon: 'success' });
|
||||
if (payload.item) setForm(fromStaff(payload.item));
|
||||
reload(status, keyword);
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '平台员工保存失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitStatus(item: PlatformStaffItem, nextStatus: 'active' | 'disabled') {
|
||||
setError('');
|
||||
if (!canChangeStatus) {
|
||||
setError('当前账号没有 platform:staff:status 权限。');
|
||||
return;
|
||||
}
|
||||
const ok = await confirm(
|
||||
nextStatus === 'disabled' ? '禁用平台员工' : '恢复平台员工',
|
||||
nextStatus === 'disabled'
|
||||
? `确认禁用 ${item.name || item.username || item.id}?后端会默认撤销迁移期 session,Supabase JWT 也会因 status=disabled 被拒绝。`
|
||||
: `确认恢复 ${item.name || item.username || item.id} 的平台后台访问?`,
|
||||
);
|
||||
if (!ok) return;
|
||||
setBusy(`status-${item.id}-${nextStatus}`);
|
||||
try {
|
||||
await updatePlatformStaffStatus({
|
||||
staffId: item.id,
|
||||
status: nextStatus,
|
||||
reason: nextStatus === 'disabled' ? 'platform admin disabled from staff page' : 'platform admin restored from staff page',
|
||||
revokeSessions: nextStatus === 'disabled',
|
||||
});
|
||||
Taro.showToast({ title: nextStatus === 'disabled' ? '已禁用' : '已恢复', icon: 'success' });
|
||||
reload(status, keyword);
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '员工状态更新失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
const activeCount = staff.filter(item => item.status === 'active').length;
|
||||
const disabledCount = staff.filter(item => item.status === 'disabled').length;
|
||||
const selectedPermissions = permissionKeys(form.platformPermissions);
|
||||
|
||||
return (
|
||||
<View className='platform-page'>
|
||||
<View className='platform-shell'>
|
||||
<View className='platform-header'>
|
||||
<Text className='platform-kicker'>Staff</Text>
|
||||
<Text className='platform-title'>平台员工</Text>
|
||||
<Text className='platform-subtitle'>绑定 Supabase Auth 账号,按平台权限点授予租户、账务、审计和公共题库后台能力。</Text>
|
||||
</View>
|
||||
|
||||
<View className='platform-actions'>
|
||||
<Input className='platform-input' placeholder='姓名、用户名、手机号、邮箱' value={keyword} onInput={event => setKeyword(String(event.detail.value || ''))} />
|
||||
<Button className='platform-button primary' onClick={() => reload(status, keyword)}>搜索</Button>
|
||||
<Button className='platform-button' onClick={resetForm}>新建</Button>
|
||||
</View>
|
||||
|
||||
<View className='platform-tabs'>
|
||||
{[
|
||||
{ label: '全部', value: '' },
|
||||
{ label: 'active', value: 'active' },
|
||||
{ label: 'disabled', value: 'disabled' },
|
||||
].map(item => (
|
||||
<Button key={item.label} className={`platform-button ${status === item.value ? 'active' : ''}`} onClick={() => chooseStatus(item.value)}>{item.label}</Button>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View className='platform-grid'>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>员工总数</Text><Text className='platform-metric-value'>{String(staff.length)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>可登录</Text><Text className='platform-metric-value'>{String(activeCount)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>已禁用</Text><Text className='platform-metric-value'>{String(disabledCount)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>当前权限点</Text><Text className='platform-metric-value'>{String(selectedPermissions.length)}</Text></View>
|
||||
</View>
|
||||
|
||||
<View className='platform-section'>
|
||||
<Text className='platform-section-title'>{form.id ? '编辑平台员工' : '创建平台员工'}</Text>
|
||||
<View className='platform-form'>
|
||||
<View className='platform-field wide'><Text className='platform-field-label'>员工 ID</Text><Input className='platform-input' placeholder='编辑已有员工时自动填充' value={form.id} onInput={event => updateForm('id', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field wide'><Text className='platform-field-label'>Supabase Auth 用户 ID</Text><Input className='platform-input' placeholder='auth.users.id' value={form.authUserId} onInput={event => updateForm('authUserId', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>用户名</Text><Input className='platform-input' placeholder='platform_operator' value={form.username} onInput={event => updateForm('username', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>姓名</Text><Input className='platform-input' placeholder='员工姓名' value={form.name} onInput={event => updateForm('name', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>邮箱</Text><Input className='platform-input' placeholder='name@example.com' value={form.email} onInput={event => updateForm('email', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>手机号</Text><Input className='platform-input' placeholder='13800138000' value={form.phone} onInput={event => updateForm('phone', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>状态</Text><Input className='platform-input' placeholder='active / disabled' value={form.status} onInput={event => updateForm('status', String(event.detail.value || ''))} /></View>
|
||||
<View className='platform-field'><Text className='platform-field-label'>头像 URL</Text><Input className='platform-input' placeholder='可选' value={form.avatarUrl} onInput={event => updateForm('avatarUrl', String(event.detail.value || ''))} /></View>
|
||||
</View>
|
||||
|
||||
<View className='platform-permission-panel'>
|
||||
<View className='platform-permission-header'>
|
||||
<Text className='platform-row-main'>权限点</Text>
|
||||
<Button className={`platform-mini-button ${form.platformPermissions['*'] ? 'active' : ''}`} onClick={toggleSuperPermission}>超级权限 *</Button>
|
||||
</View>
|
||||
{groupedCatalog.map(group => (
|
||||
<View className='platform-permission-group' key={group.group}>
|
||||
<Text className='platform-field-label'>{groupLabel(group.group)}</Text>
|
||||
<View className='platform-chip-list'>
|
||||
{group.items.map(item => (
|
||||
<Button
|
||||
key={item.key}
|
||||
className={`platform-chip ${form.platformPermissions[item.key] ? 'active' : ''}`}
|
||||
onClick={() => togglePermission(item.key)}
|
||||
>
|
||||
{permissionLabel(item)}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{!catalog.length ? <View className='platform-empty'>当前账号无法读取权限目录,或平台鉴权未通过。</View> : null}
|
||||
</View>
|
||||
|
||||
<View className='platform-actions'>
|
||||
<Button className='platform-button primary' loading={busy === 'save'} disabled={!canWrite} onClick={submitStaff}>保存员工</Button>
|
||||
<Button className='platform-button' onClick={resetForm}>清空表单</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='platform-section'>
|
||||
<Text className='platform-section-title'>员工列表</Text>
|
||||
<View className='platform-list'>
|
||||
{staff.map(item => {
|
||||
const keys = permissionKeys(item.platformPermissions);
|
||||
return (
|
||||
<View className='platform-row' key={item.id}>
|
||||
<Text className='platform-row-main'>{item.name || item.username || item.id}</Text>
|
||||
<Text className='platform-row-meta'>{item.username || '-'} · {item.status || '-'} · {item.email || '-'} · {item.phone || '-'}</Text>
|
||||
<Text className='platform-row-meta'>Auth {item.authUserId || '未绑定'} · 最近登录 {dateText(item.lastSeenAt)} · 创建 {dateText(item.createdAt)}</Text>
|
||||
<View className='platform-chip-list compact'>
|
||||
{keys.slice(0, 10).map(key => <Text className='platform-chip readonly' key={key}>{key}</Text>)}
|
||||
{keys.length > 10 ? <Text className='platform-chip readonly'>+{keys.length - 10}</Text> : null}
|
||||
{!keys.length ? <Text className='platform-chip readonly'>无权限</Text> : null}
|
||||
</View>
|
||||
<View className='platform-row-actions'>
|
||||
<Button className='platform-mini-button' onClick={() => editStaff(item)}>编辑</Button>
|
||||
{item.status === 'disabled' ? (
|
||||
<Button className='platform-mini-button' disabled={!canChangeStatus} loading={busy === `status-${item.id}-active`} onClick={() => submitStatus(item, 'active')}>恢复</Button>
|
||||
) : (
|
||||
<Button className='platform-mini-button danger' disabled={!canChangeStatus} loading={busy === `status-${item.id}-disabled`} onClick={() => submitStatus(item, 'disabled')}>禁用</Button>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
{!staff.length ? <View className='platform-empty'>暂无平台员工,或当前账号没有查看权限。</View> : null}
|
||||
</View>
|
||||
|
||||
{error ? <Text className='platform-error'>{error}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -91,6 +91,7 @@ export default function PlatformWorkbenchPage() {
|
||||
{ name: '租户管理', path: '/pages/platform-admin/tenants/index', meta: '租户状态、套餐、欠费和到期' },
|
||||
{ name: '账务中心', path: '/pages/platform-admin/billing/index', meta: 'SaaS 套餐、发票、收款、用量' },
|
||||
{ name: '公共题库', path: '/pages/platform-admin/question-banks/index', meta: '地区题库、授权、披露范围' },
|
||||
{ name: '平台员工', path: '/pages/platform-admin/staff/index', meta: '员工账号、平台权限、禁用恢复' },
|
||||
];
|
||||
|
||||
async function exportAuditLogs() {
|
||||
@@ -161,6 +162,7 @@ export default function PlatformWorkbenchPage() {
|
||||
<Button className='platform-button primary' onClick={() => Taro.navigateTo({ url: '/pages/platform-admin/tenants/index' })}>租户管理</Button>
|
||||
<Button className='platform-button' onClick={() => Taro.navigateTo({ url: '/pages/platform-admin/billing/index' })}>账务中心</Button>
|
||||
<Button className='platform-button' onClick={() => Taro.navigateTo({ url: '/pages/platform-admin/question-banks/index' })}>公共题库</Button>
|
||||
<Button className='platform-button' onClick={() => Taro.navigateTo({ url: '/pages/platform-admin/staff/index' })}>平台员工</Button>
|
||||
</View>
|
||||
|
||||
<View className='platform-section'>
|
||||
|
||||
@@ -38,6 +38,23 @@ export interface PlatformPermissionSummary {
|
||||
catalog?: PlatformPermissionCatalogItem[];
|
||||
}
|
||||
|
||||
export interface PlatformStaffItem {
|
||||
id: string;
|
||||
authUserId?: string | null;
|
||||
username?: string | null;
|
||||
email?: string | null;
|
||||
phone?: string | null;
|
||||
name?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
primaryRole?: string | null;
|
||||
status?: string | null;
|
||||
platformPermissions?: Record<string, unknown> | null;
|
||||
rawProfile?: Record<string, unknown> | null;
|
||||
lastSeenAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformSaasPlan {
|
||||
id: string;
|
||||
code: string;
|
||||
@@ -478,6 +495,26 @@ export interface UpsertPlatformQuestionBankGrantInput {
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export interface UpsertPlatformStaffInput {
|
||||
id?: string;
|
||||
authUserId?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
status?: string;
|
||||
platformPermissions?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface UpdatePlatformStaffStatusInput {
|
||||
staffId: string;
|
||||
status: 'active' | 'disabled';
|
||||
reason?: string;
|
||||
revokeSessions?: boolean;
|
||||
}
|
||||
|
||||
export async function loadPlatformOverview() {
|
||||
return apiRequest<{ item?: PlatformOverview }>('/api/platform-admin/overview', { tenantId: null });
|
||||
}
|
||||
@@ -486,6 +523,29 @@ export async function loadPlatformPermissions() {
|
||||
return apiRequest<{ item?: PlatformPermissionSummary }>('/api/platform-admin/permissions', { tenantId: null });
|
||||
}
|
||||
|
||||
export async function loadPlatformStaff(query: { q?: string; status?: string; limit?: number } = {}) {
|
||||
return apiRequest<{ items?: PlatformStaffItem[] }>('/api/platform-admin/staff', {
|
||||
query: { ...query, limit: query.limit || 80 },
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function upsertPlatformStaff(input: UpsertPlatformStaffInput) {
|
||||
return apiRequest<{ item?: PlatformStaffItem }>('/api/platform-admin/staff', {
|
||||
method: 'PUT',
|
||||
body: input,
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlatformStaffStatus(input: UpdatePlatformStaffStatusInput) {
|
||||
return apiRequest<{ item?: PlatformStaffItem }>('/api/platform-admin/staff/status', {
|
||||
method: 'PATCH',
|
||||
body: input,
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformPlans(includeArchived = false) {
|
||||
return apiRequest<{ items?: PlatformSaasPlan[] }>('/api/platform-admin/plans', {
|
||||
query: { includeArchived },
|
||||
|
||||
Reference in New Issue
Block a user