修复(权限管理):统一 PM 系统导航与权限校验

This commit is contained in:
Cheng Zhou
2026-07-10 22:12:59 +08:00
parent f11a5c84d9
commit f2fa10c178
47 changed files with 4071 additions and 1555 deletions
@@ -30,6 +30,17 @@
<section class="access-log-card">
<div class="audit-filters">
<div class="filter-inputs">
<el-tag
v-if="trendDrilldownActive"
closable
effect="plain"
size="small"
type="primary"
class="trend-drilldown-tag"
@close="clearTrendDrilldownRange"
>
权限趋势下钻{{ drilldownRangeLabel ? ` · ${drilldownRangeLabel}` : "" }}
</el-tag>
<el-select v-model="filters.role" placeholder="角色" clearable size="small" class="filter-role" @change="onFilterChange">
<el-option v-for="role in roleFilterOptions" :key="role.value" :label="role.label" :value="role.value" />
</el-select>
@@ -324,6 +335,17 @@ const TIME_RANGE_PRESETS = [
] as const;
type TimeRangePreset = (typeof TIME_RANGE_PRESETS)[number]["value"];
type AccessTimeRangePreset = TimeRangePreset | "custom";
interface TrendDrilldownPayload {
preset?: Exclude<TimeRangePreset, "all">;
startAt?: string;
endAt?: string;
allowed?: boolean;
role?: string;
clientType?: string;
keyword?: string;
}
interface IpRankingRow {
ip: string;
@@ -385,8 +407,10 @@ const summary = ref<AccessLogsSummary>(emptySummary);
const total = ref(0);
const page = ref(1);
const pageSize = ref(50);
const timeRangePreset = ref<TimeRangePreset>("24h");
const timeRangePreset = ref<AccessTimeRangePreset>("24h");
const ipRankingPeriod = ref<TimeRangePreset>("24h");
const trendDrilldownRange = ref<[Date, Date] | null>(null);
const trendDrilldownActive = ref(false);
const accessLogDialogVisible = ref(false);
const ipRankingDialogVisible = ref(false);
const accessTerminalWindowRef = ref<HTMLElement | null>(null);
@@ -636,7 +660,8 @@ const formatLastUpdated = (value: Date) => {
return `${value.getFullYear()}/${value.getMonth() + 1}/${value.getDate()} ${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}:${String(value.getSeconds()).padStart(2, "0")}`;
};
const buildTimeRangeFromPreset = (preset: TimeRangePreset): [Date, Date] | null => {
const buildTimeRangeFromPreset = (preset: AccessTimeRangePreset): [Date, Date] | null => {
if (preset === "custom") return null;
const option = TIME_RANGE_PRESETS.find((item) => item.value === preset) || TIME_RANGE_PRESETS[1];
if (!option.hours) return null;
const end = new Date();
@@ -691,6 +716,7 @@ const onFilterChange = () => {
};
const onTimePresetChange = () => {
trendDrilldownRange.value = null;
onFilterChange();
};
@@ -704,6 +730,8 @@ const onKeywordInput = () => {
const resetFilters = () => {
timeRangePreset.value = "24h";
trendDrilldownRange.value = null;
trendDrilldownActive.value = false;
filters.role = undefined;
filters.allowed = undefined;
filters.clientType = undefined;
@@ -724,12 +752,13 @@ const buildQueryParams = (overrides: Record<string, any> = {}) => {
if (filters.role) params.role = filters.role;
if (filters.allowed !== undefined) params.allowed = filters.allowed;
if (filters.clientType) params.client_type = filters.clientType;
if (trendDrilldownActive.value) params.event_type = "permission";
const keywordToken = filters.keyword.trim();
if (keywordToken) {
params.keyword = keywordToken;
if (/^[0-9a-f:.]+$/i.test(keywordToken)) params.client_ip = keywordToken;
}
const presetRange = timeRangePreset.value ? buildTimeRangeFromPreset(timeRangePreset.value) : null;
const presetRange = trendDrilldownRange.value || buildTimeRangeFromPreset(timeRangePreset.value);
if (presetRange) {
params.start_time = presetRange[0].toISOString();
params.end_time = presetRange[1].toISOString();
@@ -796,6 +825,37 @@ const refresh = () => {
loadData();
};
const drilldownRangeLabel = computed(() => {
if (!trendDrilldownRange.value) return "";
const [start, end] = trendDrilldownRange.value;
const compact = (value: Date) => `${value.getMonth() + 1}/${value.getDate()} ${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
return `${compact(start)}${compact(end)}`;
});
const clearTrendDrilldownRange = () => {
trendDrilldownRange.value = null;
trendDrilldownActive.value = false;
timeRangePreset.value = "24h";
onFilterChange();
};
const applyTrendFilter = async (payload: TrendDrilldownPayload) => {
page.value = 1;
trendDrilldownActive.value = true;
filters.allowed = payload.allowed;
filters.role = payload.role;
filters.clientType = payload.clientType;
filters.keyword = payload.keyword || "";
if (payload.startAt && payload.endAt) {
trendDrilldownRange.value = [new Date(payload.startAt), new Date(payload.endAt)];
timeRangePreset.value = "custom";
} else {
trendDrilldownRange.value = null;
timeRangePreset.value = payload.preset || "24h";
}
await loadData();
};
const openLogDetail = (row: AccessLogItem) => {
selectedLog.value = row;
detailDrawerVisible.value = true;
@@ -1023,7 +1083,7 @@ onBeforeUnmount(() => {
stopTimers();
});
defineExpose({ refresh });
defineExpose({ refresh, applyTrendFilter });
</script>
<style scoped>
@@ -1089,12 +1149,12 @@ defineExpose({ refresh });
align-items: center;
gap: 12px;
flex-wrap: wrap;
padding: 0 0 8px;
padding: 10px 12px;
background: transparent;
border: none;
box-shadow: none;
border-bottom: 1px solid var(--audit-border);
margin-bottom: 8px;
margin-bottom: 0;
}
.filter-inputs {
@@ -1249,19 +1309,14 @@ defineExpose({ refresh });
.access-log-card {
min-width: 0;
padding: 10px;
padding: 0;
overflow: hidden;
border: 1px solid var(--audit-border);
border-radius: var(--card-radius);
background: #fff;
box-shadow: var(--card-shadow);
}
.access-log-card {
overflow: hidden;
}
.section-head {
display: flex;
align-items: flex-start;
@@ -1302,14 +1357,14 @@ defineExpose({ refresh });
.log-surface {
display: flex;
flex-direction: column;
gap: 10px;
gap: 0;
}
.table-shell {
min-width: 0;
overflow: hidden;
border: 1px solid #edf2f7;
border-radius: 10px;
border: 0;
border-radius: 0;
}
.access-table {
@@ -1331,6 +1386,14 @@ defineExpose({ refresh });
padding: 0 8px;
}
.access-table :deep(.el-scrollbar__wrap) {
overflow-x: hidden;
}
.access-table :deep(.el-scrollbar__bar.is-horizontal) {
display: none;
}
.time-cell,
.ip-cell,
.location-cell,
@@ -1608,7 +1671,8 @@ defineExpose({ refresh });
.logs-pagination {
display: flex;
justify-content: flex-end;
padding: 8px 10px;
padding: 8px 12px;
border-top: 1px solid var(--audit-border);
background: #fff;
}