Remove VXE from web ele runtime
This commit is contained in:
@@ -1,290 +0,0 @@
|
|||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
import type { Recordable } from '@vben/types';
|
|
||||||
|
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
|
||||||
import { $t, $te } from '@vben/locales';
|
|
||||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
|
||||||
import { get, isFunction, isString } from '@vben/utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ElButton,
|
|
||||||
ElImage,
|
|
||||||
ElPopconfirm,
|
|
||||||
ElTag,
|
|
||||||
ElTooltip,
|
|
||||||
} from 'element-plus';
|
|
||||||
|
|
||||||
import { useVbenForm } from './form';
|
|
||||||
|
|
||||||
setupVbenVxeTable({
|
|
||||||
configVxeTable: (vxeUI) => {
|
|
||||||
vxeUI.setConfig({
|
|
||||||
grid: {
|
|
||||||
align: 'center',
|
|
||||||
border: false,
|
|
||||||
columnConfig: {
|
|
||||||
resizable: true,
|
|
||||||
},
|
|
||||||
minHeight: 180,
|
|
||||||
formConfig: {
|
|
||||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
proxyConfig: {
|
|
||||||
autoLoad: true,
|
|
||||||
response: {
|
|
||||||
result: 'items',
|
|
||||||
total: 'total',
|
|
||||||
list: '',
|
|
||||||
},
|
|
||||||
showActionMsg: true,
|
|
||||||
showResponseMsg: false,
|
|
||||||
},
|
|
||||||
round: true,
|
|
||||||
showOverflow: true,
|
|
||||||
size: 'medium',
|
|
||||||
} as VxeTableGridOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|
||||||
vxeUI.renderer.add('CellImage', {
|
|
||||||
renderTableDefault(_renderOpts, params) {
|
|
||||||
const { column, row } = params;
|
|
||||||
const src = row[column.field];
|
|
||||||
return h(ElImage, { src, previewSrcList: [src] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|
||||||
vxeUI.renderer.add('CellLink', {
|
|
||||||
renderTableDefault(renderOpts) {
|
|
||||||
const { props } = renderOpts;
|
|
||||||
return h(
|
|
||||||
ElButton,
|
|
||||||
{ size: 'small', link: true },
|
|
||||||
{ default: () => props?.text },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 单元格渲染: Tag
|
|
||||||
vxeUI.renderer.add('CellTag', {
|
|
||||||
renderTableDefault({ options, props }, { column, row }) {
|
|
||||||
const value = get(row, column.field);
|
|
||||||
const tagOptions = options ?? [
|
|
||||||
{ type: 'success', label: $t('common.enabled'), value: true },
|
|
||||||
{ type: 'danger', label: $t('common.disabled'), value: false },
|
|
||||||
];
|
|
||||||
const tagItem = tagOptions.find((item) => item.value === value);
|
|
||||||
return h(
|
|
||||||
ElTag,
|
|
||||||
{
|
|
||||||
type: tagItem?.type ?? 'info',
|
|
||||||
...props,
|
|
||||||
},
|
|
||||||
{ default: () => tagItem?.label ?? value },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册表格的操作按钮渲染器
|
|
||||||
*/
|
|
||||||
vxeUI.renderer.add('CellOperation', {
|
|
||||||
renderTableDefault({ attrs, options, props }, { column, row }) {
|
|
||||||
const defaultProps = { size: 'small', link: true, ...props };
|
|
||||||
let align = 'end';
|
|
||||||
switch (column.align) {
|
|
||||||
case 'center': {
|
|
||||||
align = 'center';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'left': {
|
|
||||||
align = 'start';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
align = 'end';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const presets: Recordable<Recordable<any>> = {
|
|
||||||
delete: {
|
|
||||||
type: 'danger',
|
|
||||||
text: $t('common.delete'),
|
|
||||||
icon: 'ep:delete',
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
text: $t('common.edit'),
|
|
||||||
icon: 'ep:edit',
|
|
||||||
type: 'primary',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const operations: Array<Recordable<any>> = (
|
|
||||||
options || ['edit', 'delete']
|
|
||||||
)
|
|
||||||
.map((opt) => {
|
|
||||||
if (isString(opt)) {
|
|
||||||
return presets[opt]
|
|
||||||
? { code: opt, ...presets[opt], ...defaultProps }
|
|
||||||
: {
|
|
||||||
code: opt,
|
|
||||||
text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt,
|
|
||||||
type: 'primary', // 自定义按钮默认使用主题色
|
|
||||||
...defaultProps,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 对象配置的按钮,如果没有 type,也默认为 primary
|
|
||||||
const buttonConfig = {
|
|
||||||
...defaultProps,
|
|
||||||
...presets[opt.code],
|
|
||||||
...opt,
|
|
||||||
};
|
|
||||||
if (!buttonConfig.type && !presets[opt.code]) {
|
|
||||||
buttonConfig.type = 'primary';
|
|
||||||
}
|
|
||||||
return buttonConfig;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map((opt) => {
|
|
||||||
const optBtn: Recordable<any> = {};
|
|
||||||
Object.keys(opt).forEach((key) => {
|
|
||||||
optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key];
|
|
||||||
});
|
|
||||||
return optBtn;
|
|
||||||
})
|
|
||||||
.filter((opt) => opt.show !== false);
|
|
||||||
|
|
||||||
function renderBtn(opt: Recordable<any>, listen = true) {
|
|
||||||
const { icon, text, code, ...btnProps } = opt;
|
|
||||||
const buttonType =
|
|
||||||
btnProps.type === 'danger'
|
|
||||||
? 'danger'
|
|
||||||
: (btnProps.type === 'primary'
|
|
||||||
? 'primary'
|
|
||||||
: 'default');
|
|
||||||
|
|
||||||
const button = h(
|
|
||||||
ElButton,
|
|
||||||
{
|
|
||||||
...btnProps,
|
|
||||||
type: buttonType,
|
|
||||||
size: 'small',
|
|
||||||
link: true,
|
|
||||||
circle: !!icon,
|
|
||||||
onClick: listen
|
|
||||||
? () =>
|
|
||||||
attrs?.onClick?.({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => {
|
|
||||||
if (icon) {
|
|
||||||
return h(IconifyIcon, { class: 'size-4', icon });
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果有图标,用 Tooltip 包装;否则直接返回按钮
|
|
||||||
if (icon) {
|
|
||||||
return h(
|
|
||||||
ElTooltip,
|
|
||||||
{
|
|
||||||
content: text,
|
|
||||||
placement: 'top',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => button,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderConfirm(opt: Recordable<any>) {
|
|
||||||
const { icon, text } = opt;
|
|
||||||
|
|
||||||
const button = h(
|
|
||||||
ElButton,
|
|
||||||
{
|
|
||||||
type: 'danger',
|
|
||||||
size: 'small',
|
|
||||||
link: true,
|
|
||||||
circle: !!icon,
|
|
||||||
title: icon ? text : undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => {
|
|
||||||
if (icon) {
|
|
||||||
return h(IconifyIcon, { class: 'size-4', icon });
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return h(
|
|
||||||
ElPopconfirm,
|
|
||||||
{
|
|
||||||
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
|
|
||||||
confirmButtonText: $t('common.confirm'),
|
|
||||||
cancelButtonText: $t('common.cancel'),
|
|
||||||
confirmButtonType: 'danger',
|
|
||||||
onConfirm: () => {
|
|
||||||
attrs?.onClick?.({
|
|
||||||
code: opt.code,
|
|
||||||
row,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: () => button,
|
|
||||||
default: () =>
|
|
||||||
$t('ui.actionMessage.deleteConfirm', [
|
|
||||||
row[attrs?.nameField || 'name'],
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const btns = operations.map((opt) =>
|
|
||||||
opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt),
|
|
||||||
);
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
class: 'flex table-operations',
|
|
||||||
style: { justifyContent: align },
|
|
||||||
},
|
|
||||||
btns,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|
||||||
// vxeUI.formats.add
|
|
||||||
},
|
|
||||||
useVbenForm,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 自定义类型定义
|
|
||||||
export type OnActionClickParams<T = Recordable<any>> = {
|
|
||||||
code: string;
|
|
||||||
row: T;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OnActionClickFn<T = Recordable<any>> = (
|
|
||||||
params: OnActionClickParams<T>,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
export { useVbenVxeGrid };
|
|
||||||
|
|
||||||
export type * from '@vben/plugins/vxe-table';
|
|
||||||
@@ -10,6 +10,7 @@ export interface LoginLog {
|
|||||||
user_id?: string;
|
user_id?: string;
|
||||||
username: string;
|
username: string;
|
||||||
status: number;
|
status: number;
|
||||||
|
login_type?: string;
|
||||||
failure_reason?: number;
|
failure_reason?: number;
|
||||||
failure_message?: string;
|
failure_message?: string;
|
||||||
login_ip: string;
|
login_ip: string;
|
||||||
@@ -33,6 +34,7 @@ export interface LoginLogListParams {
|
|||||||
username?: string;
|
username?: string;
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
status?: number;
|
status?: number;
|
||||||
|
login_type?: string;
|
||||||
failure_reason?: number;
|
failure_reason?: number;
|
||||||
login_ip?: string;
|
login_ip?: string;
|
||||||
device_type?: string;
|
device_type?: string;
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
|
||||||
import type { DeptUser } from '#/api/core/dept';
|
import type { DeptUser } from '#/api/core/dept';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
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(
|
export function useUserColumns(
|
||||||
onActionClick?: OnActionClickFn<DeptUser>,
|
onActionClick?: OnActionClickFn<DeptUser>,
|
||||||
): VxeTableGridOptions<DeptUser>['columns'] {
|
): LegacyTableColumn[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
|
||||||
import type { DictItem } from '#/api/core/dict';
|
import type { DictItem } from '#/api/core/dict';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
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(
|
export function useDictItemColumns(
|
||||||
onActionClick?: OnActionClickFn<DictItem>,
|
onActionClick?: OnActionClickFn<DictItem>,
|
||||||
): VxeTableGridOptions<DictItem>['columns'] {
|
): LegacyTableColumn[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'label',
|
field: 'label',
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
|
import type { Column } from 'element-plus';
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
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';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
/**
|
type TagType = 'danger' | 'info' | 'primary' | 'success' | 'warning';
|
||||||
* 获取登录状态选项
|
|
||||||
*/
|
|
||||||
export function getStatusOptions() {
|
export function getStatusOptions() {
|
||||||
return [
|
return [
|
||||||
{ type: 'danger', label: $t('loginLog.statusFailed'), value: 0 },
|
{ type: 'danger' as TagType, label: $t('loginLog.statusFailed'), value: 0 },
|
||||||
{ type: 'success', label: $t('loginLog.statusSuccess'), value: 1 },
|
{
|
||||||
|
type: 'success' as TagType,
|
||||||
|
label: $t('loginLog.statusSuccess'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取失败原因选项
|
|
||||||
*/
|
|
||||||
export function getFailureReasonOptions() {
|
export function getFailureReasonOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: $t('loginLog.failureReasonUnknown'), value: 0 },
|
{ label: $t('loginLog.failureReasonUnknown'), value: 0 },
|
||||||
@@ -30,9 +30,6 @@ export function getFailureReasonOptions() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取设备类型选项
|
|
||||||
*/
|
|
||||||
export function getDeviceTypeOptions() {
|
export function getDeviceTypeOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: $t('loginLog.deviceTypeDesktop'), value: 'desktop' },
|
{ label: $t('loginLog.deviceTypeDesktop'), value: 'desktop' },
|
||||||
@@ -42,28 +39,38 @@ export function getDeviceTypeOptions() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取登录方式选项
|
|
||||||
*/
|
|
||||||
export function getLoginTypeOptions() {
|
export function getLoginTypeOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: '密码登录', value: 'password', type: 'info' },
|
{ label: '密码登录', value: 'password', type: 'info' as TagType },
|
||||||
{ label: '验证码登录', value: 'code', type: 'info' },
|
{ label: '验证码登录', value: 'code', type: 'info' as TagType },
|
||||||
{ label: '二维码登录', value: 'qrcode', type: 'info' },
|
{ label: '二维码登录', value: 'qrcode', type: 'info' as TagType },
|
||||||
{ label: 'Gitee', value: 'gitee', type: 'success' },
|
{ label: 'Gitee', value: 'gitee', type: 'success' as TagType },
|
||||||
{ label: 'GitHub', value: 'github', type: 'success' },
|
{ label: 'GitHub', value: 'github', type: 'success' as TagType },
|
||||||
{ label: 'QQ', value: 'qq', type: 'success' },
|
{ label: 'QQ', value: 'qq', type: 'success' as TagType },
|
||||||
{ label: 'Google', value: 'google', type: 'success' },
|
{ label: 'Google', value: 'google', type: 'success' as TagType },
|
||||||
{ label: '微信', value: 'wechat', type: 'success' },
|
{ label: '微信', value: 'wechat', type: 'success' as TagType },
|
||||||
{ label: '微软', value: 'microsoft', type: 'success' },
|
{ label: '微软', value: 'microsoft', type: 'success' as TagType },
|
||||||
{ label: '钉钉', value: 'dingtalk', type: 'success' },
|
{ label: '钉钉', value: 'dingtalk', type: 'success' as TagType },
|
||||||
{ label: '飞书', value: 'feishu', type: 'success' },
|
{ 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[] {
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -76,7 +83,6 @@ export function useSearchFormSchema(): VbenFormSchema[] {
|
|||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
fieldName: 'status',
|
fieldName: 'status',
|
||||||
@@ -87,7 +93,6 @@ export function useSearchFormSchema(): VbenFormSchema[] {
|
|||||||
clearable: true,
|
clearable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
fieldName: 'login_type',
|
fieldName: 'login_type',
|
||||||
@@ -101,142 +106,100 @@ export function useSearchFormSchema(): VbenFormSchema[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function useZqTableColumns(): Column[] {
|
||||||
* 获取表格列配置
|
|
||||||
*/
|
|
||||||
export function useColumns(
|
|
||||||
onActionClick?: OnActionClickFn<LoginLog>,
|
|
||||||
): VxeTableGridOptions<LoginLog>['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
key: 'username',
|
||||||
minWidth: 60,
|
dataKey: 'username',
|
||||||
align: 'center',
|
|
||||||
fixed: 'left',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'username',
|
|
||||||
title: $t('loginLog.username'),
|
title: $t('loginLog.username'),
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
fixed: 'left',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
key: 'status',
|
||||||
title: $t('loginLog.status'),
|
title: $t('loginLog.status'),
|
||||||
minWidth: 100,
|
width: 100,
|
||||||
cellRender: {
|
align: 'center' as const,
|
||||||
name: 'CellTag',
|
slots: { default: 'cell-status' },
|
||||||
options: getStatusOptions(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'login_type',
|
key: 'login_type',
|
||||||
title: '登录方式',
|
title: '登录方式',
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
cellRender: {
|
align: 'center' as const,
|
||||||
name: 'CellTag',
|
slots: { default: 'cell-login_type' },
|
||||||
options: getLoginTypeOptions(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'login_ip',
|
key: 'login_ip',
|
||||||
|
dataKey: 'login_ip',
|
||||||
title: $t('loginLog.loginIp'),
|
title: $t('loginLog.loginIp'),
|
||||||
minWidth: 140,
|
width: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'ip_location',
|
key: 'ip_location',
|
||||||
|
dataKey: 'ip_location',
|
||||||
title: $t('loginLog.ipLocation'),
|
title: $t('loginLog.ipLocation'),
|
||||||
minWidth: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'failure_reason',
|
key: 'failure_reason',
|
||||||
title: $t('loginLog.failureReason'),
|
title: $t('loginLog.failureReason'),
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
cellRender: {
|
align: 'center' as const,
|
||||||
name: 'CellTag',
|
slots: { default: 'cell-failure_reason' },
|
||||||
options: getFailureReasonOptions().map((opt) => ({
|
|
||||||
...opt,
|
|
||||||
type: 'danger',
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
visible: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'failure_message',
|
key: 'failure_message',
|
||||||
|
dataKey: 'failure_message',
|
||||||
title: $t('loginLog.failureMessage'),
|
title: $t('loginLog.failureMessage'),
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
showOverflow: 'tooltip',
|
showOverflowTooltip: true,
|
||||||
visible: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'browser_type',
|
key: 'browser_type',
|
||||||
|
dataKey: 'browser_type',
|
||||||
title: $t('loginLog.browserType'),
|
title: $t('loginLog.browserType'),
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'os_type',
|
key: 'os_type',
|
||||||
|
dataKey: 'os_type',
|
||||||
title: $t('loginLog.osType'),
|
title: $t('loginLog.osType'),
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'device_type',
|
key: 'device_type',
|
||||||
title: $t('loginLog.deviceType'),
|
title: $t('loginLog.deviceType'),
|
||||||
minWidth: 100,
|
width: 100,
|
||||||
cellRender: {
|
align: 'center' as const,
|
||||||
name: 'CellTag',
|
slots: { default: 'cell-device_type' },
|
||||||
options: getDeviceTypeOptions().map((opt) => ({
|
|
||||||
...opt,
|
|
||||||
type: 'info',
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'duration',
|
key: 'duration',
|
||||||
|
dataKey: 'duration',
|
||||||
title: $t('loginLog.durationSeconds'),
|
title: $t('loginLog.durationSeconds'),
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
visible: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'remark',
|
key: 'remark',
|
||||||
|
dataKey: 'remark',
|
||||||
title: $t('loginLog.remark'),
|
title: $t('loginLog.remark'),
|
||||||
minWidth: 150,
|
width: 150,
|
||||||
showOverflow: 'tooltip',
|
showOverflowTooltip: true,
|
||||||
visible: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'sys_create_datetime',
|
key: 'sys_create_datetime',
|
||||||
|
dataKey: 'sys_create_datetime',
|
||||||
title: $t('loginLog.loginTime'),
|
title: $t('loginLog.loginTime'),
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'right',
|
key: 'actions',
|
||||||
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,
|
|
||||||
title: $t('loginLog.operation'),
|
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>
|
<script lang="ts" setup>
|
||||||
import type {
|
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { LoginLog } from '#/api/core/login-log';
|
import type { LoginLog } from '#/api/core/login-log';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { Eye, Trash2 } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
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 {
|
import {
|
||||||
batchDeleteLoginLogApi,
|
batchDeleteLoginLogApi,
|
||||||
deleteLoginLogApi,
|
deleteLoginLogApi,
|
||||||
getLoginLogDetailApi,
|
getLoginLogDetailApi,
|
||||||
getLoginLogListApi,
|
getLoginLogListApi,
|
||||||
} from '#/api/core/login-log';
|
} 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';
|
import DetailDrawer from './modules/detail-drawer.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'SystemLoginLog' });
|
defineOptions({ name: 'SystemLoginLog' });
|
||||||
@@ -29,9 +35,17 @@ const selectedRows = ref<LoginLog[]>([]);
|
|||||||
const detailDrawerRef = ref();
|
const detailDrawerRef = ref();
|
||||||
const currentLog = ref<LoginLog>();
|
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) {
|
async function onDetail(row: LoginLog) {
|
||||||
try {
|
try {
|
||||||
const log = await getLoginLogDetailApi(row.id);
|
const log = await getLoginLogDetailApi(row.id);
|
||||||
@@ -43,9 +57,6 @@ async function onDetail(row: LoginLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单条日志
|
|
||||||
*/
|
|
||||||
function onDelete(row: LoginLog) {
|
function onDelete(row: LoginLog) {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
$t('loginLog.deleteConfirm', [row.username]),
|
$t('loginLog.deleteConfirm', [row.username]),
|
||||||
@@ -65,23 +76,16 @@ function onDelete(row: LoginLog) {
|
|||||||
ElMessage.error($t('loginLog.deleteError'));
|
ElMessage.error($t('loginLog.deleteError'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {});
|
||||||
// 用户取消了操作
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除日志
|
|
||||||
*/
|
|
||||||
function onBatchDelete() {
|
function onBatchDelete() {
|
||||||
if (selectedRows.value.length === 0) {
|
if (selectedRows.value.length === 0) {
|
||||||
ElMessage.warning($t('loginLog.selectLogsToDelete'));
|
ElMessage.warning($t('loginLog.selectLogsToDelete'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const usernames = selectedRows.value
|
const usernames = selectedRows.value.map((row) => row.username).join('、');
|
||||||
.map((row: LoginLog) => row.username)
|
|
||||||
.join('、');
|
|
||||||
const confirmMessage = $t('loginLog.batchDeleteConfirm', [
|
const confirmMessage = $t('loginLog.batchDeleteConfirm', [
|
||||||
selectedRows.value.length,
|
selectedRows.value.length,
|
||||||
usernames,
|
usernames,
|
||||||
@@ -94,7 +98,7 @@ function onBatchDelete() {
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
const ids = selectedRows.value.map((row: LoginLog) => row.id);
|
const ids = selectedRows.value.map((row) => row.id);
|
||||||
await batchDeleteLoginLogApi(ids);
|
await batchDeleteLoginLogApi(ids);
|
||||||
ElMessage.success($t('loginLog.deleteSuccess'));
|
ElMessage.success($t('loginLog.deleteSuccess'));
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
@@ -103,75 +107,60 @@ function onBatchDelete() {
|
|||||||
ElMessage.error($t('loginLog.deleteError'));
|
ElMessage.error($t('loginLog.deleteError'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {});
|
||||||
// 用户取消了操作
|
}
|
||||||
|
|
||||||
|
function handleSelectionChange(items: Record<string, any>[]) {
|
||||||
|
selectedRows.value = items as LoginLog[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
* 表格操作按钮的回调函数
|
|
||||||
*/
|
|
||||||
function onActionClick({ code, row }: OnActionClickParams<LoginLog>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
formOptions: {
|
|
||||||
schema: useSearchFormSchema(),
|
|
||||||
submitOnChange: true,
|
|
||||||
},
|
|
||||||
gridEvents: {
|
|
||||||
checkboxAll: ({ records }: { records: LoginLog[] }) => {
|
|
||||||
selectedRows.value = records;
|
|
||||||
},
|
|
||||||
checkboxChange: ({ records }: { records: LoginLog[] }) => {
|
|
||||||
selectedRows.value = records;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
gridOptions: {
|
|
||||||
columns: useColumns(onActionClick),
|
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async ({ page }, formValues) => {
|
|
||||||
const params = {
|
|
||||||
page: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
|
||||||
};
|
};
|
||||||
return await getLoginLogListApi(params);
|
};
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useZqTable({
|
||||||
|
gridOptions: {
|
||||||
|
columns: useZqTableColumns(),
|
||||||
|
border: true,
|
||||||
|
stripe: true,
|
||||||
|
showIndex: true,
|
||||||
|
showSelection: true,
|
||||||
|
proxyConfig: {
|
||||||
|
autoLoad: true,
|
||||||
|
ajax: {
|
||||||
|
query: fetchLoginLogList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
pagerConfig: {
|
||||||
checkboxConfig: {
|
enabled: true,
|
||||||
reserve: true,
|
pageSize: 20,
|
||||||
trigger: 'default',
|
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
custom: true,
|
|
||||||
export: false,
|
|
||||||
refresh: { code: 'query' },
|
|
||||||
search: true,
|
search: true,
|
||||||
|
refresh: true,
|
||||||
zoom: true,
|
zoom: true,
|
||||||
|
custom: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: true,
|
||||||
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<LoginLog>,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新表格
|
|
||||||
*/
|
|
||||||
function refreshGrid() {
|
function refreshGrid() {
|
||||||
gridApi.query();
|
gridApi.reload();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -179,13 +168,61 @@ function refreshGrid() {
|
|||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DetailDrawer ref="detailDrawerRef" :log="currentLog" />
|
<DetailDrawer ref="detailDrawerRef" :log="currentLog" />
|
||||||
|
|
||||||
<Grid>
|
<Grid @selection-change="handleSelectionChange">
|
||||||
<template #table-title>
|
<template #toolbar-actions>
|
||||||
<ElButton type="danger" plain @click="onBatchDelete">
|
<ElButton type="danger" plain @click="onBatchDelete">
|
||||||
{{ $t('loginLog.batchDelete') }}
|
{{ $t('loginLog.batchDelete') }}
|
||||||
{{ selectedRows.length > 0 ? `(${selectedRows.length})` : '' }}
|
{{ selectedRows.length > 0 ? `(${selectedRows.length})` : '' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</template>
|
</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>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
|
||||||
import type { Role, RoleUser } from '#/api/core/role';
|
import type { Role, RoleUser } from '#/api/core/role';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
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(
|
export function useRoleTreeColumns(
|
||||||
_onActionClick?: OnActionClickFn<Role>,
|
_onActionClick?: OnActionClickFn<Role>,
|
||||||
): VxeTableGridOptions<Role>['columns'] {
|
): LegacyTableColumn[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
@@ -134,7 +137,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
*/
|
*/
|
||||||
export function useUserColumns(
|
export function useUserColumns(
|
||||||
onActionClick?: OnActionClickFn<RoleUser>,
|
onActionClick?: OnActionClickFn<RoleUser>,
|
||||||
): VxeTableGridOptions<RoleUser>['columns'] {
|
): LegacyTableColumn[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
|||||||
@@ -135,10 +135,6 @@ export default defineConfig(async () => {
|
|||||||
'@vben/plugins/motion',
|
'@vben/plugins/motion',
|
||||||
'../../packages/effects/plugins/src/motion/index.ts',
|
'../../packages/effects/plugins/src/motion/index.ts',
|
||||||
),
|
),
|
||||||
exactAlias(
|
|
||||||
'@vben/plugins/vxe-table',
|
|
||||||
'../../packages/effects/plugins/src/vxe-table/index.ts',
|
|
||||||
),
|
|
||||||
exactAlias(
|
exactAlias(
|
||||||
'@vben/preferences',
|
'@vben/preferences',
|
||||||
'../../packages/preferences/src/index.ts',
|
'../../packages/preferences/src/index.ts',
|
||||||
|
|||||||
Reference in New Issue
Block a user