feat: access log mangement

This commit is contained in:
zzc
2025-05-07 20:35:49 +08:00
parent 36ad546e99
commit 0355575fee
17 changed files with 6753 additions and 8745 deletions

View File

@@ -1 +1,2 @@
VUE_APP_API_BASE_URL=https://apis.lihailezzc.com VUE_APP_API_BASE_URL=http://127.0.0.1:3999
# VUE_APP_API_BASE_URL=https://apis.lihailezzc.com

15180
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,19 +23,7 @@
<h1><%= VUE_APP_TITLE %></h1> <h1><%= VUE_APP_TITLE %></h1>
</div> </div>
</div> </div>
<script> <script></script>
;/^http(s*):\/\//.test(location.href) || alert('基于vue-admin-beautiful-pro开源版开发的项目需要部署到服务器下访问') <script></script>
</script>
<script>
if (window.location.hostname !== 'localhost') {
var _hmt = _hmt || []
;(function () {
var hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?7174bade1219f9cc272e7978f9523fc8'
var s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
}
</script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,33 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/management/api/access_log/list',
method: 'get',
params: data,
})
}
export function doAdd(data) {
return request({
url: '/management/access_log/role',
method: 'post',
data,
})
}
export function doEdit(id, data) {
return request({
url: `/management/api/access_log/${id}`,
method: 'put',
data,
})
}
export function doDelete(data) {
return request({
url: '/management/api/access_log/delete',
method: 'put',
data,
})
}

View File

@@ -5,10 +5,10 @@ import router from './router'
import './plugins' import './plugins'
import '@/layouts/export' import '@/layouts/export'
if (process.env.NODE_ENV === 'production') { // if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('@/utils/static') // const { mockXHR } = require('@/utils/static')
mockXHR() // mockXHR()
} // }
Vue.config.productionTip = false Vue.config.productionTip = false

View File

@@ -324,6 +324,12 @@ export const asyncRoutes = [
component: () => import('@/views/personnelManagement/roleManagement/index'), component: () => import('@/views/personnelManagement/roleManagement/index'),
meta: { title: '角色管理' }, meta: { title: '角色管理' },
}, },
{
path: 'accessLogManagement',
name: 'AccessLogManagement',
component: () => import('@/views/personnelManagement/accessLogManagement/index'),
meta: { title: '访问日志' },
},
// { // {
// path: 'menuManagement', // path: 'menuManagement',
// name: 'MenuManagement', // name: 'MenuManagement',

View File

@@ -1,8 +1,3 @@
/**
* @author https://github.com/zxwk1998/vue-admin-better 不想保留author可删除
* @description 登录、获取用户信息、退出登录、清除accessToken逻辑不建议修改
*/
import Vue from 'vue' import Vue from 'vue'
import { getUserInfo, login, logout } from '@/api/user' import { getUserInfo, login, logout } from '@/api/user'
import { getAccessToken, removeAccessToken, setAccessToken } from '@/utils/accessToken' import { getAccessToken, removeAccessToken, setAccessToken } from '@/utils/accessToken'
@@ -13,6 +8,8 @@ const state = () => ({
accessToken: getAccessToken(), accessToken: getAccessToken(),
username: '', username: '',
avatar: '', avatar: '',
appId: '',
userId: '',
permissions: [], permissions: [],
}) })
const getters = { const getters = {
@@ -20,6 +17,8 @@ const getters = {
username: (state) => state.username, username: (state) => state.username,
avatar: (state) => state.avatar, avatar: (state) => state.avatar,
permissions: (state) => state.permissions, permissions: (state) => state.permissions,
appId: (state) => state.appId,
userId: (state) => state.userId,
} }
const mutations = { const mutations = {
setAccessToken(state, accessToken) { setAccessToken(state, accessToken) {
@@ -29,6 +28,12 @@ const mutations = {
setUsername(state, username) { setUsername(state, username) {
state.username = username state.username = username
}, },
setUserId(state, userId) {
state.userId = userId
},
setAppId(state, appId) {
state.appId = appId
},
setAvatar(state, avatar) { setAvatar(state, avatar) {
state.avatar = avatar state.avatar = avatar
}, },
@@ -63,11 +68,13 @@ const actions = {
Vue.prototype.$baseMessage('验证失败,请重新登录...', 'error') Vue.prototype.$baseMessage('验证失败,请重新登录...', 'error')
return false return false
} }
let { permissions, username, avatar, appIcon, appName } = data let { permissions, username, avatar, appIcon, appName, userId, appId } = data
if (permissions && username && Array.isArray(permissions)) { if (permissions && username && Array.isArray(permissions)) {
commit('setPermissions', permissions) commit('setPermissions', permissions)
commit('setUsername', username) commit('setUsername', username)
commit('setAvatar', avatar) commit('setAvatar', avatar)
commit('setAppId', appId)
commit('setUserId', userId)
dispatch('settings/changeLogo', appIcon, { root: true }) dispatch('settings/changeLogo', appIcon, { root: true })
dispatch('settings/changeTitle', appName, { root: true }) dispatch('settings/changeTitle', appName, { root: true })
return permissions return permissions

View File

@@ -18,12 +18,6 @@ import { isArray } from '@/utils/validate'
let loadingInstance let loadingInstance
/**
* @author https://github.com/zxwk1998/vue-admin-better 不想保留author可删除
* @description 处理code异常
* @param {*} code
* @param {*} msg
*/
const handleCode = (code, msg) => { const handleCode = (code, msg) => {
switch (code) { switch (code) {
case invalidCode: case invalidCode:
@@ -55,9 +49,13 @@ instance.interceptors.request.use(
if (store.getters['user/accessToken']) { if (store.getters['user/accessToken']) {
config.headers['authorization'] = store.getters['user/accessToken'] config.headers['authorization'] = store.getters['user/accessToken']
} }
// 处理 GET 请求参数 if (store.getters['user/userId']) {
config.headers['x-user-id'] = store.getters['user/userId']
}
if (store.getters['user/appId']) {
config.headers['x-app-id'] = store.getters['user/appId']
}
if (config.method === 'get' && config.params) { if (config.method === 'get' && config.params) {
// 过滤掉空值参数
config.params = Vue.prototype.$baseLodash.pickBy(config.params, Vue.prototype.$baseLodash.identity) config.params = Vue.prototype.$baseLodash.pickBy(config.params, Vue.prototype.$baseLodash.identity)
} }
//这里会过滤所有为空、0、false的key如果不需要请自行注释 //这里会过滤所有为空、0、false的key如果不需要请自行注释

View File

@@ -1,7 +1,3 @@
/**
* @author https://vuejs-core.cn
* @description 导入所有 controller 模块浏览器环境中自动输出controller文件夹下Mock接口请勿修改。
*/
import Mock from 'mockjs' import Mock from 'mockjs'
import { paramObj } from '@/utils' import { paramObj } from '@/utils'

View File

@@ -4,10 +4,10 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24"> <el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
<div class="pic-error"> <div class="pic-error">
<img alt="401" class="pic-error-parent" src="@/assets/error_images/401.png" /> <img alt="401" class="pic-error-parent" :src="require('@/assets/error_images/401.png')" />
<img alt="401" class="pic-error-child left" src="@/assets/error_images/cloud.png" /> <img alt="401" class="pic-error-child left" :src="require('@/assets/error_images/cloud.png')" />
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" /> <img alt="401" class="pic-error-child" :src="require('@/assets/error_images/cloud.png')" />
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" /> <img alt="401" class="pic-error-child" :src="require('@/assets/error_images/cloud.png')" />
</div> </div>
</el-col> </el-col>

View File

@@ -35,9 +35,9 @@
</span> </span>
</el-form-item> </el-form-item>
<el-button class="login-btn" :loading="loading" type="primary" @click="handleLogin">登录</el-button> <el-button class="login-btn" :loading="loading" type="primary" @click="handleLogin">登录</el-button>
<router-link to="/register"> <!-- <router-link to="/register">
<div style="margin-top: 20px">注册</div> <div style="margin-top: 20px">注册</div>
</router-link> </router-link> -->
</el-form> </el-form>
</el-col> </el-col>
</el-row> </el-row>

View File

@@ -0,0 +1,102 @@
<template>
<div class="accessLogManagement-container">
<vab-query-form>
<vab-query-form-right-panel :span="12">
<el-form :inline="true" :model="queryForm" @submit.native.prevent>
<el-form-item>
<el-input v-model.trim="queryForm.permission" clearable placeholder="请输入查询条件" />
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" type="primary" @click="queryData">查询</el-button>
</el-form-item>
</el-form>
</vab-query-form-right-panel>
</vab-query-form>
<el-table v-loading="listLoading" :data="list" :element-loading-text="elementLoadingText" @selection-change="setSelectRows">
<el-table-column show-overflow-tooltip type="selection" />
<el-table-column align="center" label="IP" prop="ip" show-overflow-tooltip />
<el-table-column align="center" label="地址" prop="address" show-overflow-tooltip />
<el-table-column align="center" label="url" prop="url" show-overflow-tooltip />
<el-table-column align="center" label="应用" show-overflow-tooltip>
<template slot-scope="{ row }">
{{ row.appName || row.addId }}
</template>
</el-table-column>
<el-table-column align="center" label="用户" show-overflow-tooltip>
<template slot-scope="{ row }">
{{ row.userName || row.userId }}
</template>
</el-table-column>
<el-table-column align="center" label="userAgent" prop="userAgent" show-overflow-tooltip />
<el-table-column align="center" label="referrer" prop="referrer" show-overflow-tooltip />
</el-table>
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</template>
<script>
import { getList } from '@/api/accessLogManagement'
import { formatTime } from '@/utils'
export default {
name: 'AccessLogManagement',
data() {
return {
list: null,
listLoading: true,
layout: 'total, sizes, prev, pager, next, jumper',
total: 0,
selectRows: '',
elementLoadingText: '正在加载...',
queryForm: {
pageNo: 1,
pageSize: 10,
permission: '',
},
timeOutID: null,
}
},
created() {
this.fetchData()
},
beforeDestroy() {
clearTimeout(this.timeOutID)
},
methods: {
setSelectRows(val) {
this.selectRows = val
},
handleSizeChange(val) {
this.queryForm.pageSize = val
this.fetchData()
},
handleCurrentChange(val) {
this.queryForm.pageNo = val
this.fetchData()
},
queryData() {
this.queryForm.pageNo = 1
this.fetchData()
},
async fetchData() {
this.listLoading = true
const { data } = await getList(this.queryForm)
this.list = data.list
this.total = data.totalCount
this.timeOutID = setTimeout(() => {
this.listLoading = false
}, 300)
},
},
}
</script>

View File

@@ -1,8 +1,5 @@
<template> <template>
<div class="appManagement-container"> <div class="appManagement-container">
<el-divider content-position="left">
演示环境仅做基础功能展示若想实现不同角色的真实菜单配置需将settings.js路由加载模式改为all模式由后端全面接管路由渲染与权限控制
</el-divider>
<vab-query-form> <vab-query-form>
<vab-query-form-left-panel :span="12"> <vab-query-form-left-panel :span="12">
<el-button icon="el-icon-plus" type="primary" @click="handleEdit">添加</el-button> <el-button icon="el-icon-plus" type="primary" @click="handleEdit">添加</el-button>

View File

@@ -1,8 +1,5 @@
<template> <template>
<div class="menuManagement-container"> <div class="menuManagement-container">
<el-divider content-position="left">
演示环境仅做基础功能展示若想实现不同角色的真实菜单配置需将settings.js路由加载模式改为all模式由后端全面接管路由渲染与权限控制
</el-divider>
<el-row> <el-row>
<el-col :lg="4" :md="8" :sm="24" :xl="4" :xs="24"> <el-col :lg="4" :md="8" :sm="24" :xl="4" :xs="24">
<el-tree :data="data" :default-expanded-keys="['root']" node-key="id" :props="defaultProps" @node-click="handleNodeClick" /> <el-tree :data="data" :default-expanded-keys="['root']" node-key="id" :props="defaultProps" @node-click="handleNodeClick" />

View File

@@ -1,8 +1,5 @@
<template> <template>
<div class="roleManagement-container"> <div class="roleManagement-container">
<el-divider content-position="left">
演示环境仅做基础功能展示若想实现不同角色的真实菜单配置需将settings.js路由加载模式改为all模式由后端全面接管路由渲染与权限控制
</el-divider>
<vab-query-form> <vab-query-form>
<vab-query-form-left-panel :span="12"> <vab-query-form-left-panel :span="12">
<el-button icon="el-icon-plus" type="primary" @click="handleEdit">添加</el-button> <el-button icon="el-icon-plus" type="primary" @click="handleEdit">添加</el-button>

View File

@@ -37,7 +37,7 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button class="register-btn" type="primary" @click.native.prevent="handleReister">注册</el-button> <!-- <el-button class="register-btn" type="primary" @click.native.prevent="handleReister">注册</el-button> -->
<router-link to="/login"> <router-link to="/login">
<div style="margin-top: 20px">登录</div> <div style="margin-top: 20px">登录</div>
</router-link> </router-link>
@@ -111,47 +111,47 @@
} }
}, },
created() { created() {
document.body.style.overflow = 'hidden' // document.body.style.overflow = 'hidden'
}, },
beforeDestroy() { beforeDestroy() {
document.body.style.overflow = 'auto' // document.body.style.overflow = 'auto'
clearInterval(this.getPhoneIntval) // clearInterval(this.getPhoneIntval)
this.getPhoneIntval = null // this.getPhoneIntval = null
}, },
methods: { methods: {
getPhoneCode() { getPhoneCode() {
if (!isPhone(this.form.phone)) { // if (!isPhone(this.form.phone)) {
//this.$baseMessage('请输入手机号', 'error') // //this.$baseMessage('请输入手机号', 'error')
this.$refs['registerForm'].validateField('phone') // this.$refs['registerForm'].validateField('phone')
return // return
} // }
this.isGetphone = true // this.isGetphone = true
let n = 60 // let n = 60
this.getPhoneIntval = setInterval(() => { // this.getPhoneIntval = setInterval(() => {
if (n > 0) { // if (n > 0) {
n-- // n--
this.phoneCode = `重新获取(${n}s)` // this.phoneCode = `重新获取(${n}s)`
} else { // } else {
clearInterval(this.getPhoneIntval) // clearInterval(this.getPhoneIntval)
this.getPhoneIntval = null // this.getPhoneIntval = null
this.phoneCode = '获取验证码' // this.phoneCode = '获取验证码'
this.isGetphone = false // this.isGetphone = false
} // }
}, 1000) // }, 1000)
}, },
handleReister() { handleReister() {
this.$refs['registerForm'].validate(async (valid) => { // this.$refs['registerForm'].validate(async (valid) => {
if (valid) { // if (valid) {
const param = { // const param = {
username: this.form.username, // username: this.form.username,
phone: this.form.phone, // phone: this.form.phone,
password: this.form.password, // password: this.form.password,
phoneCode: this.form.phoneCode, // phoneCode: this.form.phoneCode,
} // }
const { msg } = await register(param) // const { msg } = await register(param)
this.$baseMessage(msg, 'success') // this.$baseMessage(msg, 'success')
} // }
}) // })
}, },
}, },
} }

View File

@@ -26,8 +26,8 @@ const WebpackBar = require('webpackbar')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const date = dayjs().format('YYYY_M_D') const date = dayjs().format('YYYY_M_D')
const time = dayjs().format('YYYY-M-D HH:mm:ss') const time = dayjs().format('YYYY-M-D HH:mm:ss')
process.env.VUE_APP_TITLE = title || 'vue-admin-better' process.env.VUE_APP_TITLE = title || 'admin'
process.env.VUE_APP_AUTHOR = author || 'https://vuejs-core.cn' process.env.VUE_APP_AUTHOR = author || 'zzc'
process.env.VUE_APP_UPDATE_TIME = time process.env.VUE_APP_UPDATE_TIME = time
process.env.VUE_APP_VERSION = version process.env.VUE_APP_VERSION = version
@@ -136,15 +136,15 @@ module.exports = {
.plugin('banner') .plugin('banner')
.use(Webpack.BannerPlugin, [`${webpackBanner}${time}`]) .use(Webpack.BannerPlugin, [`${webpackBanner}${time}`])
.end() .end()
if (imageCompression) // if (imageCompression)
config.module // config.module
.rule('images') // .rule('images')
.use('image-webpack-loader') // // .use('image-webpack-loader')
.loader('image-webpack-loader') // // .loader('image-webpack-loader')
.options({ // .options({
bypassOnDebug: true, // bypassOnDebug: true,
}) // })
.end() // .end()
}) })
}, },
runtimeCompiler: true, runtimeCompiler: true,