refactor: 系统管理/系统日志 => 系统监控/系统日志

This commit is contained in:
2024-04-14 22:51:33 +08:00
parent d2af75fbf9
commit e0378d8a7e
12 changed files with 241 additions and 221 deletions

View File

@@ -1,128 +0,0 @@
<template>
<GiTable
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
@filterChange="filterChange"
:disabledTools="['setting']"
@refresh="search"
>
<template #custom-left>
<a-input v-model="queryForm.createUserString" placeholder="请输入登录用户" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
<a-input v-model="queryForm.ip" placeholder="请输入登录 IP 或地点" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
<DateRangePicker v-model="queryForm.createTime" @change="search" />
<a-button @click="reset">重置</a-button>
</template>
<template #custom-right>
<a-tooltip content="导出">
<a-button @click="onExportFile">
<template #icon>
<icon-download />
</template>
</a-button>
</a-tooltip>
</template>
<template #status="{ record }">
<a-tag v-if="record.status === 1" color="green">
<GiDot type="success" style="width: 5px; height: 5px" />
<span style="margin-left: 5px">成功</span>
</a-tag>
<a-tooltip v-else :content="record.errorMsg">
<a-tag color="red" style="cursor: pointer">
<GiDot type="danger" style="width: 5px; height: 5px" />
<span style="margin-left: 5px">失败</span>
</a-tag>
</a-tooltip>
</template>
</GiTable>
</template>
<script setup lang="ts">
import { listLog,exportLog } from '@/apis'
import type { TableInstance } from '@arco-design/web-vue'
import DateRangePicker from '@/components/DateRangePicker/index.vue'
import { useTable } from '@/hooks'
import {useDownload} from '@/hooks'
defineOptions({ name: 'LoginLog' })
const filterChange = (values,record)=>{
try {
const slotName = columns[values.split('_').pop()].slotName as string
const value = record.join(',')
queryForm[slotName] = value
search()
} catch (error) {
search()
}
}
const columns: TableInstance['columns'] = [
{
title: '序号',
width: 66,
align: 'center',
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
},
{ title: '登录时间', dataIndex: 'createTime', width: 180 },
{ title: '用户昵称', dataIndex: 'createUserString', ellipsis: true, tooltip: true },
{ title: '登录行为', dataIndex: 'description' },
{
title: '状态',
slotName: 'status',
align: 'center',
filterable: {
filters: [
{
text: '成功',
value: '1'
},
{
text: '失败',
value: '2'
}
],
filter: () =>{return true},
alignLeft: true
}
},
{ title: '登录 IP', dataIndex: 'ip', ellipsis: true, tooltip: true },
{ title: '登录地点', dataIndex: 'address', ellipsis: true, tooltip: true },
{ title: '浏览器', dataIndex: 'browser', ellipsis: true, tooltip: true },
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
]
//导出登录日志
const onExportFile = ()=>{
useDownload(()=>exportLog(queryForm))
}
const queryForm = reactive({
module: '登录',
ip: undefined,
createUserString: undefined,
createTime: undefined,
status: undefined,
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = undefined
queryForm.status = undefined
search()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,149 +0,0 @@
<template>
<GiTable
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
column-resizable
@filterChange="filterChange"
:disabledTools="['setting']"
@refresh="search"
>
<template #custom-left>
<a-input v-model="queryForm.createUserString" placeholder="请输入操作人" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
<a-input v-model="queryForm.ip" placeholder="请输入操作 IP 或地点" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
<DateRangePicker v-model="queryForm.createTime" @change="search" />
<a-button @click="reset">重置</a-button>
</template>
<template #custom-right>
<a-tooltip content="导出" @click="onExportFile">
<a-button>
<template #icon>
<icon-download />
</template>
</a-button>
</a-tooltip>
</template>
<template #createTime="{ record }">
<a-link @click="openDetail(record)">{{ record.createTime }}</a-link>
</template>
<template #status="{ record }">
<a-tag v-if="record.status === 1" color="green">
<GiDot type="success" style="width: 5px; height: 5px"></GiDot>
<span style="margin-left: 5px">成功</span>
</a-tag>
<a-tooltip v-else :content="record.errorMsg">
<a-tag color="red" style="cursor: pointer">
<GiDot type="danger" style="width: 5px; height: 5px"></GiDot>
<span style="margin-left: 5px">失败</span>
</a-tag>
</a-tooltip>
</template>
<template #timeTaken="{ record }">
<a-tag v-if="record.timeTaken > 500" color="red">{{ record.timeTaken }}ms</a-tag>
<a-tag v-else-if="record.timeTaken > 200" color="orange">{{ record.timeTaken }}ms</a-tag>
<a-tag v-else color="green">{{ record.timeTaken }} ms</a-tag>
</template>
</GiTable>
<OperationLogDetailDrawer ref="OperationLogDetailDrawerRef" />
</template>
<script setup lang="ts">
import { listLog, type LogResp,exportOperateLog } from '@/apis'
import type { TableInstance } from '@arco-design/web-vue'
import DateRangePicker from '@/components/DateRangePicker/index.vue'
import OperationLogDetailDrawer from './OperationLogDetailDrawer.vue'
import { useDownload, useTable } from '@/hooks'
defineOptions({ name: 'OperationLog' })
const filterChange = (values,record)=>{
try {
const slotName = columns[values.split('_').pop()].slotName as string
const value = record.join(',')
queryForm[slotName] = value
search()
} catch (error) {
search()
}
}
const columns: TableInstance['columns'] = [
{
title: '序号',
width: 66,
align: 'center',
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
},
{ title: '操作时间', slotName: 'createTime', width: 180 },
{ title: '操作人', dataIndex: 'createUserString', ellipsis: true, tooltip: true },
{ title: '操作内容', dataIndex: 'description', ellipsis: true, tooltip: true },
{ title: '所属模块', dataIndex: 'module', align: 'center', ellipsis: true, tooltip: true },
{
title: '状态',
slotName: 'status',
align: 'center',
filterable: {
filters: [
{
text: '成功',
value: '1'
},
{
text: '失败',
value: '2'
}
],
filter: () => true,
alignLeft: true
}
},
{ title: '操作 IP', dataIndex: 'ip', ellipsis: true, tooltip: true },
{ title: '操作地点', dataIndex: 'address', ellipsis: true, tooltip: true },
{ title: '耗时', slotName: 'timeTaken', align: 'center' },
{ title: '浏览器', dataIndex: 'browser', ellipsis: true, tooltip: true },
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
]
//导出操作日志
const onExportFile = ()=>{
useDownload(()=>exportOperateLog(queryForm))
}
const queryForm = reactive({
description: undefined,
ip: undefined,
createUserString: undefined,
createTime: undefined,
status: undefined,
sort: ['createTime,desc']
})
const {
loading,
tableData: dataList,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置查询
const reset = () => {
queryForm.description = undefined
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = undefined
queryForm.status = undefined
search()
}
const OperationLogDetailDrawerRef = ref<InstanceType<typeof OperationLogDetailDrawer>>()
// 查询详情
const openDetail = (item: LogResp) => {
OperationLogDetailDrawerRef.value?.open(item.id)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,105 +0,0 @@
<template>
<a-drawer v-model:visible="visible" title="日志详情" :width="720" :footer="false">
<a-descriptions title="基本信息" :column="2" size="large" class="general-description">
<a-descriptions-item label="日志 ID">{{ operationLog?.id }}</a-descriptions-item>
<a-descriptions-item label="Trace ID" >{{ operationLog?.traceId }}<TextCopy :value="operationLog?.traceId"/></a-descriptions-item>
<a-descriptions-item label="操作人">{{ operationLog?.createUserString }}</a-descriptions-item>
<a-descriptions-item label="操作时间">{{ operationLog?.createTime }}</a-descriptions-item>
<a-descriptions-item label="操作内容">{{ operationLog?.description }}</a-descriptions-item>
<a-descriptions-item label="所属模块">{{ operationLog?.module }}</a-descriptions-item>
<a-descriptions-item label="操作 IP">{{ operationLog?.ip }}</a-descriptions-item>
<a-descriptions-item label="操作地点">{{ operationLog?.address }}</a-descriptions-item>
<a-descriptions-item label="浏览器">{{ operationLog?.browser }}</a-descriptions-item>
<a-descriptions-item label="终端系统">{{ operationLog?.os }}</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag v-if="operationLog?.status === 1" color="green">成功</a-tag>
<a-tag v-else color="red">失败</a-tag>
</a-descriptions-item>
<a-descriptions-item label="耗时">
<a-tag v-if="operationLog?.timeTaken > 500" color="red">
{{ operationLog?.timeTaken }}ms
</a-tag>
<a-tag v-else-if="operationLog?.timeTaken > 200" color="orange">
{{ operationLog?.timeTaken }}ms
</a-tag>
<a-tag v-else color="green">{{ operationLog?.timeTaken }} ms</a-tag>
</a-descriptions-item>
<a-descriptions-item label="请求 URI" :span="2">
{{ operationLog?.requestUrl }}<TextCopy :value="operationLog?.requestUrl"/>
</a-descriptions-item>
</a-descriptions>
<a-descriptions
title="响应信息"
:column="2"
size="large"
class="general-description http"
style="margin-top: 20px; position: relative"
>
<a-descriptions-item :span="2">
<a-tabs type="card">
<a-tab-pane key="1" title="响应头">
<JsonPretty v-if="operationLog?.responseHeaders" :josn="operationLog?.responseHeaders"/>
<span v-else></span>
</a-tab-pane>
<a-tab-pane key="2" title="响应体">
<JsonPretty v-if="operationLog?.responseBody" :josn="operationLog?.responseBody"/>
<span v-else></span>
</a-tab-pane>
</a-tabs>
</a-descriptions-item>
</a-descriptions>
<a-descriptions
title="请求信息"
:column="2"
size="large"
class="general-description http"
style="margin-top: 20px; position: relative"
>
<a-descriptions-item :span="2">
<a-tabs type="card">
<a-tab-pane key="1" title="请求头">
<JsonPretty v-if="operationLog?.requestHeaders" :josn="operationLog?.requestHeaders"/>
<span v-else></span>
</a-tab-pane>
<a-tab-pane key="2" title="请求体">
<JsonPretty v-if="operationLog?.requestBody" :josn="operationLog?.requestBody"/>
<span v-else></span>
</a-tab-pane>
</a-tabs>
</a-descriptions-item>
</a-descriptions>
</a-drawer>
</template>
<script lang="ts" setup>
import { getLog, type LogDetailResp } from '@/apis'
import JsonPretty from '@/components/JsonPretty/index.vue'
const logId = ref('')
const operationLog = ref<LogDetailResp | null>()
// 查询详情
const getOperationLogDetail = async () => {
const res = await getLog(logId.value)
operationLog.value = res.data
}
const visible = ref(false)
// 打开详情
const open = async (id: string) => {
logId.value = id
await getOperationLogDetail()
visible.value = true
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.http :deep(.arco-descriptions-item-label-block) {
padding-right: 0;
}
:deep(.arco-tabs-content) {
padding-top: 5px;
padding-left: 15px;
}
</style>

View File

@@ -1,67 +0,0 @@
<template>
<div class="gi_page">
<a-card title="系统日志" class="general-card">
<a-tabs type="card-gutter" size="large" :active-key="activeKey" @change="change">
<a-tab-pane key="1" title="登录日志"/>
<a-tab-pane key="2" title="操作日志"/>
</a-tabs>
<keep-alive>
<component :is="PaneMap[activeKey]"></component>
</keep-alive>
</a-card>
</div>
</template>
<script setup lang="ts">
import LoginLog from './LoginLog.vue'
import OperationLog from './OperationLog.vue'
const route = useRoute()
const router = useRouter()
const PaneMap:Record<string,Component> = {
'1': LoginLog,
'2': OperationLog
}
const activeKey = ref('1')
watch(
() => route.query,
() => {
if (route.query.tabKey) {
activeKey.value = String(route.query.tabKey)
}
},
{ immediate: true }
)
const change = (key: string | number) => {
activeKey.value = key as string
router.replace({ path: route.path, query: { tabKey: key } })
}
</script>
<style lang="scss" scoped>
:deep(.arco-tabs .arco-tabs-nav-type-card-gutter .arco-tabs-tab-active) {
box-shadow: inset 0 2px 0 rgb(var(--primary-6)), inset -1px 0 0 var(--color-border-2),
inset 1px 0 0 var(--color-border-2);
position: relative;
}
:deep(.arco-tabs-nav-type-card-gutter .arco-tabs-tab) {
border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0;
}
:deep(.arco-tabs-type-card-gutter > .arco-tabs-content) {
border: none;
}
:deep(.arco-tabs-nav::before) {
left: -20px;
right: -20px;
}
:deep(.arco-tabs) {
overflow: visible;
}
:deep(.arco-tabs-nav) {
overflow: visible;
}
</style>