forked from wangziqi/gongxue-base
feat: add tenant role template console
This commit is contained in:
@@ -61,11 +61,13 @@ pages/tenant-admin/dashboard/index 数据看板
|
||||
pages/tenant-admin/students/index 学生与班级
|
||||
pages/tenant-admin/content/index 内容入口、导入任务、字段模板、异步轮询、复检、公共题库采纳/同步/单条和批量冲突处理
|
||||
pages/tenant-admin/marketing/index 优惠券、激活码、CRM、分佣摘要
|
||||
pages/tenant-admin/settings/index 品牌、域名、支付、登录、角色模板
|
||||
pages/tenant-admin/settings/index 品牌、域名、支付、登录、角色模板创建/编辑/停用
|
||||
```
|
||||
|
||||
当前后台页面已经从只读联调推进到第一批运营写操作。题库内容页已接入公共题库采纳、公共题库同步、同步冲突查看、单条/批量采纳平台版本或保留本地版本、导入任务详情、异步任务轮询、导入问题查看、模板预览/下载、导入后复检详情,以及 JSON/CSV/Excel 的 H5 选择文件或粘贴内容、后端预览、字段别名覆盖和同步/异步执行导入第一版;营销、设置和学生页仍以扫描和轻量操作为主。真正权限以后端 permission keys 为准,前端菜单隐藏只做体验优化。
|
||||
|
||||
租户设置页已接入角色模板写操作第一版:可新建、编辑、停用非系统模板,并配置权限点、菜单可见、模块可见、字段可见和基础数据范围。前端只负责操作体验,`tenant_owner`、`tenant_admin`、通配权限和系统模板保护仍以后端校验与审计为准。
|
||||
|
||||
## 当前平台后台页面
|
||||
|
||||
已接入第一批真实后端 API:
|
||||
|
||||
@@ -1,39 +1,289 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text, View } from '@tarojs/components';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { Button, Input, Text, Textarea, View } from '@tarojs/components';
|
||||
import {
|
||||
disableRoleTemplate,
|
||||
loadAuthProviders,
|
||||
loadPaymentAccounts,
|
||||
loadRoleTemplates,
|
||||
loadTenantDomains,
|
||||
loadTenantOverview,
|
||||
loadTenantPermissions,
|
||||
upsertRoleTemplate,
|
||||
type TenantOverview,
|
||||
type TenantPermissionCatalogItem,
|
||||
type TenantPermissionsPayload,
|
||||
type TenantRoleTemplateItem,
|
||||
} from '@/services/tenantAdmin';
|
||||
import '../admin.css';
|
||||
|
||||
type BooleanMapField = 'permissions' | 'menuPermissions' | 'modulePermissions' | 'fieldPermissions';
|
||||
|
||||
interface RoleTemplateForm {
|
||||
id?: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
baseRole: string;
|
||||
status: string;
|
||||
sortOrder: string;
|
||||
permissions: Record<string, boolean>;
|
||||
menuPermissions: Record<string, boolean>;
|
||||
modulePermissions: Record<string, boolean>;
|
||||
fieldPermissions: Record<string, boolean>;
|
||||
dataScopeMode: string;
|
||||
ownLeadsOnly: boolean;
|
||||
}
|
||||
|
||||
const BASE_ROLE_OPTIONS = [
|
||||
{ key: 'tenant_operator', label: '运营' },
|
||||
{ key: 'teacher', label: '教师' },
|
||||
{ key: 'sales', label: '销售' },
|
||||
{ key: 'agent', label: '代理' },
|
||||
{ key: 'student', label: '学生' },
|
||||
{ key: 'tenant_admin', label: '管理员' },
|
||||
];
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ key: 'active', label: '启用' },
|
||||
{ key: 'disabled', label: '停用' },
|
||||
{ key: 'archived', label: '归档' },
|
||||
];
|
||||
|
||||
const DATA_SCOPE_OPTIONS = [
|
||||
{ key: 'tenant', label: '全租户' },
|
||||
{ key: 'own', label: '本人' },
|
||||
{ key: 'class', label: '班级' },
|
||||
{ key: 'sales_team', label: '销售团队' },
|
||||
];
|
||||
|
||||
const MODULE_CATALOG: TenantPermissionCatalogItem[] = [
|
||||
{ key: 'banners', label: 'Banner' },
|
||||
{ key: 'announcements', label: '公告' },
|
||||
{ key: 'faqs', label: 'FAQ' },
|
||||
{ key: 'coupons', label: '优惠券' },
|
||||
{ key: 'activation_codes', label: '激活码' },
|
||||
{ key: 'badges', label: '勋章' },
|
||||
{ key: 'imports', label: '导入' },
|
||||
{ key: 'public_question_banks', label: '公共题库' },
|
||||
{ key: 'assets', label: '资料' },
|
||||
{ key: 'videos', label: '视频' },
|
||||
{ key: 'scoreline', label: '分数线' },
|
||||
{ key: 'vocabulary', label: '单词' },
|
||||
{ key: 'handbook', label: '知识手册' },
|
||||
{ key: 'classes', label: '班级' },
|
||||
{ key: 'followups', label: '跟进' },
|
||||
{ key: 'commissions', label: '分佣' },
|
||||
{ key: 'crm_queue', label: 'CRM 队列' },
|
||||
];
|
||||
|
||||
function boolRecord(value: unknown) {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
const result: Record<string, boolean> = {};
|
||||
Object.entries(source).forEach(([key, raw]) => {
|
||||
if (typeof raw === 'boolean') result[key] = raw;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function emptyRoleTemplateForm(): RoleTemplateForm {
|
||||
return {
|
||||
code: '',
|
||||
name: '',
|
||||
description: '',
|
||||
baseRole: 'tenant_operator',
|
||||
status: 'active',
|
||||
sortOrder: '100',
|
||||
permissions: {},
|
||||
menuPermissions: {},
|
||||
modulePermissions: {},
|
||||
fieldPermissions: {},
|
||||
dataScopeMode: 'tenant',
|
||||
ownLeadsOnly: false,
|
||||
};
|
||||
}
|
||||
|
||||
function roleTemplateFormFrom(item?: TenantRoleTemplateItem | null): RoleTemplateForm {
|
||||
if (!item) return emptyRoleTemplateForm();
|
||||
const dataScope = item.dataScope && typeof item.dataScope === 'object' ? item.dataScope : {};
|
||||
return {
|
||||
id: item.id,
|
||||
code: item.code || '',
|
||||
name: item.name || '',
|
||||
description: item.description || '',
|
||||
baseRole: item.baseRole || 'tenant_operator',
|
||||
status: item.status || 'active',
|
||||
sortOrder: String(item.sortOrder ?? 100),
|
||||
permissions: boolRecord(item.permissions),
|
||||
menuPermissions: boolRecord(item.menuPermissions),
|
||||
modulePermissions: boolRecord(item.modulePermissions),
|
||||
fieldPermissions: boolRecord(item.fieldPermissions),
|
||||
dataScopeMode: String(dataScope.mode || 'tenant'),
|
||||
ownLeadsOnly: dataScope.ownLeadsOnly === true,
|
||||
};
|
||||
}
|
||||
|
||||
function catalogLabel(item: TenantPermissionCatalogItem) {
|
||||
return item.label ? `${item.label}` : item.key;
|
||||
}
|
||||
|
||||
export default function TenantSettingsPage() {
|
||||
const [overview, setOverview] = useState<TenantOverview | null>(null);
|
||||
const [domains, setDomains] = useState<Record<string, unknown>[]>([]);
|
||||
const [payments, setPayments] = useState<Record<string, unknown>[]>([]);
|
||||
const [authProviders, setAuthProviders] = useState<Record<string, unknown>[]>([]);
|
||||
const [roles, setRoles] = useState<Record<string, unknown>[]>([]);
|
||||
const [roles, setRoles] = useState<TenantRoleTemplateItem[]>([]);
|
||||
const [permissionsPayload, setPermissionsPayload] = useState<TenantPermissionsPayload>({});
|
||||
const [selectedRoleId, setSelectedRoleId] = useState('');
|
||||
const [roleForm, setRoleForm] = useState<RoleTemplateForm>(() => emptyRoleTemplateForm());
|
||||
const [busy, setBusy] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
loadTenantOverview().catch(() => ({ item: null })),
|
||||
loadTenantDomains().catch(() => ({ items: [] })),
|
||||
loadPaymentAccounts().catch(() => ({ items: [] })),
|
||||
loadAuthProviders().catch(() => ({ items: [] })),
|
||||
loadRoleTemplates().catch(() => ({ items: [] })),
|
||||
]).then(([overviewPayload, domainPayload, paymentPayload, authPayload, rolePayload]) => {
|
||||
const selectedRole = useMemo(
|
||||
() => roles.find(item => item.id === selectedRoleId) || null,
|
||||
[roles, selectedRoleId],
|
||||
);
|
||||
const fieldCatalog = permissionsPayload.fieldGroups || permissionsPayload.fieldCatalog || [];
|
||||
const editingSystemRole = Boolean(selectedRole?.isSystem);
|
||||
|
||||
async function reloadSettings(preferredRoleId = selectedRoleId) {
|
||||
setError('');
|
||||
try {
|
||||
const [overviewPayload, domainPayload, paymentPayload, authPayload, rolePayload, permissionPayload] = await Promise.all([
|
||||
loadTenantOverview().catch(() => ({ item: null })),
|
||||
loadTenantDomains().catch(() => ({ items: [] })),
|
||||
loadPaymentAccounts().catch(() => ({ items: [] })),
|
||||
loadAuthProviders().catch(() => ({ items: [] })),
|
||||
loadRoleTemplates().catch(() => ({ items: [] })),
|
||||
loadTenantPermissions().catch(() => ({})),
|
||||
]);
|
||||
const nextRoles = rolePayload.items || [];
|
||||
const nextSelected = nextRoles.find(item => item.id === preferredRoleId) || nextRoles[0] || null;
|
||||
setOverview(overviewPayload.item || null);
|
||||
setDomains(domainPayload.items || []);
|
||||
setPayments(paymentPayload.items || []);
|
||||
setAuthProviders(authPayload.items || []);
|
||||
setRoles(rolePayload.items || []);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '设置加载失败'));
|
||||
setRoles(nextRoles);
|
||||
setPermissionsPayload(permissionPayload);
|
||||
setSelectedRoleId(nextSelected?.id || '');
|
||||
setRoleForm(nextSelected ? roleTemplateFormFrom(nextSelected) : emptyRoleTemplateForm());
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '设置加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void reloadSettings('');
|
||||
}, []);
|
||||
|
||||
function startNewRole() {
|
||||
setSelectedRoleId('');
|
||||
setRoleForm(emptyRoleTemplateForm());
|
||||
}
|
||||
|
||||
function selectRole(item: TenantRoleTemplateItem) {
|
||||
setSelectedRoleId(item.id);
|
||||
setRoleForm(roleTemplateFormFrom(item));
|
||||
}
|
||||
|
||||
function updateBooleanMap(field: BooleanMapField, key: string) {
|
||||
setRoleForm(prev => ({
|
||||
...prev,
|
||||
[field]: {
|
||||
...prev[field],
|
||||
[key]: !prev[field][key],
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
async function submitRoleTemplate() {
|
||||
if (!roleForm.name.trim() || !roleForm.code.trim()) {
|
||||
Taro.showToast({ title: '名称和编码必填', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (editingSystemRole) {
|
||||
Taro.showToast({ title: '系统模板不可编辑', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const sortOrder = Number(roleForm.sortOrder || 100);
|
||||
if (!Number.isFinite(sortOrder)) {
|
||||
Taro.showToast({ title: '排序值无效', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
setBusy('save-role');
|
||||
setError('');
|
||||
try {
|
||||
const result = await upsertRoleTemplate({
|
||||
id: roleForm.id,
|
||||
code: roleForm.code.trim(),
|
||||
name: roleForm.name.trim(),
|
||||
description: roleForm.description.trim() || null,
|
||||
baseRole: roleForm.baseRole,
|
||||
status: roleForm.status,
|
||||
sortOrder: Math.trunc(sortOrder),
|
||||
permissions: roleForm.permissions,
|
||||
menuPermissions: roleForm.menuPermissions,
|
||||
modulePermissions: roleForm.modulePermissions,
|
||||
fieldPermissions: roleForm.fieldPermissions,
|
||||
dataScope: {
|
||||
mode: roleForm.dataScopeMode,
|
||||
ownLeadsOnly: roleForm.ownLeadsOnly,
|
||||
},
|
||||
});
|
||||
const nextId = result.item?.id || roleForm.id || '';
|
||||
Taro.showToast({ title: '角色已保存', icon: 'success' });
|
||||
await reloadSettings(nextId);
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '角色保存失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
async function disableTemplate(item: TenantRoleTemplateItem) {
|
||||
if (item.isSystem) {
|
||||
Taro.showToast({ title: '系统模板不可停用', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const confirmed = await Taro.showModal({
|
||||
title: '停用角色模板',
|
||||
content: `确认停用 ${item.name || item.code}?`,
|
||||
confirmText: '停用',
|
||||
cancelText: '取消',
|
||||
});
|
||||
if (!confirmed.confirm) return;
|
||||
|
||||
setBusy(`disable:${item.id}`);
|
||||
setError('');
|
||||
try {
|
||||
await disableRoleTemplate(item.id);
|
||||
Taro.showToast({ title: '已停用', icon: 'success' });
|
||||
await reloadSettings(item.id);
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '角色停用失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
function renderToggleGroup(items: TenantPermissionCatalogItem[], field: BooleanMapField) {
|
||||
if (!items.length) return <View className='admin-empty'>暂无可配置项。</View>;
|
||||
return (
|
||||
<View className='admin-actions compact'>
|
||||
{items.map(item => (
|
||||
<Button
|
||||
key={item.key}
|
||||
className={`admin-mini-button ${roleForm[field][item.key] ? 'primary' : ''}`}
|
||||
onClick={() => updateBooleanMap(field, item.key)}
|
||||
>
|
||||
{catalogLabel(item)}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='admin-page'>
|
||||
<View className='admin-shell'>
|
||||
@@ -91,17 +341,138 @@ export default function TenantSettingsPage() {
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>角色模板</Text>
|
||||
<View className='admin-actions compact'>
|
||||
<Text className='admin-section-title'>角色模板</Text>
|
||||
<Button className='admin-button primary' onClick={startNewRole}>新建模板</Button>
|
||||
</View>
|
||||
<View className='admin-list'>
|
||||
{roles.map((item, index) => (
|
||||
<View className='admin-row' key={String(item.id || index)}>
|
||||
<Text className='admin-row-main'>{String(item.name || item.roleKey || '角色模板')}</Text>
|
||||
<Text className='admin-row-meta'>{String(item.status || 'active')} · 菜单和字段权限以后端模板为准</Text>
|
||||
{roles.map(item => (
|
||||
<View className='admin-row' key={item.id}>
|
||||
<Text className='admin-row-main'>{item.name || item.code}</Text>
|
||||
<Text className='admin-row-meta'>{item.code} · {item.baseRole || '-'} · {item.status || 'active'}{item.isSystem ? ' · system' : ''}</Text>
|
||||
<View className='admin-row-actions'>
|
||||
<Button className='admin-mini-button primary' onClick={() => selectRole(item)}>编辑</Button>
|
||||
{!item.isSystem ? (
|
||||
<Button className='admin-mini-button' loading={busy === `disable:${item.id}`} onClick={() => disableTemplate(item)}>停用</Button>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{!roles.length ? <View className='admin-empty'>暂无自定义角色模板。</View> : null}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>{selectedRoleId ? '编辑角色模板' : '新建角色模板'}</Text>
|
||||
{editingSystemRole ? <View className='admin-empty'>系统模板不可编辑,可新建自定义模板承载租户权限方案。</View> : null}
|
||||
<View className='admin-form-grid'>
|
||||
<Input
|
||||
className='admin-input'
|
||||
placeholder='模板名称'
|
||||
value={roleForm.name}
|
||||
onInput={event => setRoleForm(prev => ({ ...prev, name: String(event.detail.value || '') }))}
|
||||
/>
|
||||
<Input
|
||||
className='admin-input'
|
||||
placeholder='模板编码,例如 ops-marketing'
|
||||
value={roleForm.code}
|
||||
disabled={Boolean(roleForm.id)}
|
||||
onInput={event => setRoleForm(prev => ({ ...prev, code: String(event.detail.value || '') }))}
|
||||
/>
|
||||
<Input
|
||||
className='admin-input'
|
||||
placeholder='排序,数字越小越靠前'
|
||||
type='number'
|
||||
value={roleForm.sortOrder}
|
||||
onInput={event => setRoleForm(prev => ({ ...prev, sortOrder: String(event.detail.value || '') }))}
|
||||
/>
|
||||
<Textarea
|
||||
className='admin-textarea'
|
||||
placeholder='角色说明'
|
||||
value={roleForm.description}
|
||||
onInput={event => setRoleForm(prev => ({ ...prev, description: String(event.detail.value || '') }))}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>基础角色</Text>
|
||||
<View className='admin-actions compact'>
|
||||
{BASE_ROLE_OPTIONS.map(item => (
|
||||
<Button
|
||||
key={item.key}
|
||||
className={`admin-button ${roleForm.baseRole === item.key ? 'active' : ''}`}
|
||||
onClick={() => setRoleForm(prev => ({ ...prev, baseRole: item.key }))}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>模板状态</Text>
|
||||
<View className='admin-actions compact'>
|
||||
{STATUS_OPTIONS.map(item => (
|
||||
<Button
|
||||
key={item.key}
|
||||
className={`admin-button ${roleForm.status === item.key ? 'active' : ''}`}
|
||||
onClick={() => setRoleForm(prev => ({ ...prev, status: item.key }))}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>权限点</Text>
|
||||
{renderToggleGroup(permissionsPayload.permissions || [], 'permissions')}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>菜单可见</Text>
|
||||
{renderToggleGroup(permissionsPayload.menuGroups || [], 'menuPermissions')}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>模块可见</Text>
|
||||
{renderToggleGroup(MODULE_CATALOG, 'modulePermissions')}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>字段可见</Text>
|
||||
{renderToggleGroup(fieldCatalog, 'fieldPermissions')}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>数据范围</Text>
|
||||
<View className='admin-actions compact'>
|
||||
{DATA_SCOPE_OPTIONS.map(item => (
|
||||
<Button
|
||||
key={item.key}
|
||||
className={`admin-button ${roleForm.dataScopeMode === item.key ? 'active' : ''}`}
|
||||
onClick={() => setRoleForm(prev => ({ ...prev, dataScopeMode: item.key }))}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
className={`admin-button ${roleForm.ownLeadsOnly ? 'active' : ''}`}
|
||||
onClick={() => setRoleForm(prev => ({ ...prev, ownLeadsOnly: !prev.ownLeadsOnly }))}
|
||||
>
|
||||
本人客资
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<View className='admin-actions compact'>
|
||||
<Button className='admin-button primary' loading={busy === 'save-role'} onClick={submitRoleTemplate}>保存模板</Button>
|
||||
<Button className='admin-button' onClick={() => reloadSettings(selectedRoleId)}>刷新</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{error ? <Text className='admin-error'>{error}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -249,6 +249,54 @@ export interface TenantOverview {
|
||||
publicConfig?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface TenantPermissionCatalogItem {
|
||||
key: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface TenantPermissionsPayload {
|
||||
current?: Record<string, unknown>;
|
||||
permissions?: TenantPermissionCatalogItem[];
|
||||
menuGroups?: TenantPermissionCatalogItem[];
|
||||
fieldGroups?: TenantPermissionCatalogItem[];
|
||||
fieldCatalog?: TenantPermissionCatalogItem[];
|
||||
roleDefaults?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface TenantRoleTemplateItem {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
baseRole?: string;
|
||||
status?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
menuPermissions?: Record<string, boolean>;
|
||||
modulePermissions?: Record<string, boolean>;
|
||||
fieldPermissions?: Record<string, boolean>;
|
||||
dataScope?: Record<string, unknown>;
|
||||
isSystem?: boolean;
|
||||
sortOrder?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface TenantRoleTemplateInput {
|
||||
id?: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
baseRole: string;
|
||||
status?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
menuPermissions?: Record<string, boolean>;
|
||||
modulePermissions?: Record<string, boolean>;
|
||||
fieldPermissions?: Record<string, boolean>;
|
||||
dataScope?: Record<string, unknown>;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export async function loadTenantDashboard(timeRange = '30d') {
|
||||
return apiRequest<{ item?: TenantDashboard }>('/api/tenant-admin/dashboard', { query: { timeRange } });
|
||||
}
|
||||
@@ -258,7 +306,7 @@ export async function loadTenantOverview() {
|
||||
}
|
||||
|
||||
export async function loadTenantPermissions() {
|
||||
return apiRequest<{ items?: Record<string, unknown>[]; modules?: Record<string, unknown>[] }>('/api/tenant-admin/permissions');
|
||||
return apiRequest<TenantPermissionsPayload>('/api/tenant-admin/permissions');
|
||||
}
|
||||
|
||||
export async function loadTenantClasses(limit = 50) {
|
||||
@@ -394,7 +442,21 @@ export async function loadAuthProviders() {
|
||||
}
|
||||
|
||||
export async function loadRoleTemplates() {
|
||||
return apiRequest<{ items?: Record<string, unknown>[] }>('/api/tenant-admin/role-templates');
|
||||
return apiRequest<{ items?: TenantRoleTemplateItem[] }>('/api/tenant-admin/role-templates');
|
||||
}
|
||||
|
||||
export async function upsertRoleTemplate(input: TenantRoleTemplateInput) {
|
||||
return apiRequest<{ item?: TenantRoleTemplateItem }>('/api/tenant-admin/role-templates', {
|
||||
method: 'PUT',
|
||||
body: input,
|
||||
});
|
||||
}
|
||||
|
||||
export async function disableRoleTemplate(roleTemplateId: string) {
|
||||
return apiRequest<{ item?: TenantRoleTemplateItem }>('/api/tenant-admin/role-templates/disable', {
|
||||
method: 'POST',
|
||||
body: { roleTemplateId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadCrmQueue(limit = 20) {
|
||||
|
||||
Reference in New Issue
Block a user