Remove VXE from web ele runtime

This commit is contained in:
2026-06-09 10:02:10 +08:00
parent fc39464baa
commit b0057ed5a8
8 changed files with 226 additions and 509 deletions
@@ -1,11 +1,14 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { DeptUser } from '#/api/core/dept';
import { $t } from '@vben/locales';
type LegacyTableColumn = Record<string, any>;
type OnActionClickFn<T = Record<string, any>> = (params: {
code: string;
row: T;
}) => void;
/**
* 获取搜索表单的字段配置
*/
@@ -29,7 +32,7 @@ export function useSearchFormSchema(): VbenFormSchema[] {
*/
export function useUserColumns(
onActionClick?: OnActionClickFn<DeptUser>,
): VxeTableGridOptions<DeptUser>['columns'] {
): LegacyTableColumn[] {
return [
{
type: 'checkbox',
@@ -1,13 +1,16 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { DictItem } from '#/api/core/dict';
import { $t } from '@vben/locales';
import { z } from '#/adapter/form';
type LegacyTableColumn = Record<string, any>;
type OnActionClickFn<T = Record<string, any>> = (params: {
code: string;
row: T;
}) => void;
/**
* 获取字典搜索表单的字段配置
*/
@@ -144,7 +147,7 @@ export function useDictItemFormSchema(): VbenFormSchema[] {
*/
export function useDictItemColumns(
onActionClick?: OnActionClickFn<DictItem>,
): VxeTableGridOptions<DictItem>['columns'] {
): LegacyTableColumn[] {
return [
{
field: 'label',
@@ -1,22 +1,22 @@
import type { Column } from 'element-plus';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { LoginLog } from '#/api/core/login-log';
import { $t } from '@vben/locales';
/**
* 获取登录状态选项
*/
type TagType = 'danger' | 'info' | 'primary' | 'success' | 'warning';
export function getStatusOptions() {
return [
{ type: 'danger', label: $t('loginLog.statusFailed'), value: 0 },
{ type: 'success', label: $t('loginLog.statusSuccess'), value: 1 },
{ type: 'danger' as TagType, label: $t('loginLog.statusFailed'), value: 0 },
{
type: 'success' as TagType,
label: $t('loginLog.statusSuccess'),
value: 1,
},
];
}
/**
* 获取失败原因选项
*/
export function getFailureReasonOptions() {
return [
{ label: $t('loginLog.failureReasonUnknown'), value: 0 },
@@ -30,9 +30,6 @@ export function getFailureReasonOptions() {
];
}
/**
* 获取设备类型选项
*/
export function getDeviceTypeOptions() {
return [
{ label: $t('loginLog.deviceTypeDesktop'), value: 'desktop' },
@@ -42,28 +39,38 @@ export function getDeviceTypeOptions() {
];
}
/**
* 获取登录方式选项
*/
export function getLoginTypeOptions() {
return [
{ label: '密码登录', value: 'password', type: 'info' },
{ label: '验证码登录', value: 'code', type: 'info' },
{ label: '二维码登录', value: 'qrcode', type: 'info' },
{ label: 'Gitee', value: 'gitee', type: 'success' },
{ label: 'GitHub', value: 'github', type: 'success' },
{ label: 'QQ', value: 'qq', type: 'success' },
{ label: 'Google', value: 'google', type: 'success' },
{ label: '微信', value: 'wechat', type: 'success' },
{ label: '微软', value: 'microsoft', type: 'success' },
{ label: '钉钉', value: 'dingtalk', type: 'success' },
{ label: '飞书', value: 'feishu', type: 'success' },
{ label: '密码登录', value: 'password', type: 'info' as TagType },
{ label: '验证码登录', value: 'code', type: 'info' as TagType },
{ label: '二维码登录', value: 'qrcode', type: 'info' as TagType },
{ label: 'Gitee', value: 'gitee', type: 'success' as TagType },
{ label: 'GitHub', value: 'github', type: 'success' as TagType },
{ label: 'QQ', value: 'qq', type: 'success' as TagType },
{ label: 'Google', value: 'google', type: 'success' as TagType },
{ label: '微信', value: 'wechat', type: 'success' as TagType },
{ label: '微软', value: 'microsoft', type: 'success' as TagType },
{ label: '钉钉', value: 'dingtalk', type: 'success' as TagType },
{ label: '飞书', value: 'feishu', type: 'success' as TagType },
];
}
/**
* 获取搜索表单的字段配置
*/
export function getTagType(
value: any,
options: Array<{ type?: TagType; value: any }>,
): TagType {
const option = options.find((item) => item.value === value);
return option?.type || 'info';
}
export function getTagLabel(
value: any,
options: Array<{ label: string; value: any }>,
): string {
const option = options.find((item) => item.value === value);
return option?.label || String(value ?? '-');
}
export function useSearchFormSchema(): VbenFormSchema[] {
return [
{
@@ -76,7 +83,6 @@ export function useSearchFormSchema(): VbenFormSchema[] {
]),
},
},
{
component: 'Select',
fieldName: 'status',
@@ -87,7 +93,6 @@ export function useSearchFormSchema(): VbenFormSchema[] {
clearable: true,
},
},
{
component: 'Select',
fieldName: 'login_type',
@@ -101,142 +106,100 @@ export function useSearchFormSchema(): VbenFormSchema[] {
];
}
/**
* 获取表格列配置
*/
export function useColumns(
onActionClick?: OnActionClickFn<LoginLog>,
): VxeTableGridOptions<LoginLog>['columns'] {
export function useZqTableColumns(): Column[] {
return [
{
type: 'checkbox',
minWidth: 60,
align: 'center',
fixed: 'left',
},
{
field: 'username',
key: 'username',
dataKey: 'username',
title: $t('loginLog.username'),
minWidth: 120,
fixed: 'left',
width: 120,
},
{
field: 'status',
key: 'status',
title: $t('loginLog.status'),
minWidth: 100,
cellRender: {
name: 'CellTag',
options: getStatusOptions(),
},
width: 100,
align: 'center' as const,
slots: { default: 'cell-status' },
},
{
field: 'login_type',
key: 'login_type',
title: '登录方式',
minWidth: 120,
cellRender: {
name: 'CellTag',
options: getLoginTypeOptions(),
},
width: 120,
align: 'center' as const,
slots: { default: 'cell-login_type' },
},
{
field: 'login_ip',
key: 'login_ip',
dataKey: 'login_ip',
title: $t('loginLog.loginIp'),
minWidth: 140,
width: 140,
},
{
field: 'ip_location',
key: 'ip_location',
dataKey: 'ip_location',
title: $t('loginLog.ipLocation'),
minWidth: 150,
width: 150,
},
{
field: 'failure_reason',
key: 'failure_reason',
title: $t('loginLog.failureReason'),
minWidth: 120,
cellRender: {
name: 'CellTag',
options: getFailureReasonOptions().map((opt) => ({
...opt,
type: 'danger',
})),
},
visible: false,
width: 120,
align: 'center' as const,
slots: { default: 'cell-failure_reason' },
},
{
field: 'failure_message',
key: 'failure_message',
dataKey: 'failure_message',
title: $t('loginLog.failureMessage'),
minWidth: 180,
showOverflow: 'tooltip',
visible: false,
width: 180,
showOverflowTooltip: true,
},
{
field: 'browser_type',
key: 'browser_type',
dataKey: 'browser_type',
title: $t('loginLog.browserType'),
minWidth: 120,
width: 120,
},
{
field: 'os_type',
key: 'os_type',
dataKey: 'os_type',
title: $t('loginLog.osType'),
minWidth: 120,
width: 120,
},
{
field: 'device_type',
key: 'device_type',
title: $t('loginLog.deviceType'),
minWidth: 100,
cellRender: {
name: 'CellTag',
options: getDeviceTypeOptions().map((opt) => ({
...opt,
type: 'info',
})),
},
width: 100,
align: 'center' as const,
slots: { default: 'cell-device_type' },
},
{
field: 'duration',
key: 'duration',
dataKey: 'duration',
title: $t('loginLog.durationSeconds'),
minWidth: 120,
visible: false,
width: 120,
},
{
field: 'remark',
key: 'remark',
dataKey: 'remark',
title: $t('loginLog.remark'),
minWidth: 150,
showOverflow: 'tooltip',
visible: false,
width: 150,
showOverflowTooltip: true,
},
{
field: 'sys_create_datetime',
key: 'sys_create_datetime',
dataKey: 'sys_create_datetime',
title: $t('loginLog.loginTime'),
minWidth: 180,
width: 180,
sortable: true,
},
{
align: 'right',
cellRender: {
attrs: {
nameField: 'username',
nameTitle: $t('loginLog.username'),
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: $t('loginLog.detail'),
icon: 'ep:document',
},
{
code: 'delete',
text: $t('common.delete'),
icon: 'ep:delete',
},
],
},
field: 'operation',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
key: 'actions',
title: $t('loginLog.operation'),
minWidth: 150,
width: 150,
fixed: true,
align: 'center' as const,
slots: { default: 'cell-actions' },
},
];
}
@@ -1,26 +1,32 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { LoginLog } from '#/api/core/login-log';
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Eye, Trash2 } from '@vben/icons';
import { $t } from '@vben/locales';
import { ElButton, ElMessage, ElMessageBox } from 'element-plus';
import { ElButton, ElMessage, ElMessageBox, ElTag } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
batchDeleteLoginLogApi,
deleteLoginLogApi,
getLoginLogDetailApi,
getLoginLogListApi,
} from '#/api/core/login-log';
import { useZqTable } from '#/components/zq-table';
import { useColumns, useSearchFormSchema } from './data';
import {
getDeviceTypeOptions,
getFailureReasonOptions,
getLoginTypeOptions,
getStatusOptions,
getTagLabel,
getTagType,
useSearchFormSchema,
useZqTableColumns,
} from './data';
import DetailDrawer from './modules/detail-drawer.vue';
defineOptions({ name: 'SystemLoginLog' });
@@ -29,9 +35,17 @@ const selectedRows = ref<LoginLog[]>([]);
const detailDrawerRef = ref();
const currentLog = ref<LoginLog>();
/**
* 查看详情
*/
const statusOptions = getStatusOptions();
const loginTypeOptions = getLoginTypeOptions();
const failureReasonOptions = getFailureReasonOptions().map((item) => ({
...item,
type: 'danger' as const,
}));
const deviceTypeOptions = getDeviceTypeOptions().map((item) => ({
...item,
type: 'info' as const,
}));
async function onDetail(row: LoginLog) {
try {
const log = await getLoginLogDetailApi(row.id);
@@ -43,9 +57,6 @@ async function onDetail(row: LoginLog) {
}
}
/**
* 删除单条日志
*/
function onDelete(row: LoginLog) {
ElMessageBox.confirm(
$t('loginLog.deleteConfirm', [row.username]),
@@ -65,23 +76,16 @@ function onDelete(row: LoginLog) {
ElMessage.error($t('loginLog.deleteError'));
}
})
.catch(() => {
// 用户取消了操作
});
.catch(() => {});
}
/**
* 批量删除日志
*/
function onBatchDelete() {
if (selectedRows.value.length === 0) {
ElMessage.warning($t('loginLog.selectLogsToDelete'));
return;
}
const usernames = selectedRows.value
.map((row: LoginLog) => row.username)
.join('、');
const usernames = selectedRows.value.map((row) => row.username).join('、');
const confirmMessage = $t('loginLog.batchDeleteConfirm', [
selectedRows.value.length,
usernames,
@@ -94,7 +98,7 @@ function onBatchDelete() {
})
.then(async () => {
try {
const ids = selectedRows.value.map((row: LoginLog) => row.id);
const ids = selectedRows.value.map((row) => row.id);
await batchDeleteLoginLogApi(ids);
ElMessage.success($t('loginLog.deleteSuccess'));
selectedRows.value = [];
@@ -103,75 +107,60 @@ function onBatchDelete() {
ElMessage.error($t('loginLog.deleteError'));
}
})
.catch(() => {
// 用户取消了操作
});
.catch(() => {});
}
/**
* 表格操作按钮的回调函数
*/
function onActionClick({ code, row }: OnActionClickParams<LoginLog>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
}
function handleSelectionChange(items: Record<string, any>[]) {
selectedRows.value = items as LoginLog[];
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useSearchFormSchema(),
submitOnChange: true,
},
gridEvents: {
checkboxAll: ({ records }: { records: LoginLog[] }) => {
selectedRows.value = records;
},
checkboxChange: ({ records }: { records: LoginLog[] }) => {
selectedRows.value = records;
},
},
const fetchLoginLogList = async (params: any) => {
const res = await getLoginLogListApi({
page: params.page.currentPage,
pageSize: params.page.pageSize,
username: params.form?.username,
status: params.form?.status,
login_type: params.form?.login_type,
});
return {
items: res.items,
total: res.total,
};
};
const [Grid, gridApi] = useZqTable({
gridOptions: {
columns: useColumns(onActionClick),
height: 'auto',
keepSource: true,
columns: useZqTableColumns(),
border: true,
stripe: true,
showIndex: true,
showSelection: true,
proxyConfig: {
autoLoad: true,
ajax: {
query: async ({ page }, formValues) => {
const params = {
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
};
return await getLoginLogListApi(params);
},
query: fetchLoginLogList,
},
},
checkboxConfig: {
reserve: true,
trigger: 'default',
pagerConfig: {
enabled: true,
pageSize: 20,
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
refresh: true,
zoom: true,
custom: true,
},
} as VxeTableGridOptions<LoginLog>,
},
formOptions: {
schema: useSearchFormSchema(),
showCollapseButton: true,
submitOnChange: true,
},
});
/**
* 刷新表格
*/
function refreshGrid() {
gridApi.query();
gridApi.reload();
}
</script>
@@ -179,13 +168,61 @@ function refreshGrid() {
<Page auto-content-height>
<DetailDrawer ref="detailDrawerRef" :log="currentLog" />
<Grid>
<template #table-title>
<Grid @selection-change="handleSelectionChange">
<template #toolbar-actions>
<ElButton type="danger" plain @click="onBatchDelete">
{{ $t('loginLog.batchDelete') }}
{{ selectedRows.length > 0 ? `(${selectedRows.length})` : '' }}
</ElButton>
</template>
<template #cell-status="{ row }">
<ElTag :type="getTagType(row.status, statusOptions)" size="small">
{{ getTagLabel(row.status, statusOptions) }}
</ElTag>
</template>
<template #cell-login_type="{ row }">
<ElTag
v-if="row.login_type"
:type="getTagType(row.login_type, loginTypeOptions)"
size="small"
>
{{ getTagLabel(row.login_type, loginTypeOptions) }}
</ElTag>
<span v-else>-</span>
</template>
<template #cell-failure_reason="{ row }">
<ElTag
v-if="row.failure_reason !== undefined && row.failure_reason !== null"
:type="getTagType(row.failure_reason, failureReasonOptions)"
size="small"
>
{{ getTagLabel(row.failure_reason, failureReasonOptions) }}
</ElTag>
<span v-else>-</span>
</template>
<template #cell-device_type="{ row }">
<ElTag
v-if="row.device_type"
:type="getTagType(row.device_type, deviceTypeOptions)"
size="small"
>
{{ getTagLabel(row.device_type, deviceTypeOptions) }}
</ElTag>
<span v-else>-</span>
</template>
<template #cell-actions="{ row }">
<ElButton link type="primary" :icon="Eye" @click="onDetail(row)">
{{ $t('loginLog.detail') }}
</ElButton>
<ElButton link type="danger" :icon="Trash2" @click="onDelete(row)">
{{ $t('common.delete') }}
</ElButton>
</template>
</Grid>
</Page>
</template>
@@ -1,13 +1,16 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Role, RoleUser } from '#/api/core/role';
import { $t } from '@vben/locales';
import { z } from '#/adapter/form';
type LegacyTableColumn = Record<string, any>;
type OnActionClickFn<T = Record<string, any>> = (params: {
code: string;
row: T;
}) => void;
/**
* 获取搜索表单的字段配置
*/
@@ -41,7 +44,7 @@ export function getRoleTypeOptions() {
*/
export function useRoleTreeColumns(
_onActionClick?: OnActionClickFn<Role>,
): VxeTableGridOptions<Role>['columns'] {
): LegacyTableColumn[] {
return [
{
field: 'name',
@@ -134,7 +137,7 @@ export function useFormSchema(): VbenFormSchema[] {
*/
export function useUserColumns(
onActionClick?: OnActionClickFn<RoleUser>,
): VxeTableGridOptions<RoleUser>['columns'] {
): LegacyTableColumn[] {
return [
{
type: 'checkbox',