重构前端逻辑
This commit is contained in:
parent
8712f199a1
commit
8b940229fc
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Deploy Ease Platform</title>
|
<title>链宇Deploy Ease平台</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const isProd = MODE === 'production';
|
|||||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
||||||
|
|
||||||
// 应用标题
|
// 应用标题
|
||||||
export const APP_TITLE = import.meta.env.VITE_APP_TITLE || 'Deploy Ease Platform';
|
export const APP_TITLE = import.meta.env.VITE_APP_TITLE || '链宇Deploy Ease平台';
|
||||||
|
|
||||||
// 是否启用 Mock
|
// 是否启用 Mock
|
||||||
export const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true';
|
export const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true';
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const EnvironmentTabs: React.FC<EnvironmentTabsProps> = React.memo(({
|
|||||||
<div className="h-8 w-1 bg-primary rounded-full" />
|
<div className="h-8 w-1 bg-primary rounded-full" />
|
||||||
<h3 className="text-lg font-semibold">部署环境</h3>
|
<h3 className="text-lg font-semibold">部署环境</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground min-h-[20px]">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground h-[32px]">
|
||||||
{currentEnv && currentEnv.requiresApproval && currentEnv.approvers.length > 0 && (
|
{currentEnv && currentEnv.requiresApproval && currentEnv.approvers.length > 0 && (
|
||||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800">
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800">
|
||||||
<Shield className="h-3.5 w-3.5 text-amber-600" />
|
<Shield className="h-3.5 w-3.5 text-amber-600" />
|
||||||
@ -90,7 +90,7 @@ export const EnvironmentTabs: React.FC<EnvironmentTabsProps> = React.memo(({
|
|||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
{team.environments.map((env) => (
|
{team.environments.map((env) => (
|
||||||
<TabsContent key={env.environmentId} value={env.environmentId.toString()} className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0">
|
<TabsContent key={env.environmentId} value={env.environmentId.toString()} className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0">
|
||||||
<div className="p-6">
|
<div className="p-6 min-h-[400px]">
|
||||||
{env.applications.length === 0 ? (
|
{env.applications.length === 0 ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||||
|
|||||||
@ -8,6 +8,7 @@ interface TeamSelectorProps {
|
|||||||
teams: DeployTeam[];
|
teams: DeployTeam[];
|
||||||
currentTeamId: number | null;
|
currentTeamId: number | null;
|
||||||
pendingApprovalCount: number;
|
pendingApprovalCount: number;
|
||||||
|
showApprovalButton: boolean; // 是否显示待审批按钮(基于当前环境)
|
||||||
onTeamChange: (teamId: string) => void;
|
onTeamChange: (teamId: string) => void;
|
||||||
onApprovalClick: () => void;
|
onApprovalClick: () => void;
|
||||||
}
|
}
|
||||||
@ -20,12 +21,14 @@ export const TeamSelector: React.FC<TeamSelectorProps> = React.memo(({
|
|||||||
teams,
|
teams,
|
||||||
currentTeamId,
|
currentTeamId,
|
||||||
pendingApprovalCount,
|
pendingApprovalCount,
|
||||||
|
showApprovalButton,
|
||||||
onTeamChange,
|
onTeamChange,
|
||||||
onApprovalClick
|
onApprovalClick
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* 待审批按钮 */}
|
{/* 待审批按钮 - 只在当前环境需要审批且用户有权限时显示 */}
|
||||||
|
{showApprovalButton && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-orange-500 text-orange-600 hover:bg-orange-50 hover:text-orange-700 hover:border-orange-600"
|
className="border-orange-500 text-orange-600 hover:bg-orange-50 hover:text-orange-700 hover:border-orange-600"
|
||||||
@ -39,6 +42,7 @@ export const TeamSelector: React.FC<TeamSelectorProps> = React.memo(({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="h-6 w-px bg-border" />
|
<div className="h-6 w-px bg-border" />
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,18 @@ const Dashboard: React.FC = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [deploymentData.teams.length]);
|
}, [deploymentData.teams.length]);
|
||||||
|
|
||||||
|
// 计算当前环境是否应该显示待审批按钮
|
||||||
|
// 逻辑:当前环境需要审批 且 用户是该环境的审批人
|
||||||
|
const shouldShowApprovalButton = React.useMemo(() => {
|
||||||
|
if (!deploymentData.currentTeam || !deploymentData.currentEnvId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const currentEnv = deploymentData.currentTeam.environments.find(
|
||||||
|
env => env.environmentId === deploymentData.currentEnvId
|
||||||
|
);
|
||||||
|
return currentEnv?.requiresApproval === true && currentEnv?.canApprove === true;
|
||||||
|
}, [deploymentData.currentTeam, deploymentData.currentEnvId]);
|
||||||
|
|
||||||
// 处理部署成功 - 使用 useCallback 避免重复创建
|
// 处理部署成功 - 使用 useCallback 避免重复创建
|
||||||
const handleDeploy = useCallback(async (app: ApplicationConfig, remark: string) => {
|
const handleDeploy = useCallback(async (app: ApplicationConfig, remark: string) => {
|
||||||
await deploymentData.handleDeploySuccess();
|
await deploymentData.handleDeploySuccess();
|
||||||
@ -101,6 +113,7 @@ const Dashboard: React.FC = () => {
|
|||||||
teams={deploymentData.teams}
|
teams={deploymentData.teams}
|
||||||
currentTeamId={deploymentData.currentTeamId}
|
currentTeamId={deploymentData.currentTeamId}
|
||||||
pendingApprovalCount={approvalData.pendingApprovalCount}
|
pendingApprovalCount={approvalData.pendingApprovalCount}
|
||||||
|
showApprovalButton={shouldShowApprovalButton}
|
||||||
onTeamChange={deploymentData.handleTeamChange}
|
onTeamChange={deploymentData.handleTeamChange}
|
||||||
onApprovalClick={() => approvalData.setApprovalModalOpen(true)}
|
onApprovalClick={() => approvalData.setApprovalModalOpen(true)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -261,8 +261,10 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
|||||||
teamId,
|
teamId,
|
||||||
environmentId: data.environmentId,
|
environmentId: data.environmentId,
|
||||||
approvalRequired: data.approvalRequired,
|
approvalRequired: data.approvalRequired,
|
||||||
approverUserIds: data.approverUserIds,
|
// 如果不需要审批,清空审批人列表
|
||||||
notificationChannelId: data.notificationChannelId,
|
approverUserIds: data.approvalRequired ? data.approverUserIds : [],
|
||||||
|
// 如果未启用通知,清空通知渠道
|
||||||
|
notificationChannelId: data.notificationEnabled ? data.notificationChannelId : undefined,
|
||||||
notificationEnabled: data.notificationEnabled,
|
notificationEnabled: data.notificationEnabled,
|
||||||
requireCodeReview: data.requireCodeReview,
|
requireCodeReview: data.requireCodeReview,
|
||||||
remark: data.remark,
|
remark: data.remark,
|
||||||
@ -376,7 +378,13 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={(checked) => {
|
||||||
|
field.onChange(checked);
|
||||||
|
// 取消审批时自动清空审批人列表
|
||||||
|
if (!checked) {
|
||||||
|
form.setValue('approverUserIds', []);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -500,7 +508,13 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={(checked) => {
|
||||||
|
field.onChange(checked);
|
||||||
|
// 取消通知时自动清空通知渠道
|
||||||
|
if (!checked) {
|
||||||
|
form.setValue('notificationChannelId', undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const Login: React.FC = () => {
|
|||||||
} = useForm<LoginFormValues>({
|
} = useForm<LoginFormValues>({
|
||||||
resolver: zodResolver(loginFormSchema),
|
resolver: zodResolver(loginFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
tenantId: '',
|
tenantId: 'admin', // 默认租户
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
@ -69,14 +69,21 @@ const Login: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 加载菜单数据
|
// 加载菜单数据
|
||||||
const loadMenuData = async () => {
|
const loadMenuData = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const menuData = await getCurrentUserMenus();
|
const menuData = await getCurrentUserMenus();
|
||||||
if (menuData && menuData.length > 0) {
|
if (menuData && menuData.length > 0) {
|
||||||
dispatch(setMenus(menuData));
|
dispatch(setMenus(menuData));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 没有任何菜单权限
|
||||||
|
dispatch(setMenus([]));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取菜单数据失败:', error);
|
console.error('获取菜单数据失败:', error);
|
||||||
|
dispatch(setMenus([]));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,19 +104,23 @@ const Login: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 2. 获取菜单数据
|
// 2. 获取菜单数据
|
||||||
await loadMenuData();
|
const hasMenus = await loadMenuData();
|
||||||
|
|
||||||
// 3. 显示成功提示
|
// 3. 显示成功提示
|
||||||
toast({
|
toast({
|
||||||
title: '登录成功',
|
title: '登录成功',
|
||||||
description: '正在进入系统...',
|
description: hasMenus ? '正在进入系统...' : '正在加载...',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. 短暂延迟后跳转(让用户看到成功提示)
|
// 4. 短暂延迟后跳转(让用户看到成功提示)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// ✅ 最稳定方案:刷新页面重新初始化
|
if (hasMenus) {
|
||||||
// 优点:100% 可靠,使用缓存菜单启动快
|
// 有菜单权限,跳转到首页
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
// 没有任何菜单权限,跳转到无权限欢迎页
|
||||||
|
window.location.href = '/no-permission';
|
||||||
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('登录失败:', error);
|
console.error('登录失败:', error);
|
||||||
@ -141,33 +152,12 @@ const Login: React.FC = () => {
|
|||||||
<div className={styles.rightSection}>
|
<div className={styles.rightSection}>
|
||||||
<div className={styles.loginBox}>
|
<div className={styles.loginBox}>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<h1>Deploy Ease Platform</h1>
|
<h1>链宇Deploy Ease平台</h1>
|
||||||
<p className="text-gray-500 mt-2">输入您的账号密码登录系统</p>
|
<p className="text-gray-500 mt-2">输入您的账号密码登录系统</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
{/* 租户选择 */}
|
{/* 租户选择 - 隐藏,默认使用 admin */}
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="tenantId">租户</Label>
|
|
||||||
<Select
|
|
||||||
value={selectedTenantId}
|
|
||||||
onValueChange={(value) => setValue('tenantId', value, { shouldValidate: true })}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="tenantId" className="h-10">
|
|
||||||
<SelectValue placeholder="系统管理租户" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{tenants.map((tenant) => (
|
|
||||||
<SelectItem key={tenant.code} value={tenant.code}>
|
|
||||||
{tenant.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{errors.tenantId && (
|
|
||||||
<p className="text-sm text-destructive">{errors.tenantId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 用户名 */}
|
{/* 用户名 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { Loader2, Tag as TagIcon, Search, CheckCircle2 } from 'lucide-react';
|
import { Loader2, Tag as TagIcon, Search } from 'lucide-react';
|
||||||
import type { RoleTagResponse } from '../types';
|
import type { RoleTagResponse } from '../types';
|
||||||
import { getAllTags, assignTags } from '../service';
|
import { getAllTags, assignTags } from '../service';
|
||||||
|
|
||||||
@ -106,26 +106,19 @@ const AssignTagDialog: React.FC<AssignTagDialogProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[580px]">
|
<DialogContent className="sm:max-w-[550px]">
|
||||||
<DialogHeader className="space-y-3">
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2 text-lg">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500">
|
<TagIcon className="h-5 w-5" />
|
||||||
<TagIcon className="h-5 w-5 text-white" />
|
分配标签
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="font-semibold">分配标签</div>
|
|
||||||
<div className="text-sm font-normal text-muted-foreground mt-0.5">
|
|
||||||
为角色选择适合的标签分类
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-8">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||||
<span className="ml-3 text-sm text-muted-foreground">加载标签中...</span>
|
<span className="text-sm text-muted-foreground">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -143,25 +136,20 @@ const AssignTagDialog: React.FC<AssignTagDialogProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 统计信息 */}
|
{/* 统计信息 */}
|
||||||
<div className="flex items-center justify-between px-1 py-2 rounded-lg bg-muted/50">
|
{allTags.length > 0 && (
|
||||||
<span className="text-sm text-muted-foreground">
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||||
共 {filteredTags.length} 个标签
|
<span>共 {filteredTags.length} 个标签</span>
|
||||||
</span>
|
<span>已选 {selectedTagIds.length} 个</span>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
已选 {selectedTagIds.length} 个
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 标签列表 */}
|
{/* 标签列表 */}
|
||||||
<div className="space-y-2 max-h-[360px] overflow-y-auto pr-2 -mr-2">
|
<div className="space-y-2 max-h-[320px] overflow-y-auto">
|
||||||
{filteredTags.length > 0 ? (
|
{filteredTags.length > 0 ? (
|
||||||
filteredTags.map(tag => (
|
filteredTags.map(tag => (
|
||||||
<div
|
<div
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
className="flex items-center gap-3 p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors cursor-pointer group"
|
className="flex items-center gap-3 p-2.5 rounded-md hover:bg-muted/50 transition-colors cursor-pointer"
|
||||||
onClick={() => handleToggle(tag.id)}
|
onClick={() => handleToggle(tag.id)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -170,39 +158,28 @@ const AssignTagDialog: React.FC<AssignTagDialogProps> = ({
|
|||||||
onCheckedChange={() => handleToggle(tag.id)}
|
onCheckedChange={() => handleToggle(tag.id)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
className="font-medium"
|
style={{ backgroundColor: tag.color, color: '#fff' }}
|
||||||
style={{
|
|
||||||
backgroundColor: tag.color + '20',
|
|
||||||
color: tag.color,
|
|
||||||
borderColor: tag.color + '40'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
|
||||||
{tag.description && (
|
{tag.description && (
|
||||||
<p className="text-xs text-muted-foreground line-clamp-1">
|
<span className="text-xs text-muted-foreground">
|
||||||
{tag.description}
|
{tag.description}
|
||||||
</p>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : searchText ? (
|
) : searchText ? (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
<Search className="w-12 h-12 mb-3 opacity-20" />
|
|
||||||
<p className="text-sm">未找到匹配的标签</p>
|
<p className="text-sm">未找到匹配的标签</p>
|
||||||
<p className="text-xs mt-1">请尝试其他搜索词</p>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
<TagIcon className="w-12 h-12 mb-3 opacity-20" />
|
|
||||||
<p className="text-sm">暂无可用标签</p>
|
<p className="text-sm">暂无可用标签</p>
|
||||||
<p className="text-xs mt-1">请先创建标签</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -221,9 +198,7 @@ const AssignTagDialog: React.FC<AssignTagDialogProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={submitting || loading}
|
disabled={submitting || loading}
|
||||||
className="min-w-[100px]"
|
|
||||||
>
|
>
|
||||||
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
||||||
{submitting ? '保存中...' : '确定'}
|
{submitting ? '保存中...' : '确定'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import ErrorFallback from '@/components/ErrorFallback';
|
|||||||
// 错误页面
|
// 错误页面
|
||||||
const Forbidden = lazy(() => import('@/pages/Error/403'));
|
const Forbidden = lazy(() => import('@/pages/Error/403'));
|
||||||
const NotFound = lazy(() => import('@/pages/Error/404'));
|
const NotFound = lazy(() => import('@/pages/Error/404'));
|
||||||
|
const NoPermission = lazy(() => import('@/pages/Error/NoPermission'));
|
||||||
|
|
||||||
// 表单设计器测试页面(写死的路由)
|
// 表单设计器测试页面(写死的路由)
|
||||||
const FormDesignerTest = lazy(() => import('../pages/FormDesigner'));
|
const FormDesignerTest = lazy(() => import('../pages/FormDesigner'));
|
||||||
@ -67,6 +68,7 @@ const generateRoutes = (menus: MenuResponse[]): RouteObject[] => {
|
|||||||
export const createDynamicRouter = () => {
|
export const createDynamicRouter = () => {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const menus = state.user.menus || [];
|
const menus = state.user.menus || [];
|
||||||
|
const hasMenus = menus.length > 0;
|
||||||
|
|
||||||
// 动态生成路由
|
// 动态生成路由
|
||||||
const dynamicRoutes = generateRoutes(menus);
|
const dynamicRoutes = generateRoutes(menus);
|
||||||
@ -83,7 +85,7 @@ export const createDynamicRouter = () => {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
element: <Navigate to="/dashboard" replace />,
|
element: hasMenus ? <Navigate to="/dashboard" replace /> : <Navigate to="/no-permission" replace />,
|
||||||
},
|
},
|
||||||
// 写死的测试路由:表单设计器测试页面
|
// 写死的测试路由:表单设计器测试页面
|
||||||
{
|
{
|
||||||
@ -98,6 +100,15 @@ export const createDynamicRouter = () => {
|
|||||||
},
|
},
|
||||||
// 动态生成的路由
|
// 动态生成的路由
|
||||||
...dynamicRoutes,
|
...dynamicRoutes,
|
||||||
|
// 无权限欢迎页(用户没有任何菜单权限时显示)
|
||||||
|
{
|
||||||
|
path: 'no-permission',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<RouteLoading />}>
|
||||||
|
<NoPermission />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
},
|
||||||
// 403 无权限页面
|
// 403 无权限页面
|
||||||
{
|
{
|
||||||
path: '403',
|
path: '403',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user