From b0057ed5a82c565eacf1f9ff09b96bac58154694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cls=5F=E5=AE=81=E6=B3=A2=E6=9C=AC=E6=9C=BA?= <908705107@qq.com> Date: Tue, 9 Jun 2026 10:02:10 +0800 Subject: [PATCH] Remove VXE from web ele runtime --- web/apps/web-ele/src/adapter/vxe-table.ts | 290 ------------------ web/apps/web-ele/src/api/core/login-log.ts | 2 + web/apps/web-ele/src/views/_core/dept/data.ts | 11 +- web/apps/web-ele/src/views/_core/dict/data.ts | 11 +- .../web-ele/src/views/_core/login-log/data.ts | 213 ++++++------- .../src/views/_core/login-log/index.vue | 191 +++++++----- web/apps/web-ele/src/views/_core/role/data.ts | 13 +- web/apps/web-ele/vite.config.mts | 4 - 8 files changed, 226 insertions(+), 509 deletions(-) delete mode 100644 web/apps/web-ele/src/adapter/vxe-table.ts diff --git a/web/apps/web-ele/src/adapter/vxe-table.ts b/web/apps/web-ele/src/adapter/vxe-table.ts deleted file mode 100644 index 2c24656..0000000 --- a/web/apps/web-ele/src/adapter/vxe-table.ts +++ /dev/null @@ -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> = { - delete: { - type: 'danger', - text: $t('common.delete'), - icon: 'ep:delete', - }, - edit: { - text: $t('common.edit'), - icon: 'ep:edit', - type: 'primary', - }, - }; - const operations: Array> = ( - 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 = {}; - 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, 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) { - 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> = { - code: string; - row: T; -}; - -export type OnActionClickFn> = ( - params: OnActionClickParams, -) => void; - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; diff --git a/web/apps/web-ele/src/api/core/login-log.ts b/web/apps/web-ele/src/api/core/login-log.ts index f33b116..cda128c 100644 --- a/web/apps/web-ele/src/api/core/login-log.ts +++ b/web/apps/web-ele/src/api/core/login-log.ts @@ -10,6 +10,7 @@ export interface LoginLog { user_id?: string; username: string; status: number; + login_type?: string; failure_reason?: number; failure_message?: string; login_ip: string; @@ -33,6 +34,7 @@ export interface LoginLogListParams { username?: string; user_id?: string; status?: number; + login_type?: string; failure_reason?: number; login_ip?: string; device_type?: string; diff --git a/web/apps/web-ele/src/views/_core/dept/data.ts b/web/apps/web-ele/src/views/_core/dept/data.ts index d3ee22b..9b0f287 100644 --- a/web/apps/web-ele/src/views/_core/dept/data.ts +++ b/web/apps/web-ele/src/views/_core/dept/data.ts @@ -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; +type OnActionClickFn> = (params: { + code: string; + row: T; +}) => void; + /** * 获取搜索表单的字段配置 */ @@ -29,7 +32,7 @@ export function useSearchFormSchema(): VbenFormSchema[] { */ export function useUserColumns( onActionClick?: OnActionClickFn, -): VxeTableGridOptions['columns'] { +): LegacyTableColumn[] { return [ { type: 'checkbox', diff --git a/web/apps/web-ele/src/views/_core/dict/data.ts b/web/apps/web-ele/src/views/_core/dict/data.ts index 06dfb90..b2cf4e2 100644 --- a/web/apps/web-ele/src/views/_core/dict/data.ts +++ b/web/apps/web-ele/src/views/_core/dict/data.ts @@ -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; +type OnActionClickFn> = (params: { + code: string; + row: T; +}) => void; + /** * 获取字典搜索表单的字段配置 */ @@ -144,7 +147,7 @@ export function useDictItemFormSchema(): VbenFormSchema[] { */ export function useDictItemColumns( onActionClick?: OnActionClickFn, -): VxeTableGridOptions['columns'] { +): LegacyTableColumn[] { return [ { field: 'label', diff --git a/web/apps/web-ele/src/views/_core/login-log/data.ts b/web/apps/web-ele/src/views/_core/login-log/data.ts index dc14648..56d9ebd 100644 --- a/web/apps/web-ele/src/views/_core/login-log/data.ts +++ b/web/apps/web-ele/src/views/_core/login-log/data.ts @@ -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, -): VxeTableGridOptions['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' }, }, ]; } diff --git a/web/apps/web-ele/src/views/_core/login-log/index.vue b/web/apps/web-ele/src/views/_core/login-log/index.vue index 5bdf75a..4bcbad2 100644 --- a/web/apps/web-ele/src/views/_core/login-log/index.vue +++ b/web/apps/web-ele/src/views/_core/login-log/index.vue @@ -1,26 +1,32 @@ @@ -179,13 +168,61 @@ function refreshGrid() { - - diff --git a/web/apps/web-ele/src/views/_core/role/data.ts b/web/apps/web-ele/src/views/_core/role/data.ts index 282a22f..948996d 100644 --- a/web/apps/web-ele/src/views/_core/role/data.ts +++ b/web/apps/web-ele/src/views/_core/role/data.ts @@ -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; +type OnActionClickFn> = (params: { + code: string; + row: T; +}) => void; + /** * 获取搜索表单的字段配置 */ @@ -41,7 +44,7 @@ export function getRoleTypeOptions() { */ export function useRoleTreeColumns( _onActionClick?: OnActionClickFn, -): VxeTableGridOptions['columns'] { +): LegacyTableColumn[] { return [ { field: 'name', @@ -134,7 +137,7 @@ export function useFormSchema(): VbenFormSchema[] { */ export function useUserColumns( onActionClick?: OnActionClickFn, -): VxeTableGridOptions['columns'] { +): LegacyTableColumn[] { return [ { type: 'checkbox', diff --git a/web/apps/web-ele/vite.config.mts b/web/apps/web-ele/vite.config.mts index b011ff6..1d4ca69 100644 --- a/web/apps/web-ele/vite.config.mts +++ b/web/apps/web-ele/vite.config.mts @@ -135,10 +135,6 @@ export default defineConfig(async () => { '@vben/plugins/motion', '../../packages/effects/plugins/src/motion/index.ts', ), - exactAlias( - '@vben/plugins/vxe-table', - '../../packages/effects/plugins/src/vxe-table/index.ts', - ), exactAlias( '@vben/preferences', '../../packages/preferences/src/index.ts',