mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
94efcec7da | ||
![]() |
a3d0d2ed34 | ||
![]() |
90dc00b168 | ||
![]() |
ba36ce8836 | ||
![]() |
57d5a919d2 | ||
![]() |
546c0928fb | ||
![]() |
5e44aa9283 | ||
![]() |
26bec4222f | ||
![]() |
4005023fd4 | ||
![]() |
8617d3dd1e | ||
![]() |
632081e828 | ||
![]() |
6b9acf09dc | ||
![]() |
2c6edafeb2 | ||
![]() |
9cf0573921 | ||
![]() |
da7d61b160 | ||
![]() |
8f1e397077 | ||
![]() |
dcdebaf7ca | ||
![]() |
4e88ef0840 | ||
![]() |
33ce4d3cf3 | ||
![]() |
6b54cb7563 |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -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,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
|
@@ -7,6 +7,7 @@ import type {
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
@@ -18,11 +19,15 @@ import {
|
||||
} from '@vben/common-ui';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||
// 例如:url: /dashboard/workspace
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
color: '',
|
||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '开源组',
|
||||
icon: 'carbon:logo-github',
|
||||
title: 'Github',
|
||||
url: 'https://github.com',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '算法组',
|
||||
icon: 'ion:logo-vue',
|
||||
title: 'Vue',
|
||||
url: 'https://vuejs.org',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '上班摸鱼',
|
||||
icon: 'ion:logo-html5',
|
||||
title: 'Html5',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: 'UI',
|
||||
icon: 'ion:logo-angular',
|
||||
title: 'Angular',
|
||||
url: 'https://angular.io',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '技术牛',
|
||||
icon: 'bx:bxl-react',
|
||||
title: 'React',
|
||||
url: 'https://reactjs.org',
|
||||
},
|
||||
{
|
||||
color: '#EBD94E',
|
||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '架构组',
|
||||
icon: 'ion:logo-javascript',
|
||||
title: 'Js',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||
},
|
||||
];
|
||||
|
||||
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#1fdaca',
|
||||
icon: 'ion:home-outline',
|
||||
title: '首页',
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
icon: 'ion:grid-outline',
|
||||
title: '仪表盘',
|
||||
url: '/dashboard',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
icon: 'ion:layers-outline',
|
||||
title: '组件',
|
||||
url: '/demos/features/icons',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:settings-outline',
|
||||
title: '系统管理',
|
||||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||
},
|
||||
{
|
||||
color: '#4daf1bc9',
|
||||
icon: 'ion:key-outline',
|
||||
title: '权限管理',
|
||||
url: '/demos/access/page-control',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
title: '图表',
|
||||
url: '/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
title: 'Vben',
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||
// This is a sample method, adjust according to the actual project requirements
|
||||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||
if (nav.url?.startsWith('http')) {
|
||||
openWindow(nav.url);
|
||||
return;
|
||||
}
|
||||
if (nav.url?.startsWith('/')) {
|
||||
router.push(nav.url).catch((error) => {
|
||||
console.error('Navigation failed:', error);
|
||||
});
|
||||
} else {
|
||||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="mr-4 w-full lg:w-3/5">
|
||||
<WorkbenchProject :items="projectItems" title="项目" />
|
||||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
|
@@ -7,6 +7,7 @@ import type {
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
@@ -18,11 +19,15 @@ import {
|
||||
} from '@vben/common-ui';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||
// 例如:url: /dashboard/workspace
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
color: '',
|
||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '开源组',
|
||||
icon: 'carbon:logo-github',
|
||||
title: 'Github',
|
||||
url: 'https://github.com',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '算法组',
|
||||
icon: 'ion:logo-vue',
|
||||
title: 'Vue',
|
||||
url: 'https://vuejs.org',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '上班摸鱼',
|
||||
icon: 'ion:logo-html5',
|
||||
title: 'Html5',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: 'UI',
|
||||
icon: 'ion:logo-angular',
|
||||
title: 'Angular',
|
||||
url: 'https://angular.io',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '技术牛',
|
||||
icon: 'bx:bxl-react',
|
||||
title: 'React',
|
||||
url: 'https://reactjs.org',
|
||||
},
|
||||
{
|
||||
color: '#EBD94E',
|
||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '架构组',
|
||||
icon: 'ion:logo-javascript',
|
||||
title: 'Js',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||
},
|
||||
];
|
||||
|
||||
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#1fdaca',
|
||||
icon: 'ion:home-outline',
|
||||
title: '首页',
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
icon: 'ion:grid-outline',
|
||||
title: '仪表盘',
|
||||
url: '/dashboard',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
icon: 'ion:layers-outline',
|
||||
title: '组件',
|
||||
url: '/demos/features/icons',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:settings-outline',
|
||||
title: '系统管理',
|
||||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||
},
|
||||
{
|
||||
color: '#4daf1bc9',
|
||||
icon: 'ion:key-outline',
|
||||
title: '权限管理',
|
||||
url: '/demos/access/page-control',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
title: '图表',
|
||||
url: '/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
title: 'Vben',
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||
// This is a sample method, adjust according to the actual project requirements
|
||||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||
if (nav.url?.startsWith('http')) {
|
||||
openWindow(nav.url);
|
||||
return;
|
||||
}
|
||||
if (nav.url?.startsWith('/')) {
|
||||
router.push(nav.url).catch((error) => {
|
||||
console.error('Navigation failed:', error);
|
||||
});
|
||||
} else {
|
||||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="mr-4 w-full lg:w-3/5">
|
||||
<WorkbenchProject :items="projectItems" title="项目" />
|
||||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-naive",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
|
@@ -7,6 +7,7 @@ import type {
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
@@ -18,11 +19,15 @@ import {
|
||||
} from '@vben/common-ui';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||
// 例如:url: /dashboard/workspace
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
color: '',
|
||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '开源组',
|
||||
icon: 'carbon:logo-github',
|
||||
title: 'Github',
|
||||
url: 'https://github.com',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '算法组',
|
||||
icon: 'ion:logo-vue',
|
||||
title: 'Vue',
|
||||
url: 'https://vuejs.org',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '上班摸鱼',
|
||||
icon: 'ion:logo-html5',
|
||||
title: 'Html5',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: 'UI',
|
||||
icon: 'ion:logo-angular',
|
||||
title: 'Angular',
|
||||
url: 'https://angular.io',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '技术牛',
|
||||
icon: 'bx:bxl-react',
|
||||
title: 'React',
|
||||
url: 'https://reactjs.org',
|
||||
},
|
||||
{
|
||||
color: '#EBD94E',
|
||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '架构组',
|
||||
icon: 'ion:logo-javascript',
|
||||
title: 'Js',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||
},
|
||||
];
|
||||
|
||||
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#1fdaca',
|
||||
icon: 'ion:home-outline',
|
||||
title: '首页',
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
icon: 'ion:grid-outline',
|
||||
title: '仪表盘',
|
||||
url: '/dashboard',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
icon: 'ion:layers-outline',
|
||||
title: '组件',
|
||||
url: '/demos/features/icons',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:settings-outline',
|
||||
title: '系统管理',
|
||||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||
},
|
||||
{
|
||||
color: '#4daf1bc9',
|
||||
icon: 'ion:key-outline',
|
||||
title: '权限管理',
|
||||
url: '/demos/access/page-control',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
title: '图表',
|
||||
url: '/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
title: 'Vben',
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||
// This is a sample method, adjust according to the actual project requirements
|
||||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||
if (nav.url?.startsWith('http')) {
|
||||
openWindow(nav.url);
|
||||
return;
|
||||
}
|
||||
if (nav.url?.startsWith('/')) {
|
||||
router.push(nav.url).catch((error) => {
|
||||
console.error('Navigation failed:', error);
|
||||
});
|
||||
} else {
|
||||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="mr-4 w-full lg:w-3/5">
|
||||
<WorkbenchProject :items="projectItems" title="项目" />
|
||||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
|
@@ -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,
|
||||
@@ -59,6 +62,11 @@ export const shared = defineConfig({
|
||||
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
|
||||
],
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern',
|
||||
},
|
||||
},
|
||||
},
|
||||
json: {
|
||||
stringify: true,
|
||||
@@ -85,6 +93,7 @@ export const shared = defineConfig({
|
||||
GitChangelogMarkdownSection(),
|
||||
viteArchiverPlugin({ outputDir: '.vitepress' }),
|
||||
groupIconVitePlugin(),
|
||||
await viteVxeTableImportsPlugin(),
|
||||
],
|
||||
server: {
|
||||
fs: {
|
||||
@@ -93,6 +102,7 @@ export const shared = defineConfig({
|
||||
host: true,
|
||||
port: 6173,
|
||||
},
|
||||
|
||||
ssr: {
|
||||
external: ['@vue/repl'],
|
||||
},
|
||||
@@ -156,6 +166,7 @@ function pwa(): PwaOptions {
|
||||
registerType: 'autoUpdate',
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -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();
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/docs",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
@@ -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:",
|
||||
|
@@ -1 +0,0 @@
|
||||
export * from './form';
|
70
docs/src/_env/adapter/vxe-table.ts
Normal file
70
docs/src/_env/adapter/vxe-table.ts
Normal 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';
|
4
docs/src/_env/node/adapter/form.ts
Normal file
4
docs/src/_env/node/adapter/form.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const useVbenForm = () => {};
|
||||
export const z = {};
|
||||
export type VbenFormSchema = any;
|
||||
export type VbenFormProps = any;
|
3
docs/src/_env/node/adapter/vxe-table.ts
Normal file
3
docs/src/_env/node/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
export const useVbenVxeGrid = () => {};
|
@@ -280,6 +280,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
| 方法名 | 描述 | 类型 |
|
||||
| --- | --- | --- |
|
||||
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
|
||||
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` |
|
||||
| resetForm | 重置表单 | `()=>Promise<void>` |
|
||||
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
|
||||
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
|
||||
@@ -309,6 +310,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` |
|
||||
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
||||
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
||||
| fieldMappingTime | 用于将表单内时间区域的应设成 2 个字段 | `[string, [string, string], string?][]` | - |
|
||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||
| schema | 表单项的每一项配置 | `FormSchema` | - |
|
||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||
@@ -320,7 +322,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||
```ts
|
||||
export interface ActionButtonOptions {
|
||||
/** 样式 */
|
||||
class?: any;
|
||||
class?: ClassType;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 是否加载中 */
|
||||
|
@@ -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` |
|
||||
|
85
docs/src/demos/vben-vxe-table/basic/index.vue
Normal file
85
docs/src/demos/vben-vxe-table/basic/index.vue
Normal 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>
|
105
docs/src/demos/vben-vxe-table/custom-cell/index.vue
Normal file
105
docs/src/demos/vben-vxe-table/custom-cell/index.vue
Normal 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>
|
55
docs/src/demos/vben-vxe-table/edit-cell/index.vue
Normal file
55
docs/src/demos/vben-vxe-table/edit-cell/index.vue
Normal 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>
|
92
docs/src/demos/vben-vxe-table/edit-row/index.vue
Normal file
92
docs/src/demos/vben-vxe-table/edit-row/index.vue
Normal 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>
|
67
docs/src/demos/vben-vxe-table/fixed/index.vue
Normal file
67
docs/src/demos/vben-vxe-table/fixed/index.vue
Normal 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>
|
120
docs/src/demos/vben-vxe-table/form/index.vue
Normal file
120
docs/src/demos/vben-vxe-table/form/index.vue
Normal 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>
|
36
docs/src/demos/vben-vxe-table/mock-api.ts
Normal file
36
docs/src/demos/vben-vxe-table/mock-api.ts
Normal 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 };
|
112
docs/src/demos/vben-vxe-table/remote/index.vue
Normal file
112
docs/src/demos/vben-vxe-table/remote/index.vue
Normal 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>
|
384
docs/src/demos/vben-vxe-table/table-data.ts
Normal file
384
docs/src/demos/vben-vxe-table/table-data.ts
Normal 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,
|
||||
},
|
||||
];
|
80
docs/src/demos/vben-vxe-table/tree/index.vue
Normal file
80
docs/src/demos/vben-vxe-table/tree/index.vue
Normal 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>
|
64
docs/src/demos/vben-vxe-table/virtual/index.vue
Normal file
64
docs/src/demos/vben-vxe-table/virtual/index.vue
Normal 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>
|
@@ -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;
|
||||
|
||||
|
@@ -67,7 +67,7 @@ import { SvgTestIcon } from '@vben/icons';
|
||||
</template>
|
||||
```
|
||||
|
||||
## Tailwind CSS 图标 <Badge text="不推荐" type="danger"/>
|
||||
## Tailwind CSS 图标
|
||||
|
||||
### 使用
|
||||
|
||||
|
@@ -164,6 +164,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
@@ -536,5 +537,4 @@ interface Preferences {
|
||||
|
||||
- `overridesPreferences`方法只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置。
|
||||
- 任何配置项都可以覆盖,只需要在`overridesPreferences`方法内覆盖即可,不要修改默认配置文件。
|
||||
|
||||
:::
|
||||
- 更改配置后请清空缓存,否则可能不生效。:::
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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`进行安装。
|
||||
|
||||
:::
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/commitlint-config",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/stylelint-config",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/vite-config",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -6,6 +6,7 @@ import path, { relative } from 'node:path';
|
||||
|
||||
import { findMonorepoRoot } from '@vben/node-utils';
|
||||
|
||||
import { NodePackageImporter } from 'sass';
|
||||
import { defineConfig, loadEnv, mergeConfig } from 'vite';
|
||||
|
||||
import { defaultImportmapOptions, getDefaultPwaOptions } from '../options';
|
||||
@@ -85,7 +86,7 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
||||
clientFiles: [
|
||||
'./index.html',
|
||||
'./src/bootstrap.ts',
|
||||
'./src/{views,layouts,router,store,api}/*',
|
||||
'./src/{views,layouts,router,store,api,adapter}/*',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -109,11 +110,12 @@ 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;
|
||||
},
|
||||
api: 'modern-compiler',
|
||||
api: 'modern',
|
||||
importers: [new NodePackageImporter()],
|
||||
},
|
||||
}
|
||||
: {},
|
||||
|
@@ -243,4 +243,5 @@ export {
|
||||
viteDtsPlugin,
|
||||
viteHtmlPlugin,
|
||||
viteVisualizerPlugin,
|
||||
viteVxeTableImportsPlugin,
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vben-admin-monorepo",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"monorepo",
|
||||
@@ -97,7 +97,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.10.0",
|
||||
"pnpm": ">=9.5.0"
|
||||
"pnpm": ">=9.12.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"pnpm": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -31,7 +31,7 @@
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
@apply size-full overscroll-none;
|
||||
@apply size-full;
|
||||
|
||||
/* scrollbar-gutter: stable; */
|
||||
}
|
||||
|
@@ -3,19 +3,5 @@ import { defineBuildConfig } from 'unbuild';
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,7 +1,11 @@
|
||||
export { default as EmptyIcon } from './components/empty.vue';
|
||||
export * from './create-icon';
|
||||
|
||||
export * from './lucide';
|
||||
|
||||
export type { IconifyIcon as IconifyIconStructure } from '@iconify/vue';
|
||||
export { addCollection, addIcon, Icon as IconifyIcon } from '@iconify/vue';
|
||||
export {
|
||||
addCollection,
|
||||
addIcon,
|
||||
Icon as IconifyIcon,
|
||||
listIcons,
|
||||
} from '@iconify/vue';
|
||||
|
@@ -17,7 +17,6 @@ export {
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
CircleHelp,
|
||||
CloudUpload,
|
||||
Copy,
|
||||
CornerDownLeft,
|
||||
Ellipsis,
|
||||
@@ -28,6 +27,7 @@ export {
|
||||
FoldHorizontal,
|
||||
Fullscreen,
|
||||
Github,
|
||||
Grip,
|
||||
Info,
|
||||
InspectionPanel,
|
||||
Languages,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -84,6 +84,7 @@
|
||||
"@types/lodash.get": "catalog:",
|
||||
"@vue/shared": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"defu": "catalog:",
|
||||
"lodash.clonedeep": "catalog:",
|
||||
"lodash.get": "catalog:",
|
||||
|
18
packages/@core/base/shared/src/utils/date.ts
Normal file
18
packages/@core/base/shared/src/utils/date.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export function formatDate(time: number | string, format = 'YYYY-MM-DD') {
|
||||
try {
|
||||
const date = dayjs(time);
|
||||
if (!date.isValid()) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
return date.format(format);
|
||||
} catch (error) {
|
||||
console.error(`Error formatting date: ${error}`);
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDateTime(time: number | string) {
|
||||
return formatDate(time, 'YYYY-MM-DD HH:mm:ss');
|
||||
}
|
160
packages/@core/base/shared/src/utils/download.ts
Normal file
160
packages/@core/base/shared/src/utils/download.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { openWindow } from './window';
|
||||
|
||||
interface DownloadOptions<T = string> {
|
||||
fileName?: string;
|
||||
source: T;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_FILENAME = 'downloaded_file';
|
||||
|
||||
/**
|
||||
* 通过 URL 下载文件,支持跨域
|
||||
* @throws {Error} - 当下载失败时抛出错误
|
||||
*/
|
||||
export async function downloadFileFromUrl({
|
||||
fileName,
|
||||
source,
|
||||
target = '_blank',
|
||||
}: DownloadOptions): Promise<void> {
|
||||
if (!source || typeof source !== 'string') {
|
||||
throw new Error('Invalid URL.');
|
||||
}
|
||||
|
||||
const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
|
||||
const isSafari = window.navigator.userAgent.toLowerCase().includes('safari');
|
||||
|
||||
if (/iP/.test(window.navigator.userAgent)) {
|
||||
console.error('Your browser does not support download!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isChrome || isSafari) {
|
||||
triggerDownload(source, resolveFileName(source, fileName));
|
||||
}
|
||||
if (!source.includes('?')) {
|
||||
source += '?download';
|
||||
}
|
||||
|
||||
openWindow(source, { target });
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Base64 下载文件
|
||||
*/
|
||||
export function downloadFileFromBase64({ fileName, source }: DownloadOptions) {
|
||||
if (!source || typeof source !== 'string') {
|
||||
throw new Error('Invalid Base64 data.');
|
||||
}
|
||||
|
||||
const resolvedFileName = fileName || DEFAULT_FILENAME;
|
||||
triggerDownload(source, resolvedFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过图片 URL 下载图片文件
|
||||
*/
|
||||
export async function downloadFileFromImageUrl({
|
||||
fileName,
|
||||
source,
|
||||
}: DownloadOptions) {
|
||||
const base64 = await urlToBase64(source);
|
||||
downloadFileFromBase64({ fileName, source: base64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Blob 下载文件
|
||||
* @param blob - 文件的 Blob 对象
|
||||
* @param fileName - 可选,下载的文件名称
|
||||
*/
|
||||
export function downloadFileFromBlob({
|
||||
fileName = DEFAULT_FILENAME,
|
||||
source,
|
||||
}: DownloadOptions<Blob>): void {
|
||||
if (!(source instanceof Blob)) {
|
||||
throw new TypeError('Invalid Blob data.');
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(source);
|
||||
triggerDownload(url, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件,支持 Blob、字符串和其他 BlobPart 类型
|
||||
* @param data - 文件的 BlobPart 数据
|
||||
* @param fileName - 下载的文件名称
|
||||
*/
|
||||
export function downloadFileFromBlobPart({
|
||||
fileName = DEFAULT_FILENAME,
|
||||
source,
|
||||
}: DownloadOptions<BlobPart>): void {
|
||||
// 如果 data 不是 Blob,则转换为 Blob
|
||||
const blob =
|
||||
source instanceof Blob
|
||||
? source
|
||||
: new Blob([source], { type: 'application/octet-stream' });
|
||||
|
||||
// 创建对象 URL 并触发下载
|
||||
const url = URL.createObjectURL(blob);
|
||||
triggerDownload(url, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* img url to base64
|
||||
* @param url
|
||||
*/
|
||||
export function urlToBase64(url: string, mineType?: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;
|
||||
const ctx = canvas?.getContext('2d');
|
||||
const img = new Image();
|
||||
img.crossOrigin = '';
|
||||
img.addEventListener('load', () => {
|
||||
if (!canvas || !ctx) {
|
||||
return reject(new Error('Failed to create canvas.'));
|
||||
}
|
||||
canvas.height = img.height;
|
||||
canvas.width = img.width;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const dataURL = canvas.toDataURL(mineType || 'image/png');
|
||||
canvas = null;
|
||||
resolve(dataURL);
|
||||
});
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用下载触发函数
|
||||
* @param href - 文件下载的 URL
|
||||
* @param fileName - 下载文件的名称,如果未提供则自动识别
|
||||
* @param revokeDelay - 清理 URL 的延迟时间 (毫秒)
|
||||
*/
|
||||
export function triggerDownload(
|
||||
href: string,
|
||||
fileName: string | undefined,
|
||||
revokeDelay: number = 100,
|
||||
): void {
|
||||
const defaultFileName = 'downloaded_file';
|
||||
const finalFileName = fileName || defaultFileName;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = href;
|
||||
link.download = finalFileName;
|
||||
link.style.display = 'none';
|
||||
|
||||
if (link.download === undefined) {
|
||||
link.setAttribute('target', '_blank');
|
||||
}
|
||||
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
// 清理临时 URL 以释放内存
|
||||
setTimeout(() => URL.revokeObjectURL(href), revokeDelay);
|
||||
}
|
||||
|
||||
function resolveFileName(url: string, fileName?: string): string {
|
||||
return fileName || url.slice(url.lastIndexOf('/') + 1) || DEFAULT_FILENAME;
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
export * from './cn';
|
||||
export * from './date';
|
||||
export * from './diff';
|
||||
export * from './dom';
|
||||
export * from './download';
|
||||
export * from './inference';
|
||||
export * from './letter';
|
||||
export * from './merge';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/typings",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
4
packages/@core/base/typings/src/basic.d.ts
vendored
4
packages/@core/base/typings/src/basic.d.ts
vendored
@@ -30,4 +30,6 @@ interface BasicUserInfo {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export type { BasicOption, BasicUserInfo, SelectOption, TabOption };
|
||||
type ClassType = Array<object | string> | object | string;
|
||||
|
||||
export type { BasicOption, BasicUserInfo, ClassType, SelectOption, TabOption };
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/composables",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/preferences",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -40,6 +40,7 @@
|
||||
"@vben-core/composables": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vee-validate/zod": "catalog:",
|
||||
"@vueuse/core": "catalog:",
|
||||
"vee-validate": "catalog:",
|
||||
|
@@ -3,7 +3,12 @@ import { computed, toRaw, unref, watch } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import { VbenExpandableArrow } from '@vben-core/shadcn-ui';
|
||||
import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils';
|
||||
import {
|
||||
cn,
|
||||
formatDate,
|
||||
isFunction,
|
||||
triggerWindowResize,
|
||||
} from '@vben-core/shared/utils';
|
||||
|
||||
import { COMPONENT_MAP } from '../config';
|
||||
import { injectFormProps } from '../use-form-context';
|
||||
@@ -52,20 +57,64 @@ async function handleSubmit(e: Event) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
await unref(rootProps).handleSubmit?.(toRaw(form.values));
|
||||
|
||||
const values = handleRangeTimeValue(toRaw(form.values));
|
||||
await unref(rootProps).handleSubmit?.(values);
|
||||
}
|
||||
|
||||
async function handleReset(e: Event) {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
const props = unref(rootProps);
|
||||
|
||||
const values = toRaw(form.values);
|
||||
// 清理时间字段
|
||||
props.fieldMappingTime &&
|
||||
props.fieldMappingTime.forEach(([_, [startTimeKey, endTimeKey]]) => {
|
||||
delete values[startTimeKey];
|
||||
delete values[endTimeKey];
|
||||
});
|
||||
|
||||
if (isFunction(props.handleReset)) {
|
||||
await props.handleReset?.(form.values);
|
||||
await props.handleReset?.(values);
|
||||
} else {
|
||||
form.resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
function handleRangeTimeValue(values: Record<string, any>) {
|
||||
const fieldMappingTime = unref(rootProps).fieldMappingTime;
|
||||
|
||||
if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
fieldMappingTime.forEach(
|
||||
([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {
|
||||
if (!values[field]) {
|
||||
delete values[field];
|
||||
return;
|
||||
}
|
||||
|
||||
const [startTime, endTime] = values[field];
|
||||
const [startTimeFormat, endTimeFormat] = Array.isArray(format)
|
||||
? format
|
||||
: [format, format];
|
||||
|
||||
values[startTimeKey] = startTime
|
||||
? formatDate(startTime, startTimeFormat)
|
||||
: undefined;
|
||||
values[endTimeKey] = endTime
|
||||
? formatDate(endTime, endTimeFormat)
|
||||
: undefined;
|
||||
|
||||
delete values[field];
|
||||
},
|
||||
);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => collapsed.value,
|
||||
() => {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import type { Recordable } from '@vben-core/typings';
|
||||
import type {
|
||||
FormState,
|
||||
GenericObject,
|
||||
@@ -41,11 +42,14 @@ function getDefaultState(): VbenFormProps {
|
||||
}
|
||||
|
||||
export class FormApi {
|
||||
// 最后一次点击提交时的表单值
|
||||
private latestSubmissionValues: null | Recordable<any> = null;
|
||||
private prevState: null | VbenFormProps = null;
|
||||
|
||||
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
||||
public form = {} as FormActions;
|
||||
|
||||
isMounted = false;
|
||||
|
||||
public state: null | VbenFormProps = null;
|
||||
|
||||
stateHandler: StateHandler;
|
||||
@@ -110,6 +114,10 @@ export class FormApi {
|
||||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
getLatestSubmissionValues() {
|
||||
return this.latestSubmissionValues || {};
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
@@ -164,6 +172,7 @@ export class FormApi {
|
||||
if (!this.isMounted) {
|
||||
Object.assign(this.form, formActions);
|
||||
this.stateHandler.setConditionTrue();
|
||||
this.setLatestSubmissionValues({ ...toRaw(this.form.values) });
|
||||
this.isMounted = true;
|
||||
}
|
||||
}
|
||||
@@ -207,6 +216,10 @@ export class FormApi {
|
||||
form.setFieldValue(field, value, shouldValidate);
|
||||
}
|
||||
|
||||
setLatestSubmissionValues(values: null | Recordable<any>) {
|
||||
this.latestSubmissionValues = { ...toRaw(values) };
|
||||
}
|
||||
|
||||
setState(
|
||||
stateOrFn:
|
||||
| ((prev: VbenFormProps) => Partial<VbenFormProps>)
|
||||
@@ -249,11 +262,14 @@ export class FormApi {
|
||||
await form.submitForm();
|
||||
const rawValues = toRaw(form.values || {});
|
||||
await this.state?.handleSubmit?.(rawValues);
|
||||
|
||||
return rawValues;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.form?.resetForm?.();
|
||||
// this.state = null;
|
||||
this.latestSubmissionValues = null;
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
}
|
||||
@@ -302,4 +318,13 @@ export class FormApi {
|
||||
}
|
||||
return validateResult;
|
||||
}
|
||||
|
||||
async validateAndSubmitForm() {
|
||||
const form = await this.getForm();
|
||||
const { valid } = await form.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
return await this.submitForm();
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
@@ -205,6 +206,12 @@ export type HandleResetFn = (
|
||||
values: Record<string, any>,
|
||||
) => Promise<void> | void;
|
||||
|
||||
export type FieldMappingTime = [
|
||||
string,
|
||||
[string, string],
|
||||
([string, string] | string)?,
|
||||
][];
|
||||
|
||||
export interface FormSchema<
|
||||
T extends BaseFormComponentType = BaseFormComponentType,
|
||||
> extends FormCommonConfig {
|
||||
@@ -303,7 +310,11 @@ export interface VbenFormProps<
|
||||
/**
|
||||
* 表单操作区域class
|
||||
*/
|
||||
actionWrapperClass?: any;
|
||||
actionWrapperClass?: ClassType;
|
||||
/**
|
||||
* 表单字段映射成时间格式
|
||||
*/
|
||||
fieldMappingTime?: FieldMappingTime;
|
||||
/**
|
||||
* 表单重置回调
|
||||
*/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/layout-ui",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/menu-ui",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"@vben-core/icons": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"vue": "catalog:"
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ export class DrawerApi {
|
||||
confirmLoading: false,
|
||||
contentClass: '',
|
||||
footer: true,
|
||||
header: true,
|
||||
isOpen: false,
|
||||
loading: false,
|
||||
modal: true,
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import type { DrawerApi } from './drawer-api';
|
||||
|
||||
import type { Component, Ref } from 'vue';
|
||||
@@ -7,7 +9,7 @@ export interface DrawerProps {
|
||||
* 取消按钮文字
|
||||
*/
|
||||
cancelText?: string;
|
||||
class?: string;
|
||||
class?: ClassType;
|
||||
/**
|
||||
* 是否显示右上角的关闭按钮
|
||||
* @default true
|
||||
@@ -42,6 +44,20 @@ export interface DrawerProps {
|
||||
* @default true
|
||||
*/
|
||||
footer?: boolean;
|
||||
/**
|
||||
* 弹窗底部样式
|
||||
*/
|
||||
footerClass?: ClassType;
|
||||
/**
|
||||
* 是否显示顶栏
|
||||
* @default true
|
||||
*/
|
||||
header?: boolean;
|
||||
/**
|
||||
* 弹窗头部样式
|
||||
*/
|
||||
headerClass?: ClassType;
|
||||
|
||||
/**
|
||||
* 弹窗是否显示
|
||||
* @default false
|
||||
|
@@ -56,6 +56,9 @@ const {
|
||||
contentClass,
|
||||
description,
|
||||
footer: showFooter,
|
||||
footerClass,
|
||||
header: showHeader,
|
||||
headerClass,
|
||||
loading: showLoading,
|
||||
modal,
|
||||
openAutoFocus,
|
||||
@@ -129,10 +132,15 @@ function handleFocusOutside(e: Event) {
|
||||
@pointer-down-outside="pointerDownOutside"
|
||||
>
|
||||
<SheetHeader
|
||||
v-if="showHeader"
|
||||
:class="
|
||||
cn('!flex flex-row items-center justify-between border-b px-6 py-5', {
|
||||
'px-4 py-3': closable,
|
||||
})
|
||||
cn(
|
||||
'!flex flex-row items-center justify-between border-b px-6 py-5',
|
||||
headerClass,
|
||||
{
|
||||
'px-4 py-3': closable,
|
||||
},
|
||||
)
|
||||
"
|
||||
>
|
||||
<div>
|
||||
@@ -186,7 +194,12 @@ function handleFocusOutside(e: Event) {
|
||||
|
||||
<SheetFooter
|
||||
v-if="showFooter"
|
||||
class="w-full flex-row items-center justify-end border-t p-2 px-3"
|
||||
:class="
|
||||
cn(
|
||||
'w-full flex-row items-center justify-end border-t p-2 px-3',
|
||||
footerClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot name="prepend-footer"></slot>
|
||||
<slot name="footer">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
AvatarFallbackProps,
|
||||
AvatarImageProps,
|
||||
@@ -11,9 +12,9 @@ import { Avatar, AvatarFallback, AvatarImage } from '../../ui';
|
||||
|
||||
interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps {
|
||||
alt?: string;
|
||||
class?: any;
|
||||
class?: ClassType;
|
||||
dot?: boolean;
|
||||
dotClass?: any;
|
||||
dotClass?: ClassType;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
|
@@ -14,6 +14,7 @@ interface Props extends VbenButtonProps {
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
tooltip?: string;
|
||||
tooltipDelayDuration?: number;
|
||||
tooltipSide?: 'bottom' | 'left' | 'right' | 'top';
|
||||
variant?: ButtonVariants;
|
||||
}
|
||||
@@ -21,6 +22,7 @@ interface Props extends VbenButtonProps {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
onClick: () => {},
|
||||
tooltipDelayDuration: 200,
|
||||
tooltipSide: 'bottom',
|
||||
variant: 'icon',
|
||||
});
|
||||
@@ -42,7 +44,11 @@ const showTooltip = computed(() => !!slots.tooltip || !!props.tooltip);
|
||||
<slot></slot>
|
||||
</VbenButton>
|
||||
|
||||
<VbenTooltip v-else :side="tooltipSide">
|
||||
<VbenTooltip
|
||||
v-else
|
||||
:delay-duration="tooltipDelayDuration"
|
||||
:side="tooltipSide"
|
||||
>
|
||||
<template #trigger>
|
||||
<VbenButton
|
||||
:class="cn('rounded-full', props.class)"
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
ContextMenuContentProps,
|
||||
ContextMenuRootEmits,
|
||||
@@ -22,11 +23,11 @@ import {
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
class?: any;
|
||||
contentClass?: any;
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: ContextMenuContentProps;
|
||||
handlerData?: Record<string, any>;
|
||||
itemClass?: any;
|
||||
itemClass?: ClassType;
|
||||
menus: (data: any) => IContextMenuItem[];
|
||||
} & ContextMenuRootProps
|
||||
>();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
HoverCardContentProps,
|
||||
HoverCardRootEmits,
|
||||
@@ -12,8 +13,8 @@ import { useForwardPropsEmits } from 'radix-vue';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '../../ui';
|
||||
|
||||
interface Props extends HoverCardRootProps {
|
||||
class?: any;
|
||||
contentClass?: any;
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: HoverCardContentProps;
|
||||
}
|
||||
|
||||
|
@@ -10,15 +10,18 @@ defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<PinInputProps>(), {
|
||||
btnLoading: false,
|
||||
codeLength: 6,
|
||||
handleSendCode: async () => {},
|
||||
maxTime: 60,
|
||||
});
|
||||
const {
|
||||
codeLength = 6,
|
||||
createText = async () => {},
|
||||
disabled = false,
|
||||
handleSendCode = async () => {},
|
||||
loading = false,
|
||||
maxTime = 60,
|
||||
} = defineProps<PinInputProps>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
complete: [];
|
||||
sendError: [error: any];
|
||||
}>();
|
||||
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
@@ -30,11 +33,11 @@ const countdown = ref(0);
|
||||
|
||||
const btnText = computed(() => {
|
||||
const countdownValue = countdown.value;
|
||||
return props.createText?.(countdownValue);
|
||||
return createText?.(countdownValue);
|
||||
});
|
||||
|
||||
const btnLoading = computed(() => {
|
||||
return props.loading || countdown.value > 0;
|
||||
return loading || countdown.value > 0;
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -50,10 +53,16 @@ function handleComplete(e: string[]) {
|
||||
}
|
||||
|
||||
async function handleSend(e: Event) {
|
||||
e?.preventDefault();
|
||||
await props.handleSendCode();
|
||||
countdown.value = props.maxTime;
|
||||
startCountdown();
|
||||
try {
|
||||
e?.preventDefault();
|
||||
await handleSendCode();
|
||||
countdown.value = maxTime;
|
||||
startCountdown();
|
||||
} catch (error) {
|
||||
console.error('Failed to send code:', error);
|
||||
// Consider emitting an error event or showing a notification
|
||||
emit('sendError', error);
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
@@ -77,6 +86,7 @@ const id = useId();
|
||||
<PinInput
|
||||
:id="id"
|
||||
v-model="inputValue"
|
||||
:disabled="disabled"
|
||||
class="flex w-full justify-between"
|
||||
otp
|
||||
placeholder="○"
|
||||
@@ -92,6 +102,7 @@ const id = useId();
|
||||
/>
|
||||
</PinInputGroup>
|
||||
<VbenButton
|
||||
:disabled="disabled"
|
||||
:loading="btnLoading"
|
||||
class="flex-grow"
|
||||
size="lg"
|
||||
|
@@ -8,6 +8,10 @@ interface PinInputProps {
|
||||
* 发送验证码按钮文本
|
||||
*/
|
||||
createText?: (countdown: number) => string;
|
||||
/**
|
||||
* 是否禁用
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 自定义验证码发送逻辑
|
||||
* @returns
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
PopoverContentProps,
|
||||
PopoverRootEmits,
|
||||
@@ -16,8 +17,8 @@ import {
|
||||
} from '../../ui';
|
||||
|
||||
interface Props extends PopoverRootProps {
|
||||
class?: any;
|
||||
contentClass?: any;
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: PopoverContentProps;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
@@ -6,9 +8,9 @@ import { cn } from '@vben-core/shared/utils';
|
||||
import { ScrollArea, ScrollBar } from '../../ui';
|
||||
|
||||
interface Props {
|
||||
class?: any;
|
||||
class?: ClassType;
|
||||
horizontal?: boolean;
|
||||
scrollBarClass?: any;
|
||||
scrollBarClass?: ClassType;
|
||||
shadow?: boolean;
|
||||
shadowBorder?: boolean;
|
||||
shadowBottom?: boolean;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { TooltipContentProps } from 'radix-vue';
|
||||
|
||||
import type { StyleValue } from 'vue';
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
} from '../../ui';
|
||||
|
||||
interface Props {
|
||||
contentClass?: any;
|
||||
contentClass?: ClassType;
|
||||
contentStyle?: StyleValue;
|
||||
delayDuration?: number;
|
||||
side?: TooltipContentProps['side'];
|
||||
|
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
@@ -18,8 +20,8 @@ import DialogOverlay from './DialogOverlay.vue';
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{
|
||||
class?: any;
|
||||
closeClass?: any;
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
showClose?: boolean;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/tabs-ui",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -65,7 +65,7 @@ const tabsView = computed(() => {
|
||||
:style="style"
|
||||
class="tabs-chrome !flex h-full w-max overflow-y-hidden pr-6"
|
||||
>
|
||||
<TransitionGroup name="slide-down">
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
|
@@ -67,9 +67,9 @@ const tabsView = computed(() => {
|
||||
<template>
|
||||
<div
|
||||
:class="contentClass"
|
||||
class="relative !flex h-full w-max items-center overflow-y-hidden pr-6"
|
||||
class="relative !flex h-full w-max items-center overflow-hidden pr-6"
|
||||
>
|
||||
<TransitionGroup name="slide-down">
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/constants",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/access",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/common-ui",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -25,6 +25,7 @@
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import type { ClassType } from '@vben/types';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
export interface CaptchaData {
|
||||
@@ -72,7 +74,7 @@ export interface PointSelectionCaptchaProps
|
||||
}
|
||||
|
||||
export interface SliderCaptchaProps {
|
||||
class?: any;
|
||||
class?: ClassType;
|
||||
/**
|
||||
* @description 滑块的样式
|
||||
* @default {}
|
||||
|
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, watchEffect } from 'vue';
|
||||
|
||||
import { usePagination } from '@vben/hooks';
|
||||
import { EmptyIcon, Grip } from '@vben/icons';
|
||||
import {
|
||||
Button,
|
||||
Pagination,
|
||||
PaginationEllipsis,
|
||||
PaginationFirst,
|
||||
PaginationLast,
|
||||
PaginationList,
|
||||
PaginationListItem,
|
||||
PaginationNext,
|
||||
PaginationPrev,
|
||||
VbenIcon,
|
||||
VbenIconButton,
|
||||
VbenPopover,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 图标列表
|
||||
*/
|
||||
icons?: string[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
pageSize: 36,
|
||||
icons: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [string];
|
||||
'update:value': [string];
|
||||
}>();
|
||||
|
||||
const refTrigger = useTemplateRef<HTMLElement>('refTrigger');
|
||||
const currentSelect = ref('');
|
||||
const currentList = ref(props.icons);
|
||||
|
||||
watch(
|
||||
() => props.icons,
|
||||
(newIcons) => {
|
||||
currentList.value = newIcons;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const { paginationList, total, setCurrentPage } = usePagination(
|
||||
currentList,
|
||||
props.pageSize,
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
currentSelect.value = props.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => currentSelect.value,
|
||||
(v) => {
|
||||
emit('update:value', v);
|
||||
emit('change', v);
|
||||
},
|
||||
);
|
||||
|
||||
const handleClick = (icon: string) => {
|
||||
currentSelect.value = icon;
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
function changeOpenState() {
|
||||
refTrigger.value?.click?.();
|
||||
}
|
||||
|
||||
defineExpose({ changeOpenState });
|
||||
</script>
|
||||
<template>
|
||||
<VbenPopover
|
||||
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
|
||||
content-class="p-0 pt-3"
|
||||
>
|
||||
<template #trigger>
|
||||
<div ref="refTrigger">
|
||||
<VbenIcon :icon="currentSelect || Grip" class="size-5" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="paginationList.length > 0">
|
||||
<div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center">
|
||||
<VbenIconButton
|
||||
v-for="(item, index) in paginationList"
|
||||
:key="index"
|
||||
:tooltip="item"
|
||||
tooltip-side="top"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<VbenIcon
|
||||
:class="{
|
||||
'text-primary transition-all': currentSelect === item,
|
||||
}"
|
||||
:icon="item"
|
||||
/>
|
||||
</VbenIconButton>
|
||||
</div>
|
||||
<div
|
||||
v-if="total >= pageSize"
|
||||
class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3"
|
||||
>
|
||||
<Pagination
|
||||
v-slot="{ page }"
|
||||
:items-per-page="36"
|
||||
:sibling-count="1"
|
||||
:total="total"
|
||||
show-edges
|
||||
size="small"
|
||||
@update:page="handlePageChange"
|
||||
>
|
||||
<PaginationList
|
||||
v-slot="{ items }"
|
||||
class="flex w-full items-center gap-1"
|
||||
>
|
||||
<PaginationFirst class="size-5" />
|
||||
<PaginationPrev class="size-5" />
|
||||
<template v-for="(item, index) in items">
|
||||
<PaginationListItem
|
||||
v-if="item.type === 'page'"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
as-child
|
||||
>
|
||||
<Button
|
||||
:variant="item.value === page ? 'default' : 'outline'"
|
||||
class="size-5 p-0 text-sm"
|
||||
>
|
||||
{{ item.value }}
|
||||
</Button>
|
||||
</PaginationListItem>
|
||||
<PaginationEllipsis
|
||||
v-else
|
||||
:key="item.type"
|
||||
:index="index"
|
||||
class="size-5"
|
||||
/>
|
||||
</template>
|
||||
<PaginationNext class="size-5" />
|
||||
<PaginationLast class="size-5" />
|
||||
</PaginationList>
|
||||
</Pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex-col-center text-muted-foreground min-h-[150px] w-full">
|
||||
<EmptyIcon class="size-10" />
|
||||
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</VbenPopover>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as IconPicker } from './icon-picker.vue';
|
@@ -1,5 +1,6 @@
|
||||
export * from './captcha';
|
||||
export * from './ellipsis-text';
|
||||
export * from './icon-picker';
|
||||
export * from './page';
|
||||
export * from '@vben-core/form-ui';
|
||||
export * from '@vben-core/popup-ui';
|
||||
|
@@ -15,6 +15,7 @@ interface WorkbenchProjectItem {
|
||||
group: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
interface WorkbenchTrendItem {
|
||||
@@ -35,6 +36,7 @@ interface WorkbenchQuickNavItem {
|
||||
color?: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export type {
|
||||
|
@@ -21,6 +21,8 @@ defineOptions({
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
|
||||
defineEmits(['click']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -43,6 +45,7 @@ withDefaults(defineProps<Props>(), {
|
||||
:color="item.color"
|
||||
:icon="item.icon"
|
||||
class="size-8 transition-all duration-300 group-hover:scale-110"
|
||||
@click="$emit('click', item)"
|
||||
/>
|
||||
<span class="ml-4 text-lg font-medium">{{ item.title }}</span>
|
||||
</div>
|
||||
|
@@ -21,6 +21,8 @@ defineOptions({
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
|
||||
defineEmits(['click']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -37,6 +39,7 @@ withDefaults(defineProps<Props>(), {
|
||||
'border-b-0': index < 3,
|
||||
}"
|
||||
class="flex-col-center border-border group w-1/3 cursor-pointer border-b border-r border-t py-8 hover:shadow-xl"
|
||||
@click="$emit('click', item)"
|
||||
>
|
||||
<VbenIcon
|
||||
:color="item.color"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/hooks",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
export * from './use-app-config';
|
||||
export * from './use-content-maximize';
|
||||
export * from './use-design-tokens';
|
||||
export * from './use-pagination';
|
||||
export * from './use-refresh';
|
||||
export * from './use-tabs';
|
||||
export * from './use-watermark';
|
||||
|
57
packages/effects/hooks/src/use-pagination.ts
Normal file
57
packages/effects/hooks/src/use-pagination.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, ref, unref } from 'vue';
|
||||
|
||||
/**
|
||||
* Paginates an array of items
|
||||
* @param list The array to paginate
|
||||
* @param pageNo The current page number (1-based)
|
||||
* @param pageSize Number of items per page
|
||||
* @returns Paginated array slice
|
||||
* @throws {Error} If pageNo or pageSize are invalid
|
||||
*/
|
||||
function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] {
|
||||
if (pageNo < 1) throw new Error('Page number must be positive');
|
||||
if (pageSize < 1) throw new Error('Page size must be positive');
|
||||
|
||||
const offset = (pageNo - 1) * Number(pageSize);
|
||||
const ret =
|
||||
offset + pageSize >= list.length
|
||||
? list.slice(offset)
|
||||
: list.slice(offset, offset + pageSize);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) {
|
||||
const currentPage = ref(1);
|
||||
const pageSizeRef = ref(pageSize);
|
||||
|
||||
const totalPages = computed(() =>
|
||||
Math.ceil(unref(list).length / unref(pageSizeRef)),
|
||||
);
|
||||
|
||||
const paginationList = computed(() => {
|
||||
return pagination(unref(list), unref(currentPage), unref(pageSizeRef));
|
||||
});
|
||||
|
||||
const total = computed(() => {
|
||||
return unref(list).length;
|
||||
});
|
||||
|
||||
function setCurrentPage(page: number) {
|
||||
if (page < 1 || page > unref(totalPages)) {
|
||||
throw new Error('Invalid page number');
|
||||
}
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
function setPageSize(pageSize: number) {
|
||||
if (pageSize < 1) {
|
||||
throw new Error('Page size must be positive');
|
||||
}
|
||||
pageSizeRef.value = pageSize;
|
||||
// Reset to first page to prevent invalid state
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
return { setCurrentPage, total, setPageSize, paginationList };
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/layouts",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -14,7 +14,7 @@ import type { SegmentedItem } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { CloudUpload, Copy, RotateCw } from '@vben/icons';
|
||||
import { Copy, RotateCw } from '@vben/icons';
|
||||
import { $t, loadLocaleMessages } from '@vben/locales';
|
||||
import {
|
||||
clearPreferencesCache,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
|
||||
import { useClipboard, useThrottleFn } from '@vueuse/core';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
|
||||
import {
|
||||
Animation,
|
||||
@@ -217,18 +217,6 @@ async function handleReset() {
|
||||
resetPreferences();
|
||||
await loadLocaleMessages(preferences.app.locale);
|
||||
}
|
||||
const harbor = computed(() => window.$harbor);
|
||||
// 防抖
|
||||
const handleUploadLog = useThrottleFn(() => {
|
||||
if (!harbor.value) {
|
||||
return;
|
||||
}
|
||||
harbor.value.onOfflineLog('upload');
|
||||
message.copyPreferencesSuccess?.(
|
||||
$t('preferences.logUploadSuccessTitle'),
|
||||
$t('preferences.logUploadSuccess'),
|
||||
);
|
||||
}, 5000);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -240,13 +228,6 @@ const handleUploadLog = useThrottleFn(() => {
|
||||
>
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<VbenIconButton
|
||||
:disabled="!harbor"
|
||||
:tooltip="$t('preferences.logUpload')"
|
||||
class="relative"
|
||||
>
|
||||
<CloudUpload class="size-4" @click="handleUploadLog" />
|
||||
</VbenIconButton>
|
||||
<VbenIconButton
|
||||
:disabled="!diffPreference"
|
||||
:tooltip="$t('preferences.resetTip')"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/plugins",
|
||||
"version": "5.4.4",
|
||||
"version": "5.4.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -2,9 +2,7 @@ import type { VxeGridProps, VxeUIExport } from 'vxe-table';
|
||||
|
||||
import type { VxeGridApi } from './api';
|
||||
|
||||
import { isFunction } from '@vben/utils';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { formatDate, formatDateTime, isFunction } from '@vben/utils';
|
||||
|
||||
export function extendProxyOptions(
|
||||
api: VxeGridApi,
|
||||
@@ -54,13 +52,13 @@ function extendProxyOption(
|
||||
export function extendsDefaultFormatter(vxeUI: VxeUIExport) {
|
||||
vxeUI.formats.add('formatDate', {
|
||||
tableCellFormatMethod({ cellValue }) {
|
||||
return dayjs(cellValue).format('YYYY-MM-DD');
|
||||
return formatDate(cellValue);
|
||||
},
|
||||
});
|
||||
|
||||
vxeUI.formats.add('formatDateTime', {
|
||||
tableCellFormatMethod({ cellValue }) {
|
||||
return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss');
|
||||
return formatDateTime(cellValue);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export { setupVbenVxeTable } from './init';
|
||||
export * from './use-vxe-grid';
|
||||
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
|
||||
|
||||
export type { VxeGridListeners, VxeGridProps } from 'vxe-table';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user