更新最新代码
This commit is contained in:
1
docs/.vitepress/components/index.ts
Normal file
1
docs/.vitepress/components/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as DemoPreview } from './demo-preview.vue';
|
||||
231
docs/.vitepress/config/en.mts
Normal file
231
docs/.vitepress/config/en.mts
Normal file
@@ -0,0 +1,231 @@
|
||||
import type { DefaultTheme } from 'vitepress';
|
||||
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export const en = defineConfig({
|
||||
description: 'Vben Admin & Enterprise level management system framework',
|
||||
lang: 'en-US',
|
||||
themeConfig: {
|
||||
darkModeSwitchLabel: 'Theme',
|
||||
darkModeSwitchTitle: 'Switch to Dark Mode',
|
||||
docFooter: {
|
||||
next: 'Next Page',
|
||||
prev: 'Previous Page',
|
||||
},
|
||||
editLink: {
|
||||
pattern:
|
||||
'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path',
|
||||
text: 'Edit this page on GitHub',
|
||||
},
|
||||
footer: {
|
||||
copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,
|
||||
message: 'Released under the MIT License.',
|
||||
},
|
||||
langMenuLabel: 'Language',
|
||||
lastUpdated: {
|
||||
formatOptions: {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'medium',
|
||||
},
|
||||
text: 'Last updated on',
|
||||
},
|
||||
lightModeSwitchTitle: 'Switch to Light Mode',
|
||||
nav: nav(),
|
||||
outline: {
|
||||
label: 'Navigate',
|
||||
},
|
||||
returnToTopLabel: 'Back to top',
|
||||
sidebar: {
|
||||
'/en/commercial/': {
|
||||
base: '/en/commercial/',
|
||||
items: sidebarCommercial(),
|
||||
},
|
||||
'/en/guide/': { base: '/en/guide/', items: sidebarGuide() },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function sidebarGuide(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
collapsed: false,
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{
|
||||
link: 'introduction/vben',
|
||||
text: 'About Vben Admin',
|
||||
},
|
||||
{
|
||||
link: 'introduction/why',
|
||||
text: 'Why Choose Us?',
|
||||
},
|
||||
{ link: 'introduction/quick-start', text: 'Quick Start' },
|
||||
{ link: 'introduction/thin', text: 'Lite Version' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basics',
|
||||
items: [
|
||||
{ link: 'essentials/concept', text: 'Basic Concepts' },
|
||||
{ link: 'essentials/development', text: 'Local Development' },
|
||||
{ link: 'essentials/route', text: 'Routing and Menu' },
|
||||
{ link: 'essentials/settings', text: 'Configuration' },
|
||||
{ link: 'essentials/icons', text: 'Icons' },
|
||||
{ link: 'essentials/styles', text: 'Styles' },
|
||||
{ link: 'essentials/external-module', text: 'External Modules' },
|
||||
{ link: 'essentials/build', text: 'Build and Deployment' },
|
||||
{ link: 'essentials/server', text: 'Server Interaction and Data Mock' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{ link: 'in-depth/login', text: 'Login' },
|
||||
{ link: 'in-depth/theme', text: 'Theme' },
|
||||
{ link: 'in-depth/access', text: 'Access Control' },
|
||||
{ link: 'in-depth/locale', text: 'Internationalization' },
|
||||
{ link: 'in-depth/features', text: 'Common Features' },
|
||||
{ link: 'in-depth/check-updates', text: 'Check Updates' },
|
||||
{ link: 'in-depth/loading', text: 'Global Loading' },
|
||||
{ link: 'in-depth/ui-framework', text: 'UI Framework Switching' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Engineering',
|
||||
items: [
|
||||
{ link: 'project/standard', text: 'Standards' },
|
||||
{ link: 'project/cli', text: 'CLI' },
|
||||
{ link: 'project/dir', text: 'Directory Explanation' },
|
||||
{ link: 'project/test', text: 'Unit Testing' },
|
||||
{ link: 'project/tailwindcss', text: 'Tailwind CSS' },
|
||||
{ link: 'project/changeset', text: 'Changeset' },
|
||||
{ link: 'project/vite', text: 'Vite Config' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Others',
|
||||
items: [
|
||||
{ link: 'other/project-update', text: 'Project Update' },
|
||||
{ link: 'other/remove-code', text: 'Remove Code' },
|
||||
{ link: 'other/faq', text: 'FAQ' },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function sidebarCommercial(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
link: 'community',
|
||||
text: 'Community',
|
||||
},
|
||||
{
|
||||
link: 'technical-support',
|
||||
text: 'Technical-support',
|
||||
},
|
||||
{
|
||||
link: 'customized',
|
||||
text: 'Customized',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function nav(): DefaultTheme.NavItem[] {
|
||||
return [
|
||||
{
|
||||
activeMatch: '^/en/(guide|components)/',
|
||||
text: 'Doc',
|
||||
items: [
|
||||
{
|
||||
activeMatch: '^/en/guide/',
|
||||
link: '/en/guide/introduction/vben',
|
||||
text: 'Guide',
|
||||
},
|
||||
// {
|
||||
// activeMatch: '^/en/components/',
|
||||
// link: '/en/components/introduction',
|
||||
// text: 'Components',
|
||||
// },
|
||||
{
|
||||
text: 'Historical Versions',
|
||||
items: [
|
||||
{
|
||||
link: 'https://doc.vvbin.cn',
|
||||
text: '2.x Version Documentation',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Demo',
|
||||
items: [
|
||||
{
|
||||
text: 'Vben Admin',
|
||||
items: [
|
||||
{
|
||||
link: 'https://www.vben.pro',
|
||||
text: 'Demo Version',
|
||||
},
|
||||
{
|
||||
link: 'https://ant.vben.pro',
|
||||
text: 'Ant Design Vue Version',
|
||||
},
|
||||
{
|
||||
link: 'https://naive.vben.pro',
|
||||
text: 'Naive Version',
|
||||
},
|
||||
{
|
||||
link: 'https://ele.vben.pro',
|
||||
text: 'Element Plus Version',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Others',
|
||||
items: [
|
||||
{
|
||||
link: 'https://vben.vvbin.cn',
|
||||
text: 'Vben Admin 2.x',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: version,
|
||||
items: [
|
||||
{
|
||||
link: 'https://github.com/vbenjs/vue-vben-admin/releases',
|
||||
text: 'Changelog',
|
||||
},
|
||||
{
|
||||
link: 'https://github.com/orgs/vbenjs/projects/5',
|
||||
text: 'Roadmap',
|
||||
},
|
||||
{
|
||||
link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md',
|
||||
text: 'Contribution',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
link: '/commercial/technical-support',
|
||||
text: '🦄 Tech Support',
|
||||
},
|
||||
{
|
||||
link: '/sponsor/personal',
|
||||
text: '✨ Sponsor',
|
||||
},
|
||||
{
|
||||
link: '/commercial/community',
|
||||
text: '👨👦👦 Community',
|
||||
},
|
||||
// {
|
||||
// link: '/friend-links/',
|
||||
// text: '🤝 Friend Links',
|
||||
// },
|
||||
];
|
||||
}
|
||||
25
docs/.vitepress/config/index.mts
Normal file
25
docs/.vitepress/config/index.mts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { withPwa } from '@vite-pwa/vitepress';
|
||||
import { defineConfigWithTheme } from 'vitepress';
|
||||
|
||||
import { en } from './en.mts';
|
||||
import { shared } from './shared.mts';
|
||||
import { zh } from './zh.mts';
|
||||
|
||||
export default withPwa(
|
||||
defineConfigWithTheme({
|
||||
...shared,
|
||||
locales: {
|
||||
en: {
|
||||
label: 'English',
|
||||
lang: 'en',
|
||||
link: '/en/',
|
||||
...en,
|
||||
},
|
||||
root: {
|
||||
label: '简体中文',
|
||||
lang: 'zh-CN',
|
||||
...zh,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
172
docs/.vitepress/config/shared.mts
Normal file
172
docs/.vitepress/config/shared.mts
Normal file
@@ -0,0 +1,172 @@
|
||||
import type { PwaOptions } from '@vite-pwa/vitepress';
|
||||
import type { HeadConfig } from 'vitepress';
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import {
|
||||
viteArchiverPlugin,
|
||||
viteVxeTableImportsPlugin,
|
||||
} from '@vben/vite-config';
|
||||
|
||||
import {
|
||||
GitChangelog,
|
||||
GitChangelogMarkdownSection,
|
||||
} from '@nolebase/vitepress-plugin-git-changelog/vite';
|
||||
import tailwind from 'tailwindcss';
|
||||
import { defineConfig, postcssIsolateStyles } from 'vitepress';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
|
||||
import { demoPreviewPlugin } from './plugins/demo-preview';
|
||||
import { search as zhSearch } from './zh.mts';
|
||||
|
||||
export const shared = defineConfig({
|
||||
appearance: 'dark',
|
||||
head: head(),
|
||||
markdown: {
|
||||
preConfig(md) {
|
||||
md.use(demoPreviewPlugin);
|
||||
md.use(groupIconMdPlugin);
|
||||
},
|
||||
},
|
||||
pwa: pwa(),
|
||||
srcDir: 'src',
|
||||
themeConfig: {
|
||||
i18nRouting: true,
|
||||
logo: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
search: {
|
||||
options: {
|
||||
locales: {
|
||||
...zhSearch,
|
||||
},
|
||||
},
|
||||
provider: 'local',
|
||||
},
|
||||
siteTitle: 'Vben Admin',
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/vbenjs/vue-vben-admin' },
|
||||
],
|
||||
},
|
||||
title: 'Vben Admin',
|
||||
vite: {
|
||||
build: {
|
||||
chunkSizeWarningLimit: Infinity,
|
||||
minify: 'terser',
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwind(),
|
||||
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
|
||||
],
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern',
|
||||
},
|
||||
},
|
||||
},
|
||||
json: {
|
||||
stringify: true,
|
||||
},
|
||||
plugins: [
|
||||
GitChangelog({
|
||||
mapAuthors: [
|
||||
{
|
||||
mapByNameAliases: ['Vben'],
|
||||
name: 'vben',
|
||||
username: 'anncwb',
|
||||
},
|
||||
{
|
||||
name: 'vince',
|
||||
username: 'vince292007',
|
||||
},
|
||||
{
|
||||
name: 'Li Kui',
|
||||
username: 'likui628',
|
||||
},
|
||||
],
|
||||
repoURL: () => 'https://github.com/vbenjs/vue-vben-admin',
|
||||
}),
|
||||
GitChangelogMarkdownSection(),
|
||||
viteArchiverPlugin({ outputDir: '.vitepress' }),
|
||||
groupIconVitePlugin(),
|
||||
await viteVxeTableImportsPlugin(),
|
||||
],
|
||||
server: {
|
||||
fs: {
|
||||
allow: ['../..'],
|
||||
},
|
||||
host: true,
|
||||
port: 6173,
|
||||
},
|
||||
|
||||
ssr: {
|
||||
external: ['@vue/repl'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function head(): HeadConfig[] {
|
||||
return [
|
||||
['meta', { content: 'Vbenjs Team', name: 'author' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
content: 'vben, vitejs, vite, shacdn-ui, vue',
|
||||
name: 'keywords',
|
||||
},
|
||||
],
|
||||
['link', { href: '/favicon.ico', rel: 'icon', type: 'image/svg+xml' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
content:
|
||||
'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no',
|
||||
name: 'viewport',
|
||||
},
|
||||
],
|
||||
['meta', { content: 'vben admin docs', name: 'keywords' }],
|
||||
['link', { href: '/favicon.ico', rel: 'icon' }],
|
||||
// [
|
||||
// 'script',
|
||||
// {
|
||||
// src: 'https://cdn.tailwindcss.com',
|
||||
// },
|
||||
// ],
|
||||
];
|
||||
}
|
||||
|
||||
function pwa(): PwaOptions {
|
||||
return {
|
||||
includeManifestIcons: false,
|
||||
manifest: {
|
||||
description:
|
||||
'Vben Admin is a modern admin dashboard template based on Vue 3. ',
|
||||
icons: [
|
||||
{
|
||||
sizes: '192x192',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
sizes: '512x512',
|
||||
src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
],
|
||||
id: '/',
|
||||
name: 'Vben Admin Doc',
|
||||
short_name: 'vben_admin_doc',
|
||||
theme_color: '#ffffff',
|
||||
},
|
||||
outDir: resolve(process.cwd(), '.vitepress/dist'),
|
||||
registerType: 'autoUpdate',
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
},
|
||||
};
|
||||
}
|
||||
358
docs/.vitepress/config/zh.mts
Normal file
358
docs/.vitepress/config/zh.mts
Normal file
@@ -0,0 +1,358 @@
|
||||
import type { DefaultTheme } from 'vitepress';
|
||||
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export const zh = defineConfig({
|
||||
description: 'Vben Admin & 企业级管理系统框架',
|
||||
lang: 'zh-Hans',
|
||||
themeConfig: {
|
||||
darkModeSwitchLabel: '主题',
|
||||
darkModeSwitchTitle: '切换到深色模式',
|
||||
docFooter: {
|
||||
next: '下一页',
|
||||
prev: '上一页',
|
||||
},
|
||||
editLink: {
|
||||
pattern:
|
||||
'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path',
|
||||
text: '在 GitHub 上编辑此页面',
|
||||
},
|
||||
footer: {
|
||||
copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,
|
||||
message: '基于 MIT 许可发布.',
|
||||
},
|
||||
langMenuLabel: '多语言',
|
||||
lastUpdated: {
|
||||
formatOptions: {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'medium',
|
||||
},
|
||||
text: '最后更新于',
|
||||
},
|
||||
lightModeSwitchTitle: '切换到浅色模式',
|
||||
nav: nav(),
|
||||
|
||||
outline: {
|
||||
label: '页面导航',
|
||||
},
|
||||
returnToTopLabel: '回到顶部',
|
||||
|
||||
sidebar: {
|
||||
'/commercial/': { base: '/commercial/', items: sidebarCommercial() },
|
||||
'/components/': { base: '/components/', items: sidebarComponents() },
|
||||
'/guide/': { base: '/guide/', items: sidebarGuide() },
|
||||
},
|
||||
sidebarMenuLabel: '菜单',
|
||||
},
|
||||
});
|
||||
|
||||
function sidebarGuide(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
collapsed: false,
|
||||
text: '简介',
|
||||
items: [
|
||||
{
|
||||
link: 'introduction/vben',
|
||||
text: '关于 Vben Admin',
|
||||
},
|
||||
{
|
||||
link: 'introduction/why',
|
||||
text: '为什么选择我们?',
|
||||
},
|
||||
{ link: 'introduction/quick-start', text: '快速开始' },
|
||||
{ link: 'introduction/thin', text: '精简版本' },
|
||||
{
|
||||
base: '/',
|
||||
link: 'components/introduction',
|
||||
text: '组件文档',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '基础',
|
||||
items: [
|
||||
{ link: 'essentials/concept', text: '基础概念' },
|
||||
{ link: 'essentials/development', text: '本地开发' },
|
||||
{ link: 'essentials/route', text: '路由和菜单' },
|
||||
{ link: 'essentials/settings', text: '配置' },
|
||||
{ link: 'essentials/icons', text: '图标' },
|
||||
{ link: 'essentials/styles', text: '样式' },
|
||||
{ link: 'essentials/external-module', text: '外部模块' },
|
||||
{ link: 'essentials/build', text: '构建与部署' },
|
||||
{ link: 'essentials/server', text: '服务端交互与数据Mock' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '深入',
|
||||
items: [
|
||||
{ link: 'in-depth/login', text: '登录' },
|
||||
// { link: 'in-depth/layout', text: '布局' },
|
||||
{ link: 'in-depth/theme', text: '主题' },
|
||||
{ link: 'in-depth/access', text: '权限' },
|
||||
{ link: 'in-depth/locale', text: '国际化' },
|
||||
{ link: 'in-depth/features', text: '常用功能' },
|
||||
{ link: 'in-depth/check-updates', text: '检查更新' },
|
||||
{ link: 'in-depth/loading', text: '全局loading' },
|
||||
{ link: 'in-depth/ui-framework', text: '组件库切换' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '工程',
|
||||
items: [
|
||||
{ link: 'project/standard', text: '规范' },
|
||||
{ link: 'project/cli', text: 'CLI' },
|
||||
{ link: 'project/dir', text: '目录说明' },
|
||||
{ link: 'project/test', text: '单元测试' },
|
||||
{ link: 'project/tailwindcss', text: 'Tailwind CSS' },
|
||||
{ link: 'project/changeset', text: 'Changeset' },
|
||||
{ link: 'project/vite', text: 'Vite Config' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '其他',
|
||||
items: [
|
||||
{ link: 'other/project-update', text: '项目更新' },
|
||||
{ link: 'other/remove-code', text: '移除代码' },
|
||||
{ link: 'other/faq', text: '常见问题' },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function sidebarCommercial(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
link: 'community',
|
||||
text: '交流群',
|
||||
},
|
||||
{
|
||||
link: 'technical-support',
|
||||
text: '技术支持',
|
||||
},
|
||||
{
|
||||
link: 'customized',
|
||||
text: '定制开发',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
text: '组件',
|
||||
items: [
|
||||
{
|
||||
link: 'introduction',
|
||||
text: '介绍',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collapsed: false,
|
||||
text: '布局组件',
|
||||
items: [
|
||||
{
|
||||
link: 'layout-ui/page',
|
||||
text: 'Page 页面',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collapsed: false,
|
||||
text: '通用组件',
|
||||
items: [
|
||||
{
|
||||
link: 'common-ui/vben-api-component',
|
||||
text: 'ApiComponent Api组件包装器',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-alert',
|
||||
text: 'Alert 轻量提示框',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-modal',
|
||||
text: 'Modal 模态框',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-drawer',
|
||||
text: 'Drawer 抽屉',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-form',
|
||||
text: 'Form 表单',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-vxe-table',
|
||||
text: 'Vxe Table 表格',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-count-to-animator',
|
||||
text: 'CountToAnimator 数字动画',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-ellipsis-text',
|
||||
text: 'EllipsisText 省略文本',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function nav(): DefaultTheme.NavItem[] {
|
||||
return [
|
||||
{
|
||||
activeMatch: '^/(guide|components)/',
|
||||
text: '文档',
|
||||
items: [
|
||||
{
|
||||
activeMatch: '^/guide/',
|
||||
link: '/guide/introduction/vben',
|
||||
text: '指南',
|
||||
},
|
||||
{
|
||||
activeMatch: '^/components/',
|
||||
link: '/components/introduction',
|
||||
text: '组件',
|
||||
},
|
||||
{
|
||||
text: '历史版本',
|
||||
items: [
|
||||
{
|
||||
link: 'https://doc.vvbin.cn',
|
||||
text: '2.x版本文档',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '演示',
|
||||
items: [
|
||||
{
|
||||
text: 'Vben Admin',
|
||||
items: [
|
||||
{
|
||||
link: 'https://www.vben.pro',
|
||||
text: '演示版本',
|
||||
},
|
||||
{
|
||||
link: 'https://ant.vben.pro',
|
||||
text: 'Ant Design Vue 版本',
|
||||
},
|
||||
{
|
||||
link: 'https://naive.vben.pro',
|
||||
text: 'Naive 版本',
|
||||
},
|
||||
{
|
||||
link: 'https://ele.vben.pro',
|
||||
text: 'Element Plus版本',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '其他',
|
||||
items: [
|
||||
{
|
||||
link: 'https://vben.vvbin.cn',
|
||||
text: 'Vben Admin 2.x',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: version,
|
||||
items: [
|
||||
{
|
||||
link: 'https://github.com/vbenjs/vue-vben-admin/releases',
|
||||
text: '更新日志',
|
||||
},
|
||||
{
|
||||
link: 'https://github.com/orgs/vbenjs/projects/5',
|
||||
text: '路线图',
|
||||
},
|
||||
{
|
||||
link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md',
|
||||
text: '贡献',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
link: '/commercial/technical-support',
|
||||
text: '🦄 技术支持',
|
||||
},
|
||||
{
|
||||
link: '/sponsor/personal',
|
||||
text: '✨ 赞助',
|
||||
},
|
||||
{
|
||||
link: '/commercial/community',
|
||||
text: '👨👦👦 交流群',
|
||||
// items: [
|
||||
// {
|
||||
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',
|
||||
// text: 'QQ频道',
|
||||
// },
|
||||
// {
|
||||
// link: 'https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mjZmlhgVzzUxvdxllB6C1vHpX8O8QRL0&authKey=DBdFbBwERmfaKY95JvRWqLCJIRGJAmKyZbrpzZ41EKDMZ5SR6MfbjOBaaNRN73fr&noverify=0&group_code=4286109',
|
||||
// text: 'QQ群',
|
||||
// },
|
||||
// {
|
||||
// link: 'https://discord.gg/VU62jTecad',
|
||||
// text: 'Discord',
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
// {
|
||||
// link: '/friend-links/',
|
||||
// text: '🤝 友情链接',
|
||||
// },
|
||||
];
|
||||
}
|
||||
|
||||
export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = {
|
||||
root: {
|
||||
placeholder: '搜索文档',
|
||||
translations: {
|
||||
button: {
|
||||
buttonAriaLabel: '搜索文档',
|
||||
buttonText: '搜索文档',
|
||||
},
|
||||
modal: {
|
||||
errorScreen: {
|
||||
helpText: '你可能需要检查你的网络连接',
|
||||
titleText: '无法获取结果',
|
||||
},
|
||||
footer: {
|
||||
closeText: '关闭',
|
||||
navigateText: '切换',
|
||||
searchByText: '搜索提供者',
|
||||
selectText: '选择',
|
||||
},
|
||||
noResultsScreen: {
|
||||
noResultsText: '无法找到相关结果',
|
||||
reportMissingResultsLinkText: '点击反馈',
|
||||
reportMissingResultsText: '你认为该查询应该有结果?',
|
||||
suggestedQueryText: '你可以尝试查询',
|
||||
},
|
||||
searchBox: {
|
||||
cancelButtonAriaLabel: '取消',
|
||||
cancelButtonText: '取消',
|
||||
resetButtonAriaLabel: '清除查询条件',
|
||||
resetButtonTitle: '清除查询条件',
|
||||
},
|
||||
startScreen: {
|
||||
favoriteSearchesTitle: '收藏',
|
||||
noRecentSearchesText: '没有搜索历史',
|
||||
recentSearchesTitle: '搜索历史',
|
||||
removeFavoriteSearchButtonTitle: '从收藏中移除',
|
||||
removeRecentSearchButtonTitle: '从搜索历史中移除',
|
||||
saveRecentSearchButtonTitle: '保存至搜索历史',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
96
docs/.vitepress/theme/components/site-layout.vue
Normal file
96
docs/.vitepress/theme/components/site-layout.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
// import { useAntdDesignTokens } from '@vben/hooks';
|
||||
// import { initPreferences } from '@vben/preferences';
|
||||
import { ConfigProvider, theme } from 'ant-design-vue';
|
||||
import mediumZoom from 'medium-zoom';
|
||||
import { useRoute } from 'vitepress';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
|
||||
const { Layout } = DefaultTheme;
|
||||
const route = useRoute();
|
||||
// const { tokens } = useAntdDesignTokens();
|
||||
|
||||
const initZoom = () => {
|
||||
// mediumZoom('[data-zoomable]', { background: 'var(--vp-c-bg)' });
|
||||
mediumZoom('.VPContent img', { background: 'var(--vp-c-bg)' });
|
||||
};
|
||||
|
||||
const isDark = ref(true);
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => nextTick(() => initZoom()),
|
||||
);
|
||||
|
||||
// initPreferences({
|
||||
// namespace: 'docs',
|
||||
// });
|
||||
|
||||
onMounted(() => {
|
||||
initZoom();
|
||||
});
|
||||
|
||||
// 使用该函数
|
||||
const observer = watchDarkModeChange((dark) => {
|
||||
isDark.value = dark;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
observer?.disconnect();
|
||||
});
|
||||
|
||||
function watchDarkModeChange(callback: (isDark: boolean) => void) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const htmlElement = document.documentElement;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = htmlElement.classList.contains('dark');
|
||||
callback(isDark);
|
||||
});
|
||||
|
||||
observer.observe(htmlElement, {
|
||||
attributeFilter: ['class'],
|
||||
attributes: true,
|
||||
});
|
||||
|
||||
const initialIsDark = htmlElement.classList.contains('dark');
|
||||
callback(initialIsDark);
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
const tokenTheme = computed(() => {
|
||||
const algorithm = isDark.value
|
||||
? [theme.darkAlgorithm]
|
||||
: [theme.defaultAlgorithm];
|
||||
|
||||
return {
|
||||
algorithm,
|
||||
// token: tokens,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfigProvider :theme="tokenTheme">
|
||||
<Layout />
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.medium-zoom-overlay,
|
||||
.medium-zoom-image--opened {
|
||||
z-index: 2147483647;
|
||||
}
|
||||
</style>
|
||||
29
docs/.vitepress/theme/components/vben-contributors.vue
Normal file
29
docs/.vitepress/theme/components/vben-contributors.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="vp-doc vben-contributors">
|
||||
<p>Contributors</p>
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img
|
||||
alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vben-contributors {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 60px;
|
||||
|
||||
p {
|
||||
margin-bottom: 50px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
docs/.vitepress/theme/index.ts
Normal file
29
docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import type { EnhanceAppContext, Theme } from 'vitepress';
|
||||
|
||||
import { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
|
||||
import { DemoPreview } from '../components';
|
||||
import SiteLayout from './components/site-layout.vue';
|
||||
import VbenContributors from './components/vben-contributors.vue';
|
||||
import { initHmPlugin } from './plugins/hm';
|
||||
|
||||
import './styles';
|
||||
|
||||
import 'virtual:group-icons.css';
|
||||
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
|
||||
|
||||
export default {
|
||||
async enhanceApp(ctx: EnhanceAppContext) {
|
||||
const { app } = ctx;
|
||||
app.component('VbenContributors', VbenContributors);
|
||||
app.component('DemoPreview', DemoPreview);
|
||||
app.use(NolebaseGitChangelogPlugin);
|
||||
|
||||
// 百度统计
|
||||
initHmPlugin();
|
||||
},
|
||||
extends: DefaultTheme,
|
||||
Layout: SiteLayout,
|
||||
} satisfies Theme;
|
||||
28
docs/.vitepress/theme/plugins/hm.ts
Normal file
28
docs/.vitepress/theme/plugins/hm.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { inBrowser } from 'vitepress';
|
||||
|
||||
const SITE_ID = '2e443a834727c065877c01d89921545e';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
_hmt: any;
|
||||
}
|
||||
}
|
||||
|
||||
function registerAnalytics() {
|
||||
window._hmt = window._hmt || [];
|
||||
const script = document.createElement('script');
|
||||
script.innerHTML = `var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?${SITE_ID}";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})()`;
|
||||
document.querySelector('head')?.append(script);
|
||||
}
|
||||
|
||||
export function initHmPlugin() {
|
||||
if (inBrowser && import.meta.env.PROD) {
|
||||
registerAnalytics();
|
||||
}
|
||||
}
|
||||
22
docs/.vitepress/theme/styles/base.css
Normal file
22
docs/.vitepress/theme/styles/base.css
Normal file
@@ -0,0 +1,22 @@
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.dark .VPContent {
|
||||
/* background-color: #14161a; */
|
||||
}
|
||||
|
||||
.form-valid-error p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 顶部导航栏选中项样式 */
|
||||
.VPNavBarMenuLink,
|
||||
.VPNavBarMenuGroup {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.VPNavBarMenuLink.active,
|
||||
.VPNavBarMenuGroup.active {
|
||||
border-bottom-color: var(--vp-c-brand-1);
|
||||
}
|
||||
4
docs/.vitepress/theme/styles/index.ts
Normal file
4
docs/.vitepress/theme/styles/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import '@vben/styles';
|
||||
|
||||
import './variables.css';
|
||||
import './base.css';
|
||||
132
docs/.vitepress/theme/styles/variables.css
Normal file
132
docs/.vitepress/theme/styles/variables.css
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*
|
||||
* Each colors have exact same color scale system with 3 levels of solid
|
||||
* colors with different brightness, and 1 soft color.
|
||||
*
|
||||
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||
*
|
||||
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||
*
|
||||
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||
* top of it.
|
||||
*
|
||||
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||
* on top of it.
|
||||
*
|
||||
* The soft color must be semi transparent alpha channel. This is crucial
|
||||
* because it allows adding multiple "soft" colors on top of each other
|
||||
* to create a accent, such as when having inline code block inside
|
||||
* custom containers.
|
||||
*
|
||||
* - `default`: The color used purely for subtle indication without any
|
||||
* special meanings attched to it such as bg color for menu hover state.
|
||||
*
|
||||
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||
* brand theme, etc.
|
||||
*
|
||||
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||
* brand color for this by default.
|
||||
*
|
||||
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||
* container, badges, etc.
|
||||
*
|
||||
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||
* in custom container, badges, etc.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
/* --vp-c-indigo-1: #4f69fd; */
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
--vp-c-brand-1: var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: var(--vp-c-indigo-soft);
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: linear-gradient(
|
||||
120deg,
|
||||
var(--vp-c-indigo-1) 30%,
|
||||
#18cefe
|
||||
);
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#18cefe 50%,
|
||||
#c279ed 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
|
||||
/**
|
||||
* modal zIndex
|
||||
*/
|
||||
--popup-z-index: 1000;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
128
docs/src/_env/adapter/component.ts
Normal file
128
docs/src/_env/adapter/component.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
47
docs/src/_env/adapter/form.ts
Normal file
47
docs/src/_env/adapter/form.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { initComponentAdapter } from './component';
|
||||
|
||||
initComponentAdapter();
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
baseModelPropName: 'value',
|
||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||
emptyStateValue: null,
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
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 = () => {};
|
||||
30
docs/src/commercial/community.md
Normal file
30
docs/src/commercial/community.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 社区交流
|
||||
|
||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||
|
||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
||||
- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[5群](https://qm.qq.com/q/ya9XrtbS6s),主要的使用者交流群。
|
||||
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
||||
|
||||
::: tip
|
||||
|
||||
免费QQ群人数上限200,将会不定期清理。推荐加入QQ频道进行交流
|
||||
|
||||
:::
|
||||
|
||||
## 微信群
|
||||
|
||||
作者主要通过微信群提供帮助,如果你有问题,可以通过以下方式加入微信群。
|
||||
|
||||
通过微信联系作者,注明加群来意:
|
||||
|
||||
::: tip
|
||||
|
||||
因为微信群人数有限制,加微信群要求:
|
||||
|
||||
- 通过[赞助](../sponsor/personal.md)任意金额。
|
||||
- 发送赞助`截图`,备注`加入微信群`即可。
|
||||
|
||||
:::
|
||||
|
||||
<img src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/wechat.jpg" style="width: 300px;"/>
|
||||
12
docs/src/commercial/customized.md
Normal file
12
docs/src/commercial/customized.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 定制开发
|
||||
|
||||
我们提供基于 Vben Admin 的技术支持服务及定制开发,基本需求我们都可以满足。
|
||||
|
||||
详细需求可添加作者了解,并注明来意:
|
||||
|
||||
- 通过邮箱联系开发者: [ann.vben@gmail.com](mailto:ann.vben@gmail.com)
|
||||
- 通过微信联系开发者:
|
||||
|
||||
<img src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/wechat.jpg" style="width: 300px;"/>
|
||||
|
||||
我们会在第一时间回复您,定制费用根据需求而定。
|
||||
8
docs/src/commercial/technical-support.md
Normal file
8
docs/src/commercial/technical-support.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 技术支持
|
||||
|
||||
## 问题反馈
|
||||
|
||||
在使用项目的过程中,如果遇到问题,你可以先详细阅读本文档,未找到解决方案时,可以通过以下方式获取技术支持:
|
||||
|
||||
- 通过 [GitHub Issues](https://github.com/vbenjs/vue-vben-admin/issues)
|
||||
- 通过 [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)
|
||||
173
docs/src/components/common-ui/vben-api-component.md
Normal file
173
docs/src/components/common-ui/vben-api-component.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben ApiComponent Api组件包装器
|
||||
|
||||
框架提供的API“包装器”,它一般不独立使用,主要用于包装其它组件,为目标组件提供自动获取远程数据的能力,但仍然保持了目标组件的原始用法。
|
||||
|
||||
::: info 写在前面
|
||||
|
||||
我们在各个应用的组件适配器中,使用ApiComponent包装了Select、TreeSelect组件,使得这些组件可以自动获取远程数据并生成选项。其它类似的组件(比如Cascader)如有需要也可以参考示例代码自行进行包装。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label(如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。
|
||||
|
||||
::: details 包装级联选择器,点击下拉时开始加载远程数据
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { ApiComponent } from '@vben/common-ui';
|
||||
|
||||
import { Cascader } from 'ant-design-vue';
|
||||
|
||||
const treeData: Record<string, any> = [
|
||||
{
|
||||
label: '浙江',
|
||||
value: 'zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: '杭州',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: '西湖',
|
||||
},
|
||||
{
|
||||
value: 'sudi',
|
||||
label: '苏堤',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiaxing',
|
||||
label: '嘉兴',
|
||||
children: [
|
||||
{
|
||||
value: 'wuzhen',
|
||||
label: '乌镇',
|
||||
},
|
||||
{
|
||||
value: 'meihuazhou',
|
||||
label: '梅花洲',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'zhoushan',
|
||||
label: '舟山',
|
||||
children: [
|
||||
{
|
||||
value: 'putuoshan',
|
||||
label: '普陀山',
|
||||
},
|
||||
{
|
||||
value: 'taohuadao',
|
||||
label: '桃花岛',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '江苏',
|
||||
value: 'jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: '南京',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: '中华门',
|
||||
},
|
||||
{
|
||||
value: 'zijinshan',
|
||||
label: '紫金山',
|
||||
},
|
||||
{
|
||||
value: 'yuhuatai',
|
||||
label: '雨花台',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
/**
|
||||
* 模拟请求接口
|
||||
*/
|
||||
function fetchApi(): Promise<Record<string, any>> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(treeData);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ApiComponent
|
||||
:api="fetchApi"
|
||||
:component="Cascader"
|
||||
:immediate="false"
|
||||
children-field="children"
|
||||
loading-slot="suffixIcon"
|
||||
visible-event="onDropdownVisibleChange"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 并发和缓存
|
||||
|
||||
有些场景下可能需要使用多个ApiComponent,它们使用了相同的远程数据源(例如用在可编辑的表格中)。如果直接将请求后端接口的函数传递给api属性,则每一个实例都会访问一次接口,这会造成资源浪费,是完全没有必要的。Tanstack Query提供了并发控制、缓存、重试等诸多特性,我们可以将接口请求函数用useQuery包装一下再传递给ApiComponent,这样的话无论页面有多少个使用相同数据源的ApiComponent实例,都只会发起一次远程请求。演示效果请参考 [Playground vue-query](https://www.vben.pro/#/demos/features/vue-query),具体代码请查看项目文件[concurrency-caching](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/views/demos/features/vue-query/concurrency-caching.vue)
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| modelValue(v-model) | 当前值 | `any` | - | - |
|
||||
| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - |
|
||||
| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |
|
||||
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - | - |
|
||||
| params | 传递给api的参数 | `Record<string, any>` | - | - |
|
||||
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |
|
||||
| labelField | label字段名 | `string` | `label` | - |
|
||||
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - |
|
||||
| valueField | value字段名 | `string` | `value` | - |
|
||||
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |
|
||||
| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |
|
||||
| immediate | 是否立即调用api | `boolean` | `true` | - |
|
||||
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |
|
||||
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |
|
||||
| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |
|
||||
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - |
|
||||
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| ((item: OptionsItem[]) => OptionsItem) \| false` | `false` | >5.5.4 |
|
||||
|
||||
#### autoSelect 自动设置选项
|
||||
|
||||
如果当前值为undefined,在选项数据成功加载之后,自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
|
||||
|
||||
- `"first"`:自动选择第一个选项
|
||||
- `"last"`:自动选择最后一个选项
|
||||
- `"one"`:有且仅有一个选项时,自动选择它
|
||||
- `自定义函数`:自定义选择逻辑,函数的参数为options,返回值为选择的选项
|
||||
- `false`:不自动选择选项
|
||||
|
||||
### Methods
|
||||
|
||||
| 方法 | 描述 | 类型 | 版本要求 |
|
||||
| --- | --- | --- | --- |
|
||||
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
|
||||
| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record<string, any>)=>void | >5.5.4 |
|
||||
| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 |
|
||||
| getValue | 获取当前值 | ()=>any | >5.5.4 |
|
||||
59
docs/src/components/common-ui/vben-count-to-animator.md
Normal file
59
docs/src/components/common-ui/vben-count-to-animator.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben CountToAnimator 数字动画
|
||||
|
||||
框架提供的数字动画组件,支持数字动画效果。
|
||||
|
||||
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
|
||||
|
||||
::: info 写在前面
|
||||
|
||||
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
通过 `start-val` 和 `end-val`设置数字动画的开始值和结束值, 持续时间`3000`ms。
|
||||
|
||||
<DemoPreview dir="demos/vben-count-to-animator/basic" />
|
||||
|
||||
## 自定义前缀及分隔符
|
||||
|
||||
通过 `prefix` 和 `separator` 设置数字动画的前缀和分隔符。
|
||||
|
||||
<DemoPreview dir="demos/vben-count-to-animator/custom" />
|
||||
|
||||
### Props
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| ---------- | -------------- | --------- | -------- |
|
||||
| startVal | 起始值 | `number` | `0` |
|
||||
| endVal | 结束值 | `number` | `2021` |
|
||||
| duration | 动画持续时间 | `number` | `1500` |
|
||||
| autoplay | 自动执行 | `boolean` | `true` |
|
||||
| prefix | 前缀 | `string` | - |
|
||||
| suffix | 后缀 | `string` | - |
|
||||
| separator | 分隔符 | `string` | `,` |
|
||||
| color | 字体颜色 | `string` | - |
|
||||
| useEasing | 是否开启动画 | `boolean` | `true` |
|
||||
| transition | 动画效果 | `string` | `linear` |
|
||||
| decimals | 保留小数点位数 | `number` | `0` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| -------------- | -------------- | -------------- |
|
||||
| started | 动画已开始 | `()=>void` |
|
||||
| finished | 动画已结束 | `()=>void` |
|
||||
| ~~onStarted~~ | ~~动画已开始~~ | ~~`()=>void`~~ |
|
||||
| ~~onFinished~~ | ~~动画已结束~~ | ~~`()=>void`~~ |
|
||||
|
||||
### Methods
|
||||
|
||||
| 方法名 | 描述 | 类型 |
|
||||
| ------ | ------------ | ---------- |
|
||||
| start | 开始执行动画 | `()=>void` |
|
||||
| reset | 重置 | `()=>void` |
|
||||
156
docs/src/components/common-ui/vben-drawer.md
Normal file
156
docs/src/components/common-ui/vben-drawer.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben Drawer 抽屉
|
||||
|
||||
框架提供的抽屉组件,支持`自动高度`、`loading`等功能。
|
||||
|
||||
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
|
||||
|
||||
::: info 写在前面
|
||||
|
||||
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
|
||||
|
||||
:::
|
||||
|
||||
::: tip README
|
||||
|
||||
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
使用 `useVbenDrawer` 创建最基础的抽屉。
|
||||
|
||||
<DemoPreview dir="demos/vben-drawer/basic" />
|
||||
|
||||
## 组件抽离
|
||||
|
||||
Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 drawer 内的内容抽离出来,也方便复用。通过 `connectedComponent` 参数,可以将内外组件进行连接,而不用其他任何操作。
|
||||
|
||||
<DemoPreview dir="demos/vben-drawer/extra" />
|
||||
|
||||
## 自动计算高度
|
||||
|
||||
弹窗会自动计算内容高度,超过一定高度会出现滚动条,同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。
|
||||
|
||||
<DemoPreview dir="demos/vben-drawer/auto-height" />
|
||||
|
||||
## 使用 Api
|
||||
|
||||
通过 `drawerApi` 可以调用 drawer 的方法以及使用 `setState` 更新 drawer 的状态。
|
||||
|
||||
<DemoPreview dir="demos/vben-drawer/dynamic" />
|
||||
|
||||
## 数据共享
|
||||
|
||||
如果你使用了 `connectedComponent` 参数,那么内外组件会共享数据,比如一些表单回填等操作。可以用 `drawerApi` 来获取数据和设置数据,配合 `onOpenChange`,可以满足大部分的需求。
|
||||
|
||||
<DemoPreview dir="demos/vben-drawer/shared-data" />
|
||||
|
||||
::: info 注意
|
||||
|
||||
- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
||||
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
// Drawer 为弹窗组件
|
||||
// drawerApi 为弹窗的方法
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 属性
|
||||
// 事件
|
||||
});
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
所有属性都可以传入 `useVbenDrawer` 的第一个参数中。
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||
| connectedComponent | 连接另一个Drawer组件 | `Component` | - |
|
||||
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||
| title | 标题 | `string\|slot` | - |
|
||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||
| description | 描述信息 | `string\|slot` | - |
|
||||
| isOpen | 弹窗打开状态 | `boolean` | `false` |
|
||||
| loading | 弹窗加载状态 | `boolean` | `false` |
|
||||
| closable | 显示关闭按钮 | `boolean` | `true` |
|
||||
| closeIconPlacement | 关闭按钮位置 | `'left'\|'right'` | `right` |
|
||||
| modal | 显示遮罩 | `boolean` | `true` |
|
||||
| header | 显示header | `boolean` | `true` |
|
||||
| footer | 显示footer | `boolean\|slot` | `true` |
|
||||
| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |
|
||||
| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |
|
||||
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
|
||||
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
|
||||
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
|
||||
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
|
||||
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
|
||||
| class | modal的class,宽度通过这个配置 | `string` | - |
|
||||
| contentClass | modal内容区域的class | `string` | - |
|
||||
| footerClass | modal底部区域的class | `string` | - |
|
||||
| headerClass | modal顶部区域的class | `string` | - |
|
||||
| zIndex | 抽屉的ZIndex层级 | `number` | `1000` |
|
||||
| overlayBlur | 遮罩模糊度 | `number` | - |
|
||||
|
||||
::: info appendToMain
|
||||
|
||||
`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。
|
||||
|
||||
:::
|
||||
|
||||
### Event
|
||||
|
||||
以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
|
||||
|
||||
| 事件名 | 描述 | 类型 | 版本限制 |
|
||||
| --- | --- | --- | --- |
|
||||
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- |
|
||||
| onCancel | 点击取消按钮触发 | `()=>void` | --- |
|
||||
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |
|
||||
| onConfirm | 点击确认按钮触发 | `()=>void` | --- |
|
||||
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- |
|
||||
| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 |
|
||||
|
||||
### Slots
|
||||
|
||||
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
||||
|
||||
| 插槽名 | 描述 |
|
||||
| -------------- | -------------------------------------------------- |
|
||||
| default | 默认插槽 - 弹窗内容 |
|
||||
| prepend-footer | 取消按钮左侧 |
|
||||
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
|
||||
| append-footer | 确认按钮右侧 |
|
||||
| close-icon | 关闭按钮图标 |
|
||||
| extra | 额外内容(标题右侧) |
|
||||
|
||||
### drawerApi
|
||||
|
||||
| 方法 | 描述 | 类型 | 版本限制 |
|
||||
| --- | --- | --- | --- |
|
||||
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` |
|
||||
| open | 打开弹窗 | `()=>void` | --- |
|
||||
| close | 关闭弹窗 | `()=>void` | --- |
|
||||
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | --- |
|
||||
| getData | 获取共享数据 | `<T>()=>T` | --- |
|
||||
| useStore | 获取可响应式状态 | - | --- |
|
||||
| lock | 将抽屉标记为提交中,锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 |
|
||||
| unlock | lock方法的反操作,解除抽屉的锁定状态,也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 |
|
||||
|
||||
::: info lock
|
||||
|
||||
`lock`方法用于锁定抽屉的状态,一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时,抽屉的确认按钮会变为loading状态,同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭抽屉、开启抽屉的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的抽屉时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
|
||||
|
||||
:::
|
||||
64
docs/src/components/common-ui/vben-ellipsis-text.md
Normal file
64
docs/src/components/common-ui/vben-ellipsis-text.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben EllipsisText 省略文本
|
||||
|
||||
框架提供的文本展示组件,可配置超长省略、tooltip提示、展开收起等功能。
|
||||
|
||||
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
|
||||
|
||||
## 基础用法
|
||||
|
||||
通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/line" />
|
||||
|
||||
## 可折叠的文本块
|
||||
|
||||
通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/expand" />
|
||||
|
||||
## 自定义提示浮层
|
||||
|
||||
通过名为`tooltip`的插槽定制提示信息。
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
|
||||
|
||||
## 自动显示 tooltip
|
||||
|
||||
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
|
||||
|
||||
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| expand | 支持点击展开或收起 | `boolean` | `false` |
|
||||
| line | 文本最大行数 | `number` | `1` |
|
||||
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
||||
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
|
||||
| tooltip | 启用文本提示 | `boolean` | `true` |
|
||||
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
|
||||
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
|
||||
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
|
||||
| tooltipColor | 提示文本的颜色 | `string` | - |
|
||||
| tooltipFontSize | 提示文本的大小 | `string` | - |
|
||||
| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - |
|
||||
| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| ------------ | ------------ | -------------------------- |
|
||||
| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` |
|
||||
|
||||
### Slots
|
||||
|
||||
| 插槽名 | 描述 |
|
||||
| ------- | -------------------------------- |
|
||||
| tooltip | 启用文本提示时,用来定制提示内容 |
|
||||
276
docs/src/components/common-ui/vben-vxe-table.md
Normal file
276
docs/src/components/common-ui/vben-vxe-table.md
Normal file
@@ -0,0 +1,276 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben 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)。
|
||||
|
||||
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
|
||||
|
||||
### 定制分隔条
|
||||
|
||||
当你启用表单搜索时,在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色,并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器(如将其放在一个Card内),默认的表单和表格之间的分隔条可能就显得格格不入了,下面的代码演示了如何定制这个分隔条。
|
||||
|
||||
```ts
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {},
|
||||
gridOptions: {},
|
||||
// 完全移除分隔条
|
||||
separator: false,
|
||||
// 你也可以使用下面的代码来移除分隔条
|
||||
// separator: { show: false },
|
||||
// 或者使用下面的代码来改变分隔条的颜色
|
||||
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||
});
|
||||
```
|
||||
|
||||
<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` | - |
|
||||
| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 |
|
||||
|
||||
## Props
|
||||
|
||||
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
|
||||
|
||||
| 属性名 | 描述 | 类型 | 版本要求 |
|
||||
| --- | --- | --- | --- |
|
||||
| tableTitle | 表格标题 | `string` | - |
|
||||
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
|
||||
| gridClass | grid组件的class | `string` | - |
|
||||
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
|
||||
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
|
||||
| formOptions | 表单参数 | `VbenFormProps` | - |
|
||||
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
|
||||
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
|
||||
|
||||
## Slots
|
||||
|
||||
大部分插槽的说明请参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),但工具栏部分由于做了一些定制封装,需使用以下插槽定制表格的工具栏:
|
||||
|
||||
| 插槽名 | 描述 |
|
||||
| --------------- | -------------------------------------------- |
|
||||
| toolbar-actions | 工具栏左侧部分(表格标题附近) |
|
||||
| toolbar-tools | 工具栏右侧部分(vxeTable原生工具按钮的左侧) |
|
||||
| table-title | 表格标题插槽 |
|
||||
|
||||
::: info 搜索表单的插槽
|
||||
|
||||
对于使用了搜索表单的表格来说,所有以`form-`开头的命名插槽都会传递给表单。
|
||||
|
||||
:::
|
||||
15
docs/src/components/introduction.md
Normal file
15
docs/src/components/introduction.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 介绍
|
||||
|
||||
::: info README
|
||||
|
||||
该文档介绍的是框架组件的使用方法、属性、事件等。如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
|
||||
|
||||
:::
|
||||
|
||||
## 布局组件
|
||||
|
||||
布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。
|
||||
|
||||
## 通用组件
|
||||
|
||||
通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。
|
||||
44
docs/src/components/layout-ui/page.md
Normal file
44
docs/src/components/layout-ui/page.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Page 常规页面组件
|
||||
|
||||
提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。
|
||||
|
||||
::: info 写在前面
|
||||
|
||||
本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
将`Page`作为你的业务页面的根组件即可。
|
||||
|
||||
### Props
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| title | 页面标题 | `string\|slot` | - | - |
|
||||
| description | 页面描述(标题下的内容) | `string\|slot` | - | - |
|
||||
| contentClass | 内容区域的class | `string` | - | - |
|
||||
| headerClass | 头部区域的class | `string` | - | - |
|
||||
| footerClass | 底部区域的class | `string` | - | - |
|
||||
| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - |
|
||||
|
||||
::: tip 注意
|
||||
|
||||
如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。
|
||||
|
||||
:::
|
||||
|
||||
### Slots
|
||||
|
||||
| 插槽名称 | 描述 |
|
||||
| ----------- | ------------ |
|
||||
| default | 页面内容 |
|
||||
| title | 页面标题 |
|
||||
| description | 页面描述 |
|
||||
| extra | 页面头部右侧 |
|
||||
| footer | 页面底部 |
|
||||
36
docs/src/demos/vben-alert/alert/index.vue
Normal file
36
docs/src/demos/vben-alert/alert/index.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
|
||||
import { alert, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import { Result } from 'ant-design-vue';
|
||||
|
||||
function showAlert() {
|
||||
alert('This is an alert message');
|
||||
}
|
||||
|
||||
function showIconAlert() {
|
||||
alert({
|
||||
content: 'This is an alert message with icon',
|
||||
icon: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
function showCustomAlert() {
|
||||
alert({
|
||||
buttonAlign: 'center',
|
||||
content: h(Result, {
|
||||
status: 'success',
|
||||
subTitle: '已成功创建订单。订单ID:2017182818828182881',
|
||||
title: '操作成功',
|
||||
}),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showAlert">Alert</VbenButton>
|
||||
<VbenButton @click="showIconAlert">Alert With Icon</VbenButton>
|
||||
<VbenButton @click="showCustomAlert">Alert With Custom Content</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
75
docs/src/demos/vben-alert/confirm/index.vue
Normal file
75
docs/src/demos/vben-alert/confirm/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, ref } from 'vue';
|
||||
|
||||
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import { Checkbox, message } from 'ant-design-vue';
|
||||
|
||||
function showConfirm() {
|
||||
confirm('This is an alert message')
|
||||
.then(() => {
|
||||
alert('Confirmed');
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Canceled');
|
||||
});
|
||||
}
|
||||
|
||||
function showIconConfirm() {
|
||||
confirm({
|
||||
content: 'This is an alert message with icon',
|
||||
icon: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
function showfooterConfirm() {
|
||||
const checked = ref(false);
|
||||
confirm({
|
||||
cancelText: '不要虾扯蛋',
|
||||
confirmText: '是的,我们都是NPC',
|
||||
content:
|
||||
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的,你有过这种感觉吗?',
|
||||
footer: () =>
|
||||
h(
|
||||
Checkbox,
|
||||
{
|
||||
checked: checked.value,
|
||||
class: 'flex-1',
|
||||
'onUpdate:checked': (v) => (checked.value = v),
|
||||
},
|
||||
'不再提示',
|
||||
),
|
||||
icon: 'question',
|
||||
title: '未解之谜',
|
||||
}).then(() => {
|
||||
if (checked.value) {
|
||||
message.success('我不会再拿这个问题烦你了');
|
||||
} else {
|
||||
message.info('下次还要继续问你哟');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showAsyncConfirm() {
|
||||
confirm({
|
||||
beforeClose({ isConfirm }) {
|
||||
if (isConfirm) {
|
||||
// 这里可以执行一些异步操作。如果最终返回了false,将阻止关闭弹窗
|
||||
return new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
},
|
||||
content: 'This is an alert message with async confirm',
|
||||
icon: 'success',
|
||||
}).then(() => {
|
||||
alert('Confirmed');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||
<VbenButton @click="showfooterConfirm">Confirm With Footer</VbenButton>
|
||||
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
118
docs/src/demos/vben-alert/prompt/index.vue
Normal file
118
docs/src/demos/vben-alert/prompt/index.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
|
||||
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import { Input, RadioGroup, Select } from 'ant-design-vue';
|
||||
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
||||
|
||||
function showPrompt() {
|
||||
prompt({
|
||||
content: '请输入一些东西',
|
||||
})
|
||||
.then((val) => {
|
||||
alert(`已收到你的输入:${val}`);
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Canceled');
|
||||
});
|
||||
}
|
||||
|
||||
function showSlotsPrompt() {
|
||||
prompt({
|
||||
component: () => {
|
||||
// 获取弹窗上下文。注意:只能在setup或者函数式组件中调用
|
||||
const { doConfirm } = useAlertContext();
|
||||
return h(
|
||||
Input,
|
||||
{
|
||||
onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
// 调用弹窗提供的确认方法
|
||||
doConfirm();
|
||||
}
|
||||
},
|
||||
placeholder: '请输入',
|
||||
prefix: '充值金额:',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
addonAfter: () => h(BadgeJapaneseYen),
|
||||
},
|
||||
);
|
||||
},
|
||||
content:
|
||||
'此弹窗演示了如何使用自定义插槽,并且可以使用useAlertContext获取到弹窗的上下文。\n在输入框中按下回车键会触发确认操作。',
|
||||
icon: 'question',
|
||||
modelPropName: 'value',
|
||||
}).then((val) => {
|
||||
if (val) alert(`你输入的是${val}`);
|
||||
});
|
||||
}
|
||||
|
||||
function showSelectPrompt() {
|
||||
prompt({
|
||||
component: Select,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: 'Option A', value: 'Option A' },
|
||||
{ label: 'Option B', value: 'Option B' },
|
||||
{ label: 'Option C', value: 'Option C' },
|
||||
],
|
||||
placeholder: '请选择',
|
||||
// 弹窗会设置body的pointer-events为none,这回影响下拉框的点击事件
|
||||
popupClassName: 'pointer-events-auto',
|
||||
},
|
||||
content: '此弹窗演示了如何使用component传递自定义组件',
|
||||
icon: 'question',
|
||||
modelPropName: 'value',
|
||||
}).then((val) => {
|
||||
if (val) {
|
||||
alert(`你选择了${val}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function showAsyncPrompt() {
|
||||
prompt({
|
||||
async beforeClose(scope) {
|
||||
if (scope.isConfirm) {
|
||||
if (scope.value) {
|
||||
// 模拟异步操作,如果不成功,可以返回false
|
||||
await sleep(2000);
|
||||
} else {
|
||||
alert('请选择一个选项');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
component: RadioGroup,
|
||||
componentProps: {
|
||||
class: 'flex flex-col',
|
||||
options: [
|
||||
{ label: 'Option 1', value: 'option1' },
|
||||
{ label: 'Option 2', value: 'option2' },
|
||||
{ label: 'Option 3', value: 'option3' },
|
||||
],
|
||||
},
|
||||
content: '选择一个选项后再点击[确认]',
|
||||
icon: 'question',
|
||||
modelPropName: 'value',
|
||||
}).then((val) => {
|
||||
alert(`${val} 已设置。`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton>
|
||||
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
||||
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
100
docs/src/demos/vben-api-component/cascader/index.vue
Normal file
100
docs/src/demos/vben-api-component/cascader/index.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import { ApiComponent } from '@vben/common-ui';
|
||||
|
||||
import { Cascader } from 'ant-design-vue';
|
||||
|
||||
const treeData: Record<string, any> = [
|
||||
{
|
||||
label: '浙江',
|
||||
value: 'zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: '杭州',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: '西湖',
|
||||
},
|
||||
{
|
||||
value: 'sudi',
|
||||
label: '苏堤',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiaxing',
|
||||
label: '嘉兴',
|
||||
children: [
|
||||
{
|
||||
value: 'wuzhen',
|
||||
label: '乌镇',
|
||||
},
|
||||
{
|
||||
value: 'meihuazhou',
|
||||
label: '梅花洲',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'zhoushan',
|
||||
label: '舟山',
|
||||
children: [
|
||||
{
|
||||
value: 'putuoshan',
|
||||
label: '普陀山',
|
||||
},
|
||||
{
|
||||
value: 'taohuadao',
|
||||
label: '桃花岛',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '江苏',
|
||||
value: 'jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: '南京',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: '中华门',
|
||||
},
|
||||
{
|
||||
value: 'zijinshan',
|
||||
label: '紫金山',
|
||||
},
|
||||
{
|
||||
value: 'yuhuatai',
|
||||
label: '雨花台',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
/**
|
||||
* 模拟请求接口
|
||||
*/
|
||||
function fetchApi(): Promise<Record<string, any>> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(treeData);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ApiComponent
|
||||
:api="fetchApi"
|
||||
:component="Cascader"
|
||||
:immediate="false"
|
||||
children-field="children"
|
||||
loading-slot="suffixIcon"
|
||||
visible-event="onDropdownVisibleChange"
|
||||
/>
|
||||
</template>
|
||||
6
docs/src/demos/vben-count-to-animator/basic/index.vue
Normal file
6
docs/src/demos/vben-count-to-animator/basic/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenCountToAnimator } from '@vben/common-ui';
|
||||
</script>
|
||||
<template>
|
||||
<VbenCountToAnimator :duration="3000" :end-val="30000" :start-val="1" />
|
||||
</template>
|
||||
12
docs/src/demos/vben-count-to-animator/custom/index.vue
Normal file
12
docs/src/demos/vben-count-to-animator/custom/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenCountToAnimator } from '@vben/common-ui';
|
||||
</script>
|
||||
<template>
|
||||
<VbenCountToAnimator
|
||||
:duration="3000"
|
||||
:end-val="2000000"
|
||||
:start-val="1"
|
||||
prefix="$"
|
||||
separator="/"
|
||||
/>
|
||||
</template>
|
||||
21
docs/src/demos/vben-drawer/auto-height/index.vue
Normal file
21
docs/src/demos/vben-drawer/auto-height/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraDrawer from './drawer.vue';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraDrawer,
|
||||
});
|
||||
|
||||
function open() {
|
||||
drawerApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
11
docs/src/demos/vben-drawer/basic/index.vue
Normal file
11
docs/src/demos/vben-drawer/basic/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer();
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<VbenButton @click="() => drawerApi.open()">Open</VbenButton>
|
||||
<Drawer class="w-[600px]" title="基础示例"> drawer content </Drawer>
|
||||
</div>
|
||||
</template>
|
||||
26
docs/src/demos/vben-drawer/dynamic/drawer.vue
Normal file
26
docs/src/demos/vben-drawer/dynamic/drawer.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
console.info('onConfirm');
|
||||
},
|
||||
title: '动态修改配置示例',
|
||||
});
|
||||
|
||||
function handleUpdateTitle() {
|
||||
drawerApi.setState({ title: '内部动态标题' });
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Drawer>
|
||||
<div class="flex-col-center">
|
||||
<VbenButton class="mb-3" type="primary" @click="handleUpdateTitle()">
|
||||
内部动态修改标题
|
||||
</VbenButton>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
29
docs/src/demos/vben-drawer/dynamic/index.vue
Normal file
29
docs/src/demos/vben-drawer/dynamic/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraDrawer from './drawer.vue';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraDrawer,
|
||||
});
|
||||
|
||||
function open() {
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
function handleUpdateTitle() {
|
||||
drawerApi.setState({ title: '外部动态标题' }).open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
<VbenButton class="ml-2" type="primary" @click="handleUpdateTitle">
|
||||
从外部修改标题并打开
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
8
docs/src/demos/vben-drawer/extra/drawer.vue
Normal file
8
docs/src/demos/vben-drawer/extra/drawer.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
const [Drawer] = useVbenDrawer();
|
||||
</script>
|
||||
<template>
|
||||
<Drawer title="组件抽离示例"> extra drawer content </Drawer>
|
||||
</template>
|
||||
21
docs/src/demos/vben-drawer/extra/index.vue
Normal file
21
docs/src/demos/vben-drawer/extra/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraDrawer from './drawer.vue';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraDrawer,
|
||||
});
|
||||
|
||||
function open() {
|
||||
drawerApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
26
docs/src/demos/vben-drawer/shared-data/drawer.vue
Normal file
26
docs/src/demos/vben-drawer/shared-data/drawer.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
const data = ref();
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
console.info('onConfirm');
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
data.value = drawerApi.getData<Record<string, any>>();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Drawer title="数据共享示例">
|
||||
<div class="flex-col-center">外部传递数据: {{ data }}</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
27
docs/src/demos/vben-drawer/shared-data/index.vue
Normal file
27
docs/src/demos/vben-drawer/shared-data/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraDrawer from './drawer.vue';
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraDrawer,
|
||||
});
|
||||
|
||||
function open() {
|
||||
drawerApi
|
||||
.setData({
|
||||
content: '外部传递的数据 content',
|
||||
payload: '外部传递的数据 payload',
|
||||
})
|
||||
.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { EllipsisText } from '@vben/common-ui';
|
||||
|
||||
const text = `
|
||||
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。
|
||||
`;
|
||||
</script>
|
||||
<template>
|
||||
<EllipsisText :line="2" :tooltip-when-ellipsis="true">
|
||||
{{ text }}
|
||||
</EllipsisText>
|
||||
|
||||
<EllipsisText :line="3" :tooltip-when-ellipsis="true">
|
||||
{{ text }}
|
||||
</EllipsisText>
|
||||
</template>
|
||||
10
docs/src/demos/vben-ellipsis-text/expand/index.vue
Normal file
10
docs/src/demos/vben-ellipsis-text/expand/index.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { EllipsisText } from '@vben/common-ui';
|
||||
|
||||
const text = `
|
||||
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。
|
||||
`;
|
||||
</script>
|
||||
<template>
|
||||
<EllipsisText :line="3" expand>{{ text }}</EllipsisText>
|
||||
</template>
|
||||
10
docs/src/demos/vben-ellipsis-text/line/index.vue
Normal file
10
docs/src/demos/vben-ellipsis-text/line/index.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { EllipsisText } from '@vben/common-ui';
|
||||
|
||||
const text = `
|
||||
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。
|
||||
`;
|
||||
</script>
|
||||
<template>
|
||||
<EllipsisText :max-width="500">{{ text }}</EllipsisText>
|
||||
</template>
|
||||
14
docs/src/demos/vben-ellipsis-text/tooltip/index.vue
Normal file
14
docs/src/demos/vben-ellipsis-text/tooltip/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { EllipsisText } from '@vben/common-ui';
|
||||
</script>
|
||||
<template>
|
||||
<EllipsisText :max-width="240">
|
||||
住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪
|
||||
<template #tooltip>
|
||||
<div style="text-align: center">
|
||||
《秦皇岛》<br />住在我心里孤独的<br />孤独的海怪 痛苦之王<br />开始厌倦
|
||||
深海的光 停滞的海浪
|
||||
</div>
|
||||
</template>
|
||||
</EllipsisText>
|
||||
</template>
|
||||
236
docs/src/demos/vben-form/api/index.vue
Normal file
236
docs/src/demos/vben-form/api/index.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button, message, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [BaseForm, formApi] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
// 使用 tailwindcss grid布局
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
layout: 'horizontal',
|
||||
// 水平布局,label和input在同一行
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
// 对应组件的参数
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field1',
|
||||
// 界面显示的label
|
||||
label: 'field1',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
filterOption: true,
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
},
|
||||
fieldName: 'fieldOptions',
|
||||
label: '下拉选',
|
||||
},
|
||||
],
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
||||
});
|
||||
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
|
||||
function handleClick(
|
||||
action:
|
||||
| 'batchAddSchema'
|
||||
| 'batchDeleteSchema'
|
||||
| 'disabled'
|
||||
| 'hiddenAction'
|
||||
| 'hiddenResetButton'
|
||||
| 'hiddenSubmitButton'
|
||||
| 'labelWidth'
|
||||
| 'resetDisabled'
|
||||
| 'resetLabelWidth'
|
||||
| 'showAction'
|
||||
| 'showResetButton'
|
||||
| 'showSubmitButton'
|
||||
| 'updateActionAlign'
|
||||
| 'updateResetButton'
|
||||
| 'updateSchema'
|
||||
| 'updateSubmitButton',
|
||||
) {
|
||||
switch (action) {
|
||||
case 'batchAddSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
const newSchema = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
newSchema.push({
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: `field${i}${Date.now()}`,
|
||||
label: `field+`,
|
||||
});
|
||||
}
|
||||
return {
|
||||
schema: [...currentSchema, ...newSchema],
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'batchDeleteSchema': {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
return {
|
||||
schema: currentSchema.slice(0, -2),
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'disabled': {
|
||||
formApi.setState({ commonConfig: { disabled: true } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenAction': {
|
||||
formApi.setState({ showDefaultActions: false });
|
||||
break;
|
||||
}
|
||||
case 'hiddenResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'hiddenSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: false } });
|
||||
break;
|
||||
}
|
||||
case 'labelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 150,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'resetDisabled': {
|
||||
formApi.setState({ commonConfig: { disabled: false } });
|
||||
break;
|
||||
}
|
||||
case 'resetLabelWidth': {
|
||||
formApi.setState({
|
||||
commonConfig: {
|
||||
labelWidth: 100,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'showAction': {
|
||||
formApi.setState({ showDefaultActions: true });
|
||||
break;
|
||||
}
|
||||
case 'showResetButton': {
|
||||
formApi.setState({ resetButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'showSubmitButton': {
|
||||
formApi.setState({ submitButtonOptions: { show: true } });
|
||||
break;
|
||||
}
|
||||
case 'updateActionAlign': {
|
||||
formApi.setState({
|
||||
// 可以自行调整class
|
||||
actionWrapperClass: 'text-center',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateResetButton': {
|
||||
formApi.setState({
|
||||
resetButtonOptions: { disabled: true },
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'updateSchema': {
|
||||
formApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '选项3',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldName: 'fieldOptions',
|
||||
},
|
||||
]);
|
||||
message.success('字段 `fieldOptions` 下拉选项更新成功。');
|
||||
break;
|
||||
}
|
||||
case 'updateSubmitButton': {
|
||||
formApi.setState({
|
||||
submitButtonOptions: { loading: true },
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Space class="mb-5 flex-wrap">
|
||||
<Button @click="handleClick('updateSchema')">updateSchema</Button>
|
||||
<Button @click="handleClick('labelWidth')">更改labelWidth</Button>
|
||||
<Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
|
||||
<Button @click="handleClick('disabled')">禁用表单</Button>
|
||||
<Button @click="handleClick('resetDisabled')">解除禁用</Button>
|
||||
<Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
|
||||
<Button @click="handleClick('showAction')">显示操作按钮</Button>
|
||||
<Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
|
||||
<Button @click="handleClick('showResetButton')">显示重置按钮</Button>
|
||||
<Button @click="handleClick('hiddenSubmitButton')">隐藏提交按钮</Button>
|
||||
<Button @click="handleClick('showSubmitButton')">显示提交按钮</Button>
|
||||
<Button @click="handleClick('updateResetButton')">修改重置按钮</Button>
|
||||
<Button @click="handleClick('updateSubmitButton')">修改提交按钮</Button>
|
||||
<Button @click="handleClick('updateActionAlign')">
|
||||
调整操作按钮位置
|
||||
</Button>
|
||||
<Button @click="handleClick('batchAddSchema')"> 批量添加表单项 </Button>
|
||||
<Button @click="handleClick('batchDeleteSchema')">
|
||||
批量删除表单项
|
||||
</Button>
|
||||
</Space>
|
||||
<BaseForm />
|
||||
</div>
|
||||
</template>
|
||||
231
docs/src/demos/vben-form/basic/index.vue
Normal file
231
docs/src/demos/vben-form/basic/index.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [BaseForm] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
// 对应组件的参数
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'username',
|
||||
// 界面显示的label
|
||||
label: '字符串',
|
||||
},
|
||||
{
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: '密码',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'number',
|
||||
label: '数字(带后缀)',
|
||||
suffix: () => '¥',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
filterOption: true,
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
},
|
||||
fieldName: 'options',
|
||||
label: '下拉选',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldName: 'radioGroup',
|
||||
label: '单选组',
|
||||
},
|
||||
{
|
||||
component: 'Radio',
|
||||
fieldName: 'radio',
|
||||
label: '',
|
||||
renderComponentContent: () => {
|
||||
return {
|
||||
default: () => ['Radio'],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'CheckboxGroup',
|
||||
componentProps: {
|
||||
name: 'cname',
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldName: 'checkboxGroup',
|
||||
label: '多选组',
|
||||
},
|
||||
{
|
||||
component: 'Checkbox',
|
||||
fieldName: 'checkbox',
|
||||
label: '',
|
||||
renderComponentContent: () => {
|
||||
return {
|
||||
default: () => ['我已阅读并同意'],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Mentions',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'afc163',
|
||||
value: 'afc163',
|
||||
},
|
||||
{
|
||||
label: 'zombieJ',
|
||||
value: 'zombieJ',
|
||||
},
|
||||
],
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'mentions',
|
||||
label: '提及',
|
||||
},
|
||||
{
|
||||
component: 'Rate',
|
||||
fieldName: 'rate',
|
||||
label: '评分',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
componentProps: {
|
||||
class: 'w-auto',
|
||||
},
|
||||
fieldName: 'switch',
|
||||
label: '开关',
|
||||
},
|
||||
{
|
||||
component: 'DatePicker',
|
||||
fieldName: 'datePicker',
|
||||
label: '日期选择框',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'rangePicker',
|
||||
label: '范围选择器',
|
||||
},
|
||||
{
|
||||
component: 'TimePicker',
|
||||
fieldName: 'timePicker',
|
||||
label: '时间选择框',
|
||||
},
|
||||
{
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
treeData: [
|
||||
{
|
||||
label: 'root 1',
|
||||
value: 'root 1',
|
||||
children: [
|
||||
{
|
||||
label: 'parent 1',
|
||||
value: 'parent 1',
|
||||
children: [
|
||||
{
|
||||
label: 'parent 1-0',
|
||||
value: 'parent 1-0',
|
||||
children: [
|
||||
{
|
||||
label: 'my leaf',
|
||||
value: 'leaf1',
|
||||
},
|
||||
{
|
||||
label: 'your leaf',
|
||||
value: 'leaf2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'parent 1-1',
|
||||
value: 'parent 1-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'parent 2',
|
||||
value: 'parent 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
treeNodeFilterProp: 'label',
|
||||
},
|
||||
fieldName: 'treeSelect',
|
||||
label: '树选择',
|
||||
},
|
||||
],
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseForm />
|
||||
</template>
|
||||
68
docs/src/demos/vben-form/custom/index.vue
Normal file
68
docs/src/demos/vben-form/custom/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
|
||||
import { Input, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
labelClass: 'w-2/6',
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
fieldName: 'field',
|
||||
label: '自定义后缀',
|
||||
suffix: () => h('span', { class: 'text-red-600' }, '元'),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'field1',
|
||||
label: '自定义组件slot',
|
||||
renderComponentContent: () => ({
|
||||
prefix: () => 'prefix',
|
||||
suffix: () => 'suffix',
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: h(Input, { placeholder: '请输入' }),
|
||||
fieldName: 'field2',
|
||||
label: '自定义组件',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'field3',
|
||||
label: '自定义组件(slot)',
|
||||
rules: 'required',
|
||||
},
|
||||
],
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form>
|
||||
<template #field3="slotProps">
|
||||
<Input placeholder="请输入" v-bind="slotProps" />
|
||||
</template>
|
||||
</Form>
|
||||
</template>
|
||||
168
docs/src/demos/vben-form/dynamic/index.vue
Normal file
168
docs/src/demos/vben-form/dynamic/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
schema: [
|
||||
{
|
||||
component: 'Input',
|
||||
defaultValue: 'hidden value',
|
||||
dependencies: {
|
||||
show: false,
|
||||
// 随意一个字段改变时,都会触发
|
||||
triggerFields: ['field1Switch'],
|
||||
},
|
||||
fieldName: 'hiddenField',
|
||||
label: '隐藏字段',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
defaultValue: true,
|
||||
fieldName: 'field1Switch',
|
||||
help: '通过Dom控制销毁',
|
||||
label: '显示字段1',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
defaultValue: true,
|
||||
fieldName: 'field2Switch',
|
||||
help: '通过css控制隐藏',
|
||||
label: '显示字段2',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
fieldName: 'field3Switch',
|
||||
label: '禁用字段3',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
fieldName: 'field4Switch',
|
||||
label: '字段4必填',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
if(values) {
|
||||
return !!values.field1Switch;
|
||||
},
|
||||
// 只有指定的字段改变时,才会触发
|
||||
triggerFields: ['field1Switch'],
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field1',
|
||||
// 界面显示的label
|
||||
label: '字段1',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return !!values.field2Switch;
|
||||
},
|
||||
triggerFields: ['field2Switch'],
|
||||
},
|
||||
fieldName: 'field2',
|
||||
label: '字段2',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
disabled(values) {
|
||||
return !!values.field3Switch;
|
||||
},
|
||||
triggerFields: ['field3Switch'],
|
||||
},
|
||||
fieldName: 'field3',
|
||||
label: '字段3',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
required(values) {
|
||||
return !!values.field4Switch;
|
||||
},
|
||||
triggerFields: ['field4Switch'],
|
||||
},
|
||||
fieldName: 'field4',
|
||||
label: '字段4',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
if (values.field1 === '123') {
|
||||
return 'required';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
triggerFields: ['field1'],
|
||||
},
|
||||
fieldName: 'field5',
|
||||
help: '当字段1的值为`123`时,必填',
|
||||
label: '动态rules',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
filterOption: true,
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
},
|
||||
dependencies: {
|
||||
componentProps(values) {
|
||||
if (values.field2 === '123') {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '选项3',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
triggerFields: ['field2'],
|
||||
},
|
||||
fieldName: 'field6',
|
||||
help: '当字段2的值为`123`时,更改下拉选项',
|
||||
label: '动态配置',
|
||||
},
|
||||
],
|
||||
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
||||
});
|
||||
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form />
|
||||
</template>
|
||||
94
docs/src/demos/vben-form/query/index.vue
Normal file
94
docs/src/demos/vben-form/query/index.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const [QueryForm] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
// 水平布局,label和input在同一行
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
// 对应组件的参数
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'username',
|
||||
// 界面显示的label
|
||||
label: '字符串',
|
||||
},
|
||||
{
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: '密码',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'number',
|
||||
label: '数字(带后缀)',
|
||||
suffix: () => '¥',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
filterOption: true,
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
},
|
||||
fieldName: 'options',
|
||||
label: '下拉选',
|
||||
},
|
||||
{
|
||||
component: 'DatePicker',
|
||||
fieldName: 'datePicker',
|
||||
label: '日期选择框',
|
||||
},
|
||||
],
|
||||
// 是否可展开
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
content: '查询',
|
||||
},
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
||||
});
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QueryForm />
|
||||
</template>
|
||||
190
docs/src/demos/vben-form/rules/index.vue
Normal file
190
docs/src/demos/vben-form/rules/index.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm, z } from '#/adapter/form';
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: onSubmit,
|
||||
// 垂直布局,label和input在不同行,值为vertical
|
||||
// 水平布局,label和input在同一行
|
||||
scrollToFirstError: true,
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
component: 'Input',
|
||||
// 对应组件的参数
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field1',
|
||||
// 界面显示的label
|
||||
label: '字段1',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
defaultValue: '默认值',
|
||||
fieldName: 'field2',
|
||||
label: '默认值(必填)',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'field3',
|
||||
label: '默认值(非必填)',
|
||||
rules: z.string().default('默认值').optional(),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'field31',
|
||||
label: '自定义信息',
|
||||
rules: z.string().min(1, { message: '最少输入1个字符' }),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
// 对应组件的参数
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
// 字段名
|
||||
fieldName: 'field4',
|
||||
// 界面显示的label
|
||||
label: '邮箱',
|
||||
rules: z.string().email('请输入正确的邮箱'),
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'number',
|
||||
label: '数字',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
filterOption: true,
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
placeholder: '请选择',
|
||||
showSearch: true,
|
||||
},
|
||||
defaultValue: undefined,
|
||||
fieldName: 'options',
|
||||
label: '下拉选',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldName: 'radioGroup',
|
||||
label: '单选组',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'CheckboxGroup',
|
||||
componentProps: {
|
||||
name: 'cname',
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldName: 'checkboxGroup',
|
||||
label: '多选组',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Checkbox',
|
||||
fieldName: 'checkbox',
|
||||
label: '',
|
||||
renderComponentContent: () => {
|
||||
return {
|
||||
default: () => ['我已阅读并同意'],
|
||||
};
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'DatePicker',
|
||||
defaultValue: undefined,
|
||||
fieldName: 'datePicker',
|
||||
label: '日期选择框',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
defaultValue: undefined,
|
||||
fieldName: 'rangePicker',
|
||||
label: '区间选择框',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: '密码',
|
||||
rules: 'required',
|
||||
},
|
||||
],
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
function onSubmit(values: Record<string, any>) {
|
||||
message.success({
|
||||
content: `form values: ${JSON.stringify(values)}`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form />
|
||||
</template>
|
||||
21
docs/src/demos/vben-modal/auto-height/index.vue
Normal file
21
docs/src/demos/vben-modal/auto-height/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraModal from './modal.vue';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraModal,
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
11
docs/src/demos/vben-modal/basic/index.vue
Normal file
11
docs/src/demos/vben-modal/basic/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal();
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<VbenButton @click="() => modalApi.open()">Open</VbenButton>
|
||||
<Modal class="w-[600px]" title="基础示例"> modal content </Modal>
|
||||
</div>
|
||||
</template>
|
||||
21
docs/src/demos/vben-modal/draggable/index.vue
Normal file
21
docs/src/demos/vben-modal/draggable/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraModal from './modal.vue';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraModal,
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
10
docs/src/demos/vben-modal/draggable/modal.vue
Normal file
10
docs/src/demos/vben-modal/draggable/modal.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
const [Modal] = useVbenModal({
|
||||
draggable: true,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="拖拽示例"> modal content </Modal>
|
||||
</template>
|
||||
29
docs/src/demos/vben-modal/dynamic/index.vue
Normal file
29
docs/src/demos/vben-modal/dynamic/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraModal from './modal.vue';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraModal,
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
function handleUpdateTitle() {
|
||||
modalApi.setState({ title: '外部动态标题' }).open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
<VbenButton class="ml-2" type="primary" @click="handleUpdateTitle">
|
||||
从外部修改标题并打开
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
38
docs/src/demos/vben-modal/dynamic/modal.vue
Normal file
38
docs/src/demos/vben-modal/dynamic/modal.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
console.info('onConfirm');
|
||||
},
|
||||
title: '动态修改配置示例',
|
||||
});
|
||||
|
||||
const state = modalApi.useStore();
|
||||
|
||||
function handleUpdateTitle() {
|
||||
modalApi.setState({ title: '内部动态标题' });
|
||||
}
|
||||
|
||||
function handleToggleFullscreen() {
|
||||
modalApi.setState((prev) => {
|
||||
return { ...prev, fullscreen: !prev.fullscreen };
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Modal>
|
||||
<div class="flex-col-center">
|
||||
<VbenButton class="mb-3" type="primary" @click="handleUpdateTitle()">
|
||||
内部动态修改标题
|
||||
</VbenButton>
|
||||
<VbenButton class="mb-3" @click="handleToggleFullscreen()">
|
||||
{{ state.fullscreen ? '退出全屏' : '打开全屏' }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
21
docs/src/demos/vben-modal/extra/index.vue
Normal file
21
docs/src/demos/vben-modal/extra/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraModal from './modal.vue';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraModal,
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
8
docs/src/demos/vben-modal/extra/modal.vue
Normal file
8
docs/src/demos/vben-modal/extra/modal.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
const [Modal] = useVbenModal();
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="组件抽离示例"> extra modal content </Modal>
|
||||
</template>
|
||||
27
docs/src/demos/vben-modal/shared-data/index.vue
Normal file
27
docs/src/demos/vben-modal/shared-data/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal, VbenButton } from '@vben/common-ui';
|
||||
|
||||
import ExtraModal from './modal.vue';
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 连接抽离的组件
|
||||
connectedComponent: ExtraModal,
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
modalApi
|
||||
.setData({
|
||||
content: '外部传递的数据 content',
|
||||
payload: '外部传递的数据 payload',
|
||||
})
|
||||
.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
26
docs/src/demos/vben-modal/shared-data/modal.vue
Normal file
26
docs/src/demos/vben-modal/shared-data/modal.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
const data = ref();
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
console.info('onConfirm');
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
data.value = modalApi.getData<Record<string, any>>();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="数据共享示例">
|
||||
<div class="flex-col-center">外部传递数据: {{ data }}</div>
|
||||
</Modal>
|
||||
</template>
|
||||
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>
|
||||
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>
|
||||
127
docs/src/demos/vben-vxe-table/form/index.vue
Normal file
127
docs/src/demos/vben-vxe-table/form/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<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: '查询',
|
||||
},
|
||||
// 是否在字段值改变时提交表单
|
||||
submitOnChange: false,
|
||||
// 按下回车时是否提交表单
|
||||
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,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
toolbarConfig: {
|
||||
// 是否显示搜索表单控制按钮
|
||||
// @ts-ignore 正式环境时有完整的类型声明
|
||||
search: true,
|
||||
},
|
||||
};
|
||||
|
||||
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>
|
||||
243
docs/src/en/guide/essentials/build.md
Normal file
243
docs/src/en/guide/essentials/build.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Build and Deployment
|
||||
|
||||
::: tip Preface
|
||||
|
||||
Since this is a demonstration project, the package size after building is relatively large. If there are plugins in the project that are not used, you can delete the corresponding files or routes. If they are not referenced, they will not be packaged.
|
||||
|
||||
:::
|
||||
|
||||
## Building
|
||||
|
||||
After the project development is completed, execute the following command to build:
|
||||
|
||||
**Note:** Please execute the following command in the project root directory.
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
After the build is successful, a `dist` folder for the corresponding application will be generated in the root directory, which contains the built and packaged files, for example: `apps/web-antd/dist/`
|
||||
|
||||
## Preview
|
||||
|
||||
Before publishing, you can preview it locally in several ways, here are two:
|
||||
|
||||
- Using the project's custom command for preview (recommended)
|
||||
|
||||
**Note:** Please execute the following command in the project root directory.
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
After waiting for the build to succeed, visit `http://localhost:4173` to view the effect.
|
||||
|
||||
- Local server preview
|
||||
|
||||
You can globally install a `serve` service on your computer, such as `live-server`,
|
||||
|
||||
```bash
|
||||
npm i -g live-server
|
||||
```
|
||||
|
||||
Then execute the `live-server` command in the `dist` directory to view the effect locally.
|
||||
|
||||
```bash
|
||||
cd apps/web-antd/dist
|
||||
# Local preview, default port 8080
|
||||
live-server
|
||||
# Specify port
|
||||
live-server --port 9000
|
||||
```
|
||||
|
||||
## Compression
|
||||
|
||||
### Enable `gzip` Compression
|
||||
|
||||
To enable during the build process, change the `.env.production` configuration:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=gzip
|
||||
```
|
||||
|
||||
### Enable `brotli` Compression
|
||||
|
||||
To enable during the build process, change the `.env.production` configuration:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=brotli
|
||||
```
|
||||
|
||||
### Enable Both `gzip` and `brotli` Compression
|
||||
|
||||
To enable during the build process, change the `.env.production` configuration:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=gzip,brotli
|
||||
```
|
||||
|
||||
::: tip Note
|
||||
|
||||
Both `gzip` and `brotli` require specific modules to be installed for use.
|
||||
|
||||
:::
|
||||
|
||||
::: details gzip 与 brotli 在 nginx 内的配置
|
||||
|
||||
```bash
|
||||
http {
|
||||
# Enable gzip
|
||||
gzip on;
|
||||
# Enable gzip_static
|
||||
# After enabling gzip_static, there might be errors, requiring the installation of specific modules. The installation method can be researched independently.
|
||||
# Only with this enabled, the .gz files packaged by vue files will be effective; otherwise, there is no need to enable gzip for packaging.
|
||||
gzip_static on;
|
||||
gzip_proxied any;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 16k;
|
||||
# If nginx uses multiple layers of proxy, this must be set to enable gzip.
|
||||
gzip_http_version 1.0;
|
||||
gzip_comp_level 2;
|
||||
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
|
||||
gzip_vary off;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
# Enable brotli compression
|
||||
# Requires the installation of the corresponding nginx module, which can be researched independently.
|
||||
# Can coexist with gzip without conflict.
|
||||
brotli on;
|
||||
brotli_comp_level 6;
|
||||
brotli_buffers 16 8k;
|
||||
brotli_min_length 20;
|
||||
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Build Analysis
|
||||
|
||||
If your build files are large, you can optimize your code by analyzing the code size with the built-in [rollup-plugin-analyzer](https://github.com/doesdev/rollup-plugin-analyzer) plugin. Just execute the following command in the `root directory`:
|
||||
|
||||
```bash
|
||||
pnpm run build:analyze
|
||||
```
|
||||
|
||||
After running, you can see the specific distribution of sizes on the automatically opened page to analyze which dependencies are problematic.
|
||||
|
||||

|
||||
|
||||
## Deployment
|
||||
|
||||
A simple deployment only requires publishing the final static files, the static files in the dist folder, to your CDN or static server. It's important to note that the index.html is usually the entry page for your backend service. After determining the static js and css, you may need to change the page's import path.
|
||||
|
||||
For example, to upload to an nginx server, you can upload the files under the dist folder to the server's `/srv/www/project/index.html` directory, and then access the configured domain name.
|
||||
|
||||
```bash
|
||||
# nginx configuration
|
||||
location / {
|
||||
# Do not cache html to prevent cache from continuing to be effective after program updates
|
||||
if ($request_filename ~* .*\.(?:htm|html)$) {
|
||||
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
|
||||
access_log on;
|
||||
}
|
||||
# This is the storage path for the files inside the vue packaged dist folder
|
||||
root /srv/www/project/;
|
||||
index index.html index.htm;
|
||||
}
|
||||
```
|
||||
|
||||
If you find the resource path is incorrect during deployment, you just need to modify the `.env.production` file.
|
||||
|
||||
```bash
|
||||
# Configure the change according to your own path
|
||||
# Note that it needs to start and end with /
|
||||
VITE_BASE=/
|
||||
VITE_BASE=/xxx/
|
||||
```
|
||||
|
||||
### Integration of Frontend Routing and Server
|
||||
|
||||
The project uses vue-router for frontend routing, so you can choose between two modes: history and hash.
|
||||
|
||||
- `hash` mode will append `#` to the URL by default.
|
||||
- `history` mode will not, but `history` mode requires server-side support.
|
||||
|
||||
You can modify the mode in `.env.production`:
|
||||
|
||||
```bash
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
```
|
||||
|
||||
### Server Configuration for History Mode Routing
|
||||
|
||||
Enabling `history` mode requires server configuration. For more details on server configuration, see [history-mode](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode)
|
||||
|
||||
Here is an example of `nginx` configuration:
|
||||
|
||||
#### Deployment at the Root Directory
|
||||
|
||||
```bash {5}
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
# For use with History mode
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Deployment to a Non-root Directory
|
||||
|
||||
- First, you need to change the `.env.production` configuration during packaging:
|
||||
|
||||
```bash
|
||||
VITE_BASE = /sub/
|
||||
```
|
||||
|
||||
- Then configure in the nginx configuration file
|
||||
|
||||
```bash {8}
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location /sub/ {
|
||||
# This is the path where the vue packaged dist files are stored
|
||||
alias /srv/www/project/;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /sub/index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cross-Domain Handling
|
||||
|
||||
Using nginx to handle cross-domain issues after project deployment
|
||||
|
||||
1. Configure the frontend project API address in the `.env.production` file in the project directory:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=/api
|
||||
```
|
||||
|
||||
2. Configure nginx to forward requests to the backend
|
||||
|
||||
```bash {10-11}
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
# API proxy for solving cross-domain issues
|
||||
location /api {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# Backend API address
|
||||
proxy_pass http://110.110.1.1:8080/api;
|
||||
rewrite "^/api/(.*)$" /$1 break;
|
||||
proxy_redirect default;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Headers X-Requested-With;
|
||||
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
|
||||
}
|
||||
}
|
||||
```
|
||||
70
docs/src/en/guide/essentials/concept.md
Normal file
70
docs/src/en/guide/essentials/concept.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Basic Concepts
|
||||
|
||||
In the new version, the entire project has been restructured. Now, we will introduce some basic concepts to help you better understand the entire document. Please make sure to read this section first.
|
||||
|
||||
## Monorepo
|
||||
|
||||
Monorepo refers to the repository of the entire project, which includes all code, packages, applications, standards, documentation, configurations, etc., that is, the entire content of a `Monorepo` directory.
|
||||
|
||||
## Applications
|
||||
|
||||
Applications refer to a complete project; a project can contain multiple applications, which can reuse the code, packages, standards, etc., within the monorepo. Applications are placed in the `apps` directory. Each application is independent and can be run, built, tested, and deployed separately; it can also include different component libraries, etc.
|
||||
|
||||
::: tip
|
||||
|
||||
Applications are not limited to front-end applications; they can also be back-end applications, mobile applications, etc. For example, `apps/backend-mock` is a back-end service.
|
||||
|
||||
:::
|
||||
|
||||
## Packages
|
||||
|
||||
A package refers to an independent module, which can be a component, a tool, a library, etc. Packages can be referenced by multiple applications or other packages. Packages are placed in the `packages` directory.
|
||||
|
||||
You can consider these packages as independent `npm` packages, and they are used in the same way as `npm` packages.
|
||||
|
||||
### Package Import
|
||||
|
||||
Importing a package in `package.json`:
|
||||
|
||||
```json {3}
|
||||
{
|
||||
"dependencies": {
|
||||
"@vben/utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Package Usage
|
||||
|
||||
Importing a package in the code:
|
||||
|
||||
```ts
|
||||
import { isString } from '@vben/utils';
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
In the project, you can see some paths starting with `#`, such as `#/api`, `#/views`. These paths are aliases, used for quickly locating a certain directory. They are not implemented through `vite`'s `alias`, but through the principle of [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `Node.js` itself. You only need to configure the `imports` field in `package.json`.
|
||||
|
||||
```json {3}
|
||||
{
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To make these aliases recognizable by the IDE, we also need to configure them in `tsconfig.json`:
|
||||
|
||||
```json {5}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"#/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, you can use aliases in your code.
|
||||
58
docs/src/en/guide/essentials/external-module.md
Normal file
58
docs/src/en/guide/essentials/external-module.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# External Modules
|
||||
|
||||
In addition to the external modules that are included by default in the project, sometimes we need to import other external modules. Let's take [ant-design-vue](https://antdv.com/components/overview) as an example:
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
::: tip Install dependencies into a specific package
|
||||
|
||||
- Since the project uses [pnpm](https://pnpm.io/) as the package management tool, we need to use the `pnpm` command to install dependencies.
|
||||
- As the project is managed using a Monorepo module, we need to install dependencies under a specific package. Please make sure you have entered the specific package directory before installing dependencies.
|
||||
|
||||
:::
|
||||
|
||||
```bash
|
||||
# cd /path/to/your/package
|
||||
pnpm add ant-design-vue
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Global Import
|
||||
|
||||
```ts
|
||||
import { createApp } from 'vue';
|
||||
import Antd from 'ant-design-vue';
|
||||
import App from './App';
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(Antd).mount('#app');
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-button>text</a-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Partial Import
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { Button } from 'ant-design-vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button>text</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
::: warning Note
|
||||
|
||||
- If the component depends on styles, you also need to import the style file.
|
||||
|
||||
:::
|
||||
78
docs/src/en/guide/essentials/icons.md
Normal file
78
docs/src/en/guide/essentials/icons.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Icons
|
||||
|
||||
::: tip About Icon Management
|
||||
|
||||
- The icons in the project are mainly provided by the `@vben/icons` package. It is recommended to manage them within this package for unified management and maintenance.
|
||||
- If you are using `Vscode`, it is recommended to install the [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) plugin, which makes it easy to find and use icons.
|
||||
|
||||
:::
|
||||
|
||||
There are several ways to use icons in the project, you can choose according to the actual situation:
|
||||
|
||||
## Iconify Icons <Badge text="Recommended" type="tip"/>
|
||||
|
||||
Integrated with the [iconify](https://github.com/iconify/iconify) icon library
|
||||
|
||||
### Adding New Icons
|
||||
|
||||
You can add new icons in the `packages/icons/src/iconify` directory:
|
||||
|
||||
```ts
|
||||
// packages/icons/src/iconify/index.ts
|
||||
import { createIconifyIcon } from '@vben-core/icons';
|
||||
|
||||
export const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc');
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { MdiKeyboardEsc } from '@vben/icons';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- An icon with a width and height of 20px -->
|
||||
<MdiKeyboardEsc class="size-5" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## SVG Icons <Badge text="Recommended" type="tip"/>
|
||||
|
||||
Instead of using Svg Sprite, SVG icons are directly imported,
|
||||
|
||||
### Adding New Icons
|
||||
|
||||
You can add new icon files `test.svg` in the `packages/icons/src/svg/icons` directory, and then import it in `packages/icons/src/svg/index.ts`:
|
||||
|
||||
```ts
|
||||
// packages/icons/src/svg/index.ts
|
||||
import { createIconifyIcon } from '@vben-core/icons';
|
||||
|
||||
const SvgTestIcon = createIconifyIcon('svg:test');
|
||||
|
||||
export { SvgTestIcon };
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { SvgTestIcon } from '@vben/icons';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- An icon with a width and height of 20px -->
|
||||
<SvgTestIcon class="size-5" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Tailwind CSS Icons <Badge text="Not Recommended" type="danger"/>
|
||||
|
||||
### Usage
|
||||
|
||||
You can use the icons by directly adding the Tailwind CSS icon class names, which can be found on [iconify](https://github.com/iconify/iconify) :
|
||||
|
||||
```vue
|
||||
<span class="icon-[mdi--ab-testing]"></span>
|
||||
```
|
||||
603
docs/src/en/guide/essentials/route.md
Normal file
603
docs/src/en/guide/essentials/route.md
Normal file
@@ -0,0 +1,603 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Routes and Menus
|
||||
|
||||
::: info
|
||||
|
||||
This page is translated by machine translation and may not be very accurate.
|
||||
|
||||
:::
|
||||
|
||||
In the project, the framework provides a basic routing system and **automatically generates the corresponding menu structure based on the routing files**.
|
||||
|
||||
## Types of Routes
|
||||
|
||||
Routes are divided into core routes, static routes, and dynamic routes. Core routes are built-in routes of the framework, including root routes, login routes, 404 routes, etc.; static routes are routes that are determined when the project starts; dynamic routes are generally generated dynamically based on the user's permissions after the user logs in.
|
||||
|
||||
Both static and dynamic routes go through permission control, which can be controlled by configuring the `authority` field in the `meta` property of the route.
|
||||
|
||||
### Core Routes
|
||||
|
||||
Core routes are built-in routes of the framework, including root routes, login routes, 404 routes, etc. The configuration of core routes is in the `src/router/routes/core` directory under the application.
|
||||
|
||||
::: tip
|
||||
|
||||
Core routes are mainly used for the basic functions of the framework, so it is not recommended to put business-related routes in core routes. It is recommended to put business-related routes in static or dynamic routes.
|
||||
|
||||
:::
|
||||
|
||||
### Static Routes
|
||||
|
||||
The configuration of static routes is in the `src/router/routes/index` directory under the application. Open the commented file content:
|
||||
|
||||
::: tip
|
||||
|
||||
Permission control is controlled by the `authority` field in the `meta` property of the route. If your page project does not require permission control, you can omit the `authority` field.
|
||||
|
||||
:::
|
||||
|
||||
```ts
|
||||
// Uncomment if needed and create the folder
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // [!code --]
|
||||
const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); // [!code ++]
|
||||
/** Dynamic routes */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** External route list, these pages can be accessed without Layout, possibly used for embedding in other systems */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles) // [!code --]
|
||||
const externalRoutes: RouteRecordRaw[] = []; // [!code --]
|
||||
const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // [!code ++]
|
||||
```
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
The configuration of dynamic routes is in the `src/router/routes/modules` directory under the corresponding application. This directory contains all the route files. The content format of each file is consistent with the Vue Router route configuration format. Below is the configuration of secondary and multi-level routes.
|
||||
|
||||
## Route Definition
|
||||
|
||||
The configuration method of static routes and dynamic routes is the same. Below is the configuration of secondary and multi-level routes:
|
||||
|
||||
### Secondary Routes
|
||||
|
||||
::: details Secondary Route Example Code
|
||||
|
||||
```ts
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { VBEN_LOGO_URL } from '@vben/constants';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
badgeVariants: 'destructive',
|
||||
icon: VBEN_LOGO_URL,
|
||||
order: 9999,
|
||||
title: $t('page.vben.title'),
|
||||
},
|
||||
name: 'VbenProject',
|
||||
path: '/vben-admin',
|
||||
redirect: '/vben-admin/about',
|
||||
children: [
|
||||
{
|
||||
name: 'VbenAbout',
|
||||
path: '/vben-admin/about',
|
||||
component: () => import('#/views/_core/about/index.vue'),
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
badgeVariants: 'destructive',
|
||||
icon: 'lucide:copyright',
|
||||
title: $t('page.vben.about'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Multi-level Routes
|
||||
|
||||
::: tip
|
||||
|
||||
- The parent route of multi-level routes does not need to set the `component` property, just set the `children` property. Unless you really need to display content nested under the parent route.
|
||||
- In most cases, the `redirect` property of the parent route does not need to be specified, it will default to the first child route.
|
||||
|
||||
:::
|
||||
|
||||
::: details Multi-level Route Example Code
|
||||
|
||||
```ts
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('demos.title'),
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
redirect: '/demos/access',
|
||||
children: [
|
||||
// Nested menu
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('demos.nested.title'),
|
||||
},
|
||||
name: 'NestedDemos',
|
||||
path: '/demos/nested',
|
||||
redirect: '/demos/nested/menu1',
|
||||
children: [
|
||||
{
|
||||
name: 'Menu1Demo',
|
||||
path: '/demos/nested/menu1',
|
||||
component: () => import('#/views/demos/nested/menu-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('demos.nested.menu1'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Menu2Demo',
|
||||
path: '/demos/nested/menu2',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('demos.nested.menu2'),
|
||||
},
|
||||
redirect: '/demos/nested/menu2/menu2-1',
|
||||
children: [
|
||||
{
|
||||
name: 'Menu21Demo',
|
||||
path: '/demos/nested/menu2/menu2-1',
|
||||
component: () => import('#/views/demos/nested/menu-2-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('demos.nested.menu2_1'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Menu3Demo',
|
||||
path: '/demos/nested/menu3',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('demos.nested.menu3'),
|
||||
},
|
||||
redirect: '/demos/nested/menu3/menu3-1',
|
||||
children: [
|
||||
{
|
||||
name: 'Menu31Demo',
|
||||
path: 'menu3-1',
|
||||
component: () => import('#/views/demos/nested/menu-3-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('demos.nested.menu3_1'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Menu32Demo',
|
||||
path: 'menu3-2',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('demos.nested.menu3_2'),
|
||||
},
|
||||
redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',
|
||||
children: [
|
||||
{
|
||||
name: 'Menu321Demo',
|
||||
path: '/demos/nested/menu3/menu3-2/menu3-2-1',
|
||||
component: () =>
|
||||
import('#/views/demos/nested/menu-3-2-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('demos.nested.menu3_2_1'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Adding a New Page
|
||||
|
||||
To add a new page, you only need to add a route and the corresponding page component.
|
||||
|
||||
### Adding a Route
|
||||
|
||||
Add a route object in the corresponding route file, as follows:
|
||||
|
||||
```ts
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { VBEN_LOGO_URL } from '@vben/constants';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:home',
|
||||
title: $t('page.home.title'),
|
||||
},
|
||||
name: 'Home',
|
||||
path: '/home',
|
||||
redirect: '/home/index',
|
||||
children: [
|
||||
{
|
||||
name: 'HomeIndex',
|
||||
path: '/home/index',
|
||||
component: () => import('#/views/home/index.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:home',
|
||||
title: $t('page.home.index'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
```
|
||||
|
||||
### Adding a Page Component
|
||||
|
||||
In `#/views/home/`, add a new `index.vue` file, as follows:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<h1>home page</h1>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
At this point, the page has been added. Visit `http://localhost:5555/home/index` to see the corresponding page.
|
||||
|
||||
## Route Configuration
|
||||
|
||||
The route configuration items are mainly in the `meta` property of the route object. The following are common configuration items:
|
||||
|
||||
```ts {5-8}
|
||||
const routes = [
|
||||
{
|
||||
name: 'HomeIndex',
|
||||
path: '/home/index',
|
||||
meta: {
|
||||
icon: 'mdi:home',
|
||||
title: $t('page.home.index'),
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
::: details Route Meta Configuration Type Definition
|
||||
|
||||
```ts
|
||||
interface RouteMeta {
|
||||
/**
|
||||
* Active icon (menu)
|
||||
*/
|
||||
activeIcon?: string;
|
||||
/**
|
||||
* The currently active menu, sometimes you don't want to activate the existing menu, use this to activate the parent menu
|
||||
*/
|
||||
activePath?: string;
|
||||
/**
|
||||
* Whether to fix the tab
|
||||
* @default false
|
||||
*/
|
||||
affixTab?: boolean;
|
||||
/**
|
||||
* The order of fixed tabs
|
||||
* @default 0
|
||||
*/
|
||||
affixTabOrder?: number;
|
||||
/**
|
||||
* Specific roles required to access
|
||||
* @default []
|
||||
*/
|
||||
authority?: string[];
|
||||
/**
|
||||
* Badge
|
||||
*/
|
||||
badge?: string;
|
||||
/**
|
||||
* Badge type
|
||||
*/
|
||||
badgeType?: 'dot' | 'normal';
|
||||
/**
|
||||
* Badge color
|
||||
*/
|
||||
badgeVariants?:
|
||||
| 'default'
|
||||
| 'destructive'
|
||||
| 'primary'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| string;
|
||||
/**
|
||||
* The children of the current route are not displayed in the menu
|
||||
* @default false
|
||||
*/
|
||||
hideChildrenInMenu?: boolean;
|
||||
/**
|
||||
* The current route is not displayed in the breadcrumb
|
||||
* @default false
|
||||
*/
|
||||
hideInBreadcrumb?: boolean;
|
||||
/**
|
||||
* The current route is not displayed in the menu
|
||||
* @default false
|
||||
*/
|
||||
hideInMenu?: boolean;
|
||||
/**
|
||||
* The current route is not displayed in the tab
|
||||
* @default false
|
||||
*/
|
||||
hideInTab?: boolean;
|
||||
/**
|
||||
* Icon (menu/tab)
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* iframe address
|
||||
*/
|
||||
iframeSrc?: string;
|
||||
/**
|
||||
* Ignore permissions, can be accessed directly
|
||||
* @default false
|
||||
*/
|
||||
ignoreAccess?: boolean;
|
||||
/**
|
||||
* Enable KeepAlive cache
|
||||
*/
|
||||
keepAlive?: boolean;
|
||||
/**
|
||||
* External link - jump path
|
||||
*/
|
||||
link?: string;
|
||||
/**
|
||||
* Whether the route has been loaded
|
||||
*/
|
||||
loaded?: boolean;
|
||||
/**
|
||||
* Maximum number of open tabs
|
||||
* @default false
|
||||
*/
|
||||
maxNumOfOpenTab?: number;
|
||||
/**
|
||||
* The menu can be seen, but access will be redirected to 403
|
||||
*/
|
||||
menuVisibleWithForbidden?: boolean;
|
||||
/**
|
||||
* Open in a new window
|
||||
*/
|
||||
openInNewWindow?: boolean;
|
||||
/**
|
||||
* Used for route -> menu sorting
|
||||
*/
|
||||
order?: number;
|
||||
/**
|
||||
* Parameters carried by the menu
|
||||
*/
|
||||
query?: Recordable;
|
||||
/**
|
||||
* Title name
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### title
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the title of the page, which will be displayed in the menu and tab. Generally used with internationalization.
|
||||
|
||||
### icon
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the icon of the page, which will be displayed in the menu and tab. Generally used with an icon library, if it is an `http` link, the image will be loaded automatically.
|
||||
|
||||
### activeIcon
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the active icon of the page, which will be displayed in the menu. Generally used with an icon library, if it is an `http` link, the image will be loaded automatically.
|
||||
|
||||
### keepAlive
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page cache is enabled. When enabled, the page will be cached and will not reload, only effective when the tab is enabled.
|
||||
|
||||
### hideInMenu
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page is hidden in the menu. When hidden, the page will not be displayed in the menu.
|
||||
|
||||
### hideInTab
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page is hidden in the tab. When hidden, the page will not be displayed in the tab.
|
||||
|
||||
### hideInBreadcrumb
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page is hidden in the breadcrumb. When hidden, the page will not be displayed in the breadcrumb.
|
||||
|
||||
### hideChildrenInMenu
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the subpages of the page are hidden in the menu. When hidden, the subpages will not be displayed in the menu.
|
||||
|
||||
### authority
|
||||
|
||||
- Type: `string[]`
|
||||
- Default: `[]`
|
||||
|
||||
Used to configure the permissions of the page. Only users with the corresponding permissions can access the page. If not configured, no permissions are required.
|
||||
|
||||
### badge
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the badge of the page, which will be displayed in the menu.
|
||||
|
||||
### badgeType
|
||||
|
||||
- Type: `'dot' | 'normal'`
|
||||
- Default: `'normal'`
|
||||
|
||||
Used to configure the badge type of the page. `dot` is a small red dot, `normal` is text.
|
||||
|
||||
### badgeVariants
|
||||
|
||||
- Type: `'default' | 'destructive' | 'primary' | 'success' | 'warning' | string`
|
||||
- Default: `'success'`
|
||||
|
||||
Used to configure the badge color of the page.
|
||||
|
||||
### activePath
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the currently active menu. Sometimes the page is not displayed in the menu, and this is used to activate the parent menu.
|
||||
|
||||
### affixTab
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page is fixed in the tab. When fixed, the page cannot be closed.
|
||||
|
||||
### affixTabOrder
|
||||
|
||||
- Type: `number`
|
||||
- Default: `0`
|
||||
|
||||
Used to configure the order of fixed tabs, sorted in ascending order.
|
||||
|
||||
### iframeSrc
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the `iframe` address of the embedded page. When set, the corresponding page will be embedded in the current page.
|
||||
|
||||
### ignoreAccess
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page ignores permissions and can be accessed directly.
|
||||
|
||||
### link
|
||||
|
||||
- Type: `string`
|
||||
- Default: `''`
|
||||
|
||||
Used to configure the external link jump path, which will open in a new window.
|
||||
|
||||
### maxNumOfOpenTab
|
||||
|
||||
- Type: `number`
|
||||
- Default: `-1`
|
||||
|
||||
Used to configure the maximum number of open tabs. When set, the earliest opened tab will be automatically closed when opening a new tab (only effective when opening tabs with the same name).
|
||||
|
||||
### menuVisibleWithForbidden
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Used to configure whether the page can be seen in the menu, but access will be redirected to 403.
|
||||
|
||||
### openInNewWindow
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
When set to `true`, the page will open in a new window.
|
||||
|
||||
### order
|
||||
|
||||
- Type: `number`
|
||||
- Default: `0`
|
||||
|
||||
Used to configure the sorting of the page, used for route to menu sorting.
|
||||
|
||||
_Note:_ Sorting is only effective for first-level menus. The sorting of second-level menus needs to be set in the corresponding first-level menu in code order.
|
||||
|
||||
### query
|
||||
|
||||
- Type: `Recordable`
|
||||
- Default: `{}`
|
||||
|
||||
Used to configure the menu parameters of the page, which will be passed to the page in the menu.
|
||||
|
||||
## Route Refresh
|
||||
|
||||
The route refresh method is as follows:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useRefresh } from '@vben/hooks';
|
||||
|
||||
const { refresh } = useRefresh();
|
||||
|
||||
// Refresh the current route
|
||||
refresh();
|
||||
</script>
|
||||
```
|
||||
356
docs/src/en/guide/essentials/server.md
Normal file
356
docs/src/en/guide/essentials/server.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# Server Interaction and Data Mocking
|
||||
|
||||
::: tip Note
|
||||
|
||||
This document explains how to use Mock data and interact with the server in a development environment, involving technologies such as:
|
||||
|
||||
- [Nitro](https://nitro.unjs.io/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project.
|
||||
- [axios](https://axios-http.com/docs/intro) Used to send HTTP requests to interact with the server.
|
||||
|
||||
:::
|
||||
|
||||
## Interaction in Development Environment
|
||||
|
||||
If the frontend application and the backend API server are not running on the same host, you need to proxy the API requests to the API server in the development environment. If they are on the same host, you can directly request the specific API endpoint.
|
||||
|
||||
### Local Development CORS Configuration
|
||||
|
||||
::: tip Hint
|
||||
|
||||
The CORS configuration for local development has already been set up. If you have other requirements, you can add or adjust the configuration as needed.
|
||||
|
||||
:::
|
||||
|
||||
#### Configuring Local Development API Endpoint
|
||||
|
||||
Configure the API endpoint in the `.env.development` file at the project root directory, here it is set to `/api`:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=/api
|
||||
```
|
||||
|
||||
#### Configuring Development Server Proxy
|
||||
|
||||
In the development environment, if you need to handle CORS, configure the API endpoint in the `vite.config.mts` file under the corresponding application directory:
|
||||
|
||||
```ts{8-16}
|
||||
// apps/web-antd/vite.config.mts
|
||||
import { defineConfig } from '@vben/vite-config';
|
||||
|
||||
export default defineConfig(async () => {
|
||||
return {
|
||||
vite: {
|
||||
server: {
|
||||
proxy: {// [!code focus:11]
|
||||
'/api': {
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
// mock proxy
|
||||
target: 'http://localhost:5320/api',
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
#### API Requests
|
||||
|
||||
Based on the above configuration, we can use `/api` as the prefix for API requests in our frontend project, for example:
|
||||
|
||||
```ts
|
||||
import axios from 'axios';
|
||||
|
||||
axios.get('/api/user').then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
```
|
||||
|
||||
At this point, the request will be proxied to `http://localhost:5320/api/user`.
|
||||
|
||||
::: warning Note
|
||||
|
||||
From the browser's console Network tab, the request appears as `http://localhost:5555/api/user`. This is because the proxy configuration does not change the local request's URL.
|
||||
|
||||
:::
|
||||
|
||||
### Configuration Without CORS
|
||||
|
||||
If there is no CORS issue, you can directly ignore the [Configure Development Server Proxy](./server.md#configure-development-server-proxy) settings and set the API endpoint directly in `VITE_GLOB_API_URL`.
|
||||
|
||||
Configure the API endpoint in the `.env.development` file at the project root directory:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
```
|
||||
|
||||
## Production Environment Interaction
|
||||
|
||||
### API Endpoint Configuration
|
||||
|
||||
Configure the API endpoint in the `.env.production` file at the project root directory:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
```
|
||||
|
||||
::: tip How to Dynamically Modify API Endpoint in Production
|
||||
|
||||
Variables starting with `VITE_GLOB_*` in the `.env` file are injected into the `_app.config.js` file during packaging. After packaging, you can modify the corresponding API addresses in `dist/_app.config.js` and refresh the page to apply the changes. This eliminates the need to package multiple times for different environments, allowing a single package to be deployed across multiple API environments.
|
||||
|
||||
:::
|
||||
|
||||
### Cross-Origin Resource Sharing (CORS) Handling
|
||||
|
||||
In the production environment, if CORS issues arise, you can use `nginx` to proxy the API address or enable `cors` on the backend to handle it (refer to the mock service for examples).
|
||||
|
||||
## API Request Configuration
|
||||
|
||||
The project comes with a default basic request configuration based on `axios`, provided by the `@vben/request` package. The project does not overly complicate things but simply wraps some common configurations. If there are other requirements, you can add or adjust the configurations as needed. Depending on the app, different component libraries and `store` might be used, so under the `src/api/request.ts` folder in the application directory, there are corresponding request configuration files. For example, in the `web-antd` project, there's a `src/api/request.ts` file where you can configure according to your needs.
|
||||
|
||||
### Request Examples
|
||||
|
||||
#### GET Request
|
||||
|
||||
```ts
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
```
|
||||
|
||||
#### POST/PUT Request
|
||||
|
||||
```ts
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export async function saveUserApi(user: UserInfo) {
|
||||
return requestClient.post<UserInfo>('/user', user);
|
||||
}
|
||||
|
||||
export async function saveUserApi(user: UserInfo) {
|
||||
return requestClient.put<UserInfo>('/user', user);
|
||||
}
|
||||
|
||||
export async function saveUserApi(user: UserInfo) {
|
||||
const url = user.id ? `/user/${user.id}` : '/user/';
|
||||
return requestClient.request<UserInfo>(url, {
|
||||
data: user,
|
||||
// OR PUT
|
||||
method: user.id ? 'PUT' : 'POST',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### DELETE Request
|
||||
|
||||
```ts
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export async function deleteUserApi(userId: number) {
|
||||
return requestClient.delete<boolean>(`/user/${userId}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Request Configuration
|
||||
|
||||
The `src/api/request.ts` within the application can be configured according to the needs of your application:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* This file can be adjusted according to business logic
|
||||
*/
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
baseURL,
|
||||
});
|
||||
|
||||
/**
|
||||
* Re-authentication Logic
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
if (preferences.app.loginExpiredMode === 'modal') {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token Logic
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
// Request Header Processing
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// Deal Response Data
|
||||
client.addResponseInterceptor<HttpResponse>({
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data } = responseData;
|
||||
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw Object.assign({}, response, { response });
|
||||
},
|
||||
});
|
||||
|
||||
// Handling Token Expiration
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// Generic error handling; if none of the above error handling logic is triggered, it will fall back to this.
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
```
|
||||
|
||||
### Multiple API Endpoints
|
||||
|
||||
To handle multiple API endpoints, simply create multiple `requestClient` instances, as follows:
|
||||
|
||||
```ts
|
||||
const { apiURL, otherApiURL } = useAppConfig(
|
||||
import.meta.env,
|
||||
import.meta.env.PROD,
|
||||
);
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const otherRequestClient = createRequestClient(otherApiURL);
|
||||
```
|
||||
|
||||
## Refresh Token
|
||||
|
||||
The project provides a default logic for refreshing tokens. To enable it, follow the configuration below:
|
||||
|
||||
- Ensure the refresh token feature is enabled
|
||||
|
||||
Adjust the `preferences.ts` in the corresponding application directory to ensure `enableRefreshToken='true'`.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
enableRefreshToken: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Configure the `doRefreshToken` method in `src/api/request.ts` as follows:
|
||||
|
||||
```ts
|
||||
// Adjust this to your token format
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token logic
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
// Adjust this to your refresh token API
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
```
|
||||
|
||||
## Data Mocking
|
||||
|
||||
::: tip Production Environment Mock
|
||||
|
||||
The new version no longer supports mock in the production environment. Please use real interfaces.
|
||||
|
||||
:::
|
||||
|
||||
Mock data is an indispensable part of frontend development, serving as a key link in separating frontend and backend development. By agreeing on interfaces with the server side in advance and simulating request data and even logic, frontend development can proceed independently, without being blocked by the backend development process.
|
||||
|
||||
The project uses [Nitro](https://nitro.unjs.io/) for local mock data processing. The principle is to start an additional backend service locally, which is a real backend service that can handle requests and return data.
|
||||
|
||||
### Using Nitro
|
||||
|
||||
The mock service code is located in the `apps/backend-mock` directory. It does not need to be started manually and is already integrated into the project. You only need to run `pnpm dev` in the project root directory. After running successfully, the console will print `http://localhost:5320/api`, and you can access this address to view the mock service.
|
||||
|
||||
[Nitro](https://nitro.unjs.io/) syntax is simple, and you can configure and develop according to your needs. For specific configurations, you can refer to the [Nitro documentation](https://nitro.unjs.io/).
|
||||
|
||||
## Disabling Mock Service
|
||||
|
||||
Since mock is essentially a real backend service, if you do not need the mock service, you can configure `VITE_NITRO_MOCK=false` in the `.env.development` file in the project root directory to disable the mock service.
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
VITE_NITRO_MOCK=false
|
||||
```
|
||||
106
docs/src/en/guide/essentials/styles.md
Normal file
106
docs/src/en/guide/essentials/styles.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Styles
|
||||
|
||||
::: tip Preface
|
||||
|
||||
For Vue projects, the [official documentation](https://vuejs.org/api/sfc-css-features.html#deep-selectors) already provides a detailed introduction to the syntax. Here, we mainly introduce the structure and usage of style files in the project.
|
||||
|
||||
:::
|
||||
|
||||
## Project Structure
|
||||
|
||||
The style files in the project are stored in `@vben/styles`, which includes some global styles, such as reset styles, global variables, etc. It inherits the styles and capabilities of `@vben-core/design` and can be overridden according to project needs.
|
||||
|
||||
## Scss
|
||||
|
||||
The project uses `scss` as the style preprocessor, allowing the use of `scss` features such as variables, functions, mixins, etc., within the project.
|
||||
|
||||
```vue
|
||||
<style lang="scss" scoped>
|
||||
$font-size: 30px;
|
||||
|
||||
.box {
|
||||
.title {
|
||||
color: green;
|
||||
font-size: $font-size;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Postcss
|
||||
|
||||
If you're not accustomed to using `scss`, you can also use `postcss`, which is a more powerful style processor that supports a wider range of plugins. The project includes the [postcss-nested](https://github.com/postcss/postcss-nested) plugin and is configured with `Css Variables`, making it a complete substitute for `scss`.
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
.box {
|
||||
--font-size: 30px;
|
||||
.title {
|
||||
color: green;
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
The project integrates [Tailwind CSS](https://tailwindcss.com/), allowing the use of `tailwindcss` class names to quickly build pages.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="bg-white p-4">
|
||||
<p class="text-green">hello world</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## BEM Standard
|
||||
|
||||
Another option to avoid style conflicts is to use the `BEM` standard. If you choose `scss`, it is recommended to use the `BEM` naming convention for better style management. The project provides a default `useNamespace` function to easily generate namespaces.
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { useNamespace } from '@vben/hooks';
|
||||
|
||||
const { b, e, is } = useNamespace('menu');
|
||||
</script>
|
||||
<template>
|
||||
<div :class="[b()]">
|
||||
<div :class="[e('item'), is('active', true)]">item1</div>
|
||||
</div>
|
||||
</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
|
||||
@use '@vben/styles/global' as *;
|
||||
@include b('menu') {
|
||||
color: black;
|
||||
|
||||
@include e('item') {
|
||||
background-color: black;
|
||||
|
||||
@include is('active') {
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## CSS Modules
|
||||
|
||||
Another solution to address style conflicts is to use the `CSS Modules` modular approach. The usage method is as follows.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<p :class="$style.red">This should be red</p>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
For more usage, see the [CSS Modules official documentation](https://vuejs.org/api/sfc-css-features.html#css-modules).
|
||||
356
docs/src/en/guide/in-depth/access.md
Normal file
356
docs/src/en/guide/in-depth/access.md
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Access Control
|
||||
|
||||
The framework has built-in three types of access control methods:
|
||||
|
||||
- Determining whether a menu or button can be accessed based on user roles
|
||||
- Determining whether a menu or button can be accessed through an API
|
||||
- Mixed mode: Using both frontend and backend access control simultaneously
|
||||
|
||||
## Frontend Access Control
|
||||
|
||||
**Implementation Principle**: The permissions for routes are hardcoded on the frontend, specifying which permissions are required to view certain routes. Only general routes are initialized, and routes that require permissions are not added to the route table. After logging in or obtaining user roles through other means, the roles are used to traverse the route table to generate a route table that the role can access. This table is then added to the router instance using `router.addRoute`, achieving permission filtering.
|
||||
|
||||
**Disadvantage**: The permissions are relatively inflexible; if the backend changes roles, the frontend needs to be adjusted accordingly. This is suitable for systems with relatively fixed roles.
|
||||
|
||||
### Steps
|
||||
|
||||
- Ensure the current mode is set to frontend access control
|
||||
|
||||
Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='frontend'`.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
// Default value, optional
|
||||
accessMode: 'frontend',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- Configure route permissions
|
||||
|
||||
#### If not configured, it is visible by default
|
||||
|
||||
```ts {3}
|
||||
{
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
- Ensure the roles returned by the interface match the permissions in the route table
|
||||
|
||||
You can look under `src/store/auth` in the application to find the following code:
|
||||
|
||||
```ts
|
||||
// Set the login user information, ensuring that userInfo.roles is an array and contains permissions from the route table
|
||||
// For example: userInfo.roles=['super', 'admin']
|
||||
authStore.setUserInfo(userInfo);
|
||||
```
|
||||
|
||||
At this point, the configuration is complete. You need to ensure that the roles returned by the interface after login match the permissions in the route table; otherwise, access will not be possible.
|
||||
|
||||
### Menu Visible but Access Forbidden
|
||||
|
||||
Sometimes, we need the menu to be visible but access to it forbidden. This can be achieved by setting `menuVisibleWithForbidden` to `true`. In this case, the menu will be visible, but access will be forbidden, redirecting to a 403 page.
|
||||
|
||||
```ts
|
||||
{
|
||||
meta: {
|
||||
menuVisibleWithForbidden: true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
## Backend Access Control
|
||||
|
||||
**Implementation Principle**: It is achieved by dynamically generating a routing table through an API, which returns data following a certain structure. The frontend processes this data into a recognizable structure, then adds it to the routing instance using `router.addRoute`, realizing the dynamic generation of permissions.
|
||||
|
||||
**Disadvantage**: The backend needs to provide a data structure that meets the standards, and the frontend needs to process this structure. This is suitable for systems with more complex permissions.
|
||||
|
||||
### Steps
|
||||
|
||||
- Ensure the current mode is set to backend access control
|
||||
|
||||
Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='backend'`.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
accessMode: 'backend',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- Ensure the structure of the menu data returned by the interface is correct
|
||||
|
||||
You can look under `src/router/access.ts` in the application to find the following code:
|
||||
|
||||
```ts
|
||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
return await generateAccessible(preferences.app.accessMode, {
|
||||
fetchMenuListAsync: async () => {
|
||||
// This interface is for the menu data returned by the backend
|
||||
return await getAllMenus();
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- Interface returns menu data, see comments for explanation
|
||||
|
||||
::: details Example of Interface Returning Menu Data
|
||||
|
||||
```ts
|
||||
const dashboardMenus = [
|
||||
{
|
||||
// Here, 'BasicLayout' is hardcoded and cannot be changed
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
order: -1,
|
||||
title: 'page.dashboard.title',
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
redirect: '/analytics',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
// Here is the path of the page, need to remove 'views/' and '.vue'
|
||||
component: '/dashboard/analytics/index',
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: 'page.dashboard.analytics',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: '/dashboard/workspace/index',
|
||||
meta: {
|
||||
title: 'page.dashboard.workspace',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
At this point, the configuration is complete. You need to ensure that after logging in, the format of the menu returned by the interface is correct; otherwise, access will not be possible.
|
||||
|
||||
## Mixed Access Control
|
||||
|
||||
**Implementation Principle**: Mixed mode combines both frontend access control and backend access control methods. The system processes frontend fixed route permissions and backend dynamic menu data in parallel, ultimately merging both parts of routes to provide a more flexible access control solution.
|
||||
|
||||
**Advantages**: Combines the performance advantages of frontend control with the flexibility of backend control, suitable for complex business scenarios requiring permission management.
|
||||
|
||||
### Steps
|
||||
|
||||
- Ensure the current mode is set to mixed access control
|
||||
|
||||
Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='mixed'`.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
accessMode: 'mixed',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- Configure frontend route permissions
|
||||
|
||||
Same as the route permission configuration method in [Frontend Access Control](#frontend-access-control) mode.
|
||||
|
||||
- Configure backend menu interface
|
||||
|
||||
Same as the interface configuration method in [Backend Access Control](#backend-access-control) mode.
|
||||
|
||||
- Ensure roles and permissions match
|
||||
|
||||
Must satisfy both frontend route permission configuration and backend menu data return requirements, ensuring user roles match the permission configurations of both modes.
|
||||
|
||||
At this point, the configuration is complete. Mixed mode will automatically merge frontend and backend routes, providing complete access control functionality.
|
||||
|
||||
## Fine-grained Control of Buttons
|
||||
|
||||
In some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles.
|
||||
|
||||
### Permission Code
|
||||
|
||||
The permission code is the code returned by the interface. The logic to determine whether a button is displayed is located under `src/store/auth`:
|
||||
|
||||
```ts
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodes(),
|
||||
]);
|
||||
|
||||
userInfo = fetchUserInfoResult;
|
||||
authStore.setUserInfo(userInfo);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
```
|
||||
|
||||
Locate the `getAccessCodes` corresponding interface, which can be adjusted according to business logic.
|
||||
|
||||
The data structure returned by the permission code is an array of strings, for example: `['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']`
|
||||
|
||||
With the permission codes, you can use the `AccessControl` component and API provided by `@vben/access` to show and hide buttons.
|
||||
|
||||
#### Component Method
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { AccessControl, useAccess } from '@vben/access';
|
||||
|
||||
const { accessMode, hasAccessByCodes } = useAccess();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- You need to specify type="code" -->
|
||||
<AccessControl :codes="['AC_100100']" type="code">
|
||||
<Button> Visible to Super account ["AC_1000001"] </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['AC_100030']" type="code">
|
||||
<Button> Visible to Admin account ["AC_100010"] </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['AC_1000001']" type="code">
|
||||
<Button> Visible to User account ["AC_1000001"] </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['AC_100100', 'AC_100010']" type="code">
|
||||
<Button>
|
||||
Visible to Super & Admin account ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</AccessControl>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### API Method
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { AccessControl, useAccess } from '@vben/access';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button v-if="hasAccessByCodes(['AC_100100'])">
|
||||
Visible to Super account ["AC_1000001"]
|
||||
</Button>
|
||||
<Button v-if="hasAccessByCodes(['AC_100030'])">
|
||||
Visible to Admin account ["AC_100010"]
|
||||
</Button>
|
||||
<Button v-if="hasAccessByCodes(['AC_1000001'])">
|
||||
Visible to User account ["AC_1000001"]
|
||||
</Button>
|
||||
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_1000001'])">
|
||||
Visible to Super & Admin account ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Directive Method
|
||||
|
||||
> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Button class="mr-4" v-access:code="'AC_100100'">
|
||||
Visible to Super account 'AC_100100'
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:code="['AC_100030']">
|
||||
Visible to Admin account ["AC_100010"]
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:code="['AC_1000001']">
|
||||
Visible to User account ["AC_1000001"]
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:code="['AC_100100', 'AC_1000001']">
|
||||
Visible to Super & Admin account ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Roles
|
||||
|
||||
The method of determining roles does not require permission codes returned by the interface; it directly determines whether buttons are displayed based on roles.
|
||||
|
||||
#### Component Method
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { AccessControl } from '@vben/access';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccessControl :codes="['super']">
|
||||
<Button> Visible to Super account </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['admin']">
|
||||
<Button> Visible to Admin account </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['user']">
|
||||
<Button> Visible to User account </Button>
|
||||
</AccessControl>
|
||||
<AccessControl :codes="['super', 'admin']">
|
||||
<Button> Super & Visible to Admin account </Button>
|
||||
</AccessControl>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### API Method
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { useAccess } from '@vben/access';
|
||||
|
||||
const { hasAccessByRoles } = useAccess();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button v-if="hasAccessByRoles(['super'])"> Visible to Super account </Button>
|
||||
<Button v-if="hasAccessByRoles(['admin'])"> Visible to Admin account </Button>
|
||||
<Button v-if="hasAccessByRoles(['user'])"> Visible to User account </Button>
|
||||
<Button v-if="hasAccessByRoles(['super', 'admin'])">
|
||||
Super & Visible to Admin account
|
||||
</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Directive Method
|
||||
|
||||
> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Button class="mr-4" v-access:role="'super'">
|
||||
Visible to Super account
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:role="['admin']">
|
||||
Visible to Admin account
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:role="['user']">
|
||||
Visible to User account
|
||||
</Button>
|
||||
<Button class="mr-4" v-access:role="['super', 'admin']">
|
||||
Super & Visible to Admin account
|
||||
</Button>
|
||||
</template>
|
||||
```
|
||||
48
docs/src/en/guide/in-depth/check-updates.md
Normal file
48
docs/src/en/guide/in-depth/check-updates.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Check Updates
|
||||
|
||||
## Introduction
|
||||
|
||||
When there are updates to the website, you might need to check for updates. The framework provides this functionality. By periodically checking for updates, you can configure the `checkUpdatesInterval` and `enableCheckUpdates` fields in your application's preferences.ts file to enable and set the interval for checking updates (in minutes).
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
// Whether to enable check for updates
|
||||
enableCheckUpdates: true,
|
||||
// The interval for checking updates, in minutes
|
||||
checkUpdatesInterval: 1,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Effect
|
||||
|
||||
When an update is detected, a prompt will pop up asking the user whether to refresh the page:
|
||||
|
||||

|
||||
|
||||
## Replacing with Other Update Checking Methods
|
||||
|
||||
If you need to check for updates in other ways, such as through an API to more flexibly control the update logic (such as force refresh, display update content, etc.), you can do so by modifying the `src/widgets/check-updates/check-updates.vue` file under `@vben/layouts`.
|
||||
|
||||
```ts
|
||||
// Replace this with your update checking logic
|
||||
async function getVersionTag() {
|
||||
try {
|
||||
const response = await fetch('/', {
|
||||
cache: 'no-cache',
|
||||
method: 'HEAD',
|
||||
});
|
||||
|
||||
return (
|
||||
response.headers.get('etag') || response.headers.get('last-modified')
|
||||
);
|
||||
} catch {
|
||||
console.error('Failed to fetch version tag');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
84
docs/src/en/guide/in-depth/features.md
Normal file
84
docs/src/en/guide/in-depth/features.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Common Features
|
||||
|
||||
A collection of some commonly used features.
|
||||
|
||||
## Login Authentication Expiry
|
||||
|
||||
When the interface returns a `401` status code, the framework will consider the login authentication to have expired. Upon login timeout, it will redirect to the login page or open a login popup. This can be configured in `preferences.ts` in the application directory:
|
||||
|
||||
### Redirect to Login Page
|
||||
|
||||
Upon login timeout, it will redirect to the login page.
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'page',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Open Login Popup
|
||||
|
||||
When login times out, a login popup will open.
|
||||
|
||||

|
||||
|
||||
Configuration:
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'modal',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Dynamic Title
|
||||
|
||||
- Default value: `true`
|
||||
|
||||
When enabled, the webpage title changes according to the route's `title`. You can enable or disable this in the `preferences.ts` file in your application directory.
|
||||
|
||||
```ts
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
dynamicTitle: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Page Watermark
|
||||
|
||||
- Default value: `false`
|
||||
|
||||
When enabled, the webpage will display a watermark. You can enable or disable this in the `preferences.ts` file in your application directory.
|
||||
|
||||
```ts
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
watermark: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If you want to update the content of the watermark, you can do so. The parameters can be referred to [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/):
|
||||
|
||||
```ts
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
|
||||
await updateWatermark({
|
||||
// watermark content
|
||||
content: 'hello my watermark',
|
||||
});
|
||||
```
|
||||
1
docs/src/en/guide/in-depth/layout.md
Normal file
1
docs/src/en/guide/in-depth/layout.md
Normal file
@@ -0,0 +1 @@
|
||||
# Layout
|
||||
44
docs/src/en/guide/in-depth/loading.md
Normal file
44
docs/src/en/guide/in-depth/loading.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Global Loading
|
||||
|
||||
Global loading refers to the loading effect that appears when the page is refreshed, usually a spinning icon:
|
||||
|
||||

|
||||
|
||||
## Principle
|
||||
|
||||
Implemented by the `vite-plugin-inject-app-loading` plugin, the plugin injects a global `loading html` into each application.
|
||||
|
||||
## Disable
|
||||
|
||||
If you do not need global loading, you can disable it in the `.env` file:
|
||||
|
||||
```bash
|
||||
VITE_INJECT_APP_LOADING=false
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
If you want to customize the global loading, you can create a `loading.html` file in the application directory, at the same level as `index.html`. The plugin will automatically read and inject this HTML. You can define the style and animation of this HTML as you wish.
|
||||
|
||||
::: tip
|
||||
|
||||
- You can use the same syntax as in `index.html`, such as the `VITE_APP_TITLE` variable, to get the application's title.
|
||||
- You must ensure there is an element with `id="__app-loading__"`.
|
||||
- Add a `hidden` class to the element with `id="__app-loading__"`.
|
||||
- You must ensure there is a `style[data-app-loading="inject-css"]` element.
|
||||
|
||||
```html{1,4}
|
||||
<style data-app-loading="inject-css">
|
||||
#__app-loading__.hidden {
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 1s ease-out;
|
||||
}
|
||||
/* ... */
|
||||
</style>
|
||||
<div id="__app-loading__">
|
||||
<!-- ... -->
|
||||
<div class="title"><%= VITE_APP_TITLE %></div>
|
||||
</div>
|
||||
```
|
||||
227
docs/src/en/guide/in-depth/locale.md
Normal file
227
docs/src/en/guide/in-depth/locale.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Internationalization
|
||||
|
||||
The project has integrated [Vue i18n](https://kazupon.github.io/vue-i18n/), and Chinese and English language packs have been configured.
|
||||
|
||||
## IDE Plugin
|
||||
|
||||
If you are using vscode as your development tool, it is recommended to install the [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) plugin. It can help you manage internationalization copy more conveniently. After installing this plugin, you can see the corresponding language content in your code in real-time:
|
||||
|
||||

|
||||
|
||||
## Configure Default Language
|
||||
|
||||
You just need to override the default preferences. In the corresponding application, find the `src/preferences.ts` file and modify the value of `locale`:
|
||||
|
||||
```ts {3}
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
app: {
|
||||
locale: 'en-US',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Dynamic Language Switching
|
||||
|
||||
Switching languages consists of two parts:
|
||||
|
||||
- Updating preferences
|
||||
- Loading the corresponding language pack
|
||||
|
||||
```ts
|
||||
import type { SupportedLanguagesType } from '@vben/locales';
|
||||
import { loadLocaleMessages } from '@vben/locales';
|
||||
import { updatePreferences } from '@vben/preferences';
|
||||
|
||||
async function updateLocale(value: string) {
|
||||
// 1. Update preferences
|
||||
const locale = value as SupportedLanguagesType;
|
||||
updatePreferences({
|
||||
app: {
|
||||
locale,
|
||||
},
|
||||
});
|
||||
// 2. Load the corresponding language pack
|
||||
await loadLocaleMessages(locale);
|
||||
}
|
||||
|
||||
updateLocale('en-US');
|
||||
```
|
||||
|
||||
## Adding Translation Texts
|
||||
|
||||
::: warning Attention
|
||||
|
||||
- Do not place business translation texts inside `@vben/locales` to better manage business and general translation texts.
|
||||
- When adding new translation texts and multiple language packs are available, ensure to add the corresponding texts in all language packs.
|
||||
|
||||
:::
|
||||
|
||||
To add new translation texts, simply find `src/locales/langs/` in the corresponding application and add the texts accordingly, for example:
|
||||
|
||||
**src/locales/langs/zh-CN/\*.json**
|
||||
|
||||
````ts
|
||||
```json
|
||||
{
|
||||
"about": {
|
||||
"desc": "Vben Admin 是一个现代的管理模版。"
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
**src/locales/langs/en-US.ts**
|
||||
|
||||
````ts
|
||||
```json
|
||||
{
|
||||
"about": {
|
||||
"desc": "Vben Admin is a modern management template."
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
## Using Translation Texts
|
||||
|
||||
With `@vben/locales`, you can easily use translation texts:
|
||||
|
||||
### In Code
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const items = computed(() => [{ title: $t('about.desc') }]);
|
||||
</script>
|
||||
<template>
|
||||
<div>{{ $t('about.desc') }}</div>
|
||||
<template v-for="item in items.value">
|
||||
<div>{{ item.title }}</div>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Adding a New Language Pack
|
||||
|
||||
If you need to add a new language pack, follow these steps:
|
||||
|
||||
- Add the corresponding language pack file in the `packages/locales/langs` directory, for example, `zh-TW.json`, and translate the respective texts.
|
||||
- In the corresponding application, locate the `src/locales/langs` file and add the new language pack `zh-TW.json`.
|
||||
- Add the corresponding language in `packages/constants/src/core.ts`:
|
||||
|
||||
```ts
|
||||
export interface LanguageOption {
|
||||
label: string;
|
||||
value: 'en-US' | 'zh-CN'; // [!code --]
|
||||
value: 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]
|
||||
}
|
||||
export const SUPPORT_LANGUAGES: LanguageOption[] = [
|
||||
{
|
||||
label: '简体中文',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
label: '繁体中文', // [!code ++]
|
||||
value: 'zh-TW', // [!code ++]
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
- In `packages/locales/typing.ts`, add a new TypeScript type:
|
||||
|
||||
```ts
|
||||
export type SupportedLanguagesType = 'en-US' | 'zh-CN'; // [!code --]
|
||||
export type SupportedLanguagesType = 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]
|
||||
```
|
||||
|
||||
At this point, you can use the newly added language pack in the project.
|
||||
|
||||
## Interface Language Switching Function
|
||||
|
||||
If you want to disable the language switching display button on the interface, in the corresponding application, find the `src/preferences.ts` file and modify the value of `locale` accordingly:
|
||||
|
||||
```ts {3}
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
widget: {
|
||||
languageToggle: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Remote Loading of Language Packs
|
||||
|
||||
::: tip Tip
|
||||
|
||||
When making interface requests through the project's built-in `request` tool, the default request header will include [Accept-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language), allowing the server to dynamically internationalize data based on the request header.
|
||||
|
||||
:::
|
||||
|
||||
Each application has an independent language pack that can override the general language configuration. You can remotely load the corresponding language pack by finding the `src/locales/index.ts` file in the corresponding application and modifying the `loadMessages` method accordingly:
|
||||
|
||||
```ts {3-4}
|
||||
async function loadMessages(lang: SupportedLanguagesType) {
|
||||
const [appLocaleMessages] = await Promise.all([
|
||||
// Modify here to load data via a remote interface
|
||||
localesMap[lang](),
|
||||
loadThirdPartyMessage(lang),
|
||||
]);
|
||||
return appLocaleMessages.default;
|
||||
}
|
||||
```
|
||||
|
||||
## Third-Party Language Packs
|
||||
|
||||
Different applications may use third-party component libraries or plugins with varying internationalization methods, so they need to be handled differently. If you need to introduce a third-party language pack, you can find the `src/locales/index.ts` file in the corresponding application and modify the `loadThirdPartyMessage` method accordingly:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Load the dayjs language pack
|
||||
* @param lang
|
||||
*/
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
// Default to using English
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Removing Internationalization
|
||||
|
||||
Firstly, it is not recommended to remove internationalization, as it is a good development practice. However, if you really need to remove it, you can directly use Chinese copy and then retain the project's built-in language pack, which will not affect the overall development experience. The steps to remove internationalization are as follows:
|
||||
|
||||
- Hide the language switching button on the interface, see: [Interface Language Switching Function](#interface-language-switching-function)
|
||||
- Modify the default language, see: [Configure Default Language](#configure-default-language)
|
||||
- Disable `vue-i18n` warning prompts, in the `src/locales/index.ts` file, modify `missingWarn` to `false`:
|
||||
|
||||
```ts
|
||||
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
await coreSetup(app, {
|
||||
defaultLocale: preferences.app.locale,
|
||||
loadMessages,
|
||||
missingWarn: !import.meta.env.PROD, // [!code --]
|
||||
missingWarn: false, // [!code ++]
|
||||
...options,
|
||||
});
|
||||
}
|
||||
```
|
||||
119
docs/src/en/guide/in-depth/login.md
Normal file
119
docs/src/en/guide/in-depth/login.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Login
|
||||
|
||||
This document explains how to customize the login page of your application.
|
||||
|
||||
## Login Page Adjustment
|
||||
|
||||
If you want to adjust the title, description, icon, and toolbar of the login page, you can do so by configuring the `props` parameter of the `AuthPageLayout` component.
|
||||
|
||||

|
||||
|
||||
You just need to configure the `props` parameter of `AuthPageLayout` in `src/router/routes/core.ts` within your application:
|
||||
|
||||
```ts {4-8}
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
props: {
|
||||
sloganImage: "xxx/xxx.png",
|
||||
pageTitle: "开箱即用的大型中后台管理系统",
|
||||
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
||||
toolbar: true,
|
||||
toolbarList: ['color', 'language', 'layout', 'theme'],
|
||||
}
|
||||
// ...
|
||||
},
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
If these configurations do not meet your needs, you can implement your own login page. Simply implement your own `AuthPageLayout`.
|
||||
|
||||
:::
|
||||
|
||||
## Login Form Adjustment
|
||||
|
||||
If you want to adjust the content of the login form, you can configure the `AuthenticationLogin` component parameters in `src/views/_core/authentication/login.vue` within your application:
|
||||
|
||||
```vue
|
||||
<AuthenticationLogin
|
||||
:loading="authStore.loginLoading"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
```
|
||||
|
||||
::: details AuthenticationLogin Component Props
|
||||
|
||||
```ts
|
||||
{
|
||||
/**
|
||||
* @en Verification code login path
|
||||
*/
|
||||
codeLoginPath?: string;
|
||||
/**
|
||||
* @en Forget password path
|
||||
*/
|
||||
forgetPasswordPath?: string;
|
||||
|
||||
/**
|
||||
* @en Whether it is in loading state
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* @en QR code login path
|
||||
*/
|
||||
qrCodeLoginPath?: string;
|
||||
|
||||
/**
|
||||
* @en Registration path
|
||||
*/
|
||||
registerPath?: string;
|
||||
|
||||
/**
|
||||
* @en Whether to show verification code login
|
||||
*/
|
||||
showCodeLogin?: boolean;
|
||||
/**
|
||||
* @en Whether to show forget password
|
||||
*/
|
||||
showForgetPassword?: boolean;
|
||||
|
||||
/**
|
||||
* @en Whether to show QR code login
|
||||
*/
|
||||
showQrcodeLogin?: boolean;
|
||||
|
||||
/**
|
||||
* @en Whether to show registration button
|
||||
*/
|
||||
showRegister?: boolean;
|
||||
|
||||
/**
|
||||
* @en Whether to show remember account
|
||||
*/
|
||||
showRememberMe?: boolean;
|
||||
|
||||
/**
|
||||
* @en Whether to show third-party login
|
||||
*/
|
||||
showThirdPartyLogin?: boolean;
|
||||
|
||||
/**
|
||||
* @en Login box subtitle
|
||||
*/
|
||||
subTitle?: string;
|
||||
|
||||
/**
|
||||
* @en Login box title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
If these configurations do not meet your needs, you can implement your own login form and related login logic.
|
||||
|
||||
:::
|
||||
1295
docs/src/en/guide/in-depth/theme.md
Normal file
1295
docs/src/en/guide/in-depth/theme.md
Normal file
File diff suppressed because it is too large
Load Diff
17
docs/src/en/guide/in-depth/ui-framework.md
Normal file
17
docs/src/en/guide/in-depth/ui-framework.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# UI Framework Switching
|
||||
|
||||
`Vue Admin` supports your freedom to choose the UI framework. The default UI framework for the demo site is `Ant Design Vue`, consistent with the older version. The framework also has built-in versions for `Element Plus` and `Naive UI`, allowing you to choose according to your preference.
|
||||
|
||||
## Adding a New UI Framework
|
||||
|
||||
If you want to use a different UI framework, you only need to follow these steps:
|
||||
|
||||
1. Create a new folder inside `apps`, for example, `apps/web-xxx`.
|
||||
2. Change the `name` field in `apps/web-xxx/package.json` to `web-xxx`.
|
||||
3. Remove dependencies and code from other UI frameworks and replace them with your chosen UI framework's logic, which requires minimal changes.
|
||||
4. Adjust the language files within `locales`.
|
||||
5. Adjust the components in `app.vue`.
|
||||
6. Adapt the theme of the UI framework to match `Vben Admin`.
|
||||
7. Adjust the application name in `.env`.
|
||||
8. Add a `dev:xxx` script in the root directory of the repository.
|
||||
9. Run `pnpm install` to install dependencies.
|
||||
3
docs/src/en/guide/introduction/changelog.md
Normal file
3
docs/src/en/guide/introduction/changelog.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# CHANGE LOG
|
||||
|
||||
TODO
|
||||
95
docs/src/en/guide/introduction/quick-start.md
Normal file
95
docs/src/en/guide/introduction/quick-start.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Quick Start {#quick-start}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
::: info Environment Requirements
|
||||
|
||||
Before starting the project, ensure that your environment meets the following requirements:
|
||||
|
||||
- [Node.js](https://nodejs.org/en) version 20.15.0 or above. It is recommended to use [fnm](https://github.com/Schniz/fnm), [nvm](https://github.com/nvm-sh/nvm), or directly use [pnpm](https://pnpm.io/cli/env) for version management.
|
||||
- [Git](https://git-scm.com/) any version.
|
||||
|
||||
To verify if your environment meets the above requirements, you can check the versions using the following commands:
|
||||
|
||||
```bash
|
||||
# Ensure the correct node LTS version is displayed
|
||||
node -v
|
||||
# Ensure the correct git version is displayed
|
||||
git -v
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Starting the Project
|
||||
|
||||
### Obtain the Source Code
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [GitHub]
|
||||
# Clone the code
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
```bash [Gitee]
|
||||
# Clone the code
|
||||
# The Gitee repository may not have the latest code
|
||||
git clone https://gitee.com/annsion/vue-vben-admin.git
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: danger Caution
|
||||
|
||||
Ensure that the directory where you store the code and all its parent directories do not contain Chinese, Korean, Japanese characters, or spaces, as this may cause errors when installing dependencies and starting the project.
|
||||
|
||||
:::
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
Open a terminal in your code directory and execute the following commands:
|
||||
|
||||
```bash
|
||||
# Enter the project directory
|
||||
cd vue-vben-admin
|
||||
|
||||
# Enable the project-specified version of pnpm
|
||||
npm i -g corepack
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
```
|
||||
|
||||
::: tip Note
|
||||
|
||||
The project only supports using `pnpm` for installing dependencies. By default, `corepack` will be used to install the specified version of `pnpm`.
|
||||
|
||||
:::
|
||||
|
||||
### Run the Project
|
||||
|
||||
Execute the following command to run the project:
|
||||
|
||||
```bash
|
||||
# Start the project
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
You will see an output similar to the following, allowing you to select the project you want to run:
|
||||
|
||||
```bash
|
||||
│
|
||||
◆ Select the app you need to run [dev]:
|
||||
│ ● @vben/web-antd
|
||||
│ ○ @vben/web-ele
|
||||
│ ○ @vben/web-naive
|
||||
│ ○ @vben/docs
|
||||
│ ○ @vben/playground
|
||||
└
|
||||
```
|
||||
|
||||
Now, you can visit `http://localhost:5555` in your browser to view the project.
|
||||
3
docs/src/en/guide/introduction/roadmap.md
Normal file
3
docs/src/en/guide/introduction/roadmap.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Roadmap
|
||||
|
||||
TODO:
|
||||
49
docs/src/en/guide/introduction/vben.md
Normal file
49
docs/src/en/guide/introduction/vben.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# About Vben Admin
|
||||
|
||||
::: info You are reading the documentation for [Vben Admin](https://github.com/vbenjs/vue-vben-admin) version `5.0`!
|
||||
|
||||
- Vben Admin 2.x is currently archived and only receives critical fixes.
|
||||
- The new version is not compatible with the old version. If you are using the old version (v2, v3), please refer to the [Vue Vben Admin 2.x Documentation](https://doc.vvbin.cn).
|
||||
- If you find any errors in the documentation, feel free to submit an issue to help us improve.
|
||||
- If you just want to experience it, you can check out the [Quick Start](./quick-start.md).
|
||||
|
||||
:::
|
||||
|
||||
[Vben Admin](https://github.com/vbenjs/vue-vben-admin) is a backend solution based on [Vue 3.0](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), and [TypeScript](https://www.typescriptlang.org/), aimed at providing an out-of-the-box solution for developing medium to large-scale projects. It includes features like component re-encapsulation, utilities, hooks, dynamic menus, permission validation, multi-theme configurations, and button-level permission control. The project uses the latest frontend technology stack, making it a good starting template for quickly building enterprise-level mid- to backend product prototypes. It can also serve as an example for learning `vue3`, `vite`, `ts`, and other mainstream technologies. The project will continue to follow the latest technologies and apply them within the project.
|
||||
|
||||
## Features
|
||||
|
||||
- **Latest Technology Stack**: Developed using cutting-edge frontend technologies like `Vue 3`, `Vite`, and `TypeScript`.
|
||||
- **Internationalization**: Built-in comprehensive internationalization solutions with multi-language support.
|
||||
- **Permission Validation**: Comprehensive permission validation solutions, including button-level permission control.
|
||||
- **Multi-Theme**: Built-in multiple theme configurations & dark mode to meet personalized needs.
|
||||
- **Dynamic Menu**: Supports dynamic menus that can display based on permissions.
|
||||
- **Mock Data**: High-performance local Mock data solution based on `Nitro`.
|
||||
- **Rich Components**: Provides a wide range of components to meet most business needs.
|
||||
- **Standardization**: Code quality is ensured with tools like `ESLint`, `Prettier`, `Stylelint`, `Publint`, and `CSpell`.
|
||||
- **Engineering**: Development efficiency is improved with tools like `Pnpm Monorepo`, `TurboRepo`, and `Changeset`.
|
||||
- **Multi-UI Library Support**: Supports mainstream UI libraries like `Ant Design Vue`, `Element Plus`, and `Vuetify`, without being restricted to a specific framework.
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Local development** is recommended using the **latest version of Chrome**. **Versions below Chrome 80 are not supported**.
|
||||
|
||||
- **Production environment** supports modern browsers, IE is not supported.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## Contribution
|
||||
|
||||
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) is still being actively updated. Contributions are welcome to help maintain and improve the project, aiming to create a better mid- to backend solution.
|
||||
- If you wish to join us, you can start by contributing in the following ways, and we will invite you to join based on your activity.
|
||||
|
||||
::: info Join Us
|
||||
|
||||
- Regularly submit `PRs`.
|
||||
- Provide valuable suggestions.
|
||||
- Participate in discussions and help resolve some `issues`.
|
||||
- Help maintain the documentation.
|
||||
|
||||
:::
|
||||
9
docs/src/en/guide/introduction/why.md
Normal file
9
docs/src/en/guide/introduction/why.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Why Choose Us?
|
||||
|
||||
First of all, we do not compare ourselves with other frameworks. We believe that every framework has its own characteristics and is suited for different scenarios. Our goal is to provide a simple and easy-to-use framework that allows developers to get started quickly and focus on developing business logic. Therefore, we will continue to improve and optimize our framework to offer a better experience.
|
||||
|
||||
## Framework History
|
||||
|
||||
Starting from Vue Vben Admin version 1.x, the framework has undergone numerous iterations and optimizations. From initially using `Vite 0.x` when there were no ready-made plugins available, we developed many custom plugins to bridge the gap between Webpack and Vite. Although many of these have since been replaced, our original intention has remained the same: to provide a simple and easy-to-use framework.
|
||||
|
||||
Although the community maintained the project for a period, we have always closely monitored the development of Vue Vben Admin. We have witnessed many developers use Vben Admin and provide valuable suggestions and feedback. We are very grateful for everyone's support and contributions, which continue to drive us to improve Vben Admin. In the new version, we have continuously collected user feedback, started anew, and optimized the framework to provide a better user experience. Our goal is to enable developers to get started quickly and focus on developing business logic.
|
||||
159
docs/src/en/guide/other/faq.md
Normal file
159
docs/src/en/guide/other/faq.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Frequently Asked Questions #{faq}
|
||||
|
||||
::: tip Listed are some common questions
|
||||
|
||||
If you have a question, you can first look here. If not found, you can search or submit your question on [GitHub Issue](https://github.com/vbenjs/vue-vben-admin/issues), or if it's a discussion-type question, you can go to [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)
|
||||
|
||||
:::
|
||||
|
||||
## Instructions
|
||||
|
||||
If you encounter a problem, you can start looking from the following aspects:
|
||||
|
||||
1. Search the corresponding module's GitHub repository [issue](https://github.com/vbenjs/vue-vben-admin/issues)
|
||||
2. Search for the problem on [Google](https://www.google.com)
|
||||
3. Search for the problem on [Baidu](https://www.baidu.com)
|
||||
4. If you can't find the issue in the list below, you can ask in [issues](https://github.com/vbenjs/vue-vben-admin/issues)
|
||||
5. If it's not a problem type and needs discussion, please go to [discussions](https://github.com/vbenjs/vue-vben-admin/discussions) to discuss
|
||||
|
||||
## Dependency Issues
|
||||
|
||||
In a `Monorepo` project, it's important to get into the habit of running `pnpm install` after every `git pull` because new dependencies are often added. The project has configured automatic execution of `pnpm install` in `lefthook.yml`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.
|
||||
|
||||
## About Cache Update Issues
|
||||
|
||||
The project configuration is by default cached in `localStorage`, so some configurations may not change after a version update.
|
||||
|
||||
The solution is to modify the `version` number in `package.json` each time the code is updated. This is because the key for `localStorage` is based on the version number. Therefore, after an update, the configurations from a previous version will become invalid. Simply re-login to apply the new settings.
|
||||
|
||||
## About Modifying Configuration Files
|
||||
|
||||
When modifying environment files such as `.env` or the `vite.config.ts` file, Vite will automatically restart the service.
|
||||
|
||||
There's a chance that automatic restarts may encounter issues. Simply rerunning the project can resolve these problems.
|
||||
|
||||
## Errors When Running Locally
|
||||
|
||||
Since Vite does not transform code locally and the code uses relatively new syntax such as optional chaining, local development requires using a higher version of the browser (`Chrome 90+`).
|
||||
|
||||
## Blank Page After Switching Pages
|
||||
|
||||
This issue occurs because route switching animations are enabled, and the corresponding page component has multiple root nodes. Adding a `<div></div>` at the outermost layer of the page can solve this problem.
|
||||
|
||||
**Incorrect Example**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- Annotations are also considered a node -->
|
||||
<h1>text h1</h1>
|
||||
<h2>text h2</h2>
|
||||
</template>
|
||||
```
|
||||
|
||||
**正确示例**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<h1>text h1</h1>
|
||||
<h2>text h2</h2>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::: tip Tip
|
||||
|
||||
- If you want to use multiple root tags, you can disable route switching animations.
|
||||
- Root comment nodes under `template` are also counted as a node.
|
||||
|
||||
:::
|
||||
|
||||
## My code works locally but not when packaged
|
||||
|
||||
The reason for this issue could be one of the following. You can check these reasons, and if there are other possibilities, feel free to add them:
|
||||
|
||||
- The variable `ctx` was used, which is not exposed in the instance type. The Vue official documentation also advises against using this property as it is intended for internal use only.
|
||||
|
||||
```ts
|
||||
import { getCurrentInstance } from 'vue';
|
||||
getCurrentInstance().ctx.xxxx;
|
||||
```
|
||||
|
||||
## Dependency Installation Issues
|
||||
|
||||
- If you cannot install dependencies or the startup reports an error, you can try executing `pnpm run reinstall`.
|
||||
- If you cannot install dependencies or encounter errors, you can try switching to a mobile hotspot for installing dependencies.
|
||||
- If that still doesn't work, you can configure a domestic mirror for installation.
|
||||
- You can also create a `.npmrc` file in the project root directory with the following content:
|
||||
|
||||
```bash
|
||||
# .npmrc
|
||||
registry = https://registry.npmmirror.com/
|
||||
```
|
||||
|
||||
## Package File Too Large
|
||||
|
||||
- First, the full version will be larger because it includes many library files. You can use the simplified version for development.
|
||||
|
||||
- Secondly, it is recommended to enable gzip, which can reduce the size to about 1/3 of the original. Gzip can be enabled directly by the server. If so, the frontend does not need to build `.gz` format files. If the frontend has built `.gz` files, for example, with nginx, you need to enable the `gzip_static: on` option.
|
||||
|
||||
- While enabling gzip, you can also enable `brotli` for better compression than gzip. Both can coexist.
|
||||
|
||||
**Note**
|
||||
|
||||
- gzip_static: This module requires additional installation in nginx, as the default nginx does not include this module.
|
||||
|
||||
- Enabling `brotli` also requires additional nginx module installation.
|
||||
|
||||
## Runtime Errors
|
||||
|
||||
If you encounter errors similar to the following, please check that the full project path (including all parent paths) does not contain Chinese, Japanese, or Korean characters. Otherwise, you will encounter a 404 error for the path, leading to the following issue:
|
||||
|
||||
```ts
|
||||
[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css". (imported by /@/setup/ant-design-vue/index.ts)
|
||||
```
|
||||
|
||||
## Console Route Warning Issue
|
||||
|
||||
If you see the following warning in the console, and the page `can open normally`, you can ignore this warning.
|
||||
|
||||
Future versions of `vue-router` may provide an option to disable this warning.
|
||||
|
||||
```ts
|
||||
[Vue Router warn]: No match found for location with path "xxxx"
|
||||
```
|
||||
|
||||
## Startup Error
|
||||
|
||||
If you encounter the following error message, please check if your nodejs version meets the requirements.
|
||||
|
||||
```bash
|
||||
TypeError: str.matchAll is not a function
|
||||
at Object.extractor (vue-vben-admin-main\node_modules@purge-icons\core\dist\index.js:146:27)
|
||||
at Extract (vue-vben-admin-main\node_modules@purge-icons\core\dist\index.js:173:54)
|
||||
```
|
||||
|
||||
## nginx Deployment
|
||||
|
||||
After deploying to `nginx`,you might encounter the following error:
|
||||
|
||||
```bash
|
||||
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.
|
||||
```
|
||||
|
||||
Solution 1:
|
||||
|
||||
```bash
|
||||
http {
|
||||
#If there is such a configuration, it needs to be commented out
|
||||
#include mime.types;
|
||||
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Solution 2:
|
||||
|
||||
Open the `mime.types` file under `nginx` and change `application/javascript js;` to `application/javascript js mjs;`
|
||||
54
docs/src/en/guide/other/project-update.md
Normal file
54
docs/src/en/guide/other/project-update.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# PROJECT UPDATE
|
||||
|
||||
## Why Can't It Be Updated Like a npm Plugin
|
||||
|
||||
Because the project is a complete project template, not a plugin or a package, it cannot be updated like a plugin. After you use the code, you will develop it further based on business needs, and you need to manually merge and upgrade.
|
||||
|
||||
## What Should I Do
|
||||
|
||||
The project is managed using a `Monorepo` approach and has abstracted some of the more core code, such as `packages/@core`, `packages/effects`. As long as the business code has not modified this part of the code, you can directly pull the latest code and then merge it into your branch. You only need to handle some conflicts simply. Other folders will only make some minor adjustments, which will not affect the business code.
|
||||
|
||||
::: tip Recommendation
|
||||
|
||||
It is recommended to follow the repository updates actively and merge them; do not accumulate over a long time, Otherwise, it will lead to too many merge conflicts and increase the difficulty of merging.
|
||||
|
||||
:::
|
||||
|
||||
## Updating Code Using Git
|
||||
|
||||
1. Clone the code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
2. Add your company's git source address
|
||||
|
||||
```bash
|
||||
# up is the source name, can be set arbitrarily
|
||||
# gitUrl is the latest open-source code
|
||||
git remote add up gitUrl;
|
||||
```
|
||||
|
||||
3. Push the code to your company's git
|
||||
|
||||
```bash
|
||||
# Push the code to your company
|
||||
# main is the branch name, adjust according to your situation
|
||||
git push up main
|
||||
# Sync the company's code
|
||||
# main is the branch name, adjust according to your situation
|
||||
git pull up main
|
||||
```
|
||||
|
||||
4. How to sync the latest open-source code
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
::: tip Tip
|
||||
|
||||
When syncing the code, conflicts may occur. Just resolve the conflicts.
|
||||
|
||||
:::
|
||||
18
docs/src/en/guide/other/remove-code.md
Normal file
18
docs/src/en/guide/other/remove-code.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Remove Code
|
||||
|
||||
## Remove Code
|
||||
|
||||
In the corresponding application's `index.html` file, find the following code and delete it:
|
||||
|
||||
```html
|
||||
<!-- apps/web-antd -->
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
21
docs/src/en/guide/project/changeset.md
Normal file
21
docs/src/en/guide/project/changeset.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Changeset
|
||||
|
||||
The project has integrated [changeset](https://github.com/changesets/changesets) as a version management tool. Changeset is a version management tool that helps us better manage versions, generate changelogs, and automate releases.
|
||||
|
||||
For detailed usage, please refer to the official documentation. If you do not need it, you can ignore it directly.
|
||||
|
||||
## Command Line
|
||||
|
||||
The changeset command is already built into the project:
|
||||
|
||||
### Interactively Add Changesets
|
||||
|
||||
```bash
|
||||
pnpm run changeset
|
||||
```
|
||||
|
||||
### Uniformly Increment Version Numbers
|
||||
|
||||
```bash
|
||||
pnpm run version
|
||||
```
|
||||
106
docs/src/en/guide/project/cli.md
Normal file
106
docs/src/en/guide/project/cli.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# CLI
|
||||
|
||||
In the project, some command-line tools are provided for common operations, located in `scripts`.
|
||||
|
||||
## vsh
|
||||
|
||||
Used for some project operations, such as cleaning the project, checking the project, etc.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh [command] [options]
|
||||
```
|
||||
|
||||
### vsh check-circular
|
||||
|
||||
Check for circular references throughout the project. If there are circular references, the modules involved will be output to the console.
|
||||
|
||||
#### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh check-circular
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | --------------------------------------------------------- |
|
||||
| `--staged` | Only check files in the git staging area, default `false` |
|
||||
|
||||
### vsh check-dep
|
||||
|
||||
Check the dependency situation of the entire project and output `unused dependencies`, `uninstalled dependencies` information to the console.
|
||||
|
||||
#### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh check-dep
|
||||
```
|
||||
|
||||
### vsh lint
|
||||
|
||||
Lint checks the project to see if the code in the project conforms to standards.
|
||||
|
||||
#### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh lint
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | -------------------------------------------- |
|
||||
| `--format` | Check and try to fix errors, default `false` |
|
||||
|
||||
### vsh publint
|
||||
|
||||
Perform package standard checks on `Monorepo` projects to see if the packages in the project conform to standards.
|
||||
|
||||
#### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh publint
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ------------------------------------ |
|
||||
| `--check` | Only perform checks, default `false` |
|
||||
|
||||
### vsh code-workspace
|
||||
|
||||
Generate `vben-admin.code-workspace` file. Currently, it does not need to be executed manually and will be executed automatically when code is committed.
|
||||
|
||||
#### Usage
|
||||
|
||||
```bash
|
||||
pnpm vsh code-workspace
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | --------------------------------------------------------- |
|
||||
| `--auto-commit` | Automatically commit during `git commit`, default `false` |
|
||||
| `--spaces` | Indentation format, default `2` spaces |
|
||||
|
||||
## turbo-run
|
||||
|
||||
Used to quickly execute scripts in the large repository and provide option-based interactive selection.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
pnpm turbo-run [command]
|
||||
```
|
||||
|
||||
### turbo-run dev
|
||||
|
||||
Quickly execute the `dev` command and provide option-based interactive selection.
|
||||
68
docs/src/en/guide/project/dir.md
Normal file
68
docs/src/en/guide/project/dir.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Directory Explanation
|
||||
|
||||
The directory uses Monorepo management, and the project structure is as follows:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── Dockerfile # Docker image build file
|
||||
├── README.md # Project documentation
|
||||
├── apps # Project applications directory
|
||||
│ ├── backend-mock # Backend mock service application
|
||||
│ ├── web-antd # Frontend application based on Ant Design Vue
|
||||
│ ├── web-ele # Frontend application based on Element Plus
|
||||
│ └── web-naive # Frontend application based on Naive UI
|
||||
├── build-local-docker-image.sh # Script for building Docker images locally
|
||||
├── cspell.json # CSpell configuration file
|
||||
├── docs # Project documentation directory
|
||||
├── eslint.config.mjs # ESLint configuration file
|
||||
├── internal # Internal tools directory
|
||||
│ ├── lint-configs # Code linting configurations
|
||||
│ │ ├── commitlint-config # Commitlint configuration
|
||||
│ │ ├── eslint-config # ESLint configuration
|
||||
│ │ ├── prettier-config # Prettier configuration
|
||||
│ │ └── stylelint-config # Stylelint configuration
|
||||
│ ├── node-utils # Node.js tools
|
||||
│ ├── tailwind-config # Tailwind configuration
|
||||
│ ├── tsconfig # Common tsconfig settings
|
||||
│ └── vite-config # Common Vite configuration
|
||||
├── package.json # Project dependency configuration
|
||||
├── packages # Project packages directory
|
||||
│ ├── @core # Core package
|
||||
│ │ ├── base # Base package
|
||||
│ │ │ ├── design # Design related
|
||||
│ │ │ ├── icons # Icons
|
||||
│ │ │ ├── shared # Shared
|
||||
│ │ │ └── typings # Type definitions
|
||||
│ │ ├── composables # Composable APIs
|
||||
│ │ ├── preferences # Preferences
|
||||
│ │ └── ui-kit # UI component collection
|
||||
│ │ ├── layout-ui # Layout UI
|
||||
│ │ ├── menu-ui # Menu UI
|
||||
│ │ ├── shadcn-ui # shadcn UI
|
||||
│ │ └── tabs-ui # Tabs UI
|
||||
│ ├── constants # Constants
|
||||
│ ├── effects # Effects related packages
|
||||
│ │ ├── access # Access control
|
||||
│ │ ├── plugins # Plugins
|
||||
│ │ ├── common-ui # Common UI
|
||||
│ │ ├── hooks # Composable APIs
|
||||
│ │ ├── layouts # Layouts
|
||||
│ │ └── request # Request
|
||||
│ ├── icons # Icons
|
||||
│ ├── locales # Internationalization
|
||||
│ ├── preferences # Preferences
|
||||
│ ├── stores # State management
|
||||
│ ├── styles # Styles
|
||||
│ ├── types # Type definitions
|
||||
│ └── utils # Utilities
|
||||
├── playground # Demo directory
|
||||
├── pnpm-lock.yaml # pnpm lock file
|
||||
├── pnpm-workspace.yaml # pnpm workspace configuration file
|
||||
├── scripts # Scripts directory
|
||||
│ ├── turbo-run # Turbo run script
|
||||
│ └── vsh # VSH script
|
||||
├── stylelint.config.mjs # Stylelint configuration file
|
||||
├── turbo.json # Turbo configuration file
|
||||
├── vben-admin.code-workspace # VS Code workspace configuration file
|
||||
└── vitest.config.ts # Vite configuration file
|
||||
```
|
||||
210
docs/src/en/guide/project/standard.md
Normal file
210
docs/src/en/guide/project/standard.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Standards
|
||||
|
||||
::: tip Contributing Code
|
||||
|
||||
- If you want to contribute code to the project, please ensure your code complies with the project's coding standards.
|
||||
- If you are using `vscode`, you need to install the following plugins:
|
||||
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code checking
|
||||
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting
|
||||
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Word syntax checking
|
||||
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting
|
||||
|
||||
:::
|
||||
|
||||
## Purpose
|
||||
|
||||
Students with basic engineering literacy always pay attention to coding standards, and code style checking (Code Linting, simply called Lint) is an important means to ensure the consistency of coding standards.
|
||||
|
||||
Following the corresponding coding standards has the following benefits:
|
||||
|
||||
- Lower bug error rate
|
||||
- Efficient development efficiency
|
||||
- Higher readability
|
||||
|
||||
## Tools
|
||||
|
||||
The project's configuration files are located in `internal/lint-configs`, where you can modify various lint configurations.
|
||||
|
||||
The project integrates the following code verification tools:
|
||||
|
||||
- [ESLint](https://eslint.org/) for JavaScript code checking
|
||||
- [Stylelint](https://stylelint.io/) for CSS style checking
|
||||
- [Prettier](https://prettier.io/) for code formatting
|
||||
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
|
||||
- [Publint](https://publint.dev/) for checking the standard of npm packages
|
||||
- [Cspell](https://cspell.org/) for checking spelling errors
|
||||
- [lefthook](https://github.com/evilmartians/lefthook) for managing Git hooks, automatically running code checks and formatting before commits
|
||||
|
||||
## ESLint
|
||||
|
||||
ESLint is a code standard and error checking tool used to identify and report syntax errors in TypeScript code.
|
||||
|
||||
### Command
|
||||
|
||||
```bash
|
||||
pnpm eslint .
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The ESLint configuration file is `eslint.config.mjs`, with its core configuration located in the `internal/lint-configs/eslint-config` directory, which can be modified according to project needs.
|
||||
|
||||
## Stylelint
|
||||
|
||||
Stylelint is used to check the style of CSS within the project. Coupled with the editor's auto-fix feature, it can effectively unify the CSS style within the project.
|
||||
|
||||
### Command
|
||||
|
||||
```bash
|
||||
pnpm stylelint "**/*.{vue,css,less.scss}"
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The Stylelint configuration file is `stylelint.config.mjs`, with its core configuration located in the `internal/lint-configs/stylelint-config` directory, which can be modified according to project needs.
|
||||
|
||||
## Prettier
|
||||
|
||||
Prettier Can be used to unify project code style, consistent indentation, single and double quotes, trailing commas, and other styles.
|
||||
|
||||
### Command
|
||||
|
||||
```bash
|
||||
pnpm prettier .
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The Prettier configuration file is `.prettier.mjs`, with its core configuration located in the `internal/lint-configs/prettier-config` directory, which can be modified according to project needs.
|
||||
|
||||
## CommitLint
|
||||
|
||||
In a team, everyone's git commit messages can vary widely, making it difficult to ensure standardization without a mechanism. How can standardization be achieved? You might think of using git's hook mechanism to write shell scripts to implement this. Of course, this is possible, but actually, JavaScript has a great tool for implementing this template, which is commitlint (used for verifying the standard of git commit messages).
|
||||
|
||||
### Configuration
|
||||
|
||||
The CommitLint configuration file is `.commitlintrc.mjs`, with its core configuration located in the `internal/lint-configs/commitlint-config` directory, which can be modified according to project needs.
|
||||
|
||||
### Git Commit Standards
|
||||
|
||||
Refer to [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
|
||||
|
||||
- `feat` Add new features
|
||||
- `fix` Fix problems/BUGs
|
||||
- `style` Code style changes that do not affect the outcome
|
||||
- `perf` Optimization/performance improvement
|
||||
- `refactor` Refactoring
|
||||
- `revert` Revert changes
|
||||
- `test` Related to tests
|
||||
- `docs` Documentation/comments
|
||||
- `chore` Dependency updates/scaffold configuration modifications, etc.
|
||||
- `workflow` Workflow improvements
|
||||
- `ci` Continuous integration
|
||||
- `types` Type modifications
|
||||
|
||||
### Disabling Git Commit Standard Checks
|
||||
|
||||
If you want to disable Git commit standard checks, there are two ways:
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [Temporary disable]
|
||||
git commit -m 'feat: add home page' --no-verify
|
||||
```
|
||||
|
||||
```bash [Permanent closed]
|
||||
# Comment out the following code in .husky/commit-msg to disable
|
||||
pnpm exec commitlint --edit "$1" # [!code --]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Publint
|
||||
|
||||
Publint is a tool for checking the standard of npm packages, which can check whether the package version conforms to the standard, whether it conforms to the standard ESM package specification, etc.
|
||||
|
||||
### Command
|
||||
|
||||
```bash
|
||||
pnpm vsh publint
|
||||
```
|
||||
|
||||
## Cspell
|
||||
|
||||
Cspell is a tool for checking spelling errors, which can check for spelling errors in the code, avoiding bugs caused by spelling errors.
|
||||
|
||||
### Command
|
||||
|
||||
```bash
|
||||
pnpm cspell lint \"**/*.ts\" \"**/README.md\" \".changeset/*.md\" --no-progress
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The cspell configuration file is `cspell.json`, which can be modified according to project needs.
|
||||
|
||||
## Git Hook
|
||||
|
||||
Git hooks are generally combined with various lints to check code style during git commits. If the check fails, the commit will not proceed. Developers need to modify and resubmit.
|
||||
|
||||
### lefthook
|
||||
|
||||
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where lefthook comes in.
|
||||
|
||||
The most effective solution is to perform Lint checks locally before committing. A common practice is to use lefthook to perform a Lint check before local submission.
|
||||
|
||||
The project defines corresponding hooks inside `lefthook.yml`:
|
||||
|
||||
- `pre-commit`: Runs before commit, used for code formatting and checking
|
||||
- `code-workspace`: Updates VSCode workspace configuration
|
||||
- `lint-md`: Formats Markdown files
|
||||
- `lint-vue`: Formats and checks Vue files
|
||||
- `lint-js`: Formats and checks JavaScript/TypeScript files
|
||||
- `lint-style`: Formats and checks style files
|
||||
- `lint-package`: Formats package.json
|
||||
- `lint-json`: Formats other JSON files
|
||||
|
||||
- `post-merge`: Runs after merge, used for automatic dependency installation
|
||||
- `install`: Runs `pnpm install` to install new dependencies
|
||||
|
||||
- `commit-msg`: Runs during commit, used for checking commit message format
|
||||
- `commitlint`: Uses commitlint to check commit messages
|
||||
|
||||
#### How to Disable lefthook
|
||||
|
||||
If you want to disable lefthook, there are two ways:
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [Temporary disable]
|
||||
git commit -m 'feat: add home page' --no-verify
|
||||
```
|
||||
|
||||
```bash [Permanent disable]
|
||||
# Simply delete the lefthook.yml file
|
||||
rm lefthook.yml
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### How to Modify lefthook Configuration
|
||||
|
||||
If you want to modify lefthook's configuration, you can edit the `lefthook.yml` file. For example:
|
||||
|
||||
```yaml
|
||||
pre-commit:
|
||||
parallel: true # Execute tasks in parallel
|
||||
jobs:
|
||||
- name: lint-js
|
||||
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
|
||||
glob: '*.{js,jsx,ts,tsx}'
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `parallel`: Whether to execute tasks in parallel
|
||||
- `jobs`: Defines the list of tasks to execute
|
||||
- `name`: Task name
|
||||
- `run`: Command to execute
|
||||
- `glob`: File pattern to match
|
||||
- `{staged_files}`: Represents the list of staged files
|
||||
13
docs/src/en/guide/project/tailwindcss.md
Normal file
13
docs/src/en/guide/project/tailwindcss.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Tailwind CSS
|
||||
|
||||
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for quickly building custom designs.
|
||||
|
||||
## Configuration
|
||||
|
||||
The project's configuration file is located in `internal/tailwind-config`, where you can modify the Tailwind CSS configuration.
|
||||
|
||||
::: tip Restrictions on using tailwindcss in packages
|
||||
|
||||
Tailwind CSS compilation will only be enabled if there is a `tailwind.config.mjs` file present in the corresponding package. Otherwise, Tailwind CSS will not be enabled. If you have a pure SDK package that does not require Tailwind CSS, you do not need to create a `tailwind.config.mjs` file.
|
||||
|
||||
:::
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user