395 lines
9.1 KiB
Vue
395 lines
9.1 KiB
Vue
<template>
|
|
<div class="operation-log">
|
|
<!-- 搜索和操作栏 -->
|
|
<el-card class="filter-container" shadow="never">
|
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
|
<el-form-item label="操作人">
|
|
<el-input v-model="queryParams.operator" placeholder="请输入操作人" clearable />
|
|
</el-form-item>
|
|
<el-form-item label="操作类型">
|
|
<el-select v-model="queryParams.type" placeholder="请选择操作类型" clearable>
|
|
<el-option label="登录" value="login" />
|
|
<el-option label="新增" value="create" />
|
|
<el-option label="修改" value="update" />
|
|
<el-option label="删除" value="delete" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="操作时间">
|
|
<el-date-picker
|
|
v-model="queryParams.dateRange"
|
|
type="daterange"
|
|
range-separator="至"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
value-format="YYYY-MM-DD"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
|
<el-button @click="resetQuery">重置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div class="action-bar">
|
|
<el-button type="success">
|
|
<el-icon><upload /></el-icon>导入
|
|
</el-button>
|
|
<el-button type="primary" @click="handleExport">
|
|
<el-icon><download /></el-icon>导出
|
|
</el-button>
|
|
</div>
|
|
</el-card>
|
|
|
|
<!-- 日志列表 -->
|
|
<el-card class="table-container" shadow="never">
|
|
<el-table
|
|
v-loading="loading"
|
|
:data="logList"
|
|
style="width: 100%"
|
|
>
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
<el-table-column label="操作人" min-width="150">
|
|
<template #default="{ row }">
|
|
<div class="operator-info">
|
|
<el-avatar :size="32" :src="row.avatar"></el-avatar>
|
|
<div class="operator-detail">
|
|
<div class="username">{{ row.operator }}</div>
|
|
<div class="ip">{{ row.ip }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="type" label="操作类型" width="120">
|
|
<template #default="{ row }">
|
|
<el-tag :type="getTypeTag(row.type)">{{ getTypeText(row.type) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="description" label="操作描述" min-width="200" />
|
|
<el-table-column prop="createTime" label="操作时间" width="180" />
|
|
<el-table-column label="操作" width="120" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button type="primary" link @click="handleDetail(row)">详情</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<el-pagination
|
|
v-model:current-page="queryParams.pageNum"
|
|
v-model:page-size="queryParams.pageSize"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
:total="total"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
background
|
|
class="pagination"
|
|
/>
|
|
</el-card>
|
|
|
|
<!-- 详情对话框 -->
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
title="操作日志详情"
|
|
width="580px"
|
|
append-to-body
|
|
>
|
|
<el-descriptions :column="1" border>
|
|
<el-descriptions-item label="操作人">{{ detailData.operator }}</el-descriptions-item>
|
|
<el-descriptions-item label="操作类型">
|
|
<el-tag :type="getTypeTag(detailData.type)">{{ getTypeText(detailData.type) }}</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="操作描述">{{ detailData.description }}</el-descriptions-item>
|
|
<el-descriptions-item label="IP地址">{{ detailData.ip }}</el-descriptions-item>
|
|
<el-descriptions-item label="操作时间">{{ detailData.createTime }}</el-descriptions-item>
|
|
<el-descriptions-item label="请求参数" v-if="detailData.params">
|
|
<pre class="params">{{ JSON.stringify(detailData.params, null, 2) }}</pre>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
<template #footer>
|
|
<div class="dialog-footer">
|
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
|
</div>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Upload, Download } from '@element-plus/icons-vue'
|
|
|
|
// 查询参数
|
|
const queryParams = reactive({
|
|
operator: '',
|
|
type: '',
|
|
dateRange: [],
|
|
pageNum: 1,
|
|
pageSize: 10
|
|
})
|
|
|
|
// 日志列表数据
|
|
const logList = ref([
|
|
{
|
|
id: 1,
|
|
operator: 'admin',
|
|
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
|
|
type: 'login',
|
|
description: '用户登录系统',
|
|
ip: '127.0.0.1',
|
|
createTime: '2024-03-20 10:00:00',
|
|
params: {
|
|
username: 'admin',
|
|
loginTime: '2024-03-20 10:00:00'
|
|
}
|
|
},
|
|
{
|
|
id: 2,
|
|
operator: 'admin',
|
|
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
|
|
type: 'create',
|
|
description: '创建新用户',
|
|
ip: '127.0.0.1',
|
|
createTime: '2024-03-20 11:00:00',
|
|
params: {
|
|
username: 'test',
|
|
role: '测试人员'
|
|
}
|
|
}
|
|
])
|
|
|
|
// 分页相关
|
|
const loading = ref(false)
|
|
const total = ref(100)
|
|
const dialogVisible = ref(false)
|
|
const detailData = ref({})
|
|
|
|
// 获取操作类型标签样式
|
|
const getTypeTag = (type) => {
|
|
const typeMap = {
|
|
login: 'success',
|
|
create: 'primary',
|
|
update: 'warning',
|
|
delete: 'danger'
|
|
}
|
|
return typeMap[type] || 'info'
|
|
}
|
|
|
|
// 获取操作类型文本
|
|
const getTypeText = (type) => {
|
|
const typeMap = {
|
|
login: '登录',
|
|
create: '新增',
|
|
update: '修改',
|
|
delete: '删除'
|
|
}
|
|
return typeMap[type] || type
|
|
}
|
|
|
|
// 查询日志列表
|
|
const handleQuery = () => {
|
|
queryParams.pageNum = 1
|
|
getLogList()
|
|
}
|
|
|
|
// 重置查询
|
|
const resetQuery = () => {
|
|
Object.assign(queryParams, {
|
|
operator: '',
|
|
type: '',
|
|
dateRange: [],
|
|
pageNum: 1,
|
|
pageSize: 10
|
|
})
|
|
getLogList()
|
|
}
|
|
|
|
// 获取日志列表
|
|
const getLogList = () => {
|
|
loading.value = true
|
|
// TODO: 调用API获取数据
|
|
setTimeout(() => {
|
|
loading.value = false
|
|
}, 500)
|
|
}
|
|
|
|
// 导出
|
|
const handleExport = () => {
|
|
ElMessage.success('导出成功')
|
|
}
|
|
|
|
// 查看详情
|
|
const handleDetail = (row) => {
|
|
detailData.value = row
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
// 分页大小改变
|
|
const handleSizeChange = (size) => {
|
|
queryParams.pageSize = size
|
|
getLogList()
|
|
}
|
|
|
|
// 页码改变
|
|
const handleCurrentChange = (page) => {
|
|
queryParams.pageNum = page
|
|
getLogList()
|
|
}
|
|
|
|
// 初始化
|
|
onMounted(() => {
|
|
getLogList()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.operation-log {
|
|
padding: 0;
|
|
}
|
|
|
|
.filter-container {
|
|
margin-bottom: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.search-form {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.action-bar {
|
|
margin-top: 10px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.table-container {
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* 表格样式优化 */
|
|
:deep(.el-table) {
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
background: transparent;
|
|
}
|
|
|
|
:deep(.el-table th) {
|
|
background-color: #f8f9fb !important;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
height: 50px;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
:deep(.el-table td) {
|
|
padding: 12px 0;
|
|
}
|
|
|
|
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
|
background: #f8fafc;
|
|
}
|
|
|
|
:deep(.el-table__body tr:hover > td) {
|
|
background-color: #f1f5f9 !important;
|
|
}
|
|
|
|
:deep(.el-table__body tr) {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.operator-info {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 4px 0;
|
|
}
|
|
|
|
.operator-detail {
|
|
margin-left: 12px;
|
|
}
|
|
|
|
.username {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
margin-bottom: 4px;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.ip {
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
|
|
/* 分页样式优化 */
|
|
.pagination {
|
|
margin-top: 24px;
|
|
justify-content: flex-end;
|
|
padding: 0 16px;
|
|
}
|
|
|
|
:deep(.el-pagination) {
|
|
--el-pagination-hover-color: #1f2937;
|
|
}
|
|
|
|
:deep(.el-pagination button:disabled) {
|
|
background-color: #f1f5f9;
|
|
}
|
|
|
|
:deep(.el-pagination .el-pager li) {
|
|
border-radius: 4px;
|
|
margin: 0 2px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
:deep(.el-pagination .el-pager li.active) {
|
|
background-color: #1f2937;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
:deep(.el-pagination .el-pager li:hover:not(.active)) {
|
|
background-color: #f1f5f9;
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
}
|
|
|
|
.params {
|
|
background-color: #f8f9fb;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
|
|
/* 响应式优化 */
|
|
@media (max-width: 768px) {
|
|
.el-form-item {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.action-bar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.action-bar .el-button {
|
|
width: 100%;
|
|
margin-left: 0 !important;
|
|
}
|
|
|
|
:deep(.el-table th) {
|
|
padding: 6px 0;
|
|
}
|
|
|
|
:deep(.el-table td) {
|
|
padding: 8px 0;
|
|
}
|
|
}
|
|
</style> |