mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-19 21:30:01 +08:00
feat: 增加日志模块
This commit is contained in:
parent
d6c70bad0a
commit
d400ebedc0
@ -55,7 +55,7 @@
|
|||||||
"md-editor-v3": "^5.6.1",
|
"md-editor-v3": "^5.6.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.5.0",
|
"pinia-plugin-persistedstate": "^4.5.0",
|
||||||
"pro-naive-ui": "^3.0.3",
|
"pro-naive-ui": "^3.1.1",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"radash": "^12.1.1",
|
"radash": "^12.1.1",
|
||||||
"vue": "^3.5.20",
|
"vue": "^3.5.20",
|
||||||
|
@ -20,6 +20,10 @@ const propOverrides = {
|
|||||||
preset: 'card',
|
preset: 'card',
|
||||||
},
|
},
|
||||||
ProDataTable: {
|
ProDataTable: {
|
||||||
|
tableCardProps: {
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
size: 'small',
|
||||||
paginateSinglePage: false,
|
paginateSinglePage: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
31
src/api/monitor/login-log.ts
Normal file
31
src/api/monitor/login-log.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { request } from '@/utils/alova'
|
||||||
|
|
||||||
|
export type LoginLogSearchQuery = Partial<Entity.LoginLog> & Api.PageParams
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询登录日志列表
|
||||||
|
*/
|
||||||
|
export function getLoginLogPage(params: LoginLogSearchQuery) {
|
||||||
|
return request.Get<Api.ListResponse<Entity.LoginLog>>('/login-log', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录日志详情
|
||||||
|
*/
|
||||||
|
export function getLoginLogDetail(id: number) {
|
||||||
|
return request.Get<Entity.LoginLog>(`/login-log/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除登录日志
|
||||||
|
*/
|
||||||
|
export function deleteLoginLog(ids: string) {
|
||||||
|
return request.Delete(`/login-log/${ids}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空登录日志
|
||||||
|
*/
|
||||||
|
export function clearLoginLog() {
|
||||||
|
return request.Delete('/login-log/clean')
|
||||||
|
}
|
31
src/api/monitor/oper-log.ts
Normal file
31
src/api/monitor/oper-log.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { request } from '@/utils/alova'
|
||||||
|
|
||||||
|
export interface OperLogQueryParams extends Api.PageParams {
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 分页查询操作日志列表
|
||||||
|
*/
|
||||||
|
export function getOperLogPage(params: OperLogQueryParams) {
|
||||||
|
return request.Get<Api.ListResponse<Entity.OperLog>>('/oper-log', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取操作日志详情
|
||||||
|
*/
|
||||||
|
export function getOperLogDetail(id: number) {
|
||||||
|
return request.Get<Entity.OperLog>(`/oper-log/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除操作日志
|
||||||
|
*/
|
||||||
|
export function deleteOperLog(ids: string) {
|
||||||
|
return request.Delete(`/oper-log/${ids}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空操作日志
|
||||||
|
*/
|
||||||
|
export function clearOperLog() {
|
||||||
|
return request.Delete('/oper-log/clean')
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
create,
|
create,
|
||||||
|
ProDateRange,
|
||||||
|
ProDateTimeRange,
|
||||||
ProInput,
|
ProInput,
|
||||||
ProSelect,
|
ProSelect,
|
||||||
} from 'pro-naive-ui'
|
} from 'pro-naive-ui'
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
|
|
||||||
const proNaive = create({
|
const proNaive = create({
|
||||||
components: [ProInput, ProSelect],
|
components: [ProInput, ProSelect, ProDateRange, ProDateTimeRange],
|
||||||
})
|
})
|
||||||
|
|
||||||
export function install(app: App) {
|
export function install(app: App) {
|
||||||
|
@ -127,7 +127,7 @@ export function setupRouterGuard(router: Router) {
|
|||||||
|
|
||||||
router.beforeResolve((to) => {
|
router.beforeResolve((to) => {
|
||||||
// 设置菜单高亮
|
// 设置菜单高亮
|
||||||
routeStore.setActiveMenu(to.meta.activePath ?? to.fullPath)
|
routeStore.setActiveMenu(to.meta.activePath || to.fullPath)
|
||||||
// 添加tabs
|
// 添加tabs
|
||||||
tabStore.addTab(to)
|
tabStore.addTab(to)
|
||||||
// 设置高亮标签
|
// 设置高亮标签
|
||||||
|
43
src/typings/entities/login-log.d.ts
vendored
Normal file
43
src/typings/entities/login-log.d.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
declare namespace Entity {
|
||||||
|
/**
|
||||||
|
* 登录日志
|
||||||
|
*/
|
||||||
|
interface LoginLog {
|
||||||
|
/**
|
||||||
|
* 日志编号
|
||||||
|
*/
|
||||||
|
id: number
|
||||||
|
/**
|
||||||
|
* 用户账号
|
||||||
|
*/
|
||||||
|
username: string
|
||||||
|
/**
|
||||||
|
* 登录IP地址
|
||||||
|
*/
|
||||||
|
ipaddr: string
|
||||||
|
/**
|
||||||
|
* 登录地点
|
||||||
|
*/
|
||||||
|
loginLocation: string
|
||||||
|
/**
|
||||||
|
* 浏览器类型
|
||||||
|
*/
|
||||||
|
browser: string
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
os: string
|
||||||
|
/**
|
||||||
|
* 登录状态(0成功 1失败)
|
||||||
|
*/
|
||||||
|
status: number
|
||||||
|
/**
|
||||||
|
* 提示消息
|
||||||
|
*/
|
||||||
|
msg: string
|
||||||
|
/**
|
||||||
|
* 访问时间
|
||||||
|
*/
|
||||||
|
loginTime: string
|
||||||
|
}
|
||||||
|
}
|
75
src/typings/entities/oper-log.d.ts
vendored
Normal file
75
src/typings/entities/oper-log.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
declare namespace Entity {
|
||||||
|
/**
|
||||||
|
* 操作日志
|
||||||
|
*/
|
||||||
|
interface OperLog {
|
||||||
|
/**
|
||||||
|
* 日志编号
|
||||||
|
*/
|
||||||
|
id: number
|
||||||
|
/**
|
||||||
|
* 模块标题
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* 方法名称
|
||||||
|
*/
|
||||||
|
method: string
|
||||||
|
/**
|
||||||
|
* 请求方式
|
||||||
|
*/
|
||||||
|
requestMethod: string
|
||||||
|
/**
|
||||||
|
* 操作人员
|
||||||
|
*/
|
||||||
|
operName: string
|
||||||
|
/**
|
||||||
|
* 部门名称
|
||||||
|
*/
|
||||||
|
deptName: string
|
||||||
|
/**
|
||||||
|
* 请求URL
|
||||||
|
*/
|
||||||
|
operUrl: string
|
||||||
|
/**
|
||||||
|
* 主机地址
|
||||||
|
*/
|
||||||
|
operIp: string
|
||||||
|
/**
|
||||||
|
* 操作地点
|
||||||
|
*/
|
||||||
|
operLocation: string
|
||||||
|
/**
|
||||||
|
* 浏览器类型
|
||||||
|
*/
|
||||||
|
browser: string
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
os: string
|
||||||
|
/**
|
||||||
|
* 请求参数
|
||||||
|
*/
|
||||||
|
operParam: string
|
||||||
|
/**
|
||||||
|
* 返回参数
|
||||||
|
*/
|
||||||
|
jsonResult: string
|
||||||
|
/**
|
||||||
|
* 操作状态(0正常 1异常)
|
||||||
|
*/
|
||||||
|
status: number
|
||||||
|
/**
|
||||||
|
* 错误消息
|
||||||
|
*/
|
||||||
|
errorMsg: string
|
||||||
|
/**
|
||||||
|
* 消耗时间
|
||||||
|
*/
|
||||||
|
costTime: string
|
||||||
|
/**
|
||||||
|
* 操作时间
|
||||||
|
*/
|
||||||
|
operTime: Date
|
||||||
|
}
|
||||||
|
}
|
5
src/typings/global.d.ts
vendored
5
src/typings/global.d.ts
vendored
@ -9,6 +9,11 @@ declare namespace Entity {
|
|||||||
|
|
||||||
/* 各类接口返回的数据类型, 具体内容在 ./api */
|
/* 各类接口返回的数据类型, 具体内容在 ./api */
|
||||||
declare namespace Api {
|
declare namespace Api {
|
||||||
|
interface PageParams {
|
||||||
|
pageNum: number
|
||||||
|
pageSize: number
|
||||||
|
}
|
||||||
|
|
||||||
interface Response<T> {
|
interface Response<T> {
|
||||||
/** 业务状态码 */
|
/** 业务状态码 */
|
||||||
code: number
|
code: number
|
||||||
|
155
src/views/monitor/login-log/columns.tsx
Normal file
155
src/views/monitor/login-log/columns.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import { NButton, NSpace, NTag } from 'naive-ui'
|
||||||
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
|
import { renderProCopyableText } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
// 登录日志搜索表单数据类型
|
||||||
|
export interface LoginLogSearchFormData {
|
||||||
|
ipaddr?: string
|
||||||
|
userName?: string
|
||||||
|
status?: number
|
||||||
|
loginTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录日志搜索表单列配置
|
||||||
|
export const searchColumns: ProSearchFormColumns<LoginLogSearchFormData> = [
|
||||||
|
{
|
||||||
|
title: '登录地址',
|
||||||
|
path: 'ipaddr',
|
||||||
|
field: 'input',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名称',
|
||||||
|
path: 'username',
|
||||||
|
field: 'input',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
path: 'status',
|
||||||
|
field: 'select',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
options: [
|
||||||
|
{ label: '成功', value: '0' },
|
||||||
|
{ label: '失败', value: '1' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录时间',
|
||||||
|
path: 'loginTime',
|
||||||
|
field: 'date-time-range',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
format: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
defaultTime: ['00:00:00', '23:59:59'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
export function createTableColumns(options: {
|
||||||
|
onDelete: (infoId: number) => void
|
||||||
|
}): DataTableColumns<Entity.LoginLog> {
|
||||||
|
const { onDelete } = options
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名称',
|
||||||
|
key: 'username',
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录地址',
|
||||||
|
key: 'ipaddr',
|
||||||
|
width: 130,
|
||||||
|
align: 'center',
|
||||||
|
render: row => renderProCopyableText(row.ipaddr),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录地点',
|
||||||
|
key: 'loginLocation',
|
||||||
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '浏览器',
|
||||||
|
key: 'browser',
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作系统',
|
||||||
|
key: 'os',
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录状态',
|
||||||
|
key: 'status',
|
||||||
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NTag type={row.status === 0 ? 'success' : 'error'} bordered={false}>
|
||||||
|
{row.status === 0 ? '成功' : '失败'}
|
||||||
|
</NTag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作信息',
|
||||||
|
key: 'msg',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录日期',
|
||||||
|
key: 'loginTime',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NSpace justify="center">
|
||||||
|
<NButton
|
||||||
|
text
|
||||||
|
type="error"
|
||||||
|
onClick={() => onDelete(row.id)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
149
src/views/monitor/login-log/index.vue
Normal file
149
src/views/monitor/login-log/index.vue
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||||
|
import {
|
||||||
|
clearLoginLog,
|
||||||
|
deleteLoginLog,
|
||||||
|
getLoginLogPage,
|
||||||
|
} from '@/api/monitor/login-log'
|
||||||
|
import type { LoginLogSearchQuery } from '@/api/monitor/login-log'
|
||||||
|
import { createTableColumns, searchColumns } from './columns'
|
||||||
|
import type { LoginLogSearchFormData } from './columns'
|
||||||
|
|
||||||
|
// 创建搜索表单
|
||||||
|
const searchForm = createProSearchForm<LoginLogSearchFormData>({
|
||||||
|
defaultCollapsed: true,
|
||||||
|
initialValues: {
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用useNDataTable
|
||||||
|
const {
|
||||||
|
table: {
|
||||||
|
tableProps,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
proSearchFormProps,
|
||||||
|
searchLoading,
|
||||||
|
},
|
||||||
|
refresh,
|
||||||
|
} = useNDataTable(getList, {
|
||||||
|
form: searchForm,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 批量删除相关
|
||||||
|
const checkedRowKeys = ref<number[]>([])
|
||||||
|
|
||||||
|
/** 查询登录日志列表 */
|
||||||
|
async function getList({ current, pageSize }: any, formData: LoginLogSearchFormData) {
|
||||||
|
try {
|
||||||
|
const params: LoginLogSearchQuery = {
|
||||||
|
pageNum: current,
|
||||||
|
pageSize,
|
||||||
|
...formData,
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLoginLogPage(params).then(res => res.data)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取登录日志列表失败:', error)
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除登录日志 */
|
||||||
|
async function handleDelete(infoId: number | number[]) {
|
||||||
|
const isBatch = Array.isArray(infoId)
|
||||||
|
const ids = isBatch ? infoId.join(',') : infoId.toString()
|
||||||
|
const count = isBatch ? infoId.length : 1
|
||||||
|
|
||||||
|
window.$dialog.warning({
|
||||||
|
title: '确认删除',
|
||||||
|
content: isBatch
|
||||||
|
? `是否确认删除选中的 ${count} 条登录日志?`
|
||||||
|
: '是否确认删除该条登录日志?',
|
||||||
|
positiveText: '确定删除',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await deleteLoginLog(ids)
|
||||||
|
window.$message.success('删除成功')
|
||||||
|
if (isBatch) {
|
||||||
|
// 清空选中项
|
||||||
|
checkedRowKeys.value = []
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('删除登录日志失败:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空登录日志 */
|
||||||
|
async function handleClean() {
|
||||||
|
window.$dialog.warning({
|
||||||
|
title: '确认清空',
|
||||||
|
content: '是否确认清空所有登录日志数据?此操作不可恢复!',
|
||||||
|
positiveText: '确定清空',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await clearLoginLog()
|
||||||
|
window.$message.success('清空成功')
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('清空登录日志失败:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建表格列配置
|
||||||
|
const columns = createTableColumns({
|
||||||
|
onDelete: handleDelete,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-space vertical>
|
||||||
|
<n-card>
|
||||||
|
<pro-search-form
|
||||||
|
v-bind="proSearchFormProps"
|
||||||
|
:form="searchForm"
|
||||||
|
:columns="searchColumns"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<pro-data-table
|
||||||
|
v-bind="tableProps"
|
||||||
|
v-model:checked-row-keys="checkedRowKeys"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="id"
|
||||||
|
:loading="searchLoading"
|
||||||
|
title=" "
|
||||||
|
>
|
||||||
|
<template #toolbar>
|
||||||
|
<n-flex>
|
||||||
|
<n-button type="error" :disabled="checkedRowKeys.length === 0" @click="handleDelete(checkedRowKeys)">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-delete />
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
<n-button type="warning" @click="handleClean">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-clear />
|
||||||
|
</template>
|
||||||
|
清空
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</pro-data-table>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
168
src/views/monitor/oper-log/columns.tsx
Normal file
168
src/views/monitor/oper-log/columns.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import { NButton, NSpace, NTag } from 'naive-ui'
|
||||||
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
|
import { renderProCopyableText } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
// 操作日志搜索表单数据类型
|
||||||
|
export interface OperationLogSearchFormData {
|
||||||
|
operUrl?: string
|
||||||
|
title?: string
|
||||||
|
operName?: string
|
||||||
|
businessType?: number
|
||||||
|
status?: 0 | 1
|
||||||
|
operTime?: [string, string]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作日志搜索表单列配置
|
||||||
|
export const searchColumns: ProSearchFormColumns<OperationLogSearchFormData> = [
|
||||||
|
{
|
||||||
|
title: '操作地址',
|
||||||
|
path: 'operUrl',
|
||||||
|
field: 'input',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '系统模块',
|
||||||
|
path: 'title',
|
||||||
|
field: 'input',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人员',
|
||||||
|
path: 'operName',
|
||||||
|
field: 'input',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
path: 'status',
|
||||||
|
field: 'select',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 0 },
|
||||||
|
{ label: '异常', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
path: 'operTime',
|
||||||
|
field: 'date-time-range',
|
||||||
|
fieldProps: {
|
||||||
|
clearable: true,
|
||||||
|
format: 'yyyy-MM-dd',
|
||||||
|
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
defaultTime: ['00:00:00', '23:59:59'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
export function createTableColumns(options: {
|
||||||
|
onView: (row: Entity.OperLog) => void
|
||||||
|
onDelete: (operId: number) => void
|
||||||
|
}): DataTableColumns<Entity.OperLog> {
|
||||||
|
const { onView, onDelete } = options
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
width: 55,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '系统模块',
|
||||||
|
key: 'title',
|
||||||
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人员',
|
||||||
|
key: 'operName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '主机地址',
|
||||||
|
key: 'operIp',
|
||||||
|
width: 130,
|
||||||
|
align: 'center',
|
||||||
|
render: row => renderProCopyableText(row.operIp),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作地点',
|
||||||
|
key: 'operLocation',
|
||||||
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作状态',
|
||||||
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NTag type={row.status === 0 ? 'success' : 'error'} bordered={false}>
|
||||||
|
{row.status === 0 ? '正常' : '异常'}
|
||||||
|
</NTag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作日期',
|
||||||
|
key: 'operTime',
|
||||||
|
width: 180,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '消耗时间',
|
||||||
|
key: 'costTime',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: row => `${row.costTime}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NSpace justify="center">
|
||||||
|
<NButton
|
||||||
|
text
|
||||||
|
type="primary"
|
||||||
|
onClick={() => onView(row)}
|
||||||
|
>
|
||||||
|
详细
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
text
|
||||||
|
type="error"
|
||||||
|
onClick={() => onDelete(row.id)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
119
src/views/monitor/oper-log/components/DetailModal.vue
Normal file
119
src/views/monitor/oper-log/components/DetailModal.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBoolean } from '@/hooks'
|
||||||
|
|
||||||
|
const { bool: visible, setTrue: showModal, setFalse: hideModal } = useBoolean(false)
|
||||||
|
|
||||||
|
const operationLog = ref<Entity.OperLog | null>(null)
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
function openModal(row: Entity.OperLog) {
|
||||||
|
operationLog.value = row
|
||||||
|
showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
function closeModal() {
|
||||||
|
hideModal()
|
||||||
|
operationLog.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="visible"
|
||||||
|
preset="card"
|
||||||
|
title="操作日志详细"
|
||||||
|
class="w-4/5 max-w-4xl"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<div v-if="operationLog" class="space-y-4">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<n-card title="基本信息" size="small">
|
||||||
|
<n-descriptions :column="3" label-placement="left" size="small">
|
||||||
|
<n-descriptions-item label="日志编号">
|
||||||
|
{{ operationLog.id }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="系统模块">
|
||||||
|
{{ operationLog.title }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="部门名称">
|
||||||
|
{{ operationLog.deptName || '-' }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="操作状态">
|
||||||
|
<n-tag size="small">
|
||||||
|
{{ operationLog.status === 0 ? '正常' : '异常' }}
|
||||||
|
</n-tag>
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="消耗时间">
|
||||||
|
{{ `${operationLog.costTime}ms` }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="操作时间">
|
||||||
|
{{ operationLog.operTime }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 请求信息 -->
|
||||||
|
<n-card title="请求信息" size="small">
|
||||||
|
<n-descriptions :column="2" label-placement="left" size="small">
|
||||||
|
<n-descriptions-item label="调用方法" :span="2">
|
||||||
|
<n-text code>
|
||||||
|
{{ operationLog.method }}
|
||||||
|
</n-text>
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="请求地址">
|
||||||
|
<n-text code>
|
||||||
|
{{ operationLog.operUrl }}
|
||||||
|
</n-text>
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="请求方式">
|
||||||
|
<n-tag size="small">
|
||||||
|
{{ operationLog.requestMethod }}
|
||||||
|
</n-tag>
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="主机地址">
|
||||||
|
{{ operationLog.operIp }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="操作地点">
|
||||||
|
{{ operationLog.operLocation }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 请求参数 -->
|
||||||
|
<pro-card title="请求参数" size="small" :show="false">
|
||||||
|
<n-scrollbar style="max-height: 200px">
|
||||||
|
<pre>{{ JSON.parse(operationLog.operParam) }}</pre>
|
||||||
|
</n-scrollbar>
|
||||||
|
</pro-card>
|
||||||
|
|
||||||
|
<!-- 返回结果 -->
|
||||||
|
<pro-card title="返回结果" size="small">
|
||||||
|
<n-scrollbar style="max-height: 200px">
|
||||||
|
<pre>{{ operationLog.jsonResult }}</pre>
|
||||||
|
</n-scrollbar>
|
||||||
|
</pro-card>
|
||||||
|
|
||||||
|
<!-- 异常信息 -->
|
||||||
|
<n-card v-if="operationLog.status === 1" title="异常信息" size="small">
|
||||||
|
<n-scrollbar style="max-height: 200px">
|
||||||
|
<n-text type="error">
|
||||||
|
{{ operationLog.errorMsg || '无异常信息' }}
|
||||||
|
</n-text>
|
||||||
|
</n-scrollbar>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<n-button @click="closeModal">
|
||||||
|
关闭
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
170
src/views/monitor/oper-log/index.vue
Normal file
170
src/views/monitor/oper-log/index.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||||
|
import {
|
||||||
|
clearOperLog,
|
||||||
|
deleteOperLog,
|
||||||
|
getOperLogPage,
|
||||||
|
} from '@/api/monitor/oper-log'
|
||||||
|
import type { OperLogQueryParams } from '@/api/monitor/oper-log'
|
||||||
|
import { createTableColumns, searchColumns } from './columns'
|
||||||
|
import type { OperationLogSearchFormData } from './columns'
|
||||||
|
|
||||||
|
// 导入子组件
|
||||||
|
import DetailModal from './components/DetailModal.vue'
|
||||||
|
|
||||||
|
// 弹窗引用
|
||||||
|
const detailModalRef = ref()
|
||||||
|
|
||||||
|
// 创建搜索表单
|
||||||
|
const searchForm = createProSearchForm<OperationLogSearchFormData>({
|
||||||
|
defaultCollapsed: true,
|
||||||
|
initialValues: {
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用useNDataTable
|
||||||
|
const {
|
||||||
|
table: {
|
||||||
|
tableProps,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
proSearchFormProps,
|
||||||
|
searchLoading,
|
||||||
|
},
|
||||||
|
refresh,
|
||||||
|
} = useNDataTable(getList, {
|
||||||
|
form: searchForm,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 批量删除相关
|
||||||
|
const checkedRowKeys = ref<(number)[]>([])
|
||||||
|
|
||||||
|
/** 查询操作日志列表 */
|
||||||
|
interface Result {
|
||||||
|
total: number
|
||||||
|
list: Entity.OperLog[]
|
||||||
|
}
|
||||||
|
async function getList({ current, pageSize }: any, formData: OperationLogSearchFormData): Promise<Result> {
|
||||||
|
try {
|
||||||
|
const params: OperLogQueryParams = {
|
||||||
|
pageNum: current,
|
||||||
|
pageSize,
|
||||||
|
...formData,
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOperLogPage(params).then(res => res.data)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取操作日志列表失败:', error)
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查看详情 */
|
||||||
|
function handleView(row: Entity.OperLog) {
|
||||||
|
detailModalRef.value?.openModal(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除操作日志 */
|
||||||
|
async function handleDelete(operId: number | number[]) {
|
||||||
|
const isBatch = Array.isArray(operId)
|
||||||
|
const ids = isBatch ? operId.join(',') : operId.toString()
|
||||||
|
|
||||||
|
window.$dialog.warning({
|
||||||
|
title: '确认删除',
|
||||||
|
content: isBatch
|
||||||
|
? `是否确认删除选中的 ${operId.length} 条操作日志?`
|
||||||
|
: '是否确认删除该条操作日志?',
|
||||||
|
positiveText: '确定删除',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await deleteOperLog(ids)
|
||||||
|
window.$message.success('删除成功')
|
||||||
|
if (isBatch) {
|
||||||
|
// 清空选中项
|
||||||
|
checkedRowKeys.value = []
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('删除操作日志失败:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空操作日志 */
|
||||||
|
async function handleClean() {
|
||||||
|
window.$dialog.warning({
|
||||||
|
title: '确认清空',
|
||||||
|
content: '是否确认清空所有操作日志数据?此操作不可恢复!',
|
||||||
|
positiveText: '确定清空',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await clearOperLog()
|
||||||
|
window.$message.success('清空成功')
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('清空操作日志失败:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 创建表格列配置
|
||||||
|
const columns = createTableColumns({
|
||||||
|
onView: handleView,
|
||||||
|
onDelete: handleDelete,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-space vertical>
|
||||||
|
<n-card>
|
||||||
|
<pro-search-form
|
||||||
|
v-bind="proSearchFormProps"
|
||||||
|
:form="searchForm"
|
||||||
|
:columns="searchColumns"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<pro-data-table
|
||||||
|
v-bind="tableProps"
|
||||||
|
v-model:checked-row-keys="checkedRowKeys"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="id"
|
||||||
|
:loading="searchLoading"
|
||||||
|
title=" "
|
||||||
|
>
|
||||||
|
<template #toolbar>
|
||||||
|
<n-flex>
|
||||||
|
<n-button
|
||||||
|
type="error"
|
||||||
|
:disabled="checkedRowKeys.length === 0"
|
||||||
|
@click="handleDelete(checkedRowKeys.map(id => Number(id)))"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-delete />
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
<n-button type="warning" @click="handleClean">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-clear />
|
||||||
|
</template>
|
||||||
|
清空
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</pro-data-table>
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<DetailModal ref="detailModalRef" />
|
||||||
|
</n-space>
|
||||||
|
</template>
|
Loading…
x
Reference in New Issue
Block a user