fix: tighten route permission guards

This commit is contained in:
wren
2026-05-06 20:06:41 +08:00
parent 8fcd79b608
commit e7bac9a33f
8 changed files with 409 additions and 70 deletions
+11 -37
View File
@@ -1024,14 +1024,24 @@ function buildFallbackRoutes(roleKey: string): {
const mappedRoleKey = mapUserRoleToRoleKey(roleKey);
const fallbackMenus = FALLBACK_MENU_DATA[mappedRoleKey] || FALLBACK_MENU_DATA.common;
const permissionMap: Record<string, string[]> = {};
const safeFallbackMenus = stripDisallowedFallbackRoutes(fallbackMenus);
return {
success: true,
data: normalizeMenuStructure(fallbackMenus.filter(item => isMinimalMenuPath(item.path))),
data: normalizeMenuStructure(safeFallbackMenus.filter(item => isMinimalMenuPath(item.path))),
permissionMap,
};
}
function stripDisallowedFallbackRoutes(menuItems: MenuItem[]): MenuItem[] {
return menuItems
.filter((item) => item.path !== '/rule-groups')
.map((item) => ({
...item,
children: item.children ? stripDisallowedFallbackRoutes(item.children) : undefined,
}));
}
function isLegacyRuleSetsMenu(path: string | undefined): boolean {
return path === '/rules/sets';
}
@@ -1059,41 +1069,5 @@ function normalizeMenuStructure(menuItems: MenuItem[]): MenuItem[] {
const dedupedTopLevelItems = clonedMenuItems.filter(item => !nestedPathSet.has(item.path));
const ruleManagement = dedupedTopLevelItems.find(item => item.path === '/rules');
const systemSettings = dedupedTopLevelItems.find(item => item.path === '/settings');
const syntheticRuleGroupsMenu: MenuItem = {
id: 'rule-groups',
title: '规则组导航',
path: '/rule-groups',
icon: 'ri-folder-open-line',
order: 1,
};
let ruleGroupsMenu: MenuItem = syntheticRuleGroupsMenu;
if (ruleManagement?.children?.length) {
const ruleGroupIndex = ruleManagement.children.findIndex(child => child.path === '/rule-groups');
if (ruleGroupIndex !== -1) {
const [existingRuleGroupsMenu] = ruleManagement.children.splice(ruleGroupIndex, 1);
ruleGroupsMenu = existingRuleGroupsMenu;
ruleManagement.children = ruleManagement.children
.map((child, index) => ({ ...child, order: index + 1 }))
.sort((a, b) => a.order - b.order);
}
}
if (!systemSettings) {
return dedupedTopLevelItems;
}
const settingsChildren = systemSettings.children ? [...systemSettings.children] : [];
if (!settingsChildren.some(child => child.path === '/rule-groups')) {
settingsChildren.unshift({ ...ruleGroupsMenu, order: 1 });
}
systemSettings.children = settingsChildren
.map((child, index) => ({ ...child, order: index + 1 }))
.sort((a, b) => a.order - b.order);
return dedupedTopLevelItems;
}
+4 -1
View File
@@ -26,7 +26,10 @@ interface LoaderData {
export async function loader({ request }: LoaderFunctionArgs) {
try {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/document-types", userInfo?.role || "", frontendJWT || undefined);
const rootsRes = await getDocumentTypeRoots({}, frontendJWT);
return {
+3 -1
View File
@@ -33,7 +33,9 @@ interface LoaderData {
export async function loader({ request }: LoaderFunctionArgs) {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/document-types/new", userInfo?.role || "", frontendJWT || undefined);
const url = new URL(request.url);
const editId = url.searchParams.get("id");
+347 -9
View File
@@ -1,5 +1,5 @@
import { type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useLoaderData, useNavigate, useSearchParams } from "@remix-run/react";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import axios from "axios";
@@ -15,6 +15,7 @@ import {
import { Button } from "~/components/ui/Button";
import { Card } from "~/components/ui/Card";
import { API_BASE_URL } from "~/config/api-config";
import { usePermission } from "~/hooks/usePermission";
import { parseRuleSummariesFromYaml, type RuleSummary } from "~/utils/rule-yaml-parser";
export function links() {
@@ -71,6 +72,32 @@ interface LoaderData {
frontendJWT?: string | null;
}
interface RuleTemplatePayload {
groupId?: number;
groupName?: string;
parentGroupName?: string;
documentTypeName?: string;
entryModuleName?: string;
ruleType?: string;
ruleName?: string;
yamlTemplate?: string;
yamlText?: string;
ossPreviewKey?: string;
}
interface RuleDraftCreateResult {
packId?: number | null;
groupId?: number | null;
groupName?: string | null;
ruleSetId?: number | null;
ruleSetName?: string | null;
ruleName?: string | null;
ruleType?: string | null;
versionId?: number | null;
versionNo?: string | null;
bindingId?: number | null;
}
interface GroupFormState {
id?: number;
mode: "create" | "edit";
@@ -119,8 +146,12 @@ async function fetchGroupTree(token?: string | null): Promise<RuleGroupNode[]> {
export async function loader({ request }: LoaderFunctionArgs) {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/rule-groups", userInfo?.role || "", frontendJWT || undefined);
try {
const [groups, docTypesRes, entryModulesRes, ruleSetsRes] = await Promise.all([
fetchGroupTree(frontendJWT),
getDocumentTypes({ page: 1, pageSize: 500 }, frontendJWT),
@@ -135,6 +166,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
ruleSets: ruleSetsRes.data || [],
frontendJWT,
} satisfies LoaderData);
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 403) {
throw new Response("无权访问评查点分组页面", { status: 403 });
}
throw error;
}
}
function formatVersionLabel(binding: BindingItem): string {
@@ -278,6 +315,75 @@ type RulePreviewState = {
rules: RuleSummary[];
};
type RuleDraftFormState = {
groupId: number;
yamlText: string;
changeNote: string;
template: RuleTemplatePayload | null;
loading: boolean;
saving: boolean;
error: string | null;
success: RuleDraftCreateResult | null;
};
function unwrapApiData<T>(payload: any): T {
return (payload?.data ?? payload) as T;
}
function normalizeRuleTemplatePayload(payload: any): RuleTemplatePayload {
const item = unwrapApiData<any>(payload) || {};
const context = item.context || {};
return {
groupId: item.groupId ?? item.group_id ?? context.groupId ?? context.group_id,
groupName: item.groupName ?? item.group_name ?? context.groupName ?? context.group_name,
parentGroupName: item.parentGroupName ?? item.root_group_name ?? context.parentGroupName ?? context.parent_group_name,
documentTypeName: item.documentTypeName ?? item.document_type_name ?? context.documentTypeName ?? context.document_type_name,
entryModuleName: item.entryModuleName ?? item.entry_module_name ?? context.entryModuleName ?? context.entry_module_name,
ruleType: item.ruleType ?? item.rule_type,
ruleName: item.ruleName ?? item.rule_name,
yamlTemplate: item.yamlTemplate ?? item.yaml_template,
yamlText: item.yamlText ?? item.yaml_text,
ossPreviewKey: item.ossPreviewKey ?? item.oss_preview_key,
};
}
function normalizeRuleDraftCreateResult(payload: any): RuleDraftCreateResult {
const item = unwrapApiData<any>(payload) || {};
const createdVersion = item.createdVersion ?? item.created_version ?? {};
const binding = item.binding ?? {};
return {
packId: item.packId ?? item.pack_id ?? item.groupId ?? item.group_id,
groupId: item.groupId ?? item.group_id,
groupName: item.groupName ?? item.group_name,
ruleSetId: item.ruleSetId ?? item.rule_set_id ?? createdVersion.ruleSetId ?? createdVersion.rule_set_id,
ruleSetName: item.ruleSetName ?? item.rule_set_name ?? binding.rule_name,
ruleName: item.ruleName ?? item.rule_name ?? binding.rule_name,
ruleType: item.ruleType ?? item.rule_type ?? binding.rule_type,
versionId: item.versionId ?? item.version_id ?? createdVersion.id,
versionNo: item.versionNo ?? item.version_no ?? createdVersion.versionNo ?? createdVersion.version_no,
bindingId: item.bindingId ?? item.binding_id ?? binding.id,
};
}
function findGroupNode(groups: RuleGroupNode[], groupId: number): RuleGroupNode | null {
for (const root of groups) {
if (root.id === groupId) return root;
const child = (root.children || []).find((item) => item.id === groupId);
if (child) return child;
}
return null;
}
function buildRuleDraftJumpUrl(group: RuleGroupNode | null, success: RuleDraftCreateResult | null, template: RuleTemplatePayload | null): string {
if (success?.packId) {
return `/rulesTest/detail?packId=${encodeURIComponent(String(success.packId))}`;
}
const params = new URLSearchParams();
const keyword = success?.ruleType || template?.ruleType || group?.code || group?.name || "";
if (keyword) params.set("keyword", keyword);
return `/rulesTest/list${params.toString() ? `?${params.toString()}` : ""}`;
}
function GroupModal({
visible,
form,
@@ -506,10 +612,125 @@ function BindingModal({
);
}
function RuleDraftModal({
visible,
group,
form,
onClose,
onChange,
onSubmit,
onOpenRules,
}: {
visible: boolean;
group: RuleGroupNode | null;
form: RuleDraftFormState;
onClose: () => void;
onChange: (patch: Partial<RuleDraftFormState>) => void;
onSubmit: () => void;
onOpenRules: () => void;
}) {
if (!visible || !group) return null;
const template = form.template;
return (
<div className="rg-modal-backdrop">
<div className="rg-modal large">
<div className="rg-modal-header">
<h3> / YAML</h3>
<button type="button" className="icon-button" onClick={onClose}>
<i className="ri-close-line"></i>
</button>
</div>
<div className="rg-modal-body grid-form">
<div className="form-item form-item-span-2">
<div className="modal-tip">
<strong>{getSubtypeGroupDisplayName(group)}</strong>
<span>
{template?.parentGroupName || "-"} · {group.document_type_name || template?.documentTypeName || "-"}
{` · 入口模块:${group.entry_module_name || template?.entryModuleName || "-"}`}
</span>
</div>
</div>
<div className="form-item form-item-span-2">
<div className="detail-alert info compact">
<strong> YAML 稿</strong>
<span></span>
</div>
</div>
<div className="form-item">
<label></label>
<input value={template?.ruleType || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
</div>
<div className="form-item">
<label></label>
<input value={template?.ruleName || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
</div>
<div className="form-item form-item-span-2">
<label>OSS </label>
<input value={template?.ossPreviewKey || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
<p className="field-tip"></p>
</div>
<div className="form-item form-item-span-2">
<label></label>
<input
value={form.changeNote}
onChange={(event) => onChange({ changeNote: event.target.value, success: null })}
placeholder="例如:初始化建设工程合同规则草稿"
/>
</div>
<div className="form-item form-item-span-2">
<label> YAML</label>
<textarea
rows={24}
value={form.yamlText}
onChange={(event) => onChange({ yamlText: event.target.value, error: null, success: null })}
placeholder={form.loading ? "模板加载中..." : "这里显示后端生成的完整 YAML 模板"}
/>
<p className="field-tip"> YAML </p>
</div>
{form.error ? (
<div className="form-item form-item-span-2">
<div className="detail-alert danger compact">
<strong></strong>
<span>{form.error}</span>
</div>
</div>
) : null}
{form.success ? (
<div className="form-item form-item-span-2">
<div className="detail-alert success compact">
<strong>稿</strong>
<span>
{form.success.ruleName || form.success.ruleSetName || template?.ruleName || "规则集"} ·
{` 版本 ${form.success.versionNo || form.success.versionId || "已创建"} `}
{form.success.bindingId ? `· 已同步绑定 #${form.success.bindingId}` : "· 绑定结果以后端返回为准"}
</span>
</div>
</div>
) : null}
</div>
<div className="rg-modal-footer">
<Button type="default" onClick={onClose}></Button>
{form.success ? (
<Button type="primary" onClick={onOpenRules}></Button>
) : (
<Button type="primary" onClick={onSubmit} disabled={form.loading || form.saving}>
{form.loading ? "模板加载中..." : form.saving ? "保存中..." : "保存规则草稿"}
</Button>
)}
</div>
</div>
</div>
);
}
export default function RuleGroupsIndex() {
const { groups, docTypes, entryModules, ruleSets, frontendJWT } = useLoaderData<LoaderData>();
const navigate = useNavigate();
const { hasAnyPermission } = usePermission();
const [searchParams, setSearchParams] = useSearchParams();
const topGroups = useMemo(() => toTopGroups(groups), [groups]);
const [groupTree, setGroupTree] = useState<RuleGroupNode[]>(groups);
const topGroups = useMemo(() => toTopGroups(groupTree), [groupTree]);
const nameValue = searchParams.get("name") || "";
const codeValue = searchParams.get("code") || "";
const statusValue = searchParams.get("status") || "";
@@ -521,6 +742,7 @@ export default function RuleGroupsIndex() {
const [rulePreviewMap, setRulePreviewMap] = useState<Record<number, RulePreviewState>>({});
const [groupModalOpen, setGroupModalOpen] = useState(false);
const [bindingModalOpen, setBindingModalOpen] = useState(false);
const [draftModalOpen, setDraftModalOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [groupForm, setGroupForm] = useState<GroupFormState>({
mode: "create",
@@ -542,8 +764,22 @@ export default function RuleGroupsIndex() {
isActive: true,
});
const [activeBindingGroup, setActiveBindingGroup] = useState<RuleGroupNode | null>(null);
const [activeDraftGroup, setActiveDraftGroup] = useState<RuleGroupNode | null>(null);
const [draftForm, setDraftForm] = useState<RuleDraftFormState>({
groupId: 0,
yamlText: "",
changeNote: "",
template: null,
loading: false,
saving: false,
error: null,
success: null,
});
const mountedRef = useRef(false);
const childGroupStats = useMemo(() => buildChildGroupStats(topGroups), [topGroups]);
const canManageGroups = hasAnyPermission(["evaluation_group:create:write", "evaluation_group:update:write"]);
const canManageBindings = hasAnyPermission(["evaluation_group:update:write"]);
const canCreateRuleDraft = hasAnyPermission(["evaluation_group:update:write", "rules:create:write"]);
const filteredGroups = useMemo(() => {
return topGroups
@@ -605,8 +841,12 @@ export default function RuleGroupsIndex() {
const resetFilters = () => setSearchParams(new URLSearchParams());
const reloadPage = () => {
if (typeof window !== "undefined") window.location.reload();
const reloadPage = async (): Promise<RuleGroupNode[]> => {
const nextGroups = await fetchGroupTree(frontendJWT);
setGroupTree(nextGroups);
setActiveBindingGroup((current) => (current ? findGroupNode(nextGroups, current.id) : null));
setActiveDraftGroup((current) => (current ? findGroupNode(nextGroups, current.id) : null));
return nextGroups;
};
const handleApiError = (error: any) => {
@@ -656,6 +896,39 @@ export default function RuleGroupsIndex() {
setBindingModalOpen(true);
};
const openCreateRuleDraft = async (group: RuleGroupNode) => {
setActiveDraftGroup(group);
setDraftForm({
groupId: group.id,
yamlText: "",
changeNote: "",
template: null,
loading: true,
saving: false,
error: null,
success: null,
});
setDraftModalOpen(true);
try {
const response = await axios.get(`${API_BASE_URL}/api/v3/evaluation-point-groups/${group.id}/rule-template`, {
headers: authHeaders(frontendJWT),
});
const template = normalizeRuleTemplatePayload(response?.data);
const yamlText = String(template?.yamlTemplate || template?.yamlText || "");
setDraftForm((prev) => ({
...prev,
loading: false,
template,
yamlText,
changeNote: prev.changeNote || `初始化 ${getSubtypeGroupDisplayName(group)} 规则草稿`,
error: yamlText.trim() ? null : "后端未返回可编辑的 YAML 模板",
}));
} catch (error: any) {
const message = error?.response?.data?.msg || error?.response?.data?.detail || error?.message || "模板加载失败";
setDraftForm((prev) => ({ ...prev, loading: false, error: String(message) }));
}
};
const openEditBinding = (group: RuleGroupNode, binding: BindingItem) => {
setActiveBindingGroup(group);
setBindingForm({
@@ -691,7 +964,8 @@ export default function RuleGroupsIndex() {
} else {
await axios.put(`${API_BASE_URL}/api/v3/evaluation-point-groups/${groupForm.id}`, payload, { headers: { ...authHeaders(frontendJWT) } });
}
reloadPage();
await reloadPage();
setGroupModalOpen(false);
} catch (error) {
handleApiError(error);
} finally {
@@ -714,7 +988,8 @@ export default function RuleGroupsIndex() {
} else {
await axios.put(`${API_BASE_URL}/api/v3/evaluation-point-groups/bindings/${bindingForm.bindingId}`, payload, { headers: { ...authHeaders(frontendJWT) } });
}
reloadPage();
await reloadPage();
setBindingModalOpen(false);
} catch (error) {
handleApiError(error);
} finally {
@@ -729,7 +1004,7 @@ export default function RuleGroupsIndex() {
const response = await axios.delete(`${API_BASE_URL}/api/v3/evaluation-point-groups/${group.id}`, { headers: authHeaders(frontendJWT) });
const payload = response?.data?.data ?? response?.data;
if (payload?.success === false) return window.alert(payload?.message || "删除失败");
reloadPage();
await reloadPage();
} catch (error) {
handleApiError(error);
}
@@ -739,12 +1014,45 @@ export default function RuleGroupsIndex() {
if (!window.confirm(`确认解除规则集「${binding.rule_name || binding.rule_type || binding.rule_set_id}」吗?`)) return;
try {
await axios.delete(`${API_BASE_URL}/api/v3/evaluation-point-groups/bindings/${binding.id}`, { headers: authHeaders(frontendJWT) });
reloadPage();
await reloadPage();
} catch (error) {
handleApiError(error);
}
};
const submitRuleDraft = async () => {
if (!activeDraftGroup) return;
if (!draftForm.yamlText.trim()) {
setDraftForm((prev) => ({ ...prev, error: "请先确认 YAML 内容后再保存" }));
return;
}
setDraftForm((prev) => ({ ...prev, saving: true, error: null }));
try {
const payload = {
yaml_text: draftForm.yamlText,
change_note: draftForm.changeNote.trim() || null,
};
const response = await axios.post(
`${API_BASE_URL}/api/v3/evaluation-point-groups/${activeDraftGroup.id}/rule-drafts`,
payload,
{ headers: authHeaders(frontendJWT) },
);
const result = normalizeRuleDraftCreateResult(response?.data);
const nextGroups = await reloadPage();
const refreshedGroup = findGroupNode(nextGroups, activeDraftGroup.id) || activeDraftGroup;
setActiveDraftGroup(refreshedGroup);
setDraftForm((prev) => ({ ...prev, saving: false, success: result }));
} catch (error: any) {
const message = error?.response?.data?.msg || error?.response?.data?.detail || error?.message || "规则草稿保存失败";
setDraftForm((prev) => ({ ...prev, saving: false, error: String(message) }));
}
};
const openRuleDraftResult = () => {
const target = buildRuleDraftJumpUrl(activeDraftGroup, draftForm.success, draftForm.template);
navigate(target);
};
const toggleRoot = (groupId: number) => {
setExpandedGroupIds((prev) => {
const next = new Set(prev);
@@ -812,7 +1120,9 @@ export default function RuleGroupsIndex() {
<div className="page-actions">
<Button type="default" icon="ri-expand-up-down-line" onClick={() => setExpandedGroupIds(new Set(filteredGroups.map((item) => item.id)))}></Button>
<Button type="default" icon="ri-collapse-diagonal-line" onClick={() => { setExpandedGroupIds(new Set()); setExpandedBindingIds(new Set()); }}></Button>
{canManageGroups ? (
<Button type="primary" icon="ri-add-line" onClick={openCreateRoot}></Button>
) : null}
</div>
</div>
@@ -959,9 +1269,15 @@ export default function RuleGroupsIndex() {
<td>{formatDateTime(root.updated_at || root.created_at)}</td>
<td>
<div className="action-links">
{canManageGroups ? (
<button type="button" className="action-link button-link" onClick={() => openCreateChild(root)}></button>
) : null}
{canManageGroups ? (
<button type="button" className="action-link button-link" onClick={() => openEditGroup(root)}></button>
) : null}
{canManageGroups ? (
<button type="button" className="action-link danger button-link" onClick={() => deleteGroup(root)}></button>
) : null}
</div>
</td>
</tr>
@@ -1026,9 +1342,18 @@ export default function RuleGroupsIndex() {
>
{child.bindings.length ? (child.bindings.every((item) => expandedBindingIds.has(item.id)) ? "收起规则集" : "查看规则集") : "暂无规则集"}
</button>
{canCreateRuleDraft ? (
<button type="button" className="action-link button-link" onClick={() => openCreateRuleDraft(child)}>/YAML</button>
) : null}
{canManageBindings ? (
<button type="button" className="action-link button-link" onClick={() => openCreateBinding(child)}></button>
) : null}
{canManageGroups ? (
<button type="button" className="action-link button-link" onClick={() => openEditGroup(child)}></button>
) : null}
{canManageGroups ? (
<button type="button" className="action-link danger button-link" onClick={() => deleteGroup(child)}></button>
) : null}
</div>
</td>
</tr>
@@ -1071,8 +1396,12 @@ export default function RuleGroupsIndex() {
<button type="button" className="action-link button-link" onClick={() => toggleBindingPreview(binding)}>
{expandedBinding ? "收起规则明细" : "查看组内规则"}
</button>
{canManageBindings ? (
<button type="button" className="action-link button-link" onClick={() => openEditBinding(child, binding)}></button>
) : null}
{canManageBindings ? (
<button type="button" className="action-link danger button-link" onClick={() => deleteBinding(binding)}></button>
) : null}
</div>
</td>
</tr>
@@ -1157,6 +1486,15 @@ export default function RuleGroupsIndex() {
onSubmit={submitBinding}
saving={saving}
/>
<RuleDraftModal
visible={draftModalOpen}
group={activeDraftGroup}
form={draftForm}
onClose={() => setDraftModalOpen(false)}
onChange={(patch) => setDraftForm((prev) => ({ ...prev, ...patch }))}
onSubmit={submitRuleDraft}
onOpenRules={openRuleDraftResult}
/>
</div>
);
}
+8
View File
@@ -237,6 +237,10 @@ function mapApiRuleToModel(apiRule: ApiRule): Rule {
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/rules/list", userInfo?.role || "", frontendJWT || undefined);
// 从 URL 参数中提取查询条件
const params = {
@@ -280,6 +284,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
export async function action({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/rules/list", userInfo?.role || "", frontendJWT || undefined);
const formData = await request.formData();
const _action = formData.get('_action');
const ruleId = formData.get('ruleId');
+5
View File
@@ -18,6 +18,11 @@ export const handle = {
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/rules", userInfo?.role || "", frontendJWT || undefined);
if (url.pathname === '/rules') {
const query = url.searchParams.toString();
+5
View File
@@ -314,6 +314,9 @@ function validateRule(rule: RuleSummary | undefined, dependencyOptions: Dependen
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import('~/api/auth/check-route-permission.server');
await requireRoutePermission('/rulesTest/detail', userInfo?.role || '', frontendJWT || undefined);
const packId = url.searchParams.get('packId') || url.searchParams.get('id') || '';
const requestedRuleId = url.searchParams.get('ruleId') || '';
const packs = await loadRuleConfigPacks(request);
@@ -330,6 +333,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
export async function action({ request }: ActionFunctionArgs) {
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import('~/api/auth/check-route-permission.server');
await requireRoutePermission('/rulesTest/detail', userInfo?.role || '', frontendJWT || undefined);
if (!frontendJWT) {
return json<ActionData>({ success: false, intent: 'save', message: '登录已失效,请重新登录后再保存。' }, { status: 401 });
}
+4
View File
@@ -84,6 +84,10 @@ function riskColor(risk: string): TagColor {
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT, userInfo } = await getUserSession(request);
const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");
await requireRoutePermission("/rulesTest/list", userInfo?.role || "", frontendJWT || undefined);
const requestedMainType = url.searchParams.get('mainType') || url.searchParams.get('ruleTypeName') || '';
const requestedSubtype = url.searchParams.get('subtype') || url.searchParams.get('documentAttributeType') || '';
const requestedRuleGroup = url.searchParams.get('ruleGroup') || url.searchParams.getAll('ruleGroups')[0] || '';