feat: 登录日志(demo)
This commit is contained in:
147
apps/web-antd/src/views/monitor/logininfor/data.tsx
Normal file
147
apps/web-antd/src/views/monitor/logininfor/data.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table';
|
||||
|
||||
import type { DescItem } from '#/components/description';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { renderBrowserIcon, renderDict, renderOsIcon } from '#/utils/render';
|
||||
|
||||
export const columns: ColumnsType = [
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'userName',
|
||||
title: '用户账号',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'clientKey',
|
||||
title: '登录平台',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'ipaddr',
|
||||
title: 'IP地址',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'loginLocation',
|
||||
title: 'IP地点',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
customRender({ value }) {
|
||||
return renderBrowserIcon(value, true);
|
||||
},
|
||||
dataIndex: 'browser',
|
||||
title: '浏览器',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
customRender({ value }) {
|
||||
// Windows 10 or Windows Server 2016 太长了 分割一下 详情依旧能看到详细的
|
||||
// 为什么不直接保存userAgent让前端解析 很奇怪
|
||||
if (value) {
|
||||
const split = value.split(' or ');
|
||||
if (split.length === 2) {
|
||||
value = split[0];
|
||||
}
|
||||
}
|
||||
return renderOsIcon(value, true);
|
||||
},
|
||||
dataIndex: 'os',
|
||||
title: '系统',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
customRender({ value }) {
|
||||
return renderDict(value, DictEnum.SYS_COMMON_STATUS);
|
||||
},
|
||||
dataIndex: 'status',
|
||||
title: '登录结果',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
customRender({ value }) {
|
||||
return (
|
||||
<Tooltip placement={'top'} title={value}>
|
||||
{value}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
dataIndex: 'msg',
|
||||
ellipsis: true,
|
||||
title: '信息',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'loginTime',
|
||||
title: '日期',
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'action',
|
||||
title: '操作',
|
||||
},
|
||||
];
|
||||
|
||||
export const modalSchema: () => DescItem[] = () => [
|
||||
{
|
||||
field: 'status',
|
||||
label: '登录状态',
|
||||
labelMinWidth: 80,
|
||||
render(value) {
|
||||
return renderDict(value, DictEnum.SYS_COMMON_STATUS);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'clientKey',
|
||||
label: '登录平台',
|
||||
render(value) {
|
||||
if (value) {
|
||||
return value.toUpperCase();
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ipaddr',
|
||||
label: '账号信息',
|
||||
render(_, data) {
|
||||
const { ipaddr, loginLocation, userName } = data;
|
||||
return `账号: ${userName} / ${ipaddr} / ${loginLocation}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'loginTime',
|
||||
label: '登录时间',
|
||||
},
|
||||
{
|
||||
field: 'msg',
|
||||
label: '登录信息',
|
||||
render(_, data: any) {
|
||||
const { msg, status } = data;
|
||||
return (
|
||||
<span class={['font-bold', status === '0' ? '' : 'text-red-500']}>
|
||||
{msg}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'os',
|
||||
label: '登录设备',
|
||||
render(value) {
|
||||
return renderOsIcon(value);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'browser',
|
||||
label: '浏览器',
|
||||
render(value) {
|
||||
return renderBrowserIcon(value);
|
||||
},
|
||||
},
|
||||
];
|
@@ -1,9 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import CommonSkeleton from '#/views/common';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { LoginLog } from '#/api/monitor/logininfo/model';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Space, Table } from 'ant-design-vue';
|
||||
|
||||
import { loginInfoList } from '#/api/monitor/logininfo';
|
||||
|
||||
import { columns } from './data';
|
||||
import loginInfoModal from './login-info-modal.vue';
|
||||
|
||||
const [LoginInfoModal, modalApi] = useVbenModal({
|
||||
connectedComponent: loginInfoModal,
|
||||
});
|
||||
|
||||
const loginDataList = ref<LoginLog[]>([]);
|
||||
async function reload() {
|
||||
const resp = await loginInfoList({ pageNum: 1, pageSize: 20 });
|
||||
loginDataList.value = resp.rows;
|
||||
}
|
||||
|
||||
onMounted(reload);
|
||||
|
||||
function handlePreview(record: Recordable<any>) {
|
||||
modalApi.setData(record);
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CommonSkeleton />
|
||||
</div>
|
||||
<Page>
|
||||
<Table :columns="columns" :data-source="loginDataList" size="middle">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<Space>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handlePreview(record)"
|
||||
>
|
||||
{{ $t('pages.common.info') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<LoginInfoModal />
|
||||
</Page>
|
||||
</template>
|
||||
|
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Description, useDescription } from '#/components/description';
|
||||
|
||||
import { modalSchema } from './data';
|
||||
|
||||
const [registerDescription, { setDescProps }] = useDescription({
|
||||
column: 1,
|
||||
schema: modalSchema(),
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
onOpenChange: (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
const data = modalApi.getData();
|
||||
setDescProps({ data }, true);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicModal
|
||||
:footer="false"
|
||||
:fullscreen-button="false"
|
||||
class="w-[550px]"
|
||||
title="登录日志"
|
||||
>
|
||||
<Description @register="registerDescription" />
|
||||
</BasicModal>
|
||||
</template>
|
Reference in New Issue
Block a user