mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	新增:新增系统监控/登录日志功能,优化日志表结构,并新增记录错误信息(非未知异常不记录异常详情,只记录错误信息)
This commit is contained in:
		
							
								
								
									
										33
									
								
								continew-admin-ui/src/api/monitor/login-log.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								continew-admin-ui/src/api/monitor/login-log.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import axios from 'axios'; | ||||
| import qs from 'query-string'; | ||||
|  | ||||
| export interface LoginLogRecord { | ||||
|   logId: string; | ||||
|   status: number; | ||||
|   clientIp: string; | ||||
|   location: string; | ||||
|   browser: string; | ||||
|   errorMsg: string; | ||||
|   createUserString: string; | ||||
|   createTime: string; | ||||
| } | ||||
|  | ||||
| export interface LoginLogParams extends Partial<LoginLogRecord> { | ||||
|   page: number; | ||||
|   size: number; | ||||
|   sort: Array<string>; | ||||
| } | ||||
|  | ||||
| export interface LoginLogListRes { | ||||
|   list: LoginLogRecord[]; | ||||
|   total: number; | ||||
| } | ||||
|  | ||||
| export function queryLoginLogList(params: LoginLogParams) { | ||||
|   return axios.get<LoginLogListRes>('/monitor/log/login', { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| @@ -4,10 +4,11 @@ import qs from 'query-string'; | ||||
| export interface OperationLogRecord { | ||||
|   logId: string; | ||||
|   description: string; | ||||
|   status: number, | ||||
|   clientIp: string, | ||||
|   location: string, | ||||
|   browser: string, | ||||
|   status: number; | ||||
|   clientIp: string; | ||||
|   location: string; | ||||
|   browser: string; | ||||
|   errorMsg: string; | ||||
|   createUserString: string; | ||||
|   createTime: string; | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/en-US'; | ||||
| import localeDataAnalysis from '@/views/visualization/data-analysis/locale/en-US'; | ||||
| import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/en-US'; | ||||
|  | ||||
| import localeLog from '@/views/monitor/log/operation/locale/en-US'; | ||||
| import localeOperationLog from '@/views/monitor/log/operation/locale/en-US'; | ||||
| import localeLoginLog from '@/views/monitor/log/login/locale/en-US'; | ||||
|  | ||||
| import localeSearchTable from '@/views/list/search-table/locale/en-US'; | ||||
| import localeCardList from '@/views/list/card/locale/en-US'; | ||||
| @@ -55,7 +56,8 @@ export default { | ||||
|   ...localeDataAnalysis, | ||||
|   ...localeMultiDAnalysis, | ||||
|  | ||||
|   ...localeLog, | ||||
|   ...localeOperationLog, | ||||
|   ...localeLoginLog, | ||||
|  | ||||
|   ...localeSearchTable, | ||||
|   ...localeCardList, | ||||
|   | ||||
| @@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN'; | ||||
| import localeDataAnalysis from '@/views/visualization/data-analysis/locale/zh-CN'; | ||||
| import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/zh-CN'; | ||||
|  | ||||
| import localeLog from '@/views/monitor/log/operation/locale/zh-CN'; | ||||
| import localeOperationLog from '@/views/monitor/log/operation/locale/zh-CN'; | ||||
| import localeLoginLog from '@/views/monitor/log/login/locale/zh-CN'; | ||||
|  | ||||
| import localeSearchTable from '@/views/list/search-table/locale/zh-CN'; | ||||
| import localeCardList from '@/views/list/card/locale/zh-CN'; | ||||
| @@ -55,7 +56,8 @@ export default { | ||||
|   ...localeDataAnalysis, | ||||
|   ...localeMultiDAnalysis, | ||||
|  | ||||
|   ...localeLog, | ||||
|   ...localeOperationLog, | ||||
|   ...localeLoginLog, | ||||
|  | ||||
|   ...localeSearchTable, | ||||
|   ...localeCardList, | ||||
|   | ||||
| @@ -22,6 +22,16 @@ const Monitor: AppRouteRecordRaw = { | ||||
|         roles: ['*'], | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       path: 'log/login', | ||||
|       name: 'LoginLog', | ||||
|       component: () => import('@/views/monitor/log/login/index.vue'), | ||||
|       meta: { | ||||
|         locale: 'menu.log.login.list', | ||||
|         requiresAuth: true, | ||||
|         roles: ['*'], | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { DEFAULT_LAYOUT } from '../base'; | ||||
| import { AppRouteRecordRaw } from '../types'; | ||||
|  | ||||
| const USER: AppRouteRecordRaw = { | ||||
|   path: '/system/user', | ||||
|   path: '/login/user', | ||||
|   name: 'user', | ||||
|   component: DEFAULT_LAYOUT, | ||||
|   meta: { | ||||
|   | ||||
							
								
								
									
										214
									
								
								continew-admin-ui/src/views/monitor/log/login/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								continew-admin-ui/src/views/monitor/log/login/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | ||||
| <template> | ||||
|   <div class="container"> | ||||
|     <Breadcrumb :items="['menu.monitor', 'menu.log.login.list']" /> | ||||
|     <a-card class="general-card" :title="$t('menu.log.login.list')"> | ||||
|       <a-row style="margin-bottom: 15px"> | ||||
|         <a-col :span="24"> | ||||
|           <a-form | ||||
|             ref="queryFormRef" | ||||
|             :model="queryFormData" | ||||
|             layout="inline" | ||||
|           > | ||||
|             <a-form-item | ||||
|               field="status" | ||||
|               hide-label | ||||
|             > | ||||
|               <a-select | ||||
|                 v-model="queryFormData.status" | ||||
|                 :options="statusOptions" | ||||
|                 placeholder="登录状态搜索" | ||||
|                 allow-clear | ||||
|                 style="width: 150px;" | ||||
|               /> | ||||
|             </a-form-item> | ||||
|             <a-form-item | ||||
|               field="createTime" | ||||
|               hide-label | ||||
|             > | ||||
|               <a-range-picker | ||||
|                 v-model="queryFormData.createTime" | ||||
|                 format="YYYY-MM-DD HH:mm:ss" | ||||
|                 show-time | ||||
|                 style="width: 100%" | ||||
|               /> | ||||
|             </a-form-item> | ||||
|             <a-button type="primary" @click="toQuery"> | ||||
|               <template #icon> | ||||
|                 <icon-search /> | ||||
|               </template> | ||||
|               查询 | ||||
|             </a-button> | ||||
|             <a-button @click="resetQuery"> | ||||
|               <template #icon> | ||||
|                 <icon-refresh /> | ||||
|               </template> | ||||
|               重置 | ||||
|             </a-button> | ||||
|           </a-form> | ||||
|         </a-col> | ||||
|       </a-row> | ||||
|       <a-table | ||||
|         row-key="logId" | ||||
|         :loading="loading" | ||||
|         :pagination="pagination" | ||||
|         :columns="columns" | ||||
|         :data="renderData" | ||||
|         :bordered="false" | ||||
|         :stripe="true" | ||||
|         size="large" | ||||
|         @page-change="onPageChange" | ||||
|       > | ||||
|         <template #index="{ rowIndex }"> | ||||
|           {{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }} | ||||
|         </template> | ||||
|         <template #status="{ record }"> | ||||
|           <a-space v-if="record.status === 1"> | ||||
|             <a-tag color="green"> | ||||
|               <span class="circle pass"></span> | ||||
|               成功 | ||||
|             </a-tag> | ||||
|           </a-space> | ||||
|           <a-space v-else> | ||||
|             <a-tooltip :content="record.errorMsg"> | ||||
|               <a-tag color="red" style="cursor: pointer"> | ||||
|                 <span class="circle fail"></span> | ||||
|                 失败 | ||||
|               </a-tag> | ||||
|             </a-tooltip> | ||||
|           </a-space> | ||||
|         </template> | ||||
|         <template #operations> | ||||
|           <a-button v-permission="['admin']" type="text" size="small">详情</a-button> | ||||
|         </template> | ||||
|       </a-table> | ||||
|     </a-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { computed, ref, reactive } from 'vue'; | ||||
|   import useLoading from '@/hooks/loading'; | ||||
|   import { queryLoginLogList, LoginLogRecord, LoginLogParams } from '@/api/monitor/login-log'; | ||||
|   import { Pagination } from '@/types/global'; | ||||
|   import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface'; | ||||
|   import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'; | ||||
|   import { FormInstance } from '@arco-design/web-vue/es/form'; | ||||
|  | ||||
|   const { loading, setLoading } = useLoading(true); | ||||
|   const queryFormRef = ref<FormInstance>(); | ||||
|   const renderData = ref<LoginLogRecord[]>([]); | ||||
|  | ||||
|   const queryFormData = ref({ | ||||
|     status: undefined, | ||||
|     createTime: [], | ||||
|   }); | ||||
|   const statusOptions = computed<SelectOptionData[]>(() => [ | ||||
|     { | ||||
|       label: '成功', | ||||
|       value: 1, | ||||
|     }, | ||||
|     { | ||||
|       label: '失败', | ||||
|       value: 2, | ||||
|     }, | ||||
|   ]); | ||||
|  | ||||
|   const basePagination: Pagination = { | ||||
|     current: 1, | ||||
|     pageSize: 10, | ||||
|   }; | ||||
|   const pagination = reactive({ | ||||
|     ...basePagination, | ||||
|   }); | ||||
|   const columns = computed<TableColumnData[]>(() => [ | ||||
|     { | ||||
|       title: '序号', | ||||
|       dataIndex: 'index', | ||||
|       slotName: 'index', | ||||
|     }, | ||||
|     { | ||||
|       title: '用户昵称', | ||||
|       dataIndex: 'createUserString', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录行为', | ||||
|       dataIndex: 'description', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录状态', | ||||
|       dataIndex: 'status', | ||||
|       slotName: 'status', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录IP', | ||||
|       dataIndex: 'clientIp', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录地点', | ||||
|       dataIndex: 'location', | ||||
|     }, | ||||
|     { | ||||
|       title: '浏览器', | ||||
|       dataIndex: 'browser', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录时间', | ||||
|       dataIndex: 'createTime', | ||||
|     }, | ||||
|   ]); | ||||
|   const fetchData = async ( | ||||
|     params: LoginLogParams = { page: 1, size: 10, sort: ['createTime,desc'] } | ||||
|   ) => { | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       const { data } = await queryLoginLogList(params); | ||||
|       renderData.value = data.list; | ||||
|       pagination.current = params.page; | ||||
|       pagination.total = data.total; | ||||
|     } catch (err) { | ||||
|       // you can report use errorHandler or other | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const onPageChange = (current: number) => { | ||||
|     fetchData({ page: current, size: pagination.pageSize, sort: ['createTime,desc'] }); | ||||
|   }; | ||||
|  | ||||
|   // 查询 | ||||
|   const toQuery = () => { | ||||
|     fetchData({ | ||||
|       page: pagination.current, | ||||
|       size: pagination.pageSize, | ||||
|       sort: ['createTime,desc'], | ||||
|       ...queryFormData.value, | ||||
|     } as unknown as LoginLogParams); | ||||
|   }; | ||||
|  | ||||
|   // 重置 | ||||
|   const resetQuery = async () => { | ||||
|     await queryFormRef.value?.resetFields(); | ||||
|     await fetchData(); | ||||
|   }; | ||||
|   fetchData(); | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
|   export default { | ||||
|     name: 'LoginLog', | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
|   .container { | ||||
|     padding: 0 20px 20px 20px; | ||||
|   } | ||||
|   :deep(.arco-table-th) { | ||||
|     &:last-child { | ||||
|       .arco-table-th-item-title { | ||||
|         margin-left: 16px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.log.login.list': 'Login log', | ||||
| }; | ||||
| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.log.login.list': '登录日志', | ||||
| }; | ||||
| @@ -81,10 +81,12 @@ | ||||
|             </a-tag> | ||||
|           </a-space> | ||||
|           <a-space v-else> | ||||
|             <a-tag color="red"> | ||||
|               <span class="circle fail"></span> | ||||
|               失败 | ||||
|             </a-tag> | ||||
|             <a-tooltip :content="record.errorMsg"> | ||||
|               <a-tag color="red" style="cursor: pointer"> | ||||
|                 <span class="circle fail"></span> | ||||
|                 失败 | ||||
|               </a-tag> | ||||
|             </a-tooltip> | ||||
|           </a-space> | ||||
|         </template> | ||||
|       </a-table> | ||||
|   | ||||
| @@ -22,10 +22,12 @@ | ||||
|           </a-tag> | ||||
|         </a-space> | ||||
|         <a-space v-else> | ||||
|           <a-tag color="red"> | ||||
|             <span class="circle fail"></span> | ||||
|             失败 | ||||
|           </a-tag> | ||||
|           <a-tooltip :content="record.errorMsg"> | ||||
|             <a-tag color="red" style="cursor: pointer"> | ||||
|               <span class="circle fail"></span> | ||||
|               失败 | ||||
|             </a-tag> | ||||
|           </a-tooltip> | ||||
|         </a-space> | ||||
|       </template> | ||||
|       <template #pagination-left> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user