feat: 新增在线用户页面

This commit is contained in:
valarchie 2023-07-19 10:40:31 +08:00
parent 6eaaba9e92
commit fc68f70da8
4 changed files with 321 additions and 0 deletions

35
src/api/system/monitor.ts Normal file
View File

@ -0,0 +1,35 @@
import { http } from "@/utils/http";
export interface OnlineUserQuery {
ipAddress: string;
username: string;
}
export interface OnlineUserInfo {
browser?: string;
deptName?: string;
ipAddress?: string;
loginLocation?: string;
loginTime?: number;
operationSystem?: string;
tokenId?: string;
username?: string;
}
/** 获取操作日志列表 */
export const getOnlineUserListApi = (params?: OnlineUserQuery) => {
return http.request<ResponseData<PageDTO<OnlineUserInfo>>>(
"get",
"/monitor/onlineUsers",
{
params
}
);
};
export const logoutOnlineUserApi = (tokenId: string) => {
return http.request<ResponseData<void>>(
"delete",
`/monitor/onlineUser/${tokenId}`
);
};

View File

@ -114,6 +114,18 @@ export class CommonUtils {
writeFile(workBook, `${excelName}.xlsx`);
}
static paginateList(dataList: any[], pagination: PaginationProps): any[] {
// 计算起始索引
const startIndex = (pagination.currentPage - 1) * pagination.pageSize;
// 截取数组
const endIndex = startIndex + pagination.pageSize;
const paginatedList = dataList.slice(startIndex, endIndex);
// 返回截取后的数组
return paginatedList;
}
// 私有构造函数,防止类被实例化
private constructor() {}
}

View File

@ -0,0 +1,133 @@
<script setup lang="ts">
import { ref } from "vue";
import { useHook } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
/** 组件name最好和菜单表中的router_name一致 */
defineOptions({
name: "SystemOperationLog"
});
const tableRef = ref();
const searchFormRef = ref();
const {
searchFormParams,
pageLoading,
columns,
dataList,
pagination,
onSearch,
resetForm,
getList,
handleLogout
} = useHook();
</script>
<template>
<div class="main">
<!-- 搜索栏 -->
<el-form
ref="searchFormRef"
:inline="true"
:model="searchFormParams"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
<el-form-item label="登录IP" prop="ipAddress">
<el-input
v-model="searchFormParams.ipAddress"
placeholder="请输入IP地址"
clearable
class="!w-[200px]"
/>
</el-form-item>
<el-form-item label="用户名:" prop="username">
<el-input
v-model="searchFormParams.username"
placeholder="请选择用户名称"
clearable
class="!w-[200px]"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon(Search)"
:loading="pageLoading"
@click="onSearch"
>
搜索
</el-button>
<el-button
:icon="useRenderIcon(Refresh)"
@click="resetForm(searchFormRef, tableRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<!-- table bar 包裹 table -->
<PureTableBar title="登录日志列表" :columns="columns" @refresh="onSearch">
<template v-slot="{ size, dynamicColumns }">
<pure-table
border
ref="tableRef"
align-whole="center"
showOverflowTooltip
table-layout="auto"
:loading="pageLoading"
:size="size"
adaptive
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}"
@page-size-change="getList"
@page-current-change="getList"
>
<template #operation="{ row }">
<el-popconfirm
:title="`是否确认强制退出用户${row.username}`"
@confirm="handleLogout(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
>
强制退出
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,141 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import {
OnlineUserQuery,
getOnlineUserListApi,
logoutOnlineUserApi
} from "@/api/system/monitor";
import { reactive, ref, onMounted, toRaw } from "vue";
import { PaginationProps } from "@pureadmin/table";
import { CommonUtils } from "@/utils/common";
export function useHook() {
const pagination: PaginationProps = {
total: 0,
pageSize: 10,
currentPage: 1,
background: true
};
const timeRange = ref([]);
const searchFormParams = reactive<OnlineUserQuery>({
ipAddress: undefined,
username: undefined
});
// 该分页使用前端分页 所以需要一个原始数组来保存原有数据
let originalDataList = [];
const dataList = ref([]);
const pageLoading = ref(true);
const columns: TableColumnList = [
{
label: "会话编号",
prop: "tokenId",
minWidth: 100
},
{
label: "用户名",
prop: "username",
minWidth: 120
},
{
label: "所属部门",
prop: "deptName",
minWidth: 120
},
{
label: "IP地址",
prop: "ipAddress",
minWidth: 120
},
{
label: "登录地点",
prop: "loginLocation",
minWidth: 120
},
{
label: "操作系统",
prop: "operationSystem",
minWidth: 120
},
{
label: "浏览器",
prop: "browser",
minWidth: 120
},
{
label: "登录时间",
minWidth: 160,
prop: "loginTime",
formatter: ({ loginTime }) =>
dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 140,
slot: "operation"
}
];
async function onSearch() {
// 点击搜索的时候 需要重置分页
pagination.currentPage = 1;
pageLoading.value = true;
const { data } = await getOnlineUserListApi(
toRaw(searchFormParams)
).finally(() => {
pageLoading.value = false;
});
originalDataList = data.rows;
pagination.total = data.rows.length;
getList();
}
function resetForm(formEl, tableRef) {
if (!formEl) return;
// 清空查询参数
formEl.resetFields();
tableRef.getTableRef().clearSort();
pagination.currentPage = 1;
// 重置分页并查询
onSearch();
}
async function getList() {
dataList.value = CommonUtils.paginateList(originalDataList, pagination);
}
async function handleLogout(row) {
await logoutOnlineUserApi(row.tokenId).then(() => {
message(`您强制登出了用户:${row.username}`, {
type: "success"
});
// 刷新列表
onSearch();
});
}
onMounted(() => {
onSearch();
});
return {
searchFormParams,
pageLoading,
columns,
dataList,
pagination,
timeRange,
onSearch,
getList,
resetForm,
handleLogout
};
}