2 changed files with 325 additions and 0 deletions
@ -0,0 +1,11 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
/** |
||||
* 分页查询系统访问日志。 |
||||
*/ |
||||
export function listSystemLog(query) { |
||||
return request.get({ |
||||
url: "/systemLog", |
||||
query, |
||||
}); |
||||
} |
||||
@ -0,0 +1,314 @@
|
||||
<template> |
||||
<div class="container"> |
||||
<header class="mb-20"> |
||||
<el-form :label-width="100"> |
||||
<el-row :gutter="12"> |
||||
<el-col :span="6"> |
||||
<el-form-item label="请求人"> |
||||
<el-input |
||||
v-model="query.requester" |
||||
placeholder="请输入登录名或姓名" |
||||
clearable |
||||
@keyup.enter="search" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="6"> |
||||
<el-form-item label="接口地址"> |
||||
<el-input |
||||
v-model="query.requestUri" |
||||
placeholder="请输入接口地址" |
||||
clearable |
||||
@keyup.enter="search" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="6"> |
||||
<el-form-item label="访问方式"> |
||||
<el-select |
||||
v-model="query.requestMethod" |
||||
placeholder="请选择访问方式" |
||||
clearable |
||||
> |
||||
<el-option |
||||
v-for="method in requestMethods" |
||||
:key="method" |
||||
:label="method" |
||||
:value="method" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="6"> |
||||
<el-form-item label="来源IP"> |
||||
<el-input |
||||
v-model="query.sourceIp" |
||||
placeholder="请输入来源IP" |
||||
clearable |
||||
@keyup.enter="search" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row :gutter="12"> |
||||
<el-col :span="6"> |
||||
<el-form-item label="执行结果"> |
||||
<el-select |
||||
v-model="query.status" |
||||
placeholder="请选择执行结果" |
||||
clearable |
||||
> |
||||
<el-option label="成功" :value="1" /> |
||||
<el-option label="失败" :value="0" /> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="日志时间"> |
||||
<el-date-picker |
||||
v-model="query.logTimeRange" |
||||
type="datetimerange" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
format="YYYY-MM-DD HH:mm:ss" |
||||
range-separator="~" |
||||
start-placeholder="开始时间" |
||||
end-placeholder="结束时间" |
||||
clearable |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form> |
||||
<div class="flex end"> |
||||
<el-button type="primary" @click="search"> |
||||
<template #icon> |
||||
<icon name="el-icon-Search" /> |
||||
</template> |
||||
查询 |
||||
</el-button> |
||||
<el-button @click="reset">重置</el-button> |
||||
</div> |
||||
</header> |
||||
|
||||
<el-tabs v-model="activeTab" @tab-change="handleTabChange"> |
||||
<el-tab-pane label="登录日志" name="login" /> |
||||
<el-tab-pane label="操作日志" name="operation" /> |
||||
</el-tabs> |
||||
|
||||
<div class="table-container"> |
||||
<el-table :data="logs"> |
||||
<el-table-column label="请求人" min-width="160"> |
||||
<template #default="{ row }"> |
||||
{{ formatRequester(row) }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="接口地址" |
||||
prop="requestUri" |
||||
min-width="220" |
||||
show-overflow-tooltip |
||||
/> |
||||
<el-table-column |
||||
label="访问方式" |
||||
prop="requestMethod" |
||||
width="100" |
||||
align="center" |
||||
/> |
||||
<el-table-column |
||||
label="来源IP" |
||||
prop="sourceIp" |
||||
min-width="140" |
||||
show-overflow-tooltip |
||||
/> |
||||
<el-table-column label="请求参数" min-width="220"> |
||||
<template #default="{ row }"> |
||||
<span class="summary-text">{{ formatSummary(row.requestParams) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="执行耗时" width="110" align="center"> |
||||
<template #default="{ row }"> |
||||
{{ row.executionTime }} ms |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="执行结果" width="100" align="center"> |
||||
<template #default="{ row }"> |
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'"> |
||||
{{ row.status === 1 ? "成功" : "失败" }} |
||||
</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="日志时间" |
||||
prop="logTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column label="操作" width="100" fixed="right"> |
||||
<template #default="{ row }"> |
||||
<el-button type="primary" link @click="showDetail(row)"> |
||||
查看详情 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
|
||||
<div class="flex end mt-8"> |
||||
<el-pagination |
||||
v-model:page-size="query.size" |
||||
v-model:current-page="query.current" |
||||
:page-sizes="[10, 20, 50]" |
||||
:total="total" |
||||
layout="total, sizes, prev, pager, next" |
||||
@size-change="handleSizeChange" |
||||
@current-change="getList" |
||||
/> |
||||
</div> |
||||
</div> |
||||
|
||||
<el-dialog v-model="detailVisible" title="系统日志详情" width="760px"> |
||||
<el-descriptions :column="2" border> |
||||
<el-descriptions-item label="请求人"> |
||||
{{ formatRequester(activeLog) }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="日志时间"> |
||||
{{ activeLog.logTime || "-" }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="接口地址" :span="2"> |
||||
{{ activeLog.requestUri || "-" }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="访问方式"> |
||||
{{ activeLog.requestMethod || "-" }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="来源IP"> |
||||
{{ activeLog.sourceIp || "-" }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="执行耗时"> |
||||
{{ activeLog.executionTime ?? 0 }} ms |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="执行结果"> |
||||
<el-tag :type="activeLog.status === 1 ? 'success' : 'danger'"> |
||||
{{ activeLog.status === 1 ? "成功" : "失败" }} |
||||
</el-tag> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="请求参数" :span="2"> |
||||
<pre class="detail-content">{{ activeLog.requestParams || "-" }}</pre> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="异常信息" :span="2"> |
||||
<pre class="detail-content error-content">{{ activeLog.errorMessage || "-" }}</pre> |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
<template #footer> |
||||
<el-button @click="detailVisible = false">关闭</el-button> |
||||
</template> |
||||
</el-dialog> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { listSystemLog } from "@/api/system/systemLog"; |
||||
|
||||
const requestMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]; |
||||
|
||||
const createDefaultQuery = () => ({ |
||||
current: 1, |
||||
size: 10, |
||||
requester: undefined, |
||||
requestUri: undefined, |
||||
requestMethod: undefined, |
||||
sourceIp: undefined, |
||||
status: undefined, |
||||
logTimeRange: [], |
||||
}); |
||||
|
||||
const query = ref(createDefaultQuery()); |
||||
const logs = ref([]); |
||||
const total = ref(0); |
||||
const detailVisible = ref(false); |
||||
const activeLog = ref({}); |
||||
const activeTab = ref("login"); |
||||
|
||||
/** |
||||
* 将页面时间范围拆分成后端接收的开始、结束时间参数。 |
||||
*/ |
||||
function buildQueryParams() { |
||||
const { logTimeRange, ...params } = query.value; |
||||
params.logType = activeTab.value; |
||||
if (logTimeRange?.length === 2) { |
||||
params.startTime = logTimeRange[0]; |
||||
params.endTime = logTimeRange[1]; |
||||
} |
||||
return params; |
||||
} |
||||
|
||||
function getList() { |
||||
listSystemLog(buildQueryParams()).then((data) => { |
||||
logs.value = data.records; |
||||
total.value = data.total; |
||||
}); |
||||
} |
||||
|
||||
function search() { |
||||
query.value.current = 1; |
||||
getList(); |
||||
} |
||||
|
||||
function reset() { |
||||
query.value = createDefaultQuery(); |
||||
getList(); |
||||
} |
||||
|
||||
function handleSizeChange() { |
||||
query.value.current = 1; |
||||
getList(); |
||||
} |
||||
|
||||
function handleTabChange() { |
||||
query.value.current = 1; |
||||
getList(); |
||||
} |
||||
|
||||
function formatRequester(row = {}) { |
||||
if (row.nickName && row.userName) { |
||||
return `${row.nickName}(${row.userName})`; |
||||
} |
||||
return row.nickName || row.userName || "匿名/外部调用"; |
||||
} |
||||
|
||||
function formatSummary(value) { |
||||
if (!value) { |
||||
return "-"; |
||||
} |
||||
return value.length > 60 ? `${value.slice(0, 60)}...` : value; |
||||
} |
||||
|
||||
function showDetail(row) { |
||||
activeLog.value = row; |
||||
detailVisible.value = true; |
||||
} |
||||
|
||||
getList(); |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.summary-text { |
||||
display: inline-block; |
||||
max-width: 100%; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
.detail-content { |
||||
margin: 0; |
||||
max-height: 260px; |
||||
overflow: auto; |
||||
white-space: pre-wrap; |
||||
overflow-wrap: anywhere; |
||||
font-family: inherit; |
||||
line-height: 1.6; |
||||
} |
||||
|
||||
.error-content { |
||||
color: var(--el-color-danger); |
||||
} |
||||
</style> |
||||
Loading…
Reference in new issue