审计导出功能--初版
This commit is contained in:
@@ -24,6 +24,24 @@
|
||||
/>
|
||||
<div class="spacer" />
|
||||
<el-button type="primary" @click="loadLogs">查询</el-button>
|
||||
<el-button
|
||||
v-if="isAdmin"
|
||||
type="warning"
|
||||
plain
|
||||
:loading="exportLoading"
|
||||
@click="confirmExport('system')"
|
||||
>
|
||||
导出系统审计
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canProjectExport"
|
||||
type="warning"
|
||||
plain
|
||||
:loading="exportLoading"
|
||||
@click="confirmExport('project')"
|
||||
>
|
||||
导出项目审计
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="logs" v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="timestamp" label="时间" width="180" />
|
||||
@@ -67,19 +85,22 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { fetchAuditLogs } from "../../api/auditLogs";
|
||||
import { fetchUsers } from "../../api/users";
|
||||
import { auditDict, normalizeAuditEvent } from "../../audit";
|
||||
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";
|
||||
|
||||
const study = useStudyStore();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const exportLoading = ref(false);
|
||||
const rawLogs = ref<any[]>([]);
|
||||
const logs = ref<any[]>([]);
|
||||
const users = ref<any[]>([]);
|
||||
@@ -99,6 +120,8 @@ const eventTypeOptions = Object.entries(auditDict).map(([value, cfg]) => ({
|
||||
label: cfg.label,
|
||||
}));
|
||||
const userOptions = computed(() => users.value.map((u: any) => ({ label: u.username, value: u.id })));
|
||||
const isAdmin = computed(() => auth.user?.role === "ADMIN");
|
||||
const canProjectExport = computed(() => !!study.currentStudy && (auth.user?.role === "ADMIN" || auth.user?.role === "PM" || study.currentStudyRole === "PM"));
|
||||
|
||||
const ensureAccess = () => {
|
||||
const role = auth.user?.role;
|
||||
@@ -191,6 +214,66 @@ const onPageChange = (p: number) => {
|
||||
loadLogs();
|
||||
};
|
||||
|
||||
const fetchAllForExport = async () => {
|
||||
if (!study.currentStudy) return [];
|
||||
exportLoading.value = true;
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
skip: 0,
|
||||
limit: 2000,
|
||||
action: filters.value.eventType || undefined,
|
||||
operator_id: filters.value.operatorId || undefined,
|
||||
};
|
||||
const { data } = await fetchAuditLogs(study.currentStudy.id, params);
|
||||
const items = Array.isArray(data) ? data : (data as any).items || [];
|
||||
const local = loadLocalLogs();
|
||||
const merged = [...items, ...local];
|
||||
const userMap = users.value.reduce<Record<string, string>>((acc, cur) => {
|
||||
acc[cur.id] = cur.username;
|
||||
return acc;
|
||||
}, {});
|
||||
const filtered = merged.filter((log) => {
|
||||
if (filters.value.range?.length === 2) {
|
||||
const ts = new Date(log.created_at);
|
||||
const start = new Date(filters.value.range[0]);
|
||||
const end = new Date(filters.value.range[1]);
|
||||
if (ts < start || ts > end) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return filtered.map((log) => normalizeAuditEvent(log, userMap));
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || "导出数据加载失败");
|
||||
return [];
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmExport = async (scope: "system" | "project") => {
|
||||
const ok = await ElMessageBox.confirm(
|
||||
"该文件包含敏感审计信息,仅限授权人员使用。确认导出吗?",
|
||||
"审计导出确认",
|
||||
{ type: "warning", confirmButtonText: "确认", cancelButtonText: "取消" }
|
||||
).catch(() => null);
|
||||
if (!ok) return;
|
||||
const events = await fetchAllForExport();
|
||||
if (!events.length) {
|
||||
ElMessage.warning("无可导出的审计记录");
|
||||
return;
|
||||
}
|
||||
const fileName =
|
||||
scope === "system"
|
||||
? `系统审计_${new Date().toISOString().slice(0, 10)}`
|
||||
: `项目审计_${study.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",
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (!auth.user && auth.token) {
|
||||
await auth.fetchMe().catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user