提交新版本

This commit is contained in:
luob
2025-12-23 17:14:38 +08:00
3632 changed files with 498895 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import { computed } from 'vue';
import PreviewGroup from './preview-group.vue';
interface Props {
files?: string;
}
const props = withDefaults(defineProps<Props>(), { files: '() => []' });
const parsedFiles = computed(() => {
try {
return JSON.parse(decodeURIComponent(props.files ?? ''));
} catch {
return [];
}
});
</script>
<template>
<div class="relative rounded-xl border border-border shadow-float">
<div
class="not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6"
>
<div class="flex w-full max-w-[700px] px-2">
<ClientOnly>
<slot v-if="parsedFiles.length > 0"></slot>
<div v-else class="text-sm text-destructive">
<span class="rounded-sm bg-destructive px-1 py-1 text-foreground">
ERROR:
</span>
The preview directory does not exist. Please check the 'dir'
parameter.
</div>
</ClientOnly>
</div>
</div>
<PreviewGroup v-if="parsedFiles.length > 0" :files="parsedFiles">
<template v-for="file in parsedFiles" #[file]>
<slot :name="file"></slot>
</template>
</PreviewGroup>
</div>
</template>

View File

@@ -0,0 +1,110 @@
<script setup lang="ts">
import type { SetupContext } from 'vue';
import { computed, ref, useSlots } from 'vue';
import { VbenTooltip } from '@vben-core/shadcn-ui';
import { Code } from 'lucide-vue-next';
import {
TabsContent,
TabsIndicator,
TabsList,
TabsRoot,
TabsTrigger,
} from 'reka-ui';
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(
defineProps<{
files?: string[];
}>(),
{ files: () => [] },
);
const open = ref(false);
const slots: SetupContext['slots'] = useSlots();
const tabs = computed(() => {
return props.files.map((file) => {
return {
component: slots[file],
label: file,
};
});
});
const currentTab = ref('index.vue');
const toggleOpen = () => {
open.value = !open.value;
};
</script>
<template>
<TabsRoot
v-model="currentTab"
class="overflow-hidden rounded-b-xl border-t border-border bg-background-deep"
@update:model-value="open = true"
>
<div class="flex border-b-2 border-border bg-background pr-2">
<div class="flex w-full items-center justify-between text-[13px]">
<TabsList class="relative flex">
<template v-if="open">
<TabsIndicator
class="absolute bottom-0 left-0 h-[2px] w-[--reka-tabs-indicator-size] translate-x-[--reka-tabs-indicator-position] rounded-full transition-[width,transform] duration-300"
>
<div class="size-full bg-[var(--vp-c-indigo-1)]"></div>
</TabsIndicator>
<TabsTrigger
v-for="(tab, index) in tabs"
:key="index"
:value="tab.label"
class="border-box px-4 py-3 text-foreground data-[state=active]:text-[var(--vp-c-indigo-1)]"
tabindex="-1"
>
{{ tab.label }}
</TabsTrigger>
</template>
</TabsList>
<div
:class="{
'py-2': !open,
}"
class="flex items-center"
>
<VbenTooltip side="top">
<template #trigger>
<Code
class="size-7 cursor-pointer rounded-full p-1.5 hover:bg-accent"
@click="toggleOpen"
/>
</template>
{{ open ? 'Collapse code' : 'Expand code' }}
</VbenTooltip>
</div>
</div>
</div>
<div
:class="`${open ? 'h-[unset] max-h-[80vh]' : 'h-0'}`"
class="block overflow-y-scroll bg-[var(--vp-code-block-bg)] transition-all duration-300"
>
<TabsContent
v-for="tab in tabs"
:key="tab.label"
:value="tab.label"
as-child
class="rounded-xl"
>
<div class="relative rounded-xl text-foreground">
<component :is="tab.component" class="border-0" />
</div>
</TabsContent>
</div>
</TabsRoot>
</template>

View File

@@ -0,0 +1,143 @@
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress';
import crypto from 'node:crypto';
import { readdirSync } from 'node:fs';
import { join } from 'node:path';
export const rawPathRegexp =
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/strict
/^(.+?(?:\.([\da-z]+))?)(#[\w-]+)?(?: ?{(\d+(?:[,-]\d+)*)? ?(\S+)?})? ?(?:\[(.+)])?$/;
function rawPathToToken(rawPath: string) {
const [
filepath = '',
extension = '',
region = '',
lines = '',
lang = '',
rawTitle = '',
] = (rawPathRegexp.exec(rawPath) || []).slice(1);
const title = rawTitle || filepath.split('/').pop() || '';
return { extension, filepath, lang, lines, region, title };
}
export const demoPreviewPlugin = (md: MarkdownRenderer) => {
md.core.ruler.after('inline', 'demo-preview', (state) => {
const insertComponentImport = (importString: string) => {
const index = state.tokens.findIndex(
(i) => i.type === 'html_block' && i.content.match(/<script setup>/g),
);
if (index === -1) {
const importComponent = new state.Token('html_block', '', 0);
importComponent.content = `<script setup>\n${importString}\n</script>\n`;
state.tokens.splice(0, 0, importComponent);
} else {
if (state.tokens[index]) {
const content = state.tokens[index].content;
state.tokens[index].content = content.replace(
'</script>',
`${importString}\n</script>`,
);
}
}
};
// Define the regular expression to match the desired pattern
const regex = /<DemoPreview[^>]*\sdir="([^"]*)"/g;
// Iterate through the Markdown content and replace the pattern
state.src = state.src.replaceAll(regex, (_match, dir) => {
const componentDir = join(process.cwd(), 'src', dir).replaceAll(
'\\',
'/',
);
let childFiles: string[] = [];
let dirExists = true;
try {
childFiles =
readdirSync(componentDir, {
encoding: 'utf8',
recursive: false,
withFileTypes: false,
}) || [];
} catch {
dirExists = false;
}
if (!dirExists) {
return '';
}
const uniqueWord = generateContentHash(componentDir);
const ComponentName = `DemoComponent_${uniqueWord}`;
insertComponentImport(
`import ${ComponentName} from '${componentDir}/index.vue'`,
);
const { path: _path } = state.env as MarkdownEnv;
const index = state.tokens.findIndex((i) => i.content.match(regex));
if (!state.tokens[index]) {
return '';
}
const firstString = 'index.vue';
childFiles = childFiles.toSorted((a, b) => {
if (a === firstString) return -1;
if (b === firstString) return 1;
return a.localeCompare(b, 'en', { sensitivity: 'base' });
});
state.tokens[index].content =
`<DemoPreview files="${encodeURIComponent(JSON.stringify(childFiles))}" ><${ComponentName}/>
`;
const _dummyToken = new state.Token('', '', 0);
const tokenArray: Array<typeof _dummyToken> = [];
childFiles.forEach((filename) => {
// const slotName = filename.replace(extname(filename), '');
const templateStart = new state.Token('html_inline', '', 0);
templateStart.content = `<template #${filename}>`;
tokenArray.push(templateStart);
const resolvedPath = join(componentDir, filename);
const { extension, filepath, lang, lines, title } =
rawPathToToken(resolvedPath);
// Add code tokens for each line
const token = new state.Token('fence', 'code', 0);
token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${
title ? `[${title}]` : ''
}`;
token.content = `<<< ${filepath}`;
(token as any).src = [resolvedPath];
tokenArray.push(token);
const templateEnd = new state.Token('html_inline', '', 0);
templateEnd.content = '</template>';
tokenArray.push(templateEnd);
});
const endTag = new state.Token('html_inline', '', 0);
endTag.content = '</DemoPreview>';
tokenArray.push(endTag);
state.tokens.splice(index + 1, 0, ...tokenArray);
// console.log(
// state.md.renderer.render(state.tokens, state?.options ?? [], state.env),
// );
return '';
});
});
};
function generateContentHash(input: string, length: number = 10): string {
// 使用 SHA-256 生成哈希值
const hash = crypto.createHash('sha256').update(input).digest('hex');
// 将哈希值转换为 Base36 编码,并取指定长度的字符作为结果
return Number.parseInt(hash, 16).toString(36).slice(0, length);
}

35
docs/package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "@vben/docs",
"version": "5.5.9",
"private": true,
"scripts": {
"build": "vitepress build",
"dev": "vitepress dev",
"docs:preview": "vitepress preview"
},
"imports": {
"#/*": {
"node": "./src/_env/node/*",
"default": "./src/_env/*"
}
},
"dependencies": {
"@vben-core/shadcn-ui": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"ant-design-vue": "catalog:",
"lucide-vue-next": "catalog:",
"medium-zoom": "catalog:",
"reka-ui": "catalog:",
"vitepress-plugin-group-icons": "catalog:"
},
"devDependencies": {
"@nolebase/vitepress-plugin-git-changelog": "catalog:",
"@vben/vite-config": "workspace:*",
"@vite-pwa/vitepress": "catalog:",
"vitepress": "catalog:",
"vue": "catalog:"
}
}

View File

@@ -0,0 +1,166 @@
---
outline: deep
---
# Vben Alert 轻量提示框
框架提供的一些用于轻量提示的弹窗仅使用js代码即可快速动态创建提示而不需要在template写任何代码。
::: info 应用场景
Alert提供的功能与Modal类似但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求请使用VbenModal
:::
::: tip 注意
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下不支持HMR热更新代码变更后需要关闭这些弹窗后重新打开。
:::
::: tip README
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
## 基础用法
使用 `alert` 创建只有一个确认按钮的提示框。
<DemoPreview dir="demos/vben-alert/alert" />
使用 `confirm` 创建有确认和取消按钮的提示框。
<DemoPreview dir="demos/vben-alert/confirm" />
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
<DemoPreview dir="demos/vben-alert/prompt" />
## useAlertContext
当弹窗的content、footer、icon使用自定义组件时在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。
::: tip 注意
`useAlertContext`只能用在setup或者函数式组件中。
:::
### Methods
| 方法 | 描述 | 类型 | 版本要求 |
| --------- | ------------------ | -------- | -------- |
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 |
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 |
## 类型说明
```ts
/** 预置的图标类型 */
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
export type BeforeCloseScope = {
/** 是否为点击确认按钮触发的关闭 */
isConfirm: boolean;
};
/**
* alert 属性
*/
export type AlertProps = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (
scope: BeforeCloseScope,
) => boolean | Promise<boolean | undefined> | undefined;
/** 边框 */
bordered?: boolean;
/** 按钮对齐方式 */
buttonAlign?: 'center' | 'end' | 'start';
/** 取消按钮的标题 */
cancelText?: string;
/** 是否居中显示 */
centered?: boolean;
/** 确认按钮的标题 */
confirmText?: string;
/** 弹窗容器的额外样式 */
containerClass?: string;
/** 弹窗提示内容 */
content: Component | string;
/** 弹窗内容的额外样式 */
contentClass?: string;
/** 执行beforeClose回调期间在内容区域显示一个loading遮罩*/
contentMasking?: boolean;
/** 弹窗底部内容(与按钮在同一个容器中) */
footer?: Component | string;
/** 弹窗的图标(在标题的前面) */
icon?: Component | IconType;
/**
* 弹窗遮罩模糊效果
*/
overlayBlur?: number;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 弹窗标题 */
title?: string;
};
/** prompt 属性 */
export type PromptProps<T = any> = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (scope: {
isConfirm: boolean;
value: T | undefined;
}) => boolean | Promise<boolean | undefined> | undefined;
/** 用于接受用户输入的组件 */
component?: Component;
/** 输入组件的属性 */
componentProps?: Recordable<any>;
/** 输入组件的插槽 */
componentSlots?: Recordable<Component>;
/** 默认值 */
defaultValue?: T;
/** 输入组件的值属性名 */
modelPropName?: string;
} & Omit<AlertProps, 'beforeClose'>;
/**
* 函数签名
* alert和confirm的函数签名相同。
* confirm默认会显示取消按钮而alert默认只有一个按钮
* */
export function alert(options: AlertProps): Promise<void>;
export function alert(
message: string,
options?: Partial<AlertProps>,
): Promise<void>;
export function alert(
message: string,
title?: string,
options?: Partial<AlertProps>,
): Promise<void>;
/**
* 弹出输入框的函数签名。
* beforeClose的参数会传入用户当前输入的值
* component指定接受用户输入的组件默认为Input
* componentProps 为输入组件设置的属性数据
* defaultValue 默认的值
* modelPropName 输入组件的值属性名称。默认为modelValue
*/
export async function prompt<T = any>(
options: Omit<AlertProps, 'beforeClose'> & {
beforeClose?: (
scope: BeforeCloseScope & {
/** 输入组件的当前值 */
value: T;
},
) => boolean | Promise<boolean | undefined> | undefined;
component?: Component;
componentProps?: Recordable<any>;
defaultValue?: T;
modelPropName?: string;
},
): Promise<T | undefined>;
```

View File

@@ -0,0 +1,588 @@
---
outline: deep
---
# Vben Form 表单
框架提供的表单组件,可适配 `Element Plus``Ant Design Vue``Naive UI` 等框架。
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
::: info 写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
:::
## 适配器
表单底层使用 [vee-validate](https://vee-validate.logaretm.com/v4/) 进行表单验证,所以你可以使用 `vee-validate` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。
### 适配器说明
每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form``src/adapter/component` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
::: details ant design vue 表单适配器
```ts
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';
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
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 };
```
:::
::: details ant design vue 组件适配器
```ts
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
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'
| 'IconPicker';
| 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,
IconPicker,
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 };
```
:::
## 基础用法
::: tip README
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
使用 `useVbenForm` 创建最基础的表单。
<DemoPreview dir="demos/vben-form/basic" />
## 查询表单
查询表单是一种特殊的表单,用于查询数据。查询表单不会触发表单验证,只会触发查询事件。
<DemoPreview dir="demos/vben-form/query" />
## 表单校验
表单校验是一个非常重要的功能,可以通过 `rules` 属性进行校验。
<DemoPreview dir="demos/vben-form/rules" />
## 表单联动
表单联动是一个非常常见的功能,可以通过 `dependencies` 属性进行联动。
_注意_ 需要指定 `dependencies``triggerFields` 属性,设置由谁的改动来触发,以便表单组件能够正确的联动。
<DemoPreview dir="demos/vben-form/dynamic" />
## 自定义组件
如果你的业务组件库没有提供某个组件,你可以自行封装一个组件,然后加到表单内部。
<DemoPreview dir="demos/vben-form/custom" />
## 操作
一些常见的表单操作。
<DemoPreview dir="demos/vben-form/api" />
## API
`useVbenForm` 返回一个数组,第一个元素是表单组件,第二个元素是表单的方法。
```vue
<script setup lang="ts">
import { useVbenForm } from '#/adapter/form';
// Form 为弹窗组件
// formApi 为弹窗的方法
const [Form, formApi] = useVbenForm({
// 属性
// 事件
});
</script>
<template>
<Form />
</template>
```
### FormApi
useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。
| 方法名 | 描述 | 类型 | 版本号 |
| --- | --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
| resetForm | 重置表单 | `()=>Promise<void>` | - |
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` | - |
| validate | 表单校验 | `()=>Promise<void>` | - |
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` | - |
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` | - |
| resetValidate | 重置表单校验 | `()=>Promise<void>` | - |
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | - |
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` | - |
| setState | 设置组件状态props | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` | - |
| getState | 获取组件状态props | `()=>Promise<VbenFormProps>` | - |
| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - | - |
| getFieldComponentRef | 获取指定字段的组件实例 | `<T=unknown>(fieldName: string)=>T` | >5.5.3 |
| getFocusedField | 获取当前已获得焦点的字段 | `()=>string\|undefined` | >5.5.3 |
## Props
所有属性都可以传入 `useVbenForm` 的第一个参数中。
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| layout | 表单项布局 | `'horizontal' \| 'vertical'\| 'inline'` | `horizontal` |
| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
| wrapperClass | 表单的布局基于tailwindcss | `any` | - |
| actionWrapperClass | 表单操作区域class | `any` | - |
| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` |
| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` |
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
| handleCollapsedChange | 表单收起展开状态变化回调 | `(collapsed: boolean) => void` | - |
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
| collapsed | 是否折叠,在`showCollapseButton``true`时生效 | `boolean` | `false` |
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
| scrollToFirstError | 表单验证失败时是否自动滚动到第一个错误字段 | `boolean` | false |
::: tip handleValuesChange
`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组包含了所有被改变的字段名。注意第二个参数仅在v5.5.4(不含)以上版本可用并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话请注意该参数并不会包含映射后的字段名。
:::
::: tip fieldMappingTime
此属性用于将表单内的数组值映射成 2 个字段它应当传入一个数组数组的每一项是一个映射规则规则的第一个成员是一个字符串表示需要映射的字段名第二个成员是一个数组表示映射后的字段名第三个成员是一个可选的格式掩码用于格式化日期时间字段也可以提供一个格式化函数参数分别为当前值和当前字段名返回格式化后的值。如果明确地将格式掩码设为null则原值映射而不进行格式化适用于非日期时间字段。例如`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]``timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime``endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
:::
### TS 类型说明
::: details ActionButtonOptions
```ts
export interface ActionButtonOptions {
/** 样式 */
class?: ClassType;
/** 是否禁用 */
disabled?: boolean;
/** 是否加载中 */
loading?: boolean;
/** 按钮大小 */
size?: ButtonVariantSize;
/** 按钮类型 */
variant?: ButtonVariants;
/** 是否显示 */
show?: boolean;
/** 按钮文本 */
content?: string;
/** 任意属性 */
[key: string]: any;
}
```
:::
::: details FormCommonConfig
```ts
export interface FormCommonConfig {
/**
* 所有表单项的props
*/
componentProps?: ComponentProps;
/**
* 所有表单项的控件样式
*/
controlClass?: string;
/**
* 在表单项的Label后显示一个冒号
*/
colon?: boolean;
/**
* 所有表单项的禁用状态
* @default false
*/
disabled?: boolean;
/**
* 所有表单项的控件样式
* @default {}
*/
formFieldProps?: Partial<typeof Field>;
/**
* 所有表单项的栅格布局
* @default ""
*/
formItemClass?: (() => string) | string;
/**
* 隐藏所有表单项label
* @default false
*/
hideLabel?: boolean;
/**
* 是否隐藏必填标记
* @default false
*/
hideRequiredMark?: boolean;
/**
* 所有表单项的label样式
* @default ""
*/
labelClass?: string;
/**
* 所有表单项的label宽度
*/
labelWidth?: number;
/**
* 所有表单项的model属性名。使用自定义组件时可通过此配置指定组件的model属性名。已经在modelPropNameMap中注册的组件不受此配置影响
* @default "modelValue"
*/
modelPropName?: string;
/**
* 所有表单项的wrapper样式
*/
wrapperClass?: string;
}
```
:::
::: details FormSchema
```ts
export interface FormSchema<
T extends BaseFormComponentType = BaseFormComponentType,
> extends FormCommonConfig {
/** 组件 */
component: Component | T;
/** 组件参数 */
componentProps?: ComponentProps;
/** 默认值 */
defaultValue?: any;
/** 依赖 */
dependencies?: FormItemDependencies;
/** 描述 */
description?: string;
/** 字段名,也作为自定义插槽的名称 */
fieldName: string;
/** 帮助信息 */
help?: CustomRenderType;
/** 是否隐藏表单项 */
hide?: boolean;
/** 表单的标签如果是一个string会用于默认必选规则的消息提示 */
label?: CustomRenderType;
/** 自定义组件内部渲染 */
renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
rules?: FormSchemaRuleType;
/** 后缀 */
suffix?: CustomRenderType;
}
```
:::
### 表单联动
表单联动需要通过 schema 内的 `dependencies` 属性进行联动,允许您添加字段之间的依赖项,以根据其他字段的值控制字段。
```ts
dependencies: {
// 触发字段。只有这些字段值变动时,联动才会触发
triggerFields: ['name'],
// 动态判断当前字段是否需要显示,不显示则直接销毁
if(values,formApi){},
// 动态判断当前字段是否需要显示不显示用css隐藏
show(values,formApi){},
// 动态判断当前字段是否需要禁用
disabled(values,formApi){},
// 字段变更时,都会触发该函数
trigger(values,formApi){},
// 动态rules
rules(values,formApi){},
// 动态必填
required(values,formApi){},
// 动态组件参数
componentProps(values,formApi){},
}
```
### 表单校验
表单校验需要通过 schema 内的 `rules` 属性进行配置。
rules的值可以是字符串预定义的校验规则名称也可以是一个zod的schema。
#### 预定义的校验规则
```ts
// 表示字段必填默认会根据适配器的required进行国际化
{
rules: 'required';
}
// 表示字段必填默认会根据适配器的required进行国际化用于下拉选择之类
{
rules: 'selectRequired';
}
```
#### zod
rules也支持 zod 的 schema可以进行更复杂的校验zod 的使用请查看 [zod文档](https://zod.dev/)。
```ts
import { z } from '#/adapter/form';
// 基础类型
{
rules: z.string().min(1, { message: '请输入字符串' });
}
// 可选(可以是undefined)并且携带默认值。注意zod的optional不包括空字符串''
{
rules: z.string().default('默认值').optional();
}
// 可以是空字符串、undefined或者一个邮箱地址(两种不同的用法)
{
rules: z.union([z.string().email().optional(), z.literal('')]);
}
{
rules: z.string().email().or(z.literal('')).optional();
}
// 复杂校验
{
z.string()
.min(1, { message: '请输入' })
.refine((value) => value === '123', {
message: '值必须为123',
});
}
```
## Slots
可以使用以下插槽在表单中插入自定义的内容
| 插槽名 | 描述 |
| ------------- | ------------------ |
| reset-before | 重置按钮之前的位置 |
| submit-before | 提交按钮之前的位置 |
| expand-before | 展开按钮之前的位置 |
| expand-after | 展开按钮之后的位置 |
::: tip 字段插槽
除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。
:::

View File

@@ -0,0 +1,174 @@
---
outline: deep
---
# Vben Modal 模态框
框架提供的模态框组件,支持`拖拽``全屏``自动高度``loading`等功能。
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
::: info 写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
:::
::: tip README
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
## 基础用法
使用 `useVbenModal` 创建最基础的模态框。
<DemoPreview dir="demos/vben-modal/basic" />
## 组件抽离
Modal 内的内容一般业务中,会比较复杂,所以我们可以将 modal 内的内容抽离出来,也方便复用。通过 `connectedComponent` 参数,可以将内外组件进行连接,而不用其他任何操作。
<DemoPreview dir="demos/vben-modal/extra" />
## 开启拖拽
通过 `draggable` 参数,可开启拖拽功能。
<DemoPreview dir="demos/vben-modal/draggable" />
## 自动计算高度
弹窗会自动计算内容高度,超过一定高度会出现滚动条,同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。
<DemoPreview dir="demos/vben-modal/auto-height" />
## 使用 Api
通过 `modalApi` 可以调用 modal 的方法以及使用 `setState` 更新 modal 的状态。
<DemoPreview dir="demos/vben-modal/dynamic" />
## 数据共享
如果你使用了 `connectedComponent` 参数,那么内外组件会共享数据,比如一些表单回填等操作。可以用 `modalApi` 来获取数据和设置数据,配合 `onOpenChange`,可以满足大部分的需求。
<DemoPreview dir="demos/vben-modal/shared-data" />
## 动画类型
通过 `animationType` 属性可以控制弹窗的动画效果:
- `slide`(默认):从顶部向下滑动进入/退出
- `scale`:缩放淡入/淡出效果
<DemoPreview dir="demos/vben-modal/animation-type" />
::: info 注意
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`内部Modal及其子组件会在被关闭后<b>完全销毁</b>。
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
:::
## API
```ts
// Modal 为弹窗组件
// modalApi 为弹窗的方法
const [Modal, modalApi] = useVbenModal({
// 属性
// 事件
});
```
### Props
所有属性都可以传入 `useVbenModal` 的第一个参数中。
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
| isOpen | 弹窗打开状态 | `boolean` | `false` |
| loading | 弹窗加载状态 | `boolean` | `false` |
| fullscreen | 全屏显示 | `boolean` | `false` |
| fullscreenButton | 显示全屏按钮 | `boolean` | `true` |
| draggable | 可拖拽 | `boolean` | `false` |
| closable | 显示关闭按钮 | `boolean` | `true` |
| centered | 居中显示 | `boolean` | `false` |
| modal | 显示遮罩 | `boolean` | `true` |
| header | 显示header | `boolean` | `true` |
| footer | 显示footer | `boolean\|slot` | `true` |
| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |
| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |
| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - |
| bordered | 是否显示border | `boolean` | `false` |
| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |
| overlayBlur | 遮罩模糊度 | `number` | - |
| animationType | 动画类型 | `'slide' \| 'scale'` | `'slide'` |
| submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` |
::: info appendToMain
`appendToMain`可以指定将弹窗挂载到内容区域打开这种弹窗时内容区域以外的部分标签栏、导航菜单等等不会被遮挡。默认情况下弹窗会挂载到body上。但是挂载到内容区域时作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。
:::
### Event
以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。
| 事件名 | 描述 | 类型 | 版本号 |
| --- | --- | --- | --- |
| onBeforeClose | 关闭前触发,返回 `false`或者被`reject`则禁止关闭 | `()=>Promise<boolean>\|boolean` | >5.5.2支持Promise |
| onCancel | 点击取消按钮触发 | `()=>void` | |
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.4.3 |
| onConfirm | 点击确认按钮触发 | `()=>void` | |
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | |
| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.4.3 |
### Slots
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
| 插槽名 | 描述 |
| -------------- | -------------------------------------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
| append-footer | 确认按钮右侧 |
### modalApi
| 方法 | 描述 | 类型 | 版本 |
| --- | --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>modalApi` | - |
| open | 打开弹窗 | `()=>void` | - |
| close | 关闭弹窗 | `()=>void` | - |
| setData | 设置共享数据 | `<T>(data:T)=>modalApi` | - |
| getData | 获取共享数据 | `<T>()=>T` | - |
| useStore | 获取可响应式状态 | - | - |
| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |
| unlock | lock方法的反操作解除弹窗的锁定状态也是lock(false)的别名 | `()=>modalApi` | >5.5.3 |
::: info lock
`lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
:::

View File

@@ -0,0 +1,45 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer, VbenButton } from '@vben/common-ui';
const list = ref<number[]>([]);
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onConfirm() {
console.log('onConfirm');
},
onOpenChange(isOpen) {
if (isOpen) {
handleUpdate(10);
}
},
});
function handleUpdate(len: number) {
drawerApi.setState({ loading: true });
setTimeout(() => {
list.value = Array.from({ length: len }, (_v, k) => k + 1);
drawerApi.setState({ loading: false });
}, 2000);
}
</script>
<template>
<Drawer title="自动计算高度">
<div
v-for="item in list"
:key="item"
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
>
{{ item }}
</div>
<template #prepend-footer>
<VbenButton type="link" @click="handleUpdate(6)">
点击更新数据
</VbenButton>
</template>
</Drawer>
</template>

View File

@@ -0,0 +1,36 @@
<script lang="ts" setup>
import { useVbenModal, VbenButton } from '@vben/common-ui';
const [SlideModal, slideModalApi] = useVbenModal({
animationType: 'slide',
});
const [ScaleModal, scaleModalApi] = useVbenModal({
animationType: 'scale',
});
function openSlideModal() {
slideModalApi.open();
}
function openScaleModal() {
scaleModalApi.open();
}
</script>
<template>
<div class="space-y-4">
<div class="flex gap-4">
<VbenButton @click="openSlideModal">滑动动画</VbenButton>
<VbenButton @click="openScaleModal">缩放动画</VbenButton>
</div>
<SlideModal title="滑动动画示例" class="w-[500px]">
<p>这是使用滑动动画的弹窗从顶部向下滑动进入</p>
</SlideModal>
<ScaleModal title="缩放动画示例" class="w-[500px]">
<p>这是使用缩放动画的弹窗以缩放淡入淡出的方式显示</p>
</ScaleModal>
</div>
</template>

View File

@@ -0,0 +1,45 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal, VbenButton } from '@vben/common-ui';
const list = ref<number[]>([]);
const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
onConfirm() {
console.log('onConfirm');
},
onOpenChange(isOpen) {
if (isOpen) {
handleUpdate(10);
}
},
});
function handleUpdate(len: number) {
modalApi.setState({ loading: true });
setTimeout(() => {
list.value = Array.from({ length: len }, (_v, k) => k + 1);
modalApi.setState({ loading: false });
}, 2000);
}
</script>
<template>
<Modal title="自动计算高度">
<div
v-for="item in list"
:key="item"
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
>
{{ item }}
</div>
<template #prepend-footer>
<VbenButton type="link" @click="handleUpdate(6)">
点击更新数据
</VbenButton>
</template>
</Modal>
</template>

View File

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

View File

@@ -0,0 +1,257 @@
# Local Development {#development}
::: tip Code Acquisition
If you haven't acquired the code yet, you can start by reading the documentation from [Quick Start](../introduction/quick-start.md).
:::
## Prerequisites
For a better development experience, we provide some tool configurations and project descriptions to facilitate your development.
### Required Basic Knowledge
This project requires some basic frontend knowledge. Please ensure you are familiar with the basics of Vue to handle common issues. It is recommended to learn the following topics before development. Understanding these will be very helpful for the project:
- [Vue3](https://vuejs.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [TypeScript](https://www.typescriptlang.org/)
- [Vue Router](https://router.vuejs.org/)
- [Vitejs](https://vitejs.dev/)
- [Pnpm](https://pnpm.io/)
- [Turbo](https://turbo.build/)
### Tool Configuration
If you are using [vscode](https://code.visualstudio.com/) (recommended) as your IDE, you can install the following tools to improve development efficiency and code formatting:
- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Official Vue plugin (essential).
- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwind CSS autocomplete plugin.
- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=vunguyentuan.vscode-css-variables) - CSS variable autocomplete plugin.
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify icon plugin.
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n plugin.
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code linting.
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting.
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting.
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Spelling checker.
- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env file highlighting.
## Npm Scripts
Npm scripts are common configurations used in the project to perform common tasks such as starting the project, building the project, etc. The following scripts can be found in the `package.json` file at the root of the project.
The execution command is: `pnpm run [script]` or `npm run [script]`.
```json
{
"scripts": {
// Build the project
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// Build the project with analysis
"build:analyze": "turbo build:analyze",
// Build a local Docker image
"build:docker": "./build-local-docker-image.sh",
// Build the web-antd application separately
"build:antd": "pnpm run build --filter=@vben/web-antd",
// Build the documentation separately
"build:docs": "pnpm run build --filter=@vben/docs",
// Build the web-ele application separately
"build:ele": "pnpm run build --filter=@vben/web-ele",
// Build the web-naive application separately
"build:naive": "pnpm run build --filter=@vben/naive",
// Build the web-tdesign application separately
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
// Build the playground application separately
"build:play": "pnpm run build --filter=@vben/playground",
// Changeset version management
"changeset": "pnpm exec changeset",
// Check for various issues in the project
"check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell",
// Check for circular dependencies
"check:circular": "vsh check-circular",
// Check spelling
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress"
// Check dependencies
"check:dep": "vsh check-dep",
// Check types
"check:type": "turbo run typecheck",
// Clean the project (delete node_modules, dist, .turbo, etc.)
"clean": "node ./scripts/clean.mjs",
// Commit code
"commit": "czg",
// Start the project (by default, the dev scripts of all packages in the entire repository will run)
"dev": "turbo-run dev",
// Start the web-antd application
"dev:antd": "pnpm -F @vben/web-antd run dev",
// Start the documentation
"dev:docs": "pnpm -F @vben/docs run dev",
// Start the web-ele application
"dev:ele": "pnpm -F @vben/web-ele run dev",
// Start the web-naive application
"dev:naive": "pnpm -F @vben/web-naive run dev",
// Start the playground application
"dev:play": "pnpm -F @vben/playground run dev",
// Format code
"format": "vsh lint --format",
// Lint code
"lint": "vsh lint",
// After installing dependencies, execute the stub script for all packages
"postinstall": "pnpm -r run stub --if-present",
// Only allow using pnpm
"preinstall": "npx only-allow pnpm",
// Install lefthook
"prepare": "is-ci || lefthook install",
// Preview the application
"preview": "turbo-run preview",
// Package specification check
"publint": "vsh publint",
// Delete all node_modules, yarn.lock, package.lock.json, and reinstall dependencies
"reinstall": "pnpm clean --del-lock && pnpm install",
// Run vitest unit tests
"test:unit": "vitest run --dom",
// Update project dependencies
"update:deps": " pnpm update --latest --recursive",
// Changeset generation and versioning
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
}
}
```
## Running the Project Locally
To run the documentation locally and make adjustments, you can execute the following command. This command allows you to select the application you want to develop:
```bash
pnpm dev
```
If you want to run a specific application directly, you can execute the following commands:
To run the `web-antd` application:
```bash
pnpm dev:antd
```
To run the `web-naive` application:
```bash
pnpm dev:naive
```
To run the `web-ele` application:
```bash
pnpm dev:ele
```
To run the `docs` application:
```bash
pnpm dev:docs
```
### Distinguishing Build Environments
In actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`.
At this point, you can modify three files and add corresponding script configurations to distinguish between production environments.
Take the addition of the test environment `test` to `@vben/web-antd` as an example:
- `apps\web-antd\package.json`
```json
"scripts": {
"build:prod": "pnpm vite build --mode production",
"build:test": "pnpm vite build --mode test",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
}
```
Add the command `"build:test"` and change the original `"build"` to `"build:prod"` to avoid building packages for two environments simultaneously.
- `package.json`
```json
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"build:analyze": "turbo build:analyze",
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
······
}
```
Add the command to build the test environment in the root directory `package.json`.
- `turbo.json`
```json
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
"dist.zip",
".vitepress/dist.zip",
".vitepress/dist/**"
]
},
"build-test:antd": {
"dependsOn": ["@vben/web-antd#build:test"],
"outputs": ["dist/**"]
},
"@vben/web-antd#build:test": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
······
```
Add the relevant dependent commands in `turbo.json`.
## Public Static Resources
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
You need to put the resource in the corresponding project's `public/static` directory. The import path for the resource should be `src="/static/xxx.png"`.
## DevTools
The project has a built-in [Vue DevTools](https://github.com/vuejs/devtools-next) plugin, which can be used during development. It is disabled by default, but can be enabled in the `.env.development` file. After enabling it, restart the project:
```bash
VITE_DEVTOOLS=true
```
Once enabled, a Vue DevTools icon will appear at the bottom of the page during project runtime. Click it to open the DevTools.
![Vue DevTools](/guide/devtools.png)
## Running Documentation Locally
To run the documentation locally and make adjustments, you can execute the following command:
```bash
pnpm dev:docs
```
## Troubleshooting
If you encounter dependency-related issues, you can try reinstalling the dependencies:
```bash
# Execute this command at the root of the project.
# This command will delete all node_modules, yarn.lock, and package.lock.json files
# and then reinstall dependencies (this process will be noticeably slower).
pnpm reinstall
```

View File

@@ -0,0 +1,629 @@
# Configuration
## Environment Variable Configuration
The project's environment variable configuration is located in the application directory under `.env`, `.env.development`, `.env.production`.
The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/guide/env-and-mode.html). The format is as follows:
```bash
.env # Loaded in all environments
.env.local # Loaded in all environments, but ignored by git
.env.[mode] # Only loaded in the specified mode
.env.[mode].local # Only loaded in the specified mode, but ignored by git
```
::: tip
- Only variables starting with `VITE_` will be embedded into the client-side package. You can access them in the project code like this:
```ts
console.log(import.meta.env.VITE_PROT);
```
- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging.
:::
## Environment Configuration Description
::: code-group
```bash [.env]
# Application title
VITE_APP_TITLE=Vben Admin
# Application namespace, used as a prefix for caching, store, etc., to ensure isolation
VITE_APP_NAMESPACE=vben-web-antd
```
```bash [.env.development]
# Port Number
VITE_PORT=5555
# Public Path for Resources, must start and end with /
VITE_BASE=/
# API URL
VITE_GLOB_API_URL=/api
# Whether to enable Nitro Mock service, true to enable, false to disable
VITE_NITRO_MOCK=true
# Whether to open devtools, true to open, false to close
VITE_DEVTOOLS=true
# Whether to inject global loading
VITE_INJECT_APP_LOADING=true
# Whether to generate after packaging dist.zip
VITE_ARCHIVER=true
```
```bash [.env.production]
# Public Path for Resources, must start and end with /
VITE_BASE=/
# API URL
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
# Whether to enable compression, can be set to none, brotli, gzip
VITE_COMPRESS=gzip
# Whether to enable PWA
VITE_PWA=false
# vue-router mode
VITE_ROUTER_HISTORY=hash
# Whether to inject global loading
VITE_INJECT_APP_LOADING=true
# Whether to generate dist.zip after packaging
VITE_ARCHIVER=true
```
:::
## Dynamic Configuration in Production Environment
When executing `pnpm build` in the root directory of the monorepo, a `dist/_app.config.js` file will be automatically generated in the corresponding application and inserted into `index.html`.
`_app.config.js` is a dynamic configuration file that allows for modifications to the configuration dynamically based on different environments after the project has been built. The content is as follows:
```ts
window._VBEN_ADMIN_PRO_APP_CONF_ = {
VITE_GLOB_API_URL: 'https://mock-napi.vben.pro/api',
};
Object.freeze(window._VBEN_ADMIN_PRO_APP_CONF_);
Object.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {
configurable: false,
writable: false,
});
```
### Purpose
`_app.config.js` is used for projects that need to dynamically modify configurations after packaging, such as API endpoints. There's no need to repackage; you can simply modify the variables in `/dist/_app.config.js` after packaging, and refresh to update the variables in the code. A `js` file is used to ensure that the configuration file is loaded early in the order.
### Usage
To access the variables inside `_app.config.js`, you need to use the `useAppConfig` method provided by `@vben/hooks`.
```ts
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
```
### Adding New
To add a new dynamically modifiable configuration item, simply follow the steps below:
- First, add the variable that needs to be dynamically configurable in the `.env` file or the corresponding development environment configuration file. The variable must start with `VITE_GLOB_*`, for example:
```bash
VITE_GLOB_OTHER_API_URL=https://mock-napi.vben.pro/other-api
```
- In `packages/types/global.d.ts`, add the corresponding type definition, such as:
```ts
export interface VbenAdminProAppConfigRaw {
VITE_GLOB_API_URL: string;
VITE_GLOB_OTHER_API_URL: string; // [!code ++]
}
export interface ApplicationConfig {
apiURL: string;
otherApiURL: string; // [!code ++]
}
```
- In `packages/effects/hooks/src/use-app-config.ts`, add the corresponding configuration item, such as:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// In production environment, directly use the window._VBEN_ADMIN_PRO_APP_CONF_ global variable
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
At this point, you can use the `useAppConfig` method within the project to access the newly added configuration item.
```ts
const { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
```
::: warning Warning
The `useAppConfig` method should only be used within the application and not be coupled with the internals of a package. The reason for passing `import.meta.env` and `import.meta.env.PROD` is to avoid such coupling. A pure package should avoid using variables specific to a particular build tool.
:::
## Preferences
The project offers a wide range of preference settings for dynamically configuring various features of the project:
![](/guide/preferences.png)
If you cannot find documentation for a setting, you can try configuring it yourself and then click `Copy Preferences` to override the project defaults. The configuration file is located in the application directory under `preferences.ts`, where you can override the framework's default configurations to achieve custom settings.
```ts
import { useAppConfig } from '@vben/hooks';
import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description Project configuration file
* Only a part of the configuration in the project needs to be covered, and unnecessary configurations do not need to be covered. The default configuration will be automatically used
* !!! Please clear the cache after changing the configuration, otherwise it may not take effect
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides
});
```
### Framework default configuration
::: details View the default configuration of the framework
```ts
const defaultPreferences: Preferences = {
app: {
accessMode: 'frontend',
authPageLayout: 'panel-right',
checkUpdatesInterval: 1,
colorGrayMode: false,
colorWeakMode: false,
compact: false,
contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
enableRefreshToken: false,
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'page',
name: 'Vben Admin',
preferencesButtonPosition: 'auto',
watermark: false,
zIndex: 200,
},
breadcrumb: {
enable: true,
hideOnlyOne: false,
showHome: false,
showIcon: true,
styleType: 'normal',
},
copyright: {
companyName: 'Vben',
companySiteLink: 'https://www.vben.pro',
date: '2024',
enable: true,
icp: '',
icpLink: '',
settingShow: true,
},
footer: {
enable: false,
fixed: false,
height: 32,
},
header: {
enable: true,
height: 50,
hidden: false,
menuAlign: 'start',
mode: 'fixed',
},
logo: {
enable: true,
fit: 'contain',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
// sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // Optional: Dark theme logo
},
navigation: {
accordion: true,
split: true,
styleType: 'rounded',
},
shortcutKeys: {
enable: true,
globalLockScreen: true,
globalLogout: true,
globalPreferences: true,
globalSearch: true,
},
sidebar: {
autoActivateChild: false,
collapsed: false,
collapsedButton: true,
collapsedShowTitle: false,
collapseWidth: 60,
enable: true,
expandOnHover: true,
extraCollapse: false,
extraCollapsedWidth: 60,
fixedButton: true,
hidden: false,
mixedWidth: 80,
width: 224,
},
tabbar: {
draggable: true,
enable: true,
height: 38,
keepAlive: true,
maxCount: 0,
middleClickToClose: false,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
styleType: 'chrome',
wheelable: true,
},
theme: {
builtinType: 'default',
colorDestructive: 'hsl(348 100% 61%)',
colorPrimary: 'hsl(212 100% 45%)',
colorSuccess: 'hsl(144 57% 58%)',
colorWarning: 'hsl(42 84% 61%)',
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkSidebar: false,
},
transition: {
enable: true,
loading: true,
name: 'fade-slide',
progress: true,
},
widget: {
fullscreen: true,
globalSearch: true,
languageToggle: true,
lockScreen: true,
notification: true,
refresh: true,
sidebarToggle: true,
themeToggle: true,
},
};
```
:::
::: details View the default configuration type of the framework
```ts
interface AppPreferences {
/** Permission mode */
accessMode: AccessModeType;
/** Layout of the login/registration page */
authPageLayout: AuthPageLayoutType;
/** Interval for checking updates */
checkUpdatesInterval: number;
/** Whether to enable gray mode */
colorGrayMode: boolean;
/** Whether to enable color weakness mode */
colorWeakMode: boolean;
/** Whether to enable compact mode */
compact: boolean;
/** Whether to enable content compact mode */
contentCompact: ContentCompactType;
/** Content compact width */
contentCompactWidth: number;
/** Content padding */
contentPadding: number;
/** Content bottom padding */
contentPaddingBottom: number;
/** Content left padding */
contentPaddingLeft: number;
/** Content right padding */
contentPaddingRight: number;
/** Content top padding */
contentPaddingTop: number;
// /** Default application avatar */
defaultAvatar: string;
/** Default homepage path */
defaultHomePath: string;
// /** Enable dynamic title */
dynamicTitle: boolean;
/** Whether to enable update checks */
enableCheckUpdates: boolean;
/** Whether to display preferences */
enablePreferences: boolean;
/**
* @zh_CN Whether to enable refreshToken
*/
enableRefreshToken: boolean;
/** Whether it's mobile */
isMobile: boolean;
/** Layout method */
layout: LayoutType;
/** Supported languages */
locale: SupportedLanguagesType;
/** Login expiration mode */
loginExpiredMode: LoginExpiredModeType;
/** Application name */
name: string;
/** Position of the preferences button */
preferencesButtonPosition: PreferencesButtonPositionType;
/**
* @zh_CN Whether to enable watermark
*/
watermark: boolean;
/** z-index */
zIndex: number;
}
interface BreadcrumbPreferences {
/** Whether breadcrumbs are enabled */
enable: boolean;
/** Whether to hide breadcrumbs when there is only one */
hideOnlyOne: boolean;
/** Whether the home icon in breadcrumbs is visible */
showHome: boolean;
/** Whether the icon in breadcrumbs is visible */
showIcon: boolean;
/** Breadcrumb style */
styleType: BreadcrumbStyleType;
}
interface CopyrightPreferences {
/** Copyright company name */
companyName: string;
/** Link to the copyright company's site */
companySiteLink: string;
/** Copyright date */
date: string;
/** Whether copyright is visible */
enable: boolean;
/** ICP number */
icp: string;
/** Link to the ICP */
icpLink: string;
/** Whether to show in settings panel */
settingShow?: boolean;
}
interface FooterPreferences {
/** Whether the footer is visible */
enable: boolean;
/** Whether the footer is fixed */
fixed: boolean;
/** Footer height */
height: number;
}
interface HeaderPreferences {
/** Whether the header is enabled */
enable: boolean;
/** Header height */
height: number;
/** Whether the header is hidden, css-hidden */
hidden: boolean;
/** Header menu alignment */
menuAlign: LayoutHeaderMenuAlignType;
/** Header display mode */
mode: LayoutHeaderModeType;
}
interface LogoPreferences {
/** Whether the logo is visible */
enable: boolean;
/** Logo image fitting method */
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
/** Logo URL */
source: string;
/** Dark theme logo URL (optional, if not set, use source) */
sourceDark?: string;
}
interface NavigationPreferences {
/** Navigation menu accordion mode */
accordion: boolean;
/** Whether the navigation menu is split, only effective in layout=mixed-nav */
split: boolean;
/** Navigation menu style */
styleType: NavigationStyleType;
}
interface SidebarPreferences {
/** Automatically activate child menu when clicking on directory */
autoActivateChild: boolean;
/** Whether the sidebar is collapsed */
collapsed: boolean;
/** Whether the sidebar collapse button is visible */
collapsedButton: boolean;
/** Whether to show title when sidebar is collapsed */
collapsedShowTitle: boolean;
/** Sidebar collapse width */
collapseWidth: number;
/** Whether the sidebar is visible */
enable: boolean;
/** Menu auto-expand state */
expandOnHover: boolean;
/** Whether the sidebar extension area is collapsed */
extraCollapse: boolean;
/** Sidebar extension area collapse width */
extraCollapsedWidth: number;
/** Whether the sidebar fixed button is visible */
fixedButton: boolean;
/** Whether the sidebar is hidden - css */
hidden: boolean;
/** Mixed sidebar width */
mixedWidth: number;
/** Sidebar width */
width: number;
}
interface ShortcutKeyPreferences {
/** Whether shortcut keys are enabled globally */
enable: boolean;
/** Whether the global lock screen shortcut is enabled */
globalLockScreen: boolean;
/** Whether the global logout shortcut is enabled */
globalLogout: boolean;
/** Whether the global preferences shortcut is enabled */
globalPreferences: boolean;
/** Whether the global search shortcut is enabled */
globalSearch: boolean;
}
interface TabbarPreferences {
/** Whether dragging of multiple tabs is enabled */
draggable: boolean;
/** Whether multiple tabs are enabled */
enable: boolean;
/** Tab height */
height: number;
/** Whether tab caching is enabled */
keepAlive: boolean;
/** Maximum number of tabs */
maxCount: number;
/** Whether to close tab when middle-clicked */
middleClickToClose: boolean;
/** Whether tabs are persistent */
persist: boolean;
/** Whether icons in multiple tabs are enabled */
showIcon: boolean;
/** Whether to show the maximize button */
showMaximize: boolean;
/** Whether to show the more button */
showMore: boolean;
/** Tab style */
styleType: TabsStyleType;
/** Whether mouse wheel response is enabled */
wheelable: boolean;
}
interface ThemePreferences {
/** Built-in theme name */
builtinType: BuiltinThemeType;
/** Destructive color */
colorDestructive: string;
/** Primary color */
colorPrimary: string;
/** Success color */
colorSuccess: string;
/** Warning color */
colorWarning: string;
/** Current theme */
mode: ThemeModeType;
/** Radius */
radius: string;
/** Whether to enable semi-dark header (only effective when theme='light') */
semiDarkHeader: boolean;
/** Whether to enable semi-dark sidebar (only effective when theme='light') */
semiDarkSidebar: boolean;
}
interface TransitionPreferences {
/** Whether page transition animations are enabled */
enable: boolean;
// /** Whether page loading loading is enabled */
loading: boolean;
/** Page transition animation */
name: PageTransitionType | string;
/** Whether page loading progress animation is enabled */
progress: boolean;
}
interface WidgetPreferences {
/** Whether fullscreen widgets are enabled */
fullscreen: boolean;
/** Whether global search widget is enabled */
globalSearch: boolean;
/** Whether language switch widget is enabled */
languageToggle: boolean;
/** Whether lock screen functionality is enabled */
lockScreen: boolean;
/** Whether notification widget is displayed */
notification: boolean;
/** Whether to show the refresh button */
refresh: boolean;
/** Whether sidebar show/hide widget is displayed */
sidebarToggle: boolean;
/** Whether theme switch widget is displayed */
themeToggle: boolean;
}
interface Preferences {
/** Global configuration */
app: AppPreferences;
/** Header configuration */
breadcrumb: BreadcrumbPreferences;
/** Copyright configuration */
copyright: CopyrightPreferences;
/** Footer configuration */
footer: FooterPreferences;
/** Breadcrumb configuration */
header: HeaderPreferences;
/** Logo configuration */
logo: LogoPreferences;
/** Navigation configuration */
navigation: NavigationPreferences;
/** Shortcut key configuration */
shortcutKeys: ShortcutKeyPreferences;
/** Sidebar configuration */
sidebar: SidebarPreferences;
/** Tab bar configuration */
tabbar: TabbarPreferences;
/** Theme configuration */
theme: ThemePreferences;
/** Animation configuration */
transition: TransitionPreferences;
/** Widget configuration */
widget: WidgetPreferences;
}
```
:::
::: warning Warning
- The `overridesPreferences` method only needs to override a part of the configurations in the project. There's no need to override configurations that are not needed; they will automatically use the default settings.
- Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file.
- Please clear the cache after changing the configuration, otherwise it may not take effect.
:::

View File

@@ -0,0 +1,68 @@
# Slimmed-Down Version
Starting from version `5.0`, we no longer provide slimmed-down repositories or branches. Our goal is to offer a more consistent development experience while reducing maintenance costs. Heres how we introduce our project, slim down, and remove unnecessary features.
## Application Slimming
First, identify the version of the `UI` component library you need, and then delete the corresponding applications. For example, if you choose to use `Ant Design Vue`, you can delete the other applications. Simply remove the following two folders:
```bash
apps/web-ele
apps/web-native
```
::: tip
If your project doesnt include the `UI` component library you need, you can delete all other applications and create your own new application as needed.
:::
## Demo Code Slimming
If you dont need demo code, you can simply delete the `playground` folder
## Documentation Slimming
If you dont need documentation, you can delete the `docs` folder.
## Remove Mock Service
If you dont need the `Mock` service, you can delete the `apps/backend-mock` folder. Also, remove the `VITE_NITRO_MOCK` variable from the `.env.development` file in your application.
```bash
# Whether to enable Nitro Mock service, true to enable, false to disable
VITE_NITRO_MOCK=false
```
## Installing Dependencies
Now that youve completed the slimming operations, you can install the dependencies and start your project:
```bash
# Run in the root directory
pnpm install
```
## Adjusting Commands
After slimming down, you may need to adjust commands according to your project. In the `package.json` file in the root directory, you can adjust the `scripts` field and remove any commands you dont need.
```json
{
"scripts": {
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build:docs": "pnpm run build --filter=@vben/docs",
"build:ele": "pnpm run build --filter=@vben/web-ele",
"build:naive": "pnpm run build --filter=@vben/web-naive",
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
"build:play": "pnpm run build --filter=@vben/playground",
"dev:antd": "pnpm -F @vben/web-antd run dev",
"dev:docs": "pnpm -F @vben/docs run dev",
"dev:ele": "pnpm -F @vben/web-ele run dev",
"dev:play": "pnpm -F @vben/playground run dev",
"dev:naive": "pnpm -F @vben/web-naive run dev"
}
}
```

View File

@@ -0,0 +1,257 @@
# 本地开发 {#development}
::: tip 代码获取
如果你还没有获取代码,可以先从 [快速开始](../introduction/quick-start.md) 处开始阅读文档。
:::
## 前置准备
为了更好的开发体验,我们提供了一些工具配置、项目说明,以便于您更好的开发。
### 需要掌握的基础知识
本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:
- [Vue3](https://vuejs.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [TypeScript](https://www.typescriptlang.org/)
- [Vue Router](https://router.vuejs.org/)
- [Vitejs](https://vitejs.dev/)
- [Pnpm](https://pnpm.io/)
- [Turbo](https://turbo.build/)
### 工具配置
如果您使用的 IDE 是[vscode](https://code.visualstudio.com/)(推荐)的话,可以安装以下工具来提高开发效率及代码格式化:
- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 官方插件(必备)。
- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwindcss 提示插件。
- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=bradlc.vunguyentuan.vscode-css-variables) - Css 变量提示插件。
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标插件
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 插件
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - css 格式化
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查
- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env 文件 高亮
## Npm Scripts
npm 脚本是项目常见的配置,用于执行一些常见的任务,比如启动项目、打包项目等。以下的脚本都可以在项目根目录的 `package.json` 文件中找到。
执行方式为:`pnpm run [script]``npm run [script]`
```json
{
"scripts": {
// 构建项目
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
// 构建项目并分析
"build:analyze": "turbo build:analyze",
// 构建本地 docker 镜像
"build:docker": "./build-local-docker-image.sh",
// 单独构建 web-antd 应用
"build:antd": "pnpm run build --filter=@vben/web-antd",
// 单独构建文档
"build:docs": "pnpm run build --filter=@vben/docs",
// 单独构建 web-ele 应用
"build:ele": "pnpm run build --filter=@vben/web-ele",
// 单独构建 web-naive 应用
"build:naive": "pnpm run build --filter=@vben/naive",
// 单独构建 web-tdesign 应用
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
// 单独构建 playground 应用
"build:play": "pnpm run build --filter=@vben/playground",
// changeset 版本管理
"changeset": "pnpm exec changeset",
// 检查项目各种问题
"check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell",
// 检查循环引用
"check:circular": "vsh check-circular",
// 检查拼写
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress"
// 检查依赖
"check:dep": "vsh check-dep",
// 检查类型
"check:type": "turbo run typecheck",
// 清理项目删除node_modules、dist、.turbo等目录
"clean": "node ./scripts/clean.mjs",
// 提交代码
"commit": "czg",
// 启动项目默认会运行整个仓库所有包的dev脚本
"dev": "turbo-run dev",
// 启动web-antd应用
"dev:antd": "pnpm -F @vben/web-antd run dev",
// 启动文档
"dev:docs": "pnpm -F @vben/docs run dev",
// 启动web-ele应用
"dev:ele": "pnpm -F @vben/web-ele run dev",
// 启动web-naive应用
"dev:naive": "pnpm -F @vben/web-naive run dev",
// 启动演示应用
"dev:play": "pnpm -F @vben/playground run dev",
// 格式化代码
"format": "vsh lint --format",
// lint 代码
"lint": "vsh lint",
// 依赖安装完成之后执行所有包的stub脚本
"postinstall": "pnpm -r run stub --if-present",
// 只允许使用pnpm
"preinstall": "npx only-allow pnpm",
// lefthook的安装
"prepare": "is-ci || lefthook install",
// 预览应用
"preview": "turbo-run preview",
// 包规范检查
"publint": "vsh publint",
// 删除所有的node_modules、yarn.lock、package.lock.json重新安装依赖
"reinstall": "pnpm clean --del-lock && pnpm install",
// 运行 vitest 单元测试
"test:unit": "vitest run --dom",
// 更新项目依赖
"update:deps": " pnpm update --latest --recursive",
// changeset生成提交集
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
}
}
```
## 本地运行项目
如需本地运行文档,并进行调整,可以执行以下命令,执行该命令,你可以选择需要的应用进行开发:
```bash
pnpm dev
```
如果你想直接运行某个应用,可以执行以下命令:
运行 `web-antd` 应用:
```bash
pnpm dev:antd
```
运行 `web-naive` 应用:
```bash
pnpm dev:naive
```
运行 `web-ele` 应用:
```bash
pnpm dev:ele
```
运行 `docs` 应用:
```bash
pnpm dev:docs
```
## 区分构建环境
在实际的业务开发中,通常会在构建时区分多种环境,如测试环境`test`、生产环境`build`等。
此时可以修改三个文件,在其中增加对应的脚本配置来达到区分生产环境的效果。
`@vben/web-antd`添加测试环境`test`为例:
- `apps\web-antd\package.json`
```json
"scripts": {
"build:prod": "pnpm vite build --mode production",
"build:test": "pnpm vite build --mode test",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
```
增加命令`"build:test"`, 并将原`"build"`改为`"build:prod"`以避免同时构建两个环境的包。
- `package.json`
```json
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"build:analyze": "turbo build:analyze",
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
······
}
```
在根目录`package.json`中加入构建测试环境的命令
- `turbo.json`
```json
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
"dist.zip",
".vitepress/dist.zip",
".vitepress/dist/**"
]
},
"build-test:antd": {
"dependsOn": ["@vben/web-antd#build:test"],
"outputs": ["dist/**"]
},
"@vben/web-antd#build:test": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
······
```
`turbo.json`中加入相关依赖的命令
## 公共静态资源
项目中需要使用到的公共静态资源图片、静态HTML等需要在开发中通过 `src="/xxx.png"` 直接引入的。
需要将资源放在对应项目的 `public/static` 目录下。引入的路径为:`src="/static/xxx.png"`
## DevTools
项目内置了 [Vue DevTools](https://github.com/vuejs/devtools-next) 插件,可以在开发过程中使用。默认关闭,可在`.env.development` 内开启,并重新运行项目即可:
```bash
VITE_DEVTOOLS=true
```
开启后,项目运行会在页面底部显示一个 Vue DevTools 的图标,点击即可打开 DevTools。
![Vue DevTools](/guide/devtools.png)
## 本地运行文档
如需本地运行文档,并进行调整,可以执行以下命令:
```bash
pnpm dev:docs
```
## 问题解决
如果你在使用过程中遇到依赖相关的问题,可以尝试以下重新安装依赖:
```bash
# 请在项目根目录下执行
# 该命令会删除整个仓库所有的 node_modules、yarn.lock、package.lock.json后
# 再进行依赖重新安装(安装速度会明显变慢)。
pnpm reinstall
```

View File

@@ -0,0 +1,632 @@
# 配置
## 环境变量配置
项目的环境变量配置位于应用目录下的 `.env``.env.development``.env.production`
规则与 [Vite Env Variables and Modes](https://vitejs.dev/guide/env-and-mode.html) 一致。格式如下:
```bash
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
```
::: tip
- 只有以 `VITE_` 开头的变量会被嵌入到客户端侧的包中,你可以在项目代码中这样访问它们:
```ts
console.log(import.meta.env.VITE_PROT);
```
- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中.
:::
## 环境配置说明
::: code-group
```bash [.env]
# 应用标题
VITE_APP_TITLE=Vben Admin
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-antd
```
```bash [.env.development]
# 端口号
VITE_PORT=5555
# 资源公共路径,需要以 / 开头和结尾
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=true
# 是否打开 devtoolstrue 为打开false 为关闭
VITE_DEVTOOLS=true
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
```
```bash [.env.production]
# 资源公共路径,需要以 / 开头和结尾
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=gzip
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=hash
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
```
:::
## 生产环境动态配置
当在大仓根目录下,执行 `pnpm build`构建项目之后,会自动在对应的应用下生成 `dist/_app.config.js`文件并插入 `index.html`。
`_app.config.js` 是一个动态配置文件,可以在项目构建之后,根据不同的环境动态修改配置。内容如下:
```ts
window._VBEN_ADMIN_PRO_APP_CONF_ = {
VITE_GLOB_API_URL: 'https://mock-napi.vben.pro/api',
};
Object.freeze(window._VBEN_ADMIN_PRO_APP_CONF_);
Object.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {
configurable: false,
writable: false,
});
```
### 作用
`_app.config.js` 用于项目在打包后,需要动态修改配置的需求,如接口地址。不用重新进行打包,可在打包后修改 /`dist/_app.config.js` 内的变量,刷新即可更新代码内的局部变量。这里使用`js`文件,是为了确保配置文件加载顺序保持在前面。
### 使用
想要获取 `_app.config.js` 内的变量,需要使用`@vben/hooks`提供的 `useAppConfig`方法。
```ts
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
```
### 新增
新增一个可动态修改的配置项,只需要按照如下步骤即可:
- 首先在 `.env` 或者对应的开发环境配置文件内,新增需要可动态配置的变量,需要以 `VITE_GLOB_*` 开头的变量,如:
```bash
VITE_GLOB_OTHER_API_URL=https://mock-napi.vben.pro/other-api
```
- 在 `packages/types/global.d.ts`,新增对应的类型定义,如:
```ts
export interface VbenAdminProAppConfigRaw {
VITE_GLOB_API_URL: string;
VITE_GLOB_OTHER_API_URL: string; // [!code ++]
}
export interface ApplicationConfig {
apiURL: string;
otherApiURL: string; // [!code ++]
}
```
- 在 `packages/effects/hooks/src/use-app-config.ts` 中,新增对应的配置项,如:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
到这里,就可以在项目内使用 `useAppConfig`方法获取到新增的配置项了。
```ts
const { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
```
::: warning 注意
`useAppConfig`方法只能在应用内使用,不要耦合到包内部去使用。这里传入 `import.meta.env`和`import.meta.env.PROD`是为了避免这种情况,一个纯粹的包,应避免使用特定构建工具的变量。
:::
## 偏好设置
项目提供了非常丰富的偏好设置,用于动态配置项目的各种功能:
![](/guide/preferences.png)
如果你找不到文档说明,可以尝试自己配置好以后,点击`复制偏好设置`,覆盖项目默认即可。配置文件位于应用目录下的`preferences.ts`,在这里,你可以覆盖框架默认的配置,实现自定义配置。
```ts
import { useAppConfig } from '@vben/hooks';
import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides
});
```
### 框架默认配置
::: details 查看框架默认配置
```ts
const defaultPreferences: Preferences = {
app: {
accessMode: 'frontend',
authPageLayout: 'panel-right',
checkUpdatesInterval: 1,
colorGrayMode: false,
colorWeakMode: false,
compact: false,
contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
enableRefreshToken: false,
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'page',
name: 'Vben Admin',
preferencesButtonPosition: 'auto',
watermark: false,
zIndex: 200,
},
breadcrumb: {
enable: true,
hideOnlyOne: false,
showHome: false,
showIcon: true,
styleType: 'normal',
},
copyright: {
companyName: 'Vben',
companySiteLink: 'https://www.vben.pro',
date: '2024',
enable: true,
icp: '',
icpLink: '',
settingShow: true,
},
footer: {
enable: false,
fixed: false,
height: 32,
},
header: {
enable: true,
height: 50,
hidden: false,
menuAlign: 'start',
mode: 'fixed',
},
logo: {
enable: true,
fit: 'contain',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
// sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // 可选暗色主题logo
},
navigation: {
accordion: true,
split: true,
styleType: 'rounded',
},
shortcutKeys: {
enable: true,
globalLockScreen: true,
globalLogout: true,
globalPreferences: true,
globalSearch: true,
},
sidebar: {
autoActivateChild: false,
collapsed: false,
collapsedButton: true,
collapsedShowTitle: false,
collapseWidth: 60,
enable: true,
expandOnHover: true,
extraCollapse: false,
extraCollapsedWidth: 60,
fixedButton: true,
hidden: false,
mixedWidth: 80,
width: 224,
},
tabbar: {
draggable: true,
enable: true,
height: 38,
keepAlive: true,
maxCount: 0,
middleClickToClose: false,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
styleType: 'chrome',
wheelable: true,
},
theme: {
builtinType: 'default',
colorDestructive: 'hsl(348 100% 61%)',
colorPrimary: 'hsl(212 100% 45%)',
colorSuccess: 'hsl(144 57% 58%)',
colorWarning: 'hsl(42 84% 61%)',
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkSidebar: false,
},
transition: {
enable: true,
loading: true,
name: 'fade-slide',
progress: true,
},
widget: {
fullscreen: true,
globalSearch: true,
languageToggle: true,
lockScreen: true,
notification: true,
refresh: true,
sidebarToggle: true,
themeToggle: true,
},
};
```
:::
::: details 查看框架默认配置类型
```ts
interface AppPreferences {
/** 权限模式 */
accessMode: AccessModeType;
/** 登录注册页面布局 */
authPageLayout: AuthPageLayoutType;
/** 检查更新轮询时间 */
checkUpdatesInterval: number;
/** 是否开启灰色模式 */
colorGrayMode: boolean;
/** 是否开启色弱模式 */
colorWeakMode: boolean;
/** 是否开启紧凑模式 */
compact: boolean;
/** 是否开启内容紧凑模式 */
contentCompact: ContentCompactType;
/** 内容紧凑宽度 */
contentCompactWidth: number;
/** 内容内边距 */
contentPadding: number;
/** 内容底部内边距 */
contentPaddingBottom: number;
/** 内容左侧内边距 */
contentPaddingLeft: number;
/** 内容右侧内边距 */
contentPaddingRight: number;
/** 内容顶部内边距 */
contentPaddingTop: number;
// /** 应用默认头像 */
defaultAvatar: string;
/** 默认首页地址 */
defaultHomePath: string;
// /** 开启动态标题 */
dynamicTitle: boolean;
/** 是否开启检查更新 */
enableCheckUpdates: boolean;
/** 是否显示偏好设置 */
enablePreferences: boolean;
/**
* @zh_CN 是否开启refreshToken
*/
enableRefreshToken: boolean;
/** 是否移动端 */
isMobile: boolean;
/** 布局方式 */
layout: LayoutType;
/** 支持的语言 */
locale: SupportedLanguagesType;
/** 登录过期模式 */
loginExpiredMode: LoginExpiredModeType;
/** 应用名 */
name: string;
/** 偏好设置按钮位置 */
preferencesButtonPosition: PreferencesButtonPositionType;
/**
* @zh_CN 是否开启水印
*/
watermark: boolean;
/** z-index */
zIndex: number;
}
interface BreadcrumbPreferences {
/** 面包屑是否启用 */
enable: boolean;
/** 面包屑是否只有一个时隐藏 */
hideOnlyOne: boolean;
/** 面包屑首页图标是否可见 */
showHome: boolean;
/** 面包屑图标是否可见 */
showIcon: boolean;
/** 面包屑风格 */
styleType: BreadcrumbStyleType;
}
interface CopyrightPreferences {
/** 版权公司名 */
companyName: string;
/** 版权公司名链接 */
companySiteLink: string;
/** 版权日期 */
date: string;
/** 版权是否可见 */
enable: boolean;
/** 备案号 */
icp: string;
/** 备案号链接 */
icpLink: string;
/** 设置面板是否显示*/
settingShow?: boolean;
}
interface FooterPreferences {
/** 底栏是否可见 */
enable: boolean;
/** 底栏是否固定 */
fixed: boolean;
/** 底栏高度 */
height: number;
}
interface HeaderPreferences {
/** 顶栏是否启用 */
enable: boolean;
/** 顶栏高度 */
height: number;
/** 顶栏是否隐藏,css-隐藏 */
hidden: boolean;
/** 顶栏菜单位置 */
menuAlign: LayoutHeaderMenuAlignType;
/** header显示模式 */
mode: LayoutHeaderModeType;
}
interface LogoPreferences {
/** logo是否可见 */
enable: boolean;
/** logo图片适应方式 */
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
/** logo地址 */
source: string;
/** 暗色主题logo地址 (可选,若不设置则使用 source) */
sourceDark?: string;
}
interface NavigationPreferences {
/** 导航菜单手风琴模式 */
accordion: boolean;
/** 导航菜单是否切割,只在 layout=mixed-nav 生效 */
split: boolean;
/** 导航菜单风格 */
styleType: NavigationStyleType;
}
interface SidebarPreferences {
/** 点击目录时自动激活子菜单 */
autoActivateChild: boolean;
/** 侧边栏是否折叠 */
collapsed: boolean;
/** 侧边栏折叠按钮是否可见 */
collapsedButton: boolean;
/** 侧边栏折叠时是否显示title */
collapsedShowTitle: boolean;
/** 侧边栏折叠宽度 */
collapseWidth: number;
/** 侧边栏是否可见 */
enable: boolean;
/** 菜单自动展开状态 */
expandOnHover: boolean;
/** 侧边栏扩展区域是否折叠 */
extraCollapse: boolean;
/** 侧边栏扩展区域折叠宽度 */
extraCollapsedWidth: number;
/** 侧边栏固定按钮是否可见 */
fixedButton: boolean;
/** 侧边栏是否隐藏 - css */
hidden: boolean;
/** 混合侧边栏宽度 */
mixedWidth: number;
/** 侧边栏宽度 */
width: number;
}
interface ShortcutKeyPreferences {
/** 是否启用快捷键-全局 */
enable: boolean;
/** 是否启用全局锁屏快捷键 */
globalLockScreen: boolean;
/** 是否启用全局注销快捷键 */
globalLogout: boolean;
/** 是否启用全局偏好设置快捷键 */
globalPreferences: boolean;
/** 是否启用全局搜索快捷键 */
globalSearch: boolean;
}
interface TabbarPreferences {
/** 是否开启多标签页拖拽 */
draggable: boolean;
/** 是否开启多标签页 */
enable: boolean;
/** 标签页高度 */
height: number;
/** 开启标签页缓存功能 */
keepAlive: boolean;
/** 限制最大数量 */
maxCount: number;
/** 是否点击中键时关闭标签 */
middleClickToClose: boolean;
/** 是否持久化标签 */
persist: boolean;
/** 是否开启多标签页图标 */
showIcon: boolean;
/** 显示最大化按钮 */
showMaximize: boolean;
/** 显示更多按钮 */
showMore: boolean;
/** 标签页风格 */
styleType: TabsStyleType;
/** 是否开启鼠标滚轮响应 */
wheelable: boolean;
}
interface ThemePreferences {
/** 内置主题名 */
builtinType: BuiltinThemeType;
/** 错误色 */
colorDestructive: string;
/** 主题色 */
colorPrimary: string;
/** 成功色 */
colorSuccess: string;
/** 警告色 */
colorWarning: string;
/** 当前主题 */
mode: ThemeModeType;
/** 圆角 */
radius: string;
/** 是否开启半深色header只在theme='light'时生效) */
semiDarkHeader: boolean;
/** 是否开启半深色菜单只在theme='light'时生效) */
semiDarkSidebar: boolean;
}
interface TransitionPreferences {
/** 页面切换动画是否启用 */
enable: boolean;
// /** 是否开启页面加载loading */
loading: boolean;
/** 页面切换动画 */
name: PageTransitionType | string;
/** 是否开启页面加载进度动画 */
progress: boolean;
}
interface WidgetPreferences {
/** 是否启用全屏部件 */
fullscreen: boolean;
/** 是否启用全局搜索部件 */
globalSearch: boolean;
/** 是否启用语言切换部件 */
languageToggle: boolean;
/** 是否开启锁屏功能 */
lockScreen: boolean;
/** 是否显示通知部件 */
notification: boolean;
/** 显示刷新按钮 */
refresh: boolean;
/** 是否显示侧边栏显示/隐藏部件 */
sidebarToggle: boolean;
/** 是否显示主题切换部件 */
themeToggle: boolean;
}
interface Preferences {
/** 全局配置 */
app: AppPreferences;
/** 顶栏配置 */
breadcrumb: BreadcrumbPreferences;
/** 版权配置 */
copyright: CopyrightPreferences;
/** 底栏配置 */
footer: FooterPreferences;
/** 面包屑配置 */
header: HeaderPreferences;
/** logo配置 */
logo: LogoPreferences;
/** 导航配置 */
navigation: NavigationPreferences;
/** 快捷键配置 */
shortcutKeys: ShortcutKeyPreferences;
/** 侧边栏配置 */
sidebar: SidebarPreferences;
/** 标签页配置 */
tabbar: TabbarPreferences;
/** 主题配置 */
theme: ThemePreferences;
/** 动画配置 */
transition: TransitionPreferences;
/** 功能配置 */
widget: WidgetPreferences;
}
```
:::
::: warning 注意
- `overridesPreferences`方法只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置。
- 任何配置项都可以覆盖,只需要在`overridesPreferences`方法内覆盖即可,不要修改默认配置文件。
- 更改配置后请清空缓存,否则可能不生效。
:::

View File

@@ -0,0 +1,95 @@
---
outline: deep
---
# 精简版本
`5.0` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。
## 应用精简
首先,确认你需要的 `UI` 组件库版本,然后删除对应的应用,比如你选择使用 `Ant Design Vue`,那么你可以删除其他应用, 只需要删除下面两个文件夹即可:
```bash
apps/web-ele
apps/web-naive
```
::: tip
如果项目没有内置你需要的 `UI` 组件库应用,你可以直接全部删除其他应用。然后自行新建应用即可。
:::
## 演示代码精简
如果你不需要演示代码,你可以直接删除的`playground`文件夹。
## 文档精简
如果你不需要文档,你可以直接删除`docs`文件夹。
## Mock 服务精简
如果你不需要`Mock`服务,你可以直接删除`apps/backend-mock`文件夹。同时在你的应用下`.env.development`文件中删除`VITE_NITRO_MOCK`变量。
```bash
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=false
```
## 安装依赖
到这里,你已经完成了精简操作,接下来你可以安装依赖,并启动你的项目:
```bash
# 根目录下执行
pnpm install
```
## 命令调整
在精简后,你可能需要根据你的项目调整命令,在根目录下的`package.json`文件中,你可以调整`scripts`字段,移除你不需要的命令。
```json
{
"scripts": {
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build:docs": "pnpm run build --filter=@vben/docs",
"build:ele": "pnpm run build --filter=@vben/web-ele",
"build:naive": "pnpm run build --filter=@vben/web-naive",
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
"build:play": "pnpm run build --filter=@vben/playground",
"dev:antd": "pnpm -F @vben/web-antd run dev",
"dev:docs": "pnpm -F @vben/docs run dev",
"dev:ele": "pnpm -F @vben/web-ele run dev",
"dev:play": "pnpm -F @vben/playground run dev",
"dev:naive": "pnpm -F @vben/web-naive run dev"
}
}
```
## 其他
如果你想更进一步精简,你可以删除参考以下文件或者文件夹的作用,判断自己是否需要,不需要删除即可:
- `.changeset` 文件夹用于管理版本变更
- `.github` 文件夹用于存放 GitHub 的配置文件
- `.vscode` 文件夹用于存放 VSCode 的配置文件,如果你使用其他编辑器,可以删除
- `./scripts/deploy` 文件夹用于存放部署脚本如果你不需要docker部署可以删除
## 应用精简
当你确定了某个应用,你还可以进一步精简:
### 删除不需要的路由及页面
- 在应用的 `src/router/routes` 文件中,你可以删除不需要的路由。其中 `core` 文件夹内,如果只需要登录和忘记密码,你可以删除其他路由,如忘记密码、注册等。路由删除后,你可以删除对应的页面文件,在 `src/views/_core` 文件夹中。
- 在应用的 `src/router/routes` 文件中,你可以按需求删除不需要的路由,如`demos``vben` 目录等。路由删除后,你可以删除对应的页面文件,在 `src/views` 文件夹中。
### 删除不需要的组件
- 在应用的 `packages/effects/common-ui/src/ui` 文件夹中,你可以删除不需要的组件,如`about``dashboard` 目录等。删除之前请先确保你的路由中没有引用到这些组件。