Merge pull request !9 from 玲娜贝er/offline-icon
This commit is contained in:
玲娜贝er 2024-11-08 03:45:17 +00:00 committed by Gitee
commit a3652b7971
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
74 changed files with 2144 additions and 188 deletions

View File

@ -160,6 +160,7 @@
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true,

View File

@ -1,4 +1,8 @@
# 1.0.1
# 1.1.0
**FEATURES**
- 支持离线图标功能(全局可在内网环境中使用)
**BUG FIXES**

View File

@ -8,6 +8,8 @@ v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/we
目前对应后端版本: **5.2.3/2.2.3**
V1.1.0版本已支持离线图标
## 进度
**工作流相关模块等待后端重构后开发**
@ -82,7 +84,7 @@ pnpm install
- 菜单图标替换
[根目录/scripts/菜单图标替换sql/update_icon.sql](https://gitee.com/dapppp/ruoyi-plus-vben5/blob/main/scripts/%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E6%9B%BF%E6%8D%A2sql/update_icon.sql)
参考 [菜单图标替换](https://dapdap.top/guide/quick-start.html#%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E5%AF%BC%E5%85%A5)
- 关于代码生成

View File

@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "1.0.1",
"version": "1.1.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -77,7 +77,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
@ -85,7 +85,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,

View File

@ -1,12 +1,15 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import { useTitle } from '@vueuse/core';
import { setupGlobalComponent } from '#/components/global';
import { setupI18n } from '#/locales';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
@ -33,6 +36,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@ -5,7 +5,12 @@ import { useRouter } from 'vue-router';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { BookOpenText, CircleHelp, MdiGithub, ProfileIcon } from '@vben/icons';
import {
BookOpenText,
CircleHelp,
GitHubOutlined,
UserOutlined,
} from '@vben/icons';
import {
BasicLayout,
LockScreen,
@ -47,7 +52,7 @@ const menus = computed(() => {
handler: () => {
router.push('/profile');
},
icon: ProfileIcon,
icon: UserOutlined,
text: $t('ui.widgets.profile'),
},
{
@ -56,7 +61,7 @@ const menus = computed(() => {
target: '_blank',
});
},
icon: MdiGithub,
icon: GitHubOutlined,
text: 'GitHub',
},
{

View File

@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description
* 使
* !!!
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}

View File

@ -48,7 +48,7 @@ const profileRoute: RouteRecordStringComponent[] = [
component: '/system/oss-config/index',
meta: {
activePath: '/system/oss',
icon: 'mingcute:profile-line',
icon: 'ant-design:setting-outlined',
keepAlive: true,
title: 'oss配置',
},
@ -57,7 +57,6 @@ const profileRoute: RouteRecordStringComponent[] = [
},
],
},
// TODO: 图标要改
{
component: 'BasicLayout',
meta: {
@ -73,7 +72,7 @@ const profileRoute: RouteRecordStringComponent[] = [
component: '/tool/gen/edit-gen',
meta: {
activePath: '/tool/gen',
icon: 'mingcute:profile-line',
icon: 'tabler:code',
keepAlive: true,
title: '生成配置',
},
@ -97,7 +96,7 @@ const profileRoute: RouteRecordStringComponent[] = [
component: '/system/role-assign/index',
meta: {
activePath: '/system/role',
icon: 'mingcute:profile-line',
icon: 'eos-icons:role-binding-outlined',
keepAlive: true,
title: '分配角色',
},

View File

@ -28,7 +28,7 @@ const routes: RouteRecordRaw[] = [
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
icon: 'lucide:copyright|offline',
title: $t('demos.vben.about'),
},
},
@ -37,7 +37,7 @@ const routes: RouteRecordRaw[] = [
path: '/vben-admin/document',
component: IFrameView,
meta: {
icon: 'lucide:book-open-text',
icon: 'lucide:book-open-text|offline',
link: VBEN_DOC_URL,
title: $t('demos.vben.document'),
},

View File

@ -1,7 +1,29 @@
import type { DictData } from '#/api/system/dict/dict-data-model';
import { type Component as ComponentType, h } from 'vue';
import { JsonPreview } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import {
AndroidIcon,
BaiduIcon,
ChromeIcon,
DefaultBrowserIcon,
DefaultOsIcon,
DingtalkIcon,
EdgeIcon,
FirefoxIcon,
IconifyIcon,
IPhoneIcon,
LinuxIcon,
MicromessengerIcon,
OperaIcon,
OSXIcon,
QQIcon,
QuarkIcon,
SafariIcon,
UcIcon,
WindowsIcon,
} from '@vben/icons';
import { Tag } from 'ant-design-vue';
@ -129,28 +151,28 @@ export function renderDict(value: string, dictName: string) {
const dictInfo = getDict(dictName);
return renderDictTag(value, dictInfo);
}
export function renderIconSpan(
icon: string,
icon: ComponentType,
value: string,
center = false,
marginLeft = '2px',
) {
const justifyCenter = center ? 'justify-center' : '';
return (
<span class={['flex', 'items-center', justifyCenter]}>
{renderIcon(icon)}
{h(icon)}
<span style={{ marginLeft }}>{value}</span>
</span>
);
}
const osOptions = [
{ icon: 'devicon:windows8', value: 'windows' },
{ icon: 'devicon:linux', value: 'linux' },
{ icon: 'wpf:macos', value: 'osx' },
{ icon: 'flat-color-icons:android-os', value: 'android' },
{ icon: 'majesticons:iphone-x-apps-line', value: 'iphone' },
{ icon: WindowsIcon, value: 'windows' },
{ icon: LinuxIcon, value: 'linux' },
{ icon: OSXIcon, value: 'osx' },
{ icon: AndroidIcon, value: 'android' },
{ icon: IPhoneIcon, value: 'iphone' },
];
/**
@ -158,18 +180,18 @@ const osOptions = [
* cn.hutool.http.useragent -> browers
*/
const browserOptions = [
{ icon: 'logos:chrome', value: 'chrome' },
{ icon: 'logos:microsoft-edge', value: 'edge' },
{ icon: 'logos:firefox', value: 'firefox' },
{ icon: 'logos:opera', value: 'opera' },
{ icon: 'logos:safari', value: 'safari' },
{ icon: 'mdi:wechat', value: 'micromessenger' },
{ icon: 'logos:quarkus-icon', value: 'quark' },
{ icon: 'mdi:wechat', value: 'wxwork' },
{ icon: 'simple-icons:tencentqq', value: 'qq' },
{ icon: 'ri:dingding-line', value: 'dingtalk' },
{ icon: 'arcticons:uc-browser', value: 'uc' },
{ icon: 'ri:baidu-fill', value: 'baidu' },
{ icon: ChromeIcon, value: 'chrome' },
{ icon: EdgeIcon, value: 'edge' },
{ icon: FirefoxIcon, value: 'firefox' },
{ icon: OperaIcon, value: 'opera' },
{ icon: SafariIcon, value: 'safari' },
{ icon: MicromessengerIcon, value: 'micromessenger' },
{ icon: QuarkIcon, value: 'quark' },
{ icon: MicromessengerIcon, value: 'wxwork' },
{ icon: QQIcon, value: 'qq' },
{ icon: DingtalkIcon, value: 'dingtalk' },
{ icon: UcIcon, value: 'uc' },
{ icon: BaiduIcon, value: 'baidu' },
];
export function renderOsIcon(os: string, center = false) {
@ -187,7 +209,7 @@ export function renderOsIcon(os: string, center = false) {
return renderIconSpan(current.icon, os, center, '5px');
}
// 返回默认
const defaultIcon = 'ic:outline-computer';
const defaultIcon = DefaultOsIcon;
return renderIconSpan(defaultIcon, os, center, '5px');
}
@ -202,6 +224,6 @@ export function renderBrowserIcon(browser: string, center = false) {
return renderIconSpan(current.icon, browser, center, '5px');
}
// 返回默认
const defaultIcon = 'ph:browser-duotone';
const defaultIcon = DefaultBrowserIcon;
return renderIconSpan(defaultIcon, browser, center, '5px');
}

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { Col, Row, Tooltip } from 'ant-design-vue';
@ -31,7 +30,7 @@ const clientList = accountBindList.filter((item) => item.action);
<Tooltip :title="`${item.title}登录`">
<span class="flex cursor-pointer items-center justify-center">
<component
:is="createIconifyIcon(item.avatar)"
:is="item.avatar"
v-if="item.avatar"
:style="{ color: item.color }"
class="size-[24px]"

View File

@ -1,3 +1,13 @@
import type { Component } from 'vue';
import {
AlipayIcon,
DingdingIcon,
GiteeIcon,
GithubOAuthIcon,
TaobaoIcon,
} from '@vben/icons';
import { authBinding } from '#/api/core/auth';
/**
* @description:
@ -13,7 +23,7 @@ export interface ListItem {
title: string;
description: string;
extra?: string;
avatar?: string;
avatar?: Component;
color?: string;
}
@ -50,7 +60,7 @@ async function handleAuthBinding(source: string) {
*/
export const accountBindList: BindItem[] = [
{
avatar: 'ri:taobao-fill',
avatar: TaobaoIcon,
color: '#ff4000',
description: '绑定淘宝账号',
key: '1',
@ -58,7 +68,7 @@ export const accountBindList: BindItem[] = [
title: '淘宝',
},
{
avatar: 'fa-brands:alipay',
avatar: AlipayIcon,
color: '#2eabff',
description: '绑定支付宝账号',
key: '2',
@ -66,7 +76,7 @@ export const accountBindList: BindItem[] = [
title: '支付宝',
},
{
avatar: 'ri:dingding-fill',
avatar: DingdingIcon,
color: '#2eabff',
description: '绑定钉钉账号',
key: '3',
@ -75,7 +85,7 @@ export const accountBindList: BindItem[] = [
},
{
action: () => handleAuthBinding('gitee'),
avatar: 'simple-icons:gitee',
avatar: GiteeIcon,
color: '#c71d23',
description: '绑定GITEE账号',
key: '4',
@ -84,7 +94,7 @@ export const accountBindList: BindItem[] = [
},
{
action: () => handleAuthBinding('github'),
avatar: 'uiw:github',
avatar: GithubOAuthIcon,
color: '',
description: '绑定GITHUB账号',
key: '5',

View File

@ -5,8 +5,6 @@ import type { SocialInfo } from '#/api/system/social/model';
import { computed, onMounted, ref, unref } from 'vue';
import { createIconifyIcon } from '@vben/icons';
import {
Alert,
Avatar,
@ -127,7 +125,7 @@ onMounted(reload);
<div class="flex w-full items-center gap-4">
<div>
<component
:is="createIconifyIcon(item.avatar)"
:is="item.avatar"
v-if="item.avatar"
:style="{ color: item.color }"
class="size-[40px]"

View File

@ -1,12 +1,15 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { h } from 'vue';
import { DictEnum } from '@vben/constants';
import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter, z } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict, renderIcon } from '#/utils/render';
import { renderDict } from '#/utils/render';
export const querySchema: FormSchemaGetter = () => [
{
@ -48,11 +51,10 @@ export const yesNoOptions = [
// M目录 C菜单 F按钮
const menuTypes = {
M: { value: '目录', icon: 'fxemoji:folder' },
C: { value: '菜单', icon: 'fluent-emoji-flat:open-book' },
F: { value: '按钮', icon: 'fluent-emoji:ok-button' },
C: { icon: MenuIcon, value: '菜单' },
F: { icon: OkButtonIcon, value: '按钮' },
M: { icon: FolderIcon, value: '目录' },
};
export const columns: VxeGridProps['columns'] = [
{
title: '菜单名称',
@ -74,7 +76,9 @@ export const columns: VxeGridProps['columns'] = [
return '';
}
return (
<span class={'flex justify-center'}>{renderIcon(row.icon)}</span>
<span class={'flex justify-center'}>
<VbenIcon icon={row.icon} />
</span>
);
},
},
@ -95,9 +99,9 @@ export const columns: VxeGridProps['columns'] = [
return '未知';
}
return (
<span class={['flex', 'items-center', 'justify-center']}>
{renderIcon(current.icon)}
<span style={{ marginLeft: '2px' }}>{current.value}</span>
<span class="flex items-center justify-center gap-1">
{h(current.icon, { class: 'size-[18px]' })}
<span>{current.value}</span>
</span>
);
},
@ -189,7 +193,8 @@ export const drawerSchema: FormSchemaGetter = () => [
show: (values) => values.menuType !== 'F',
triggerFields: ['menuType'],
},
renderComponentContent: () => ({
renderComponentContent: (model) => ({
addonBefore: () => <VbenIcon icon={model.icon} />,
addonAfter: () => (
<a href="https://icon-sets.iconify.design/" target="_blank">

View File

@ -2,14 +2,22 @@
import type { Recordable } from '@vben/types';
import type { Key } from 'ant-design-vue/es/vc-tree/interface';
import { ref } from 'vue';
import { type Component, ref } from 'vue';
import {
CodeMirror,
type LanguageSupport,
useVbenModal,
} from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import {
DefaultFileIcon,
FolderIcon,
JavaIcon,
SqlIcon,
TsIcon,
VueIcon,
XmlIcon,
} from '@vben/icons';
import { useClipboard } from '@vueuse/core';
import { Skeleton, Tree } from 'ant-design-vue';
@ -20,7 +28,7 @@ interface TreeNode {
children: TreeNode[];
title: string;
key: string;
icon: string; //
icon: Component; //
}
const treeData = ref<TreeNode[]>([]);
@ -90,16 +98,16 @@ function convertToTree(paths: string[]): TreeNode[] {
}
const iconMap = [
{ key: 'java', value: 'skill-icons:java-light' },
{ key: 'xml', value: 'tabler:file-type-xml' },
{ key: 'sql', value: 'carbon:sql' },
{ key: 'ts', value: 'skill-icons:typescript' },
{ key: 'vue', value: 'logos:vue' },
{ key: 'folder', value: 'flat-color-icons:folder' },
{ key: 'java', value: JavaIcon },
{ key: 'xml', value: XmlIcon },
{ key: 'sql', value: SqlIcon },
{ key: 'ts', value: TsIcon },
{ key: 'vue', value: VueIcon },
{ key: 'folder', value: FolderIcon },
];
function findIcon(path: string) {
const defaultFileIcon = 'bx:file';
const defaultFolderIcon = 'flat-color-icons:folder';
const defaultFileIcon = DefaultFileIcon;
const defaultFolderIcon = FolderIcon;
if (path.endsWith('.vm')) {
const realPath = path.slice(0, -3);
//
@ -172,7 +180,7 @@ const { copy } = useClipboard({ legacy: true });
>
<template #title="{ title, icon }">
<div class="flex items-center gap-[16px]">
<IconifyIcon :icon="icon" />
<component :is="icon" />
<span>{{ title }}</span>
</div>
</template>

View File

@ -10,10 +10,6 @@ export default defineConfig(async () => {
vite: {
optimizeDeps: {
include: [
'echarts/core',
'echarts/charts',
'echarts/components',
'echarts/renderers',
'ant-design-vue/es/locale/zh_CN',
'ant-design-vue/es/locale/en_US',
],

View File

@ -38,7 +38,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
const src = row[column.field];
return h(ElImage, { src, previewSrcList: [src] });
@ -47,7 +47,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
ElButton,

View File

@ -1,11 +1,14 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/ele';
import { setupI18n } from '#/locales';
import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
@ -28,6 +31,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description
* 使
* !!!
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}

View File

@ -38,7 +38,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(NImage, { src: row[column.field] });
},
@ -46,7 +46,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
NButton,

View File

@ -1,10 +1,13 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import { setupI18n } from '#/locales';
import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
@ -27,6 +30,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description
* 使
* !!!
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}

View File

@ -3,7 +3,10 @@ import type { HeadConfig } from 'vitepress';
import { resolve } from 'node:path';
import { viteArchiverPlugin } from '@vben/vite-config';
import {
viteArchiverPlugin,
viteVxeTableImportsPlugin,
} from '@vben/vite-config';
import {
GitChangelog,
@ -85,6 +88,7 @@ export const shared = defineConfig({
GitChangelogMarkdownSection(),
viteArchiverPlugin({ outputDir: '.vitepress' }),
groupIconVitePlugin(),
await viteVxeTableImportsPlugin(),
],
server: {
fs: {
@ -156,6 +160,7 @@ function pwa(): PwaOptions {
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
},
};
}

View File

@ -15,11 +15,12 @@ import 'virtual:group-icons.css';
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
export default {
enhanceApp(ctx: EnhanceAppContext) {
async enhanceApp(ctx: EnhanceAppContext) {
const { app } = ctx;
app.component('VbenContributors', VbenContributors);
app.component('DemoPreview', DemoPreview);
app.use(NolebaseGitChangelogPlugin);
// 百度统计
initHmPlugin();
},

View File

@ -8,12 +8,16 @@
"docs:preview": "vitepress preview"
},
"imports": {
"#/*": "./src/_env/*"
"#/*": {
"node": "./src/_env/node/*",
"default": "./src/_env/*"
}
},
"dependencies": {
"@vben-core/shadcn-ui": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"ant-design-vue": "catalog:",
"lucide-vue-next": "catalog:",

View File

@ -1 +0,0 @@
export * from './form';

View File

@ -0,0 +1,70 @@
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
if (!import.meta.env.SSR) {
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
resizable: true,
},
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
}
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';

View File

@ -0,0 +1,4 @@
export const useVbenForm = () => {};
export const z = {};
export type VbenFormSchema = any;
export type VbenFormProps = any;

View File

@ -0,0 +1,3 @@
export type * from '@vben/plugins/vxe-table';
export const useVbenVxeGrid = () => {};

View File

@ -4,4 +4,235 @@ outline: deep
# Vben Vxe Table 表格
文档待补充,如果需要使用,可先行查看 vxe-table 文档和 示例代码,内部有部分注释。
框架提供的Table 列表组件基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid),结合`Vben Form 表单`进行了二次封装。
其中,表头的 **表单搜索** 部分采用了`Vben Form表单`,表格主体部分使用了`vxe-grid`组件,支持表格的分页、排序、筛选等功能。
> 如果文档内没有参数说明,可以尝试在在线示例或者在 [vxe-grid 官方API 文档](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 内寻找
::: info 写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
:::
## 适配器
表格底层使用 [vxe-table](https://vxetable.cn/#/start/install) 进行实现,所以你可以使用 `vxe-table` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。
### 适配器说明
每个应用都可以自己配置`vxe-table`的适配器,你可以根据自己的需求。下面是一个简单的配置示例:
::: details vxe-table 表格适配器
```ts
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
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: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
```
:::
## 基础表格
使用 `useVbenVxeGrid` 创建最基础的表格。
<DemoPreview dir="demos/vben-vxe-table/basic" />
## 远程加载
通过指定 `proxyConfig.ajax``query` 方法,可以实现远程加载数据。
<DemoPreview dir="demos/vben-vxe-table/remote" />
## 树形表格
树形表格的数据源为扁平结构,可以指定`treeConfig`配置项,实现树形表格。
```typescript
treeConfig: {
transform: true, // 指定表格为树形表格
parentField: 'parentId', // 父节点字段名
rowField: 'id', // 行数据字段名
},
```
<DemoPreview dir="demos/vben-vxe-table/tree" />
## 固定表头/列
列固定可选参数: `'left' | 'right' | '' | null`
<DemoPreview dir="demos/vben-vxe-table/fixed" />
## 自定义单元格
自定义单元格有两种实现方式
- 通过 `slots` 插槽
- 通过 `customCell` 自定义单元格,但是要先添加渲染器
```typescript
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] } as any); // 注意此处的Image 组件来源于Antd需要自行引入,否则会使用js的Image类
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
```
<DemoPreview dir="demos/vben-vxe-table/custom-cell" />
## 搜索表单
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
<DemoPreview dir="demos/vben-vxe-table/form" />
## 单元格编辑
通过指定`editConfig.mode`为`cell`,可以实现单元格编辑。
<DemoPreview dir="demos/vben-vxe-table/edit-cell" />
## 行编辑
通过指定`editConfig.mode`为`row`,可以实现行编辑。
<DemoPreview dir="demos/vben-vxe-table/edit-row" />
## 虚拟滚动
通过 scroll-y.enabled 与 scroll-y.gt 组合开启,其中 enabled 为总开关gt 是指当总行数大于指定行数时自动开启。
> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)。
<DemoPreview dir="demos/vben-vxe-table/virtual" />
## API
`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格的方法。
```vue
<script setup lang="ts">
import { useVbenVxeGrid } from '#/adapter/vxe-table';
// Grid 为表格组件
// gridApi 为表格的方法
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {},
formOptions: {},
gridEvents: {},
// 属性
// 事件
});
</script>
<template>
<Grid />
</template>
```
### GridApi
useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
| 方法名 | 描述 | 类型 |
| --- | --- | --- |
| setLoading | 设置loading状态 | `(loading)=>void` |
| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` |
| reload | 重载表格,会进行初始化 | `(params:any)=>void` |
| query | 重载表格,会保留当前分页 | `(params:any)=>void` |
| grid | vxe-table grid实例 | `VxeGridInstance` |
| formApi | vbenForm api实例 | `FormApi` |
## Props
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
| 属性名 | 描述 | 类型 |
| -------------- | ------------------ | ------------------- |
| tableTitle | 表格标题 | `string` |
| tableTitleHelp | 表格标题帮助信息 | `string` |
| gridClass | grid组件的class | `string` |
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
| gridEvents | grid组件的触发的⌚ | `VxeGridListeners` |
| formOptions | 表单参数 | `VbenFormProps` |

View File

@ -0,0 +1,85 @@
<script lang="ts" setup>
import type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { MOCK_TABLE_DATA } from '../table-data';
interface RowType {
address: string;
age: number;
id: number;
name: string;
nickname: string;
role: string;
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ field: 'name', title: 'Name' },
{ field: 'age', sortable: true, title: 'Age' },
{ field: 'nickname', title: 'Nickname' },
{ field: 'role', title: 'Role' },
{ field: 'address', showOverflow: true, title: 'Address' },
],
data: MOCK_TABLE_DATA,
pagerConfig: {
enabled: false,
},
sortConfig: {
multiple: true,
},
};
const gridEvents: VxeGridListeners<RowType> = {
cellClick: ({ row }) => {
message.info(`cell-click: ${row.name}`);
},
};
const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions });
const showBorder = gridApi.useStore((state) => state.gridOptions?.border);
const showStripe = gridApi.useStore((state) => state.gridOptions?.stripe);
function changeBorder() {
gridApi.setGridOptions({
border: !showBorder.value,
});
}
function changeStripe() {
gridApi.setGridOptions({
stripe: !showStripe.value,
});
}
function changeLoading() {
gridApi.setLoading(true);
setTimeout(() => {
gridApi.setLoading(false);
}, 2000);
}
</script>
<template>
<!-- 此处的`vp-raw` 是为了适配文档的展示效果实际使用时不需要 -->
<div class="vp-raw w-full">
<Grid>
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="changeBorder">
{{ showBorder ? '隐藏' : '显示' }}边框
</Button>
<Button class="mr-2" type="primary" @click="changeLoading">
显示loading
</Button>
<Button class="mr-2" type="primary" @click="changeStripe">
{{ showStripe ? '隐藏' : '显示' }}斑马纹
</Button>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,105 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button, Image, Switch, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getExampleTableApi } from '../mock-api';
interface RowType {
category: string;
color: string;
id: string;
imageUrl: string;
open: boolean;
price: string;
productName: string;
releaseDate: string;
status: 'error' | 'success' | 'warning';
}
const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: 'name',
},
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ field: 'category', title: 'Category', width: 100 },
{
field: 'imageUrl',
slots: { default: 'image-url' },
title: 'Image',
width: 100,
},
{
cellRender: { name: 'CellImage' },
field: 'imageUrl2',
title: 'Render Image',
width: 130,
},
{
field: 'open',
slots: { default: 'open' },
title: 'Open',
width: 100,
},
{
field: 'status',
slots: { default: 'status' },
title: 'Status',
width: 100,
},
{ field: 'color', title: 'Color', width: 100 },
{ field: 'productName', title: 'Product Name', width: 200 },
{ field: 'price', title: 'Price', width: 100 },
{
field: 'releaseDate',
formatter: 'formatDateTime',
title: 'Date',
width: 200,
},
{
cellRender: { name: 'CellLink', props: { text: '编辑' } },
field: 'action',
fixed: 'right',
title: '操作',
width: 120,
},
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
},
};
const [Grid] = useVbenVxeGrid({ gridOptions });
</script>
<template>
<div class="vp-raw w-full">
<Grid>
<template #image-url="{ row }">
<Image :src="row.imageUrl" height="30" width="30" />
</template>
<template #open="{ row }">
<Switch v-model:checked="row.open" />
</template>
<template #status="{ row }">
<Tag :color="row.color">{{ row.status }}</Tag>
</template>
<template #action>
<Button type="link">编辑</Button>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,55 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getExampleTableApi } from '../mock-api';
interface RowType {
category: string;
color: string;
id: string;
price: string;
productName: string;
releaseDate: string;
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ editRender: { name: 'input' }, field: 'category', title: 'Category' },
{ editRender: { name: 'input' }, field: 'color', title: 'Color' },
{
editRender: { name: 'input' },
field: 'productName',
title: 'Product Name',
},
{ field: 'price', title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
],
editConfig: {
mode: 'cell',
trigger: 'click',
},
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
},
showOverflow: true,
};
const [Grid] = useVbenVxeGrid({ gridOptions });
</script>
<template>
<div class="vp-raw w-full">
<Grid />
</div>
</template>

View File

@ -0,0 +1,92 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getExampleTableApi } from '../mock-api';
interface RowType {
category: string;
color: string;
id: string;
price: string;
productName: string;
releaseDate: string;
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ editRender: { name: 'input' }, field: 'category', title: 'Category' },
{ editRender: { name: 'input' }, field: 'color', title: 'Color' },
{
editRender: { name: 'input' },
field: 'productName',
title: 'Product Name',
},
{ field: 'price', title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
{ slots: { default: 'action' }, title: '操作' },
],
editConfig: {
mode: 'row',
trigger: 'click',
},
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
},
showOverflow: true,
};
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
function hasEditStatus(row: RowType) {
return gridApi.grid?.isEditByRow(row);
}
function editRowEvent(row: RowType) {
gridApi.grid?.setEditRow(row);
}
async function saveRowEvent(row: RowType) {
await gridApi.grid?.clearEdit();
gridApi.setLoading(true);
setTimeout(() => {
gridApi.setLoading(false);
message.success({
content: `保存成功category=${row.category}`,
});
}, 600);
}
const cancelRowEvent = (_row: RowType) => {
gridApi.grid?.clearEdit();
};
</script>
<template>
<div class="vp-raw w-full">
<Grid>
<template #action="{ row }">
<template v-if="hasEditStatus(row)">
<Button type="link" @click="saveRowEvent(row)">保存</Button>
<Button type="link" @click="cancelRowEvent(row)">取消</Button>
</template>
<template v-else>
<Button type="link" @click="editRowEvent(row)">编辑</Button>
</template>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,67 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getExampleTableApi } from '../mock-api';
interface RowType {
category: string;
color: string;
id: string;
price: string;
productName: string;
releaseDate: string;
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ fixed: 'left', title: '序号', type: 'seq', width: 50 },
{ field: 'category', title: 'Category', width: 300 },
{ field: 'color', title: 'Color', width: 300 },
{ field: 'productName', title: 'Product Name', width: 300 },
{ field: 'price', title: 'Price', width: 300 },
{
field: 'releaseDate',
formatter: 'formatDateTime',
title: 'DateTime',
width: 500,
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 120,
},
],
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
},
rowConfig: {
isHover: true,
},
};
const [Grid] = useVbenVxeGrid({ gridOptions });
</script>
<template>
<div class="vp-raw w-full">
<Grid>
<template #action>
<Button type="link">编辑</Button>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,120 @@
<script lang="ts" setup>
import type { VbenFormProps } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getExampleTableApi } from '../mock-api';
interface RowType {
category: string;
color: string;
id: string;
price: string;
productName: string;
releaseDate: string;
}
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
componentProps: {
placeholder: 'Please enter category',
},
defaultValue: '1',
fieldName: 'category',
label: 'Category',
},
{
component: 'Input',
componentProps: {
placeholder: 'Please enter productName',
},
fieldName: 'productName',
label: 'ProductName',
},
{
component: 'Input',
componentProps: {
placeholder: 'Please enter price',
},
fieldName: 'price',
label: 'Price',
},
{
component: 'Select',
componentProps: {
allowClear: true,
options: [
{
label: 'Color1',
value: '1',
},
{
label: 'Color2',
value: '2',
},
],
placeholder: '请选择',
},
fieldName: 'color',
label: 'Color',
},
{
component: 'DatePicker',
fieldName: 'datePicker',
label: 'Date',
},
],
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
//
submitOnEnter: false,
};
const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: 'name',
},
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 },
{ field: 'category', title: 'Category' },
{ field: 'color', title: 'Color' },
{ field: 'productName', title: 'Product Name' },
{ field: 'price', title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
message.success(`Query params: ${JSON.stringify(formValues)}`);
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
};
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
</script>
<template>
<div class="vp-raw w-full">
<Grid />
</div>
</template>

View File

@ -0,0 +1,36 @@
import { MOCK_API_DATA } from './table-data';
export namespace DemoTableApi {
export interface PageFetchParams {
[key: string]: any;
page: number;
pageSize: number;
}
}
export function sleep(time = 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
}
/**
*
*/
async function getExampleTableApi(params: DemoTableApi.PageFetchParams) {
return new Promise<{ items: any; total: number }>((resolve) => {
const { page, pageSize } = params;
const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);
sleep(1000).then(() => {
resolve({
total: items.length,
items,
});
});
});
}
export { getExampleTableApi };

View File

@ -0,0 +1,112 @@
<script lang="ts" setup>
import type { DemoTableApi } from '../mock-api';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { MOCK_API_DATA } from '../table-data';
interface RowType {
category: string;
color: string;
id: string;
price: string;
productName: string;
releaseDate: string;
}
//
// const MOCK_TREE_TABLE_DATA = [
// {
// date: '2020-08-01',
// id: 10_000,
// name: 'Test1',
// parentId: null,
// size: 1024,
// type: 'mp3',
// },
// ]
const sleep = (time = 1000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
/**
* 获取示例表格数据
*/
async function getExampleTableApi(params: DemoTableApi.PageFetchParams) {
return new Promise<{ items: any; total: number }>((resolve) => {
const { page, pageSize } = params;
const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);
sleep(1000).then(() => {
resolve({
total: items.length,
items,
});
});
});
}
const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: 'name',
},
columns: [
{ title: '序号', type: 'seq', width: 50 },
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 },
{ field: 'category', title: 'Category' },
{ field: 'color', title: 'Color' },
{ field: 'productName', title: 'Product Name' },
{ field: 'price', title: 'Price' },
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },
],
exportConfig: {},
// height: 'auto', // auto
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getExampleTableApi({
page: page.currentPage,
pageSize: page.pageSize,
});
},
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions,
});
</script>
<template>
<div class="vp-raw w-full">
<Grid>
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="() => gridApi.query()">
刷新当前页面
</Button>
<Button type="primary" @click="() => gridApi.reload()">
刷新并返回第一页
</Button>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,384 @@
interface TableRowData {
address: string;
age: number;
id: number;
name: string;
nickname: string;
role: string;
}
const roles = ['User', 'Admin', 'Manager', 'Guest'];
export const MOCK_TABLE_DATA: TableRowData[] = (() => {
const data: TableRowData[] = [];
for (let i = 0; i < 10; i++) {
data.push({
address: `New York${i}`,
age: i + 1,
id: i,
name: `Test${i}`,
nickname: `Test${i}`,
role: roles[Math.floor(Math.random() * roles.length)] as string,
});
}
return data;
})();
export const MOCK_TREE_TABLE_DATA = [
{
date: '2020-08-01',
id: 10_000,
name: 'Test1',
parentId: null,
size: 1024,
type: 'mp3',
},
{
date: '2021-04-01',
id: 10_050,
name: 'Test2',
parentId: null,
size: 0,
type: 'mp4',
},
{
date: '2020-03-01',
id: 24_300,
name: 'Test3',
parentId: 10_050,
size: 1024,
type: 'avi',
},
{
date: '2021-04-01',
id: 20_045,
name: 'Test4',
parentId: 24_300,
size: 600,
type: 'html',
},
{
date: '2021-04-01',
id: 10_053,
name: 'Test5',
parentId: 24_300,
size: 0,
type: 'avi',
},
{
date: '2021-10-01',
id: 24_330,
name: 'Test6',
parentId: 10_053,
size: 25,
type: 'txt',
},
{
date: '2020-01-01',
id: 21_011,
name: 'Test7',
parentId: 10_053,
size: 512,
type: 'pdf',
},
{
date: '2021-06-01',
id: 22_200,
name: 'Test8',
parentId: 10_053,
size: 1024,
type: 'js',
},
{
date: '2020-11-01',
id: 23_666,
name: 'Test9',
parentId: null,
size: 2048,
type: 'xlsx',
},
{
date: '2021-06-01',
id: 23_677,
name: 'Test10',
parentId: 23_666,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 23_671,
name: 'Test11',
parentId: 23_677,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 23_672,
name: 'Test12',
parentId: 23_677,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 23_688,
name: 'Test13',
parentId: 23_666,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 23_681,
name: 'Test14',
parentId: 23_688,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 23_682,
name: 'Test15',
parentId: 23_688,
size: 1024,
type: 'js',
},
{
date: '2020-10-01',
id: 24_555,
name: 'Test16',
parentId: null,
size: 224,
type: 'avi',
},
{
date: '2021-06-01',
id: 24_566,
name: 'Test17',
parentId: 24_555,
size: 1024,
type: 'js',
},
{
date: '2021-06-01',
id: 24_577,
name: 'Test18',
parentId: 24_555,
size: 1024,
type: 'js',
},
];
export const MOCK_API_DATA = [
{
available: true,
category: 'Computers',
color: 'purple',
currency: 'NAD',
description:
'Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support',
id: '45a613df-227a-4907-a89f-4a7f1252ca0c',
imageUrl: 'https://avatars.githubusercontent.com/u/62715097',
imageUrl2: 'https://avatars.githubusercontent.com/u/75395683',
inProduction: false,
open: true,
price: '48.89',
productName: 'Handcrafted Steel Salad',
quantity: 70,
rating: 3.780_582_329_574_367,
releaseDate: '2024-09-09T04:06:57.793Z',
status: 'error',
tags: ['Bespoke', 'Handmade', 'Luxurious'],
weight: 1.031_015_671_912_002_5,
},
{
available: true,
category: 'Toys',
color: 'green',
currency: 'CZK',
description:
'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',
id: 'd02e5ee9-bc98-4de2-98fa-25a6567ecc19',
imageUrl: 'https://avatars.githubusercontent.com/u/51512330',
imageUrl2: 'https://avatars.githubusercontent.com/u/58698113',
inProduction: false,
open: false,
price: '68.15',
productName: 'Generic Cotton Gloves',
quantity: 3,
rating: 1.681_749_367_682_703_3,
releaseDate: '2024-06-16T09:00:36.806Z',
status: 'warning',
tags: ['Rustic', 'Handcrafted', 'Recycled'],
weight: 9.601_076_149_300_575,
},
{
available: true,
category: 'Beauty',
color: 'teal',
currency: 'OMR',
description:
'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',
id: '2b72521c-225c-4e64-8030-611b76b10b37',
imageUrl: 'https://avatars.githubusercontent.com/u/50300075',
imageUrl2: 'https://avatars.githubusercontent.com/u/36541691',
inProduction: true,
open: true,
price: '696.94',
productName: 'Gorgeous Soft Ball',
quantity: 50,
rating: 2.361_581_777_372_057_5,
releaseDate: '2024-06-03T13:24:19.809Z',
status: 'warning',
tags: ['Gorgeous', 'Ergonomic', 'Licensed'],
weight: 8.882_340_049_286_19,
},
{
available: true,
category: 'Games',
color: 'silver',
currency: 'SOS',
description:
'Carbonite web goalkeeper gloves are ergonomically designed to give easy fit',
id: 'bafab694-3801-452c-b102-9eb519bd1143',
imageUrl: 'https://avatars.githubusercontent.com/u/89827115',
imageUrl2: 'https://avatars.githubusercontent.com/u/55952747',
inProduction: false,
open: false,
price: '553.84',
productName: 'Bespoke Soft Computer',
quantity: 29,
rating: 2.176_412_873_760_271_7,
releaseDate: '2024-09-17T12:16:27.034Z',
status: 'error',
tags: ['Elegant', 'Rustic', 'Recycled'],
weight: 9.653_285_869_978_038,
},
{
available: true,
category: 'Toys',
color: 'indigo',
currency: 'BIF',
description:
'Andy shoes are designed to keeping in mind durability as well as trends, the most stylish range of shoes & sandals',
id: 'bf6dea6b-2a55-441d-8773-937e03d99389',
imageUrl: 'https://avatars.githubusercontent.com/u/21431092',
imageUrl2: 'https://avatars.githubusercontent.com/u/3771350',
inProduction: true,
open: true,
price: '237.39',
productName: 'Handcrafted Cotton Mouse',
quantity: 54,
rating: 4.363_265_388_265_461,
releaseDate: '2023-10-23T13:42:34.947Z',
status: 'error',
tags: ['Unbranded', 'Handmade', 'Generic'],
weight: 9.513_203_612_535_571,
},
{
available: false,
category: 'Tools',
color: 'violet',
currency: 'TZS',
description:
'New ABC 13 9370, 13.3, 5th Gen CoreA5-8250U, 8GB RAM, 256GB SSD, power UHD Graphics, OS 10 Home, OS Office A & J 2016',
id: '135ba6ab-32ee-4989-8189-5cfa658ef970',
imageUrl: 'https://avatars.githubusercontent.com/u/29946092',
imageUrl2: 'https://avatars.githubusercontent.com/u/23842994',
inProduction: false,
open: false,
price: '825.25',
productName: 'Awesome Bronze Ball',
quantity: 94,
rating: 4.251_159_804_726_753,
releaseDate: '2023-12-30T07:31:43.464Z',
status: 'warning',
tags: ['Handmade', 'Elegant', 'Unbranded'],
weight: 2.247_473_385_732_636_8,
},
{
available: true,
category: 'Automotive',
color: 'teal',
currency: 'BOB',
description: 'The Football Is Good For Training And Recreational Purposes',
id: '652ef256-7d4e-48b7-976c-7afaa781ea92',
imageUrl: 'https://avatars.githubusercontent.com/u/2531904',
imageUrl2: 'https://avatars.githubusercontent.com/u/15215990',
inProduction: false,
open: false,
price: '780.49',
productName: 'Oriental Rubber Pants',
quantity: 70,
rating: 2.636_323_417_377_916,
releaseDate: '2024-02-23T23:30:49.628Z',
status: 'success',
tags: ['Unbranded', 'Elegant', 'Unbranded'],
weight: 4.812_965_858_018_838,
},
{
available: false,
category: 'Garden',
color: 'plum',
currency: 'LRD',
description:
'The slim & simple Maple Gaming Keyboard from Dev Byte comes with a sleek body and 7- Color RGB LED Back-lighting for smart functionality',
id: '3ea24798-6589-40cc-85f0-ab78752244a0',
imageUrl: 'https://avatars.githubusercontent.com/u/23165285',
imageUrl2: 'https://avatars.githubusercontent.com/u/14595665',
inProduction: false,
open: true,
price: '583.85',
productName: 'Handcrafted Concrete Hat',
quantity: 15,
rating: 1.371_600_527_752_802_7,
releaseDate: '2024-03-02T19:40:50.255Z',
status: 'error',
tags: ['Rustic', 'Sleek', 'Ergonomic'],
weight: 4.926_949_366_405_728_4,
},
{
available: false,
category: 'Industrial',
color: 'salmon',
currency: 'AUD',
description:
'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',
id: '997113dd-f6e4-4acc-9790-ef554c7498d1',
imageUrl: 'https://avatars.githubusercontent.com/u/49021914',
imageUrl2: 'https://avatars.githubusercontent.com/u/4690621',
inProduction: true,
open: false,
price: '67.99',
productName: 'Generic Rubber Bacon',
quantity: 68,
rating: 4.129_840_682_128_08,
releaseDate: '2023-12-17T01:40:25.415Z',
status: 'error',
tags: ['Oriental', 'Small', 'Handcrafted'],
weight: 1.080_114_331_801_906_4,
},
{
available: false,
category: 'Tools',
color: 'sky blue',
currency: 'NOK',
description:
'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',
id: 'f697a250-6cb2-46c8-b0f7-871ab1f2fa8d',
imageUrl: 'https://avatars.githubusercontent.com/u/95928385',
imageUrl2: 'https://avatars.githubusercontent.com/u/47588244',
inProduction: false,
open: false,
price: '613.89',
productName: 'Gorgeous Frozen Ball',
quantity: 55,
rating: 1.646_947_205_998_534_6,
releaseDate: '2024-10-13T12:31:04.929Z',
status: 'warning',
tags: ['Handmade', 'Unbranded', 'Unbranded'],
weight: 9.430_690_557_758_114,
},
];

View File

@ -0,0 +1,80 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { MOCK_TREE_TABLE_DATA } from '../table-data';
interface RowType {
date: string;
id: number;
name: string;
parentId: null | number;
size: number;
type: string;
}
//
// const MOCK_TREE_TABLE_DATA = [
// {
// date: '2020-08-01',
// id: 10_000,
// name: 'Test1',
// parentId: null,
// size: 1024,
// type: 'mp3',
// },
// {
// date: '2021-04-01',
// id: 10_050,
// name: 'Test2',
// parentId: 10_000,
// size: 0,
// type: 'mp4',
// },
// ];
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ type: 'seq', width: 70 },
{ field: 'name', minWidth: 300, title: 'Name', treeNode: true },
{ field: 'size', title: 'Size' },
{ field: 'type', title: 'Type' },
{ field: 'date', title: 'Date' },
],
data: MOCK_TREE_TABLE_DATA,
pagerConfig: {
enabled: false,
},
treeConfig: {
parentField: 'parentId',
rowField: 'id',
transform: true,
},
};
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
const expandAll = () => {
gridApi.grid?.setAllTreeExpand(true);
};
const collapseAll = () => {
gridApi.grid?.setAllTreeExpand(false);
};
</script>
<template>
<div class="vp-raw h-[300px] w-full">
<Grid>
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="expandAll">
展开全部
</Button>
<Button type="primary" @click="collapseAll"> 折叠全部 </Button>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,64 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { onMounted } from 'vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
interface RowType {
id: number;
name: string;
role: string;
sex: string;
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ type: 'seq', width: 70 },
{ field: 'name', title: 'Name' },
{ field: 'role', title: 'Role' },
{ field: 'sex', title: 'Sex' },
],
data: [],
height: 'auto',
pagerConfig: {
enabled: false,
},
scrollY: {
enabled: true,
gt: 0,
},
showOverflow: true,
};
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
//
const loadList = (size = 200) => {
try {
const dataList: RowType[] = [];
for (let i = 0; i < size; i++) {
dataList.push({
id: 10_000 + i,
name: `Test${i}`,
role: 'Developer',
sex: '男',
});
}
gridApi.setGridOptions({ data: dataList });
} catch (error) {
console.error('Failed to load data:', error);
// Implement user-friendly error handling
}
};
onMounted(() => {
loadList(1000);
});
</script>
<template>
<div class="vp-raw h-[500px] w-full">
<Grid />
</div>
</template>

View File

@ -72,7 +72,7 @@ const { b, e, is } = useNamespace('menu');
</template>
<style lang="scss" scoped>
// If you use it within the application, this line of code can be omitted as it has already been globally introduced in all applications
@import '@vben/styles/global';
@use '@vben/styles/global' as *;
@include b('menu') {
color: black;

View File

@ -164,6 +164,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides
@ -536,5 +537,4 @@ interface Preferences {
- `overridesPreferences`方法只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置。
- 任何配置项都可以覆盖,只需要在`overridesPreferences`方法内覆盖即可,不要修改默认配置文件。
:::
- 更改配置后请清空缓存,否则可能不生效。:::

View File

@ -72,7 +72,7 @@ const { b, e, is } = useNamespace('menu');
</template>
<style lang="scss" scoped>
// 如果你在应用内使用,这行代码可以省略,已经在所有的应用内全局引入了
@import '@vben/styles/global';
@use '@vben/styles/global' as *;
@include b('menu') {
color: black;

View File

@ -66,7 +66,9 @@ pnpm install
::: tip 注意
项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
- 项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
- 如果你的网络环境无法访问npm源你可以设置系统的环境变量`COREPACK_REGISTRY=https://registry.npmmirror.com`,然后再执行`pnpm install`。
- 如果你不想使用`corepack`,你需要禁用`corepack`,然后使用你自己的`pnpm`进行安装。
:::

View File

@ -109,7 +109,7 @@ function createCssOptions(injectGlobalScss = true) {
const relativePath = relative(root, filepath);
// apps下的包注入全局样式
if (relativePath.startsWith(`apps${path.sep}`)) {
return `@import "@vben/styles/global";\n${content}`;
return `@use "@vben/styles/global" as *;\n${content}`;
}
return content;
},

View File

@ -243,4 +243,5 @@ export {
viteDtsPlugin,
viteHtmlPlugin,
viteVisualizerPlugin,
viteVxeTableImportsPlugin,
};

View File

@ -4,6 +4,35 @@ import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';
async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
return [
// {
// config() {
// return {
// optimizeDeps: {
// include: [
// 'vxe-pc-ui/es/vxe-button/index.js',
// 'vxe-pc-ui/es/vxe-checkbox/index.js',
// 'vxe-pc-ui/es/vxe-icon/index.js',
// 'vxe-pc-ui/es/vxe-input/index.js',
// 'vxe-pc-ui/es/vxe-loading/index.js',
// 'vxe-pc-ui/es/vxe-modal/index.js',
// 'vxe-pc-ui/es/vxe-pager/index.js',
// 'vxe-pc-ui/es/vxe-radio-group/index.js',
// 'vxe-pc-ui/es/vxe-select/index.js',
// 'vxe-pc-ui/es/vxe-tooltip/index.js',
// 'vxe-pc-ui/es/vxe-ui/index.js',
// 'vxe-pc-ui/es/vxe-upload/index.js',
// 'vxe-table/es/vxe-colgroup/index.js',
// 'vxe-table/es/vxe-column/index.js',
// 'vxe-table/es/vxe-grid/index.js',
// 'vxe-table/es/vxe-table/index.js',
// 'vxe-table/es/vxe-toolbar/index.js',
// 'vxe-table/es/vxe-ui/index.js',
// ],
// },
// };
// },
// name: 'vxe-table-adapter',
// },
lazyImport({
resolvers: [
VxeResolver({

View File

@ -97,7 +97,7 @@
},
"engines": {
"node": ">=20.10.0",
"pnpm": ">=9.5.0"
"pnpm": ">=9.12.0"
},
"packageManager": "pnpm@9.12.3",
"pnpm": {

View File

@ -1,6 +1,6 @@
import { defineComponent, h } from 'vue';
import { Icon } from '@iconify/vue';
import { addIcon, Icon, type IconifyIcon } from '@iconify/vue';
function createIconifyIcon(icon: string) {
return defineComponent({
@ -11,4 +11,20 @@ function createIconifyIcon(icon: string) {
});
}
export { createIconifyIcon };
/**
* 线
* @param icon iconify的名称保持一致
* @param iconComponent @iconify/icon-xxx/xxx导入的图标
* @returns IconComponent
*/
function createIconifyOfflineIcon(icon: string, iconComponent: IconifyIcon) {
return defineComponent({
name: `Icon-${icon}`,
setup(props, { attrs }) {
addIcon(icon, iconComponent);
return () => h(Icon, { icon, ...props, ...attrs });
},
});
}
export { createIconifyIcon, createIconifyOfflineIcon };

View File

@ -5,3 +5,8 @@ export * from './lucide';
export type { IconifyIcon as IconifyIconStructure } from '@iconify/vue';
export { addCollection, addIcon, Icon as IconifyIcon } from '@iconify/vue';
/**
* @iconify/vue/dist/offline'线ICON 线
* @iconify/vue'线 线()
*/

View File

@ -253,6 +253,7 @@ export class FormApi {
}
unmount() {
this.form?.resetForm?.();
// this.state = null;
this.isMounted = false;
this.stateHandler.reset();

View File

@ -99,7 +99,7 @@ onBeforeUnmount(() => {
>
<template #trigger>
<div :class="[nsMenu.be('tooltip', 'trigger')]">
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
<slot></slot>
<span v-if="collapseShowTitle" :class="nsMenu.e('name')">
<slot name="title"></slot>

View File

@ -144,7 +144,6 @@ const tabsView = computed(() => {
:icon="tab.icon"
class="mr-1 flex size-4 items-center overflow-hidden"
/>
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
{{ tab.title }}
</span>

View File

@ -38,9 +38,9 @@
"@vben/types": "workspace:*",
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"codemirror": "^6.0.1",
"codemirror": "6.0.1",
"qrcode": "catalog:",
"vditor": "^3.10.6",
"vditor": "3.10.7",
"vue": "catalog:",
"vue-codemirror6": "1.3.4",
"vue-json-pretty": "^2.4.0",

View File

@ -1,4 +1,4 @@
:root {
:root .vxe-grid {
--vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary));
@ -14,7 +14,7 @@
/* layout */
--vxe-ui-layout-background-color: hsl(var(--background));
--vxe-ui-table-resizable-line-color: hsl(var(--border));
--vxe-ui-table-resizable-line-color: hsl(var(--heavy));
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
@ -66,15 +66,13 @@
box-shadow: 0 0 0 1px hsl(var(--border));
}
.vxe-pager {
&--wrapper {
display: flex;
align-items: center;
}
.vxe-pager--wrapper {
display: flex;
align-items: center;
}
&--sizes {
margin-right: auto;
}
.vxe-pager--sizes {
margin-right: auto;
}
}

View File

@ -41,3 +41,5 @@ export function useVbenVxeGrid(options: VxeGridProps) {
return [Grid, extendedApi] as const;
}
export type UseVbenVxeGrid = typeof useVbenVxeGrid;

View File

@ -11,6 +11,7 @@ import {
computed,
nextTick,
onMounted,
onUnmounted,
toRaw,
useSlots,
useTemplateRef,
@ -261,6 +262,11 @@ onMounted(() => {
props.api?.mount?.(gridRef.value, formApi);
init();
});
onUnmounted(() => {
formApi?.unmount?.();
props.api?.unmount?.();
});
</script>
<template>

View File

@ -17,6 +17,44 @@
}
},
"dependencies": {
"@vben-core/icons": "workspace:*"
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:^"
},
"devDependencies": {
"@iconify/icons-akar-icons": "^1.2.19",
"@iconify/icons-ant-design": "^1.2.7",
"@iconify/icons-arcticons": "^1.2.77",
"@iconify/icons-bi": "^1.2.19",
"@iconify/icons-bx": "^1.2.6",
"@iconify/icons-carbon": "^1.2.20",
"@iconify/icons-devicon": "^1.2.17",
"@iconify/icons-emojione": "^1.2.6",
"@iconify/icons-eos-icons": "^1.2.6",
"@iconify/icons-fa-brands": "^1.2.4",
"@iconify/icons-fe": "^1.2.5",
"@iconify/icons-flat-color-icons": "^1.2.5",
"@iconify/icons-fluent": "^1.2.38",
"@iconify/icons-fluent-mdl2": "^1.2.1",
"@iconify/icons-ic": "^1.2.13",
"@iconify/icons-icon-park-outline": "^1.2.11",
"@iconify/icons-icon-park-twotone": "^1.2.8",
"@iconify/icons-la": "^1.2.3",
"@iconify/icons-logos": "^1.2.36",
"@iconify/icons-lucide": "^1.2.135",
"@iconify/icons-majesticons": "^1.2.6",
"@iconify/icons-material-symbols": "^1.2.58",
"@iconify/icons-mdi": "^1.2.48",
"@iconify/icons-mingcute": "^1.2.9",
"@iconify/icons-noto": "^1.2.10",
"@iconify/icons-ph": "^1.2.5",
"@iconify/icons-ri": "^1.2.10",
"@iconify/icons-simple-icons": "^1.2.74",
"@iconify/icons-skill-icons": "^1.2.1",
"@iconify/icons-solar": "^1.2.3",
"@iconify/icons-streamline": "^1.2.3",
"@iconify/icons-tabler": "^1.2.95",
"@iconify/icons-uiw": "^1.2.6",
"@iconify/icons-vscode-icons": "^1.2.29",
"@iconify/icons-wpf": "^1.2.3"
}
}

View File

@ -0,0 +1,186 @@
import { createIconifyOfflineIcon } from '@vben-core/icons';
import githubOutlined from '@iconify/icons-ant-design/github-outlined';
import inboxIcon from '@iconify/icons-ant-design/inbox-outlined';
import userOutlined from '@iconify/icons-ant-design/user-outlined';
import ucIcon from '@iconify/icons-arcticons/uc-browser';
import defaultFileIcon from '@iconify/icons-bx/file';
import sqlIcon from '@iconify/icons-carbon/sql';
import linuxIcon from '@iconify/icons-devicon/linux';
import windowsIcon from '@iconify/icons-devicon/windows8';
import alipayIcon from '@iconify/icons-fa-brands/alipay';
import androidIcon from '@iconify/icons-flat-color-icons/android-os';
import comandLine from '@iconify/icons-flat-color-icons/command-line';
import folderIcon from '@iconify/icons-flat-color-icons/folder';
import defaultOsIcon from '@iconify/icons-ic/outline-computer';
import memoryIcon from '@iconify/icons-la/memory';
import chromeIcon from '@iconify/icons-logos/chrome';
import firefoxIcon from '@iconify/icons-logos/firefox';
import edgeIcon from '@iconify/icons-logos/microsoft-edge';
import operaIcon from '@iconify/icons-logos/opera';
import quarkIcon from '@iconify/icons-logos/quarkus-icon';
import redisIcon from '@iconify/icons-logos/redis';
import safariIcon from '@iconify/icons-logos/safari';
import vueIcon from '@iconify/icons-logos/vue';
import iphoneIcon from '@iconify/icons-majesticons/iphone-x-apps-line';
import menuIcon from '@iconify/icons-material-symbols/menu';
import okButtonIcon from '@iconify/icons-mdi/button-pointer';
import micromessengerIcon from '@iconify/icons-mdi/wechat';
import defaultBrowserIcon from '@iconify/icons-ph/browser-duotone';
import baiduIcon from '@iconify/icons-ri/baidu-fill';
import dingdingFill from '@iconify/icons-ri/dingding-fill';
import dingtalkIcon from '@iconify/icons-ri/dingding-line';
import taobaoIconFill from '@iconify/icons-ri/taobao-fill';
import giteeIcon from '@iconify/icons-simple-icons/gitee';
import qqIcon from '@iconify/icons-simple-icons/tencentqq';
import javaIcon from '@iconify/icons-skill-icons/java-light';
import tsIcon from '@iconify/icons-skill-icons/typescript';
import xmlIcon from '@iconify/icons-tabler/file-type-xml';
import githubOAuthIcon from '@iconify/icons-uiw/github';
import excelIcon from '@iconify/icons-vscode-icons/file-type-excel';
import osxIcon from '@iconify/icons-wpf/macos';
import './menu-icons';
// 用户 下拉菜单
export const GitHubOutlined = createIconifyOfflineIcon(
'ant-design:github-outlined',
githubOutlined,
);
export const UserOutlined = createIconifyOfflineIcon(
'ant-design:user-outlined',
userOutlined,
);
// 缓存监控使用
export const RedisIcon = createIconifyOfflineIcon('logos:redis', redisIcon);
export const CommandLineIcon = createIconifyOfflineIcon(
'flat-color-icons:command-line',
comandLine,
);
export const MemoryIcon = createIconifyOfflineIcon('la:memory', memoryIcon);
// 用户管理 导入
// Excel图标
export const ExcelIcon = createIconifyOfflineIcon(
'vscode-icons:file-type-excel',
excelIcon,
);
// 拖拽上传图标
export const InBoxIcon = createIconifyOfflineIcon(
'ant-design:inbox-outlined',
inboxIcon,
);
// 第三方登录相关图标
export const TaobaoIcon = createIconifyOfflineIcon(
'ri:taobao-fill',
taobaoIconFill,
);
export const AlipayIcon = createIconifyOfflineIcon(
'fa-brands:alipay',
alipayIcon,
);
export const DingdingIcon = createIconifyOfflineIcon(
'ri:dingding-fill',
dingdingFill,
);
export const GiteeIcon = createIconifyOfflineIcon(
'simple-icons:gitee',
giteeIcon,
);
export const GithubOAuthIcon = createIconifyOfflineIcon(
'uiw:github',
githubOAuthIcon,
);
// 系统相关图标
export const WindowsIcon = createIconifyOfflineIcon(
'devicon:windows8',
windowsIcon,
);
export const LinuxIcon = createIconifyOfflineIcon('devicon:linux', linuxIcon);
export const OSXIcon = createIconifyOfflineIcon('wpf:macos', osxIcon);
export const AndroidIcon = createIconifyOfflineIcon(
'flat-color-icons:android-os',
androidIcon,
);
export const IPhoneIcon = createIconifyOfflineIcon(
'majesticons:iphone-x-apps-line',
iphoneIcon,
);
// 上面图标没找到 默认图标
export const DefaultOsIcon = createIconifyOfflineIcon(
'ic:outline-computer',
defaultOsIcon,
);
// 浏览器相关图标
export const ChromeIcon = createIconifyOfflineIcon('logos:chrome', chromeIcon);
export const EdgeIcon = createIconifyOfflineIcon(
'logos:microsoft-edge',
edgeIcon,
);
export const FirefoxIcon = createIconifyOfflineIcon(
'logos:firefox',
firefoxIcon,
);
export const OperaIcon = createIconifyOfflineIcon('logos:opera', operaIcon);
export const SafariIcon = createIconifyOfflineIcon('logos:safari', safariIcon);
export const MicromessengerIcon = createIconifyOfflineIcon(
'mdi:wechat',
micromessengerIcon,
);
export const QuarkIcon = createIconifyOfflineIcon(
'logos:quarkus-icon',
quarkIcon,
);
export const QQIcon = createIconifyOfflineIcon(
'simple-icons:tencentqq',
qqIcon,
);
export const DingtalkIcon = createIconifyOfflineIcon(
'ri:dingding-line',
dingtalkIcon,
);
export const UcIcon = createIconifyOfflineIcon('arcticons:uc-browser', ucIcon);
export const BaiduIcon = createIconifyOfflineIcon('ri:baidu-fill', baiduIcon);
// 未知浏览器图标
export const DefaultBrowserIcon = createIconifyOfflineIcon(
'ph:browser-duotone',
defaultBrowserIcon,
);
// 菜单类型 目录/按钮/菜单
export const FolderIcon = createIconifyOfflineIcon(
'flat-color-icons:folder',
folderIcon,
);
export const OkButtonIcon = createIconifyOfflineIcon(
'mdi:button-pointer',
okButtonIcon,
);
export const MenuIcon = createIconifyOfflineIcon(
'material-symbols:menu',
menuIcon,
);
export const JavaIcon = createIconifyOfflineIcon(
'skill-icons:java-light',
javaIcon,
);
export const XmlIcon = createIconifyOfflineIcon(
'tabler:file-type-xml',
xmlIcon,
);
export const SqlIcon = createIconifyOfflineIcon('carbon:sql', sqlIcon);
export const TsIcon = createIconifyOfflineIcon(
'skill-icons:typescript',
tsIcon,
);
export const VueIcon = createIconifyOfflineIcon('logos:vue', vueIcon);
export const DefaultFileIcon = createIconifyOfflineIcon(
'flat-color-icons:folder',
defaultFileIcon,
);

View File

@ -0,0 +1,102 @@
import { addIcon } from '@vben-core/icons';
import schedule from '@iconify/icons-akar-icons/schedule';
import settingOutline from '@iconify/icons-ant-design/setting-outlined';
import Operation from '@iconify/icons-arcticons/one-hand-operation';
import BaseLineHousesFill from '@iconify/icons-bi/houses-fill';
import BxPackage from '@iconify/icons-bx/package';
import modelAlt from '@iconify/icons-carbon/model-alt';
import taskApproved from '@iconify/icons-carbon/task-approved';
import redisWordmark from '@iconify/icons-devicon/redis-wordmark';
import springWordmark from '@iconify/icons-devicon/spring-wordmark';
import vscode from '@iconify/icons-devicon/vscode';
import evergreenTree from '@iconify/icons-emojione/evergreen-tree';
import RoleBindingOutlined from '@iconify/icons-eos-icons/role-binding-outlined';
import SystemGroup from '@iconify/icons-eos-icons/system-group';
import NoticePush from '@iconify/icons-fe/notice-push';
import plus from '@iconify/icons-flat-color-icons/plus';
import from24 from '@iconify/icons-fluent/form-24-regular';
import builDefinition from '@iconify/icons-fluent-mdl2/build-definition';
import Dictionary from '@iconify/icons-fluent-mdl2/dictionary';
import leaveUser from '@iconify/icons-fluent-mdl2/leave-user';
import BaseLineHouse from '@iconify/icons-ic/baseline-house';
import monitor from '@iconify/icons-ic/baseline-monitor';
import roundLaunch from '@iconify/icons-ic/round-launch';
import MenuSharp from '@iconify/icons-ic/sharp-menu';
import Appointment from '@iconify/icons-icon-park-outline/appointment';
import SettingTwo from '@iconify/icons-icon-park-twotone/setting-two';
import boolOpenText from '@iconify/icons-lucide/book-open-text';
import copyright from '@iconify/icons-lucide/copyright';
import table from '@iconify/icons-lucide/table';
import cloudDoneOutlineRounded from '@iconify/icons-material-symbols/cloud-done-outline-rounded';
import generatingTokensOutline from '@iconify/icons-material-symbols/generating-tokens-outline';
import LogoDevOutline from '@iconify/icons-material-symbols/logo-dev-outline';
import ccOutline from '@iconify/icons-mdi/cc-outline';
import tools from '@iconify/icons-mdi/tools';
import workflowOutline from '@iconify/icons-mdi/workflow-outline';
import DepartmentLine from '@iconify/icons-mingcute/department-line';
import profileLine from '@iconify/icons-mingcute/profile-line';
import UserDuotone from '@iconify/icons-ph/user-duotone';
import insatnceLine from '@iconify/icons-ri/instance-line';
import todoLine from '@iconify/icons-ri/todo-line';
import Authy from '@iconify/icons-simple-icons/authy';
import FolderWithFilesOutline from '@iconify/icons-solar/folder-with-files-outline';
import monitorBoldDuotone from '@iconify/icons-solar/monitor-bold-duotone';
import InterfaceLoginDialPadFingerPasswordDialPadDotFinger from '@iconify/icons-streamline/interface-login-dial-pad-finger-password-dial-pad-dot-finger';
import categoryPlus from '@iconify/icons-tabler/category-plus';
import code from '@iconify/icons-tabler/code';
/**
*
*/
addIcon('eos-icons:system-group', SystemGroup);
addIcon('ph:user-duotone', UserDuotone);
addIcon('eos-icons:role-binding-outlined', RoleBindingOutlined);
addIcon('ic:sharp-menu', MenuSharp);
addIcon('mingcute:department-line', DepartmentLine);
addIcon('icon-park-outline:appointment', Appointment);
addIcon('fluent-mdl2:dictionary', Dictionary);
addIcon('icon-park-twotone:setting-two', SettingTwo);
addIcon('fe:notice-push', NoticePush);
addIcon('material-symbols:logo-dev-outline', LogoDevOutline);
addIcon('arcticons:one-hand-operation', Operation);
addIcon(
'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger',
InterfaceLoginDialPadFingerPasswordDialPadDotFinger,
);
addIcon('solar:folder-with-files-outline', FolderWithFilesOutline);
addIcon('simple-icons:authy', Authy);
addIcon('ic:baseline-house', BaseLineHouse);
addIcon('bi:houses-fill', BaseLineHousesFill);
addIcon('bx:package', BxPackage);
addIcon('solar:monitor-bold-duotone', monitorBoldDuotone);
addIcon('material-symbols:generating-tokens-outline', generatingTokensOutline);
addIcon('devicon:redis-wordmark', redisWordmark);
addIcon('devicon:spring-wordmark', springWordmark);
addIcon('akar-icons:schedule', schedule);
addIcon('mdi:tools', tools);
addIcon('tabler:code', code);
addIcon('flat-color-icons:plus', plus);
addIcon('devicon:vscode', vscode);
addIcon('lucide:table', table);
addIcon('emojione:evergreen-tree', evergreenTree);
addIcon('fluent-mdl2:leave-user', leaveUser);
addIcon('mdi:workflow-outline', workflowOutline);
addIcon('tabler:category-plus', categoryPlus);
addIcon('carbon:model-alt', modelAlt);
addIcon('fluent-mdl2:build-definition', builDefinition);
addIcon('fluent-mdl2:build-definition', builDefinition);
addIcon('icon-park-outline:monitor', monitor);
addIcon('ri:instance-line', insatnceLine);
addIcon('ri:todo-line', todoLine);
addIcon('fluent:form-24-regular', from24);
addIcon('carbon:task-approved', taskApproved);
addIcon('ic:round-launch', roundLaunch);
addIcon('material-symbols:cloud-done-outline-rounded', cloudDoneOutlineRounded);
addIcon('mdi:cc-outline', ccOutline);
addIcon('lucide:book-open-text', boolOpenText);
addIcon('lucide:copyright', copyright);
// 个人中心
addIcon('mingcute:profile-line', profileLine);
// oss配置
addIcon('ant-design:setting-outlined', settingOutline);

View File

@ -13,18 +13,6 @@ export const MdiGoogle = createIconifyIcon('mdi:google');
export const MdiQqchat = createIconifyIcon('mdi:qqchat');
export const EosSystem = createIconifyIcon('eos-icons:system-group');
// 缓存监控使用
export const RedisIcon = createIconifyIcon('logos:redis');
export const CommandLineIcon = createIconifyIcon(
'flat-color-icons:command-line',
);
export const MemoryIcon = createIconifyIcon('la:memory');
export const GiteeIcon = createIconifyIcon('simple-icons:gitee');
// 个人中心
export const ProfileIcon = createIconifyIcon('mingcute:profile-line');
// Excel图标
export const ExcelIcon = createIconifyIcon('vscode-icons:file-type-excel');
// 拖拽上传图标
export const InBoxIcon = createIconifyIcon('ant-design:inbox-outlined');

View File

@ -1,2 +1,4 @@
export * from './iconify/index.js';
export * from './iconify-offline/index.js';
export * from './svg/index.js';
export { VbenIcon } from '@vben-core/shadcn-ui';

View File

@ -1 +1 @@
@import '@vben-core/design/bem';
@use '@vben-core/design/bem' as *;

View File

@ -39,7 +39,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
@ -47,7 +47,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,

View File

@ -1,17 +1,19 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import { VueQueryPlugin } from '@tanstack/vue-query';
import { useTitle } from '@vueuse/core';
import { setupI18n } from '#/locales';
import { $t, setupI18n } from '#/locales';
import { router } from '#/router';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
@ -34,6 +36,16 @@ async function bootstrap(namespace: string) {
// 配置@tanstack/vue-query
app.use(VueQueryPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description
* 使
* !!!
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
@ -39,13 +36,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}

View File

@ -23,7 +23,7 @@ catalog:
'@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.14.0
'@faker-js/faker': ^9.2.0
'@iconify/json': ^2.2.267
'@iconify/json': ^2.2.269
'@iconify/tailwind': ^1.1.3
'@iconify/vue': ^4.1.2
'@intlify/core-base': ^10.0.4
@ -36,7 +36,7 @@ catalog:
'@stylistic/stylelint-plugin': ^3.1.1
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15
'@tanstack/vue-query': ^5.59.17
'@tanstack/vue-query': ^5.59.20
'@tanstack/vue-store': ^0.5.6
'@types/archiver': ^6.0.3
'@types/eslint': ^9.6.1
@ -44,13 +44,13 @@ catalog:
'@types/jsonwebtoken': ^9.0.7
'@types/lodash.clonedeep': ^4.5.9
'@types/lodash.get': ^4.4.9
'@types/node': ^22.8.7
'@types/node': ^22.9.0
'@types/nprogress': ^0.2.3
'@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.12.2
'@typescript-eslint/parser': ^8.12.2
'@typescript-eslint/eslint-plugin': ^8.13.0
'@typescript-eslint/parser': ^8.13.0
'@vee-validate/zod': ^4.14.6
'@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.1.4
@ -71,10 +71,10 @@ catalog:
circular-dependency-scanner: ^2.3.0
class-variance-authority: ^0.7.0
clsx: ^2.1.1
commitlint-plugin-function-rules: ^4.0.0
commitlint-plugin-function-rules: ^4.0.1
consola: ^3.2.3
cross-env: ^7.0.3
cspell: ^8.15.7
cspell: ^8.16.0
cssnano: ^7.0.6
cz-git: ^1.10.1
czg: ^1.10.1
@ -91,7 +91,7 @@ catalog:
eslint-plugin-import-x: ^4.4.0
eslint-plugin-jsdoc: ^50.4.3
eslint-plugin-jsonc: ^2.16.0
eslint-plugin-n: ^17.12.0
eslint-plugin-n: ^17.13.1
eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^3.9.1
eslint-plugin-prettier: ^5.2.1
@ -105,7 +105,7 @@ catalog:
get-port: ^7.1.0
globals: ^15.12.0
h3: ^1.13.0
happy-dom: ^15.8.3
happy-dom: ^15.11.0
html-minifier-terser: ^7.2.0
husky: ^9.1.6
is-ci: ^3.0.1
@ -117,7 +117,7 @@ catalog:
lucide-vue-next: ^0.454.0
medium-zoom: ^1.1.0
naive-ui: ^2.40.1
nitropack: ^2.10.2
nitropack: ^2.10.3
nprogress: ^0.2.0
ora: ^8.1.1
pinia: 2.2.2
@ -139,7 +139,7 @@ catalog:
rimraf: ^6.0.1
rollup: ^4.24.4
rollup-plugin-visualizer: ^5.12.0
sass: 1.79.5
sass: 1.80.6
sortablejs: ^1.15.3
stylelint: ^16.10.0
stylelint-config-recess-order: ^5.1.1
@ -165,8 +165,8 @@ catalog:
vite-plugin-html: ^3.2.2
vite-plugin-lazy-import: ^1.0.7
vite-plugin-pwa: ^0.20.5
vite-plugin-vue-devtools: ^7.6.2
vitepress: ^1.4.5
vite-plugin-vue-devtools: ^7.6.3
vitepress: ^1.5.0
vitepress-plugin-group-icons: ^1.3.0
vitest: ^2.1.4
vue: ^3.5.12
@ -174,8 +174,8 @@ catalog:
vue-i18n: ^10.0.4
vue-router: ^4.4.5
vue-tsc: ^2.1.10
vxe-pc-ui: ^4.2.40
vxe-table: ^4.8.0
vxe-pc-ui: ^4.2.45
vxe-table: ^4.8.2
watermark-js-plus: ^1.5.7
zod: ^3.23.8
zod-defaults: ^0.1.3

View File

@ -33,13 +33,13 @@ UPDATE sys_menu SET icon = 'emojione:evergreen-tree' WHERE menu_id = 1506;
UPDATE sys_menu SET icon = 'mdi:workflow-outline' WHERE menu_id = 11616;
UPDATE sys_menu SET icon = 'carbon:model-alt' WHERE menu_id = 11617;
UPDATE sys_menu SET icon = 'carbon:task-approved' WHERE menu_id = 11618;
UPDATE sys_menu SET icon = 'streamline:waiting-appointments-calendar' WHERE menu_id = 11619;
UPDATE sys_menu SET icon = 'ri:todo-line' WHERE menu_id = 11619;
UPDATE sys_menu SET icon = 'fluent-mdl2:build-definition' WHERE menu_id = 11620;
UPDATE sys_menu SET icon = 'ri:instance-line' WHERE menu_id = 11621;
UPDATE sys_menu SET icon = 'tabler:category-plus' WHERE menu_id = 11622;
UPDATE sys_menu SET icon = 'ic:round-launch' WHERE menu_id = 11629;
UPDATE sys_menu SET icon = 'icon-park-outline:monitor' WHERE menu_id = 11630;
UPDATE sys_menu SET icon = 'streamline:waiting-appointments-calendar' WHERE menu_id = 11631;
UPDATE sys_menu SET icon = 'ri:todo-line' WHERE menu_id = 11631;
UPDATE sys_menu SET icon = 'material-symbols:cloud-done-outline-rounded' WHERE menu_id = 11632;
UPDATE sys_menu SET icon = 'mdi:cc-outline' WHERE menu_id = 11633;
UPDATE sys_menu SET icon = 'fluent-mdl2:leave-user' WHERE menu_id = 11638;