立项配置数据入库、审计日志数据入库、编辑页UI界面美化、设备管理内容补充、审计日志显示优化
This commit is contained in:
@@ -148,7 +148,7 @@ import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import { fetchAuditLogs } from "../../api/auditLogs";
|
||||
import { createAuditEvent, fetchAuditLogs } from "../../api/auditLogs";
|
||||
import { fetchUsers } from "../../api/users";
|
||||
import { listMembers } from "../../api/members";
|
||||
import { auditDict, normalizeAuditEvent } from "../../audit";
|
||||
@@ -156,27 +156,9 @@ import { useStudyStore } from "../../store/study";
|
||||
import { useAuthStore } from "../../store/auth";
|
||||
import { roleDict, getDictLabel } from "../../dictionaries";
|
||||
import { exportAuditCsv } from "../../audit/export/auditExportService";
|
||||
import { logAudit } from "../../audit";
|
||||
import { displayDateTime } from "../../utils/display";
|
||||
import { TEXT } from "../../locales";
|
||||
|
||||
const buildLogKey = (log: any) => {
|
||||
if (log.id) return `id:${log.id}`;
|
||||
return `${log.action || log.eventType || "event"}-${log.entity_id || log.entityId || ""}-${log.created_at || log.timestamp || ""}`;
|
||||
};
|
||||
|
||||
const dedupeLogs = (list: any[]) => {
|
||||
const seen = new Set<string>();
|
||||
const result: any[] = [];
|
||||
list.forEach((log) => {
|
||||
const key = buildLogKey(log);
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
result.push(log);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
@@ -250,10 +232,8 @@ const loadLogs = async () => {
|
||||
};
|
||||
const { data } = await fetchAuditLogs(study.currentStudy.id, params);
|
||||
const items = Array.isArray(data) ? data : (data as any).items || [];
|
||||
const local = loadLocalLogs();
|
||||
const merged = dedupeLogs([...items, ...local]);
|
||||
rawLogs.value = merged;
|
||||
total.value = (data as any).total || merged.length;
|
||||
rawLogs.value = items;
|
||||
total.value = (data as any).total || items.length;
|
||||
enrichLogs();
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || TEXT.modules.adminAuditLogs.loadFailed);
|
||||
@@ -262,18 +242,6 @@ const loadLogs = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadLocalLogs = () => {
|
||||
try {
|
||||
const key = `audit_local_${study.currentStudy?.id || "global"}`;
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return [];
|
||||
const list = JSON.parse(raw);
|
||||
return Array.isArray(list) ? list : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const enrichLogs = () => {
|
||||
const userMap = users.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = resolveUserDisplayName(cur);
|
||||
@@ -333,13 +301,11 @@ const fetchAllForExport = async () => {
|
||||
};
|
||||
const { data } = await fetchAuditLogs(study.currentStudy.id, params);
|
||||
const items = Array.isArray(data) ? data : (data as any).items || [];
|
||||
const local = loadLocalLogs();
|
||||
const merged = dedupeLogs([...items, ...local]);
|
||||
const userMap = users.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = resolveUserDisplayName(cur);
|
||||
return acc;
|
||||
}, {});
|
||||
const filtered = merged.filter((log) => {
|
||||
const filtered = items.filter((log: any) => {
|
||||
if (filters.value.range?.length === 2) {
|
||||
const ts = new Date(log.created_at);
|
||||
const start = new Date(filters.value.range[0]);
|
||||
@@ -348,7 +314,7 @@ const fetchAllForExport = async () => {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return filtered.map((log) => normalizeAuditEvent(log, userMap));
|
||||
return filtered.map((log: any) => normalizeAuditEvent(log, userMap));
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || TEXT.modules.adminAuditLogs.exportLoadFailed);
|
||||
return [];
|
||||
@@ -362,6 +328,8 @@ const handleExportCommand = (command: string) => {
|
||||
};
|
||||
|
||||
const confirmExport = async (scope: "system" | "project") => {
|
||||
const currentStudy = study.currentStudy;
|
||||
if (!currentStudy) return;
|
||||
const ok = await ElMessageBox.confirm(
|
||||
TEXT.modules.adminAuditLogs.exportConfirm,
|
||||
TEXT.modules.adminAuditLogs.exportConfirmTitle,
|
||||
@@ -376,13 +344,22 @@ const confirmExport = async (scope: "system" | "project") => {
|
||||
const fileName =
|
||||
scope === "system"
|
||||
? `${TEXT.modules.adminAuditLogs.exportSystemName}_${new Date().toISOString().slice(0, 10)}`
|
||||
: `${TEXT.modules.adminAuditLogs.exportProjectName}_${study.currentStudy?.code || ""}_${new Date().toISOString().slice(0, 10)}`;
|
||||
: `${TEXT.modules.adminAuditLogs.exportProjectName}_${currentStudy.code || ""}_${new Date().toISOString().slice(0, 10)}`;
|
||||
exportAuditCsv(events, { fileName });
|
||||
logAudit(scope === "system" ? "AUDIT_EXPORT_SYSTEM" : "AUDIT_EXPORT_PROJECT", {
|
||||
targetId: study.currentStudy?.id,
|
||||
targetName: study.currentStudy?.name,
|
||||
severity: "normal",
|
||||
});
|
||||
await createAuditEvent(currentStudy.id, {
|
||||
action: scope === "system" ? "AUDIT_EXPORT_SYSTEM" : "AUDIT_EXPORT_PROJECT",
|
||||
entity_type: "audit_log",
|
||||
entity_id: null,
|
||||
detail: JSON.stringify(
|
||||
{
|
||||
targetName: currentStudy.name,
|
||||
scope,
|
||||
exportedCount: events.length,
|
||||
},
|
||||
null,
|
||||
0
|
||||
),
|
||||
}).catch(() => null);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user