「权限 × 状态 × 审计」联合约束机制

This commit is contained in:
Cheng Zhou
2025-12-17 21:48:29 +08:00
parent 13efb0a3a9
commit 4202ed7922
36 changed files with 714 additions and 85 deletions
+47
View File
@@ -65,6 +65,8 @@ import { usePermission } from "../utils/permission";
import { aeMachine, getAvailableActions } from "../state-machine";
import type { ActionConfig } from "../state-machine";
import { statusDict, getDictColor, getDictLabel } from "../dictionaries";
import { evaluateAction } from "../guards/actionGuard";
import { logAudit } from "../audit";
const route = useRoute();
const study = useStudyStore();
@@ -102,6 +104,36 @@ const load = async () => {
const onAction = async (action: ActionConfig) => {
if (!study.currentStudy || !ae.value) return;
const prevStatus = ae.value.status;
if (prevStatus === action.to) {
ElMessage.warning("当前状态不允许该操作");
logAudit("INVALID_STATE_TRANSITION_ATTEMPT", {
targetId: ae.value.id,
targetName: ae.value.term,
before: { status: prevStatus },
after: { status: action.to },
severity: "warning",
});
return;
}
const decision = evaluateAction({
actorRole: auth.user?.role || null,
requiredPermission: "ae.close",
stateMachine: aeMachine,
currentState: ae.value.status,
actionKey: action.key,
target: { id: ae.value.id },
});
if (!decision.allowed) {
ElMessage.warning(decision.reason || "当前不可执行该操作");
logAudit(decision.auditType, {
targetId: ae.value.id,
targetName: ae.value.term,
reason: decision.reason,
severity: decision.severity,
});
return;
}
if (action.confirm) {
const ok = await ElMessageBox.confirm(action.confirmText || "确认执行该操作?", "提示").catch(() => null);
if (!ok) return;
@@ -110,9 +142,24 @@ const onAction = async (action: ActionConfig) => {
const resp = await updateAe(study.currentStudy.id, ae.value.id, { status: action.to });
ElMessage.success("状态已更新");
ae.value = enrichOverdue(resp.data);
logAudit("AE_CLOSED", {
targetId: ae.value.id,
targetName: ae.value.term,
before: { status: prevStatus },
after: { status: action.to },
severity: "normal",
});
await load();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "更新失败");
logAudit("AE_CLOSED", {
targetId: ae.value.id,
targetName: ae.value.term,
before: { status: prevStatus },
after: { status: action.to },
severity: "warning",
reason: e?.response?.data?.message,
});
}
};