64 lines
4.2 KiB
Vue
64 lines
4.2 KiB
Vue
<template>
|
||
<div>
|
||
<div class="n-layout-page-header"><n-card :bordered="false" title="会员登录日志">记录C端会员每次登录成功或失败,敏感信息均脱敏展示。</n-card></div>
|
||
<n-card :bordered="false" class="proCard">
|
||
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" />
|
||
<BasicTable ref="actionRef" :columns="columns" :request="loadDataTable" :row-key="row => row.id" :action-column="actionColumn" :scroll-x="scrollX" :resize-height-offset="-10000">
|
||
<template #tableTitle><n-button v-if="hasPermission(['/appMemberLoginLog/export'])" type="primary" @click="exportRows">导出</n-button></template>
|
||
</BasicTable>
|
||
</n-card>
|
||
<n-modal v-model:show="showDetail" preset="card" title="登录日志详情" class="detail-modal">
|
||
<n-spin :show="detailLoading">
|
||
<n-descriptions v-if="detail.id" bordered :column="2" label-placement="left">
|
||
<n-descriptions-item label="日志ID">{{ detail.id }}</n-descriptions-item>
|
||
<n-descriptions-item label="请求ID">{{ detail.reqId }}</n-descriptions-item>
|
||
<n-descriptions-item label="会员编号">{{ detail.memberNo || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="用户名">{{ detail.username || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="手机号">{{ detail.mobileMask || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录方式">{{ dict.getLabel('appMemberLoginTypeOptions', detail.loginType) }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录状态">{{ dict.getLabel('sys_login_status', detail.status) }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录时间">{{ detail.loginAt }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录IP">{{ detail.loginIpMask || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录地区">{{ detail.loginRegion || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="登录域名">{{ detail.loginDomain || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="操作系统">{{ detail.os }}</n-descriptions-item>
|
||
<n-descriptions-item label="浏览器">{{ detail.browser }}</n-descriptions-item>
|
||
<n-descriptions-item label="失败原因">{{ detail.errMsg || '-' }}</n-descriptions-item>
|
||
<n-descriptions-item label="User-Agent" :span="2">{{ detail.userAgent || '-' }}</n-descriptions-item>
|
||
</n-descriptions>
|
||
</n-spin>
|
||
</n-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||
import { BasicTable, TableAction } from '@/components/Table';
|
||
import { BasicForm, useForm } from '@/components/Form';
|
||
import { usePermission } from '@/hooks/web/usePermission';
|
||
import { useDictStore } from '@/store/modules/dict';
|
||
import { adaTableScrollX } from '@/utils/hotgo';
|
||
import { List, View, Export } from '@/api/appMemberLoginLog';
|
||
import { columns, schemas, loadOptions } from './model';
|
||
|
||
const dict = useDictStore();
|
||
const { hasPermission } = usePermission();
|
||
const actionRef = ref();
|
||
const searchFormRef = ref<any>({});
|
||
const showDetail = ref(false);
|
||
const detailLoading = ref(false);
|
||
const detail = reactive<any>({});
|
||
const [register] = useForm({ gridProps: { cols: '1 s:1 m:2 l:3' }, labelWidth: 80, schemas });
|
||
const loadDataTable = (res) => List({ ...searchFormRef.value?.formModel, ...res });
|
||
const reloadTable = () => actionRef.value?.reload();
|
||
const actionColumn = reactive({ width: 100, title: '操作', key: 'action', fixed: 'right', render: (row) => h(TableAction as any, { style: 'button', actions: [{ label: '详情', onClick: () => openDetail(row), auth: ['/appMemberLoginLog/view'] }] }) });
|
||
const scrollX = computed(() => adaTableScrollX(columns, actionColumn.width));
|
||
async function openDetail(row) { showDetail.value = true; detailLoading.value = true; try { Object.assign(detail, await View({ id: row.id })); } finally { detailLoading.value = false; } }
|
||
function exportRows() { Export({ ...searchFormRef.value?.formModel }); }
|
||
onMounted(loadOptions);
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
:deep(.detail-modal) { width: min(900px, 94vw); }
|
||
</style>
|