feat: 完成 F18 审批规则维护(API+前端)\n\n- 新增 /api/approval-rules 管理接口(system.settings)并写审计\n- 前端新增审批规则页与菜单入口(system.settings)\n- 更新 P5 蓝图 v3 中 F18 状态为 DONE
这个提交包含在:
@@ -0,0 +1,68 @@
|
||||
import { apiRequestData } from "./http";
|
||||
|
||||
export type ApprovalRoleCode =
|
||||
| "employee"
|
||||
| "manager"
|
||||
| "ceo"
|
||||
| "finance"
|
||||
| "finance_mgr"
|
||||
| "administration"
|
||||
| "hr"
|
||||
| "admin";
|
||||
|
||||
export type ApprovalRule = {
|
||||
id: string;
|
||||
name: string;
|
||||
minAmount: string;
|
||||
maxAmount: string | null;
|
||||
level1Role: ApprovalRoleCode;
|
||||
level2Role: ApprovalRoleCode | null;
|
||||
level3Role: ApprovalRoleCode | null;
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type ApprovalRulesListResponse = {
|
||||
enabled: boolean | null;
|
||||
items: ApprovalRule[];
|
||||
};
|
||||
|
||||
export async function fetchApprovalRules(params?: { enabled?: boolean }) {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.enabled != null) {
|
||||
qs.set("enabled", params.enabled ? "true" : "false");
|
||||
}
|
||||
|
||||
const query = qs.toString();
|
||||
return apiRequestData<ApprovalRulesListResponse>(`/api/approval-rules${query ? `?${query}` : ""}`);
|
||||
}
|
||||
|
||||
export type ApprovalRuleCreatePayload = {
|
||||
name: string;
|
||||
minAmount: string;
|
||||
maxAmount: string | null;
|
||||
level1Role: ApprovalRoleCode;
|
||||
level2Role: ApprovalRoleCode | null;
|
||||
level3Role: ApprovalRoleCode | null;
|
||||
enabled?: boolean;
|
||||
priority?: number;
|
||||
};
|
||||
|
||||
export async function createApprovalRule(payload: ApprovalRuleCreatePayload) {
|
||||
return apiRequestData<ApprovalRule>("/api/approval-rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export type ApprovalRulePatchPayload = Partial<ApprovalRuleCreatePayload>;
|
||||
|
||||
export async function patchApprovalRule(approvalRuleId: string, payload: ApprovalRulePatchPayload) {
|
||||
return apiRequestData<ApprovalRule>(`/api/approval-rules/${approvalRuleId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ export const NAV_ITEMS: NavItem[] = [
|
||||
label: "部门管理",
|
||||
anyOf: ["system.users"],
|
||||
},
|
||||
{
|
||||
to: "/system/approval-rules",
|
||||
label: "审批规则",
|
||||
anyOf: ["system.settings"],
|
||||
},
|
||||
{ to: "/settings/profile", label: "个人设置" },
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { isApiError } from "../api/types";
|
||||
import {
|
||||
createApprovalRule,
|
||||
fetchApprovalRules,
|
||||
patchApprovalRule,
|
||||
type ApprovalRoleCode,
|
||||
type ApprovalRule,
|
||||
} from "../api/approvalRules";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Card } from "../components/ui/card";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../components/ui/dialog";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
|
||||
|
||||
const ROLE_OPTIONS: Array<{ code: ApprovalRoleCode; label: string }> = [
|
||||
{ code: "manager", label: "manager" },
|
||||
{ code: "ceo", label: "ceo" },
|
||||
{ code: "finance", label: "finance" },
|
||||
{ code: "finance_mgr", label: "finance_mgr" },
|
||||
{ code: "admin", label: "admin" },
|
||||
];
|
||||
|
||||
function formatAmount(value: string | null) {
|
||||
if (!value) return "-";
|
||||
return value;
|
||||
}
|
||||
|
||||
export function ApprovalRulesPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [minAmount, setMinAmount] = useState("");
|
||||
const [maxAmount, setMaxAmount] = useState("");
|
||||
const [priority, setPriority] = useState("");
|
||||
const [level1Role, setLevel1Role] = useState<ApprovalRoleCode>("manager");
|
||||
const [level2Role, setLevel2Role] = useState<ApprovalRoleCode | "">("");
|
||||
const [level3Role, setLevel3Role] = useState<ApprovalRoleCode | "">("");
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [editRule, setEditRule] = useState<ApprovalRule | null>(null);
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["approval-rules"],
|
||||
queryFn: () => fetchApprovalRules(),
|
||||
});
|
||||
|
||||
const rules = listQuery.data?.items ?? [];
|
||||
|
||||
const createPayload = useMemo(() => {
|
||||
return {
|
||||
name: name.trim(),
|
||||
minAmount: minAmount.trim(),
|
||||
maxAmount: maxAmount.trim() ? maxAmount.trim() : null,
|
||||
level1Role,
|
||||
level2Role: level2Role ? (level2Role as ApprovalRoleCode) : null,
|
||||
level3Role: level3Role ? (level3Role as ApprovalRoleCode) : null,
|
||||
enabled,
|
||||
priority: priority.trim() ? Number(priority) : undefined,
|
||||
};
|
||||
}, [enabled, level1Role, level2Role, level3Role, maxAmount, minAmount, name, priority]);
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () => createApprovalRule(createPayload),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["approval-rules"] });
|
||||
setName("");
|
||||
setMinAmount("");
|
||||
setMaxAmount("");
|
||||
setPriority("");
|
||||
setLevel1Role("manager");
|
||||
setLevel2Role("");
|
||||
setLevel3Role("");
|
||||
setEnabled(true);
|
||||
setError("");
|
||||
},
|
||||
onError: (err) => {
|
||||
if (isApiError(err)) {
|
||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : "创建审批规则失败");
|
||||
},
|
||||
});
|
||||
|
||||
const toggleEnabledMutation = useMutation({
|
||||
mutationFn: (rule: ApprovalRule) => patchApprovalRule(rule.id, { enabled: !rule.enabled }),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["approval-rules"] });
|
||||
setError("");
|
||||
},
|
||||
onError: (err) => {
|
||||
if (isApiError(err)) {
|
||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : "更新审批规则失败");
|
||||
},
|
||||
});
|
||||
|
||||
const editMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!editRule) {
|
||||
throw new Error("请选择要编辑的规则");
|
||||
}
|
||||
|
||||
return patchApprovalRule(editRule.id, createPayload);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["approval-rules"] });
|
||||
setEditOpen(false);
|
||||
setEditRule(null);
|
||||
setError("");
|
||||
},
|
||||
onError: (err) => {
|
||||
if (isApiError(err)) {
|
||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : "更新审批规则失败");
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!editOpen || !editRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
setName(editRule.name);
|
||||
setMinAmount(editRule.minAmount);
|
||||
setMaxAmount(editRule.maxAmount ?? "");
|
||||
setPriority(String(editRule.priority));
|
||||
setEnabled(editRule.enabled);
|
||||
setLevel1Role(editRule.level1Role);
|
||||
setLevel2Role(editRule.level2Role ?? "");
|
||||
setLevel3Role(editRule.level3Role ?? "");
|
||||
}, [editOpen, editRule]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="审批规则"
|
||||
subtitle="维护金额段与审批层级(仅影响新提交的报销单,历史流程不回写)。"
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">新建审批规则</h3>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="ruleName">规则名称</Label>
|
||||
<Input
|
||||
id="ruleName"
|
||||
data-testid="approval-rules-create-name"
|
||||
placeholder="5000 以下走经理"
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="minAmount">最小金额</Label>
|
||||
<Input
|
||||
id="minAmount"
|
||||
data-testid="approval-rules-create-min"
|
||||
placeholder="0.00"
|
||||
value={minAmount}
|
||||
onChange={(event) => setMinAmount(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="maxAmount">最大金额(可空)</Label>
|
||||
<Input
|
||||
id="maxAmount"
|
||||
data-testid="approval-rules-create-max"
|
||||
placeholder="5000.00"
|
||||
value={maxAmount}
|
||||
onChange={(event) => setMaxAmount(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="priority">优先级(越小越先匹配)</Label>
|
||||
<Input
|
||||
id="priority"
|
||||
data-testid="approval-rules-create-priority"
|
||||
placeholder="1"
|
||||
value={priority}
|
||||
onChange={(event) => setPriority(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="level1Role">一级审批角色</Label>
|
||||
<select
|
||||
id="level1Role"
|
||||
data-testid="approval-rules-create-level1"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level1Role}
|
||||
onChange={(event) => setLevel1Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="level2Role">二级审批角色(可空)</Label>
|
||||
<select
|
||||
id="level2Role"
|
||||
data-testid="approval-rules-create-level2"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level2Role}
|
||||
onChange={(event) => setLevel2Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
<option value="">无</option>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="level3Role">三级审批角色(可空)</Label>
|
||||
<select
|
||||
id="level3Role"
|
||||
data-testid="approval-rules-create-level3"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level3Role}
|
||||
onChange={(event) => setLevel3Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
<option value="">无</option>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 pt-6">
|
||||
<input
|
||||
id="ruleEnabled"
|
||||
data-testid="approval-rules-create-enabled"
|
||||
checked={enabled}
|
||||
onChange={(event) => setEnabled(event.target.checked)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Label htmlFor="ruleEnabled">启用</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
data-testid="approval-rules-create-submit"
|
||||
type="button"
|
||||
disabled={createMutation.isPending || !name.trim() || !minAmount.trim()}
|
||||
onClick={() => createMutation.mutate()}
|
||||
>
|
||||
{createMutation.isPending ? "创建中..." : "创建"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">规则列表</h3>
|
||||
|
||||
{listQuery.isLoading ? <p className="text-sm text-slate-500">加载中...</p> : null}
|
||||
|
||||
{listQuery.isError ? (
|
||||
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
||||
{isApiError(listQuery.error)
|
||||
? `${listQuery.error.code}: ${mapApiErrorMessage(
|
||||
listQuery.error.code,
|
||||
listQuery.error.details,
|
||||
listQuery.error.message,
|
||||
)}`
|
||||
: "加载审批规则失败"}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{!listQuery.isLoading && !listQuery.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<Table data-testid="approval-rules-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>金额段</TableHead>
|
||||
<TableHead>层级</TableHead>
|
||||
<TableHead>优先级</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="w-44">操作</TableHead>
|
||||
</tr>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rules.length ? (
|
||||
rules.map((rule) => (
|
||||
<TableRow key={rule.id} data-testid="approval-rule-row">
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{rule.name}</div>
|
||||
<div className="font-mono text-xs text-slate-500">{rule.id}</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-700">
|
||||
{formatAmount(rule.minAmount)} ~ {rule.maxAmount ? formatAmount(rule.maxAmount) : "∞"}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-700">
|
||||
{[rule.level1Role, rule.level2Role, rule.level3Role].filter(Boolean).join(" -> ") || "-"}
|
||||
</TableCell>
|
||||
<TableCell>{rule.priority}</TableCell>
|
||||
<TableCell className="text-sm">
|
||||
{rule.enabled ? <span className="text-emerald-600">启用</span> : <span className="text-slate-500">停用</span>}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
data-testid="approval-rule-edit"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setEditRule(rule);
|
||||
setEditOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="approval-rule-toggle"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
type="button"
|
||||
disabled={toggleEnabledMutation.isPending}
|
||||
onClick={() => {
|
||||
if (!window.confirm(`确认${rule.enabled ? "停用" : "启用"}该审批规则?`)) return;
|
||||
toggleEnabledMutation.mutate(rule);
|
||||
}}
|
||||
>
|
||||
{rule.enabled ? "停用" : "启用"}
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={6}>
|
||||
暂无审批规则
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
<Dialog open={editOpen} onOpenChange={setEditOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑审批规则</DialogTitle>
|
||||
<DialogDescription>修改后只影响新提交的报销单。</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
||||
<div className="space-y-1 md:col-span-2">
|
||||
<Label htmlFor="editRuleName">规则名称</Label>
|
||||
<Input
|
||||
id="editRuleName"
|
||||
data-testid="approval-rule-edit-name"
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRuleMin">最小金额</Label>
|
||||
<Input
|
||||
id="editRuleMin"
|
||||
data-testid="approval-rule-edit-min"
|
||||
value={minAmount}
|
||||
onChange={(event) => setMinAmount(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRuleMax">最大金额(可空)</Label>
|
||||
<Input
|
||||
id="editRuleMax"
|
||||
data-testid="approval-rule-edit-max"
|
||||
value={maxAmount}
|
||||
onChange={(event) => setMaxAmount(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRulePriority">优先级</Label>
|
||||
<Input
|
||||
id="editRulePriority"
|
||||
data-testid="approval-rule-edit-priority"
|
||||
value={priority}
|
||||
onChange={(event) => setPriority(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRuleLevel1">一级审批角色</Label>
|
||||
<select
|
||||
id="editRuleLevel1"
|
||||
data-testid="approval-rule-edit-level1"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level1Role}
|
||||
onChange={(event) => setLevel1Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRuleLevel2">二级审批角色(可空)</Label>
|
||||
<select
|
||||
id="editRuleLevel2"
|
||||
data-testid="approval-rule-edit-level2"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level2Role}
|
||||
onChange={(event) => setLevel2Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
<option value="">无</option>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="editRuleLevel3">三级审批角色(可空)</Label>
|
||||
<select
|
||||
id="editRuleLevel3"
|
||||
data-testid="approval-rule-edit-level3"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={level3Role}
|
||||
onChange={(event) => setLevel3Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
<option value="">无</option>
|
||||
{ROLE_OPTIONS.map((role) => (
|
||||
<option key={role.code} value={role.code}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 pt-6">
|
||||
<input
|
||||
id="editRuleEnabled"
|
||||
data-testid="approval-rule-edit-enabled"
|
||||
checked={enabled}
|
||||
onChange={(event) => setEnabled(event.target.checked)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Label htmlFor="editRuleEnabled">启用</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
data-testid="approval-rule-edit-save"
|
||||
type="button"
|
||||
disabled={editMutation.isPending}
|
||||
onClick={() => editMutation.mutate()}
|
||||
>
|
||||
{editMutation.isPending ? "保存中..." : "保存"}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="approval-rule-edit-cancel"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={editMutation.isPending}
|
||||
onClick={() => setEditOpen(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { AppShellLayout } from "../layouts/AppShellLayout";
|
||||
import { AuthLayout } from "../layouts/AuthLayout";
|
||||
import { AuditLogsPage } from "../pages/AuditLogsPage";
|
||||
import { ApprovalRulesPage } from "../pages/ApprovalRulesPage";
|
||||
import { ChangePasswordPage } from "../pages/ChangePasswordPage";
|
||||
import { DashboardPage } from "../pages/DashboardPage";
|
||||
import { DepartmentsPage } from "../pages/DepartmentsPage";
|
||||
@@ -138,6 +139,15 @@ export function AppRouter() {
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="system/approval-rules"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.settings"]}>
|
||||
<ApprovalRulesPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="settings/profile" element={<ProfilePage />} />
|
||||
</Route>
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户