fix: 一些基本功能
This commit is contained in:
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 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 } 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 };
|
||||
@@ -1,47 +0,0 @@
|
||||
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 };
|
||||
@@ -1,70 +0,0 @@
|
||||
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';
|
||||
@@ -1,4 +0,0 @@
|
||||
export const useVbenForm = () => {};
|
||||
export const z = {};
|
||||
export type VbenFormSchema = any;
|
||||
export type VbenFormProps = any;
|
||||
@@ -1,3 +0,0 @@
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
export const useVbenVxeGrid = () => {};
|
||||
@@ -1,30 +0,0 @@
|
||||
# 社区交流
|
||||
|
||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||
|
||||
- [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),主要使用者的交流群。
|
||||
- [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;"/>
|
||||
@@ -1,12 +0,0 @@
|
||||
# 定制开发
|
||||
|
||||
我们提供基于 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;"/>
|
||||
|
||||
我们会在第一时间回复您,定制费用根据需求而定。
|
||||
@@ -1,8 +0,0 @@
|
||||
# 技术支持
|
||||
|
||||
## 问题反馈
|
||||
|
||||
在使用项目的过程中,如果遇到问题,你可以先详细阅读本文档,未找到解决方案时,可以通过以下方式获取技术支持:
|
||||
|
||||
- 通过 [GitHub Issues](https://github.com/vbenjs/vue-vben-admin/issues)
|
||||
- 通过 [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
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` |
|
||||
|
||||
### Methods
|
||||
|
||||
以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| ------ | ------------ | ---------- |
|
||||
| start | 开始执行动画 | `()=>void` |
|
||||
| reset | 重置 | `()=>void` |
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
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`事件除外,内外都会触发。
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
// Drawer 为弹窗组件
|
||||
// drawerApi 为弹窗的方法
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 属性
|
||||
// 事件
|
||||
});
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
所有属性都可以传入 `useVbenDrawer` 的第一个参数中。
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||
| title | 标题 | `string\|slot` | - |
|
||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||
| description | 描述信息 | `string\|slot` | - |
|
||||
| isOpen | 弹窗打开状态 | `boolean` | `false` |
|
||||
| loading | 弹窗加载状态 | `boolean` | `false` |
|
||||
| closable | 显示关闭按钮 | `boolean` | `true` |
|
||||
| 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` |
|
||||
|
||||
::: info appendToMain
|
||||
|
||||
`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。
|
||||
|
||||
:::
|
||||
|
||||
### Event
|
||||
|
||||
以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| --- | --- | --- |
|
||||
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` |
|
||||
| onCancel | 点击取消按钮触发 | `()=>void` |
|
||||
| onConfirm | 点击确认按钮触发 | `()=>void` |
|
||||
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` |
|
||||
|
||||
### Slots
|
||||
|
||||
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
||||
|
||||
| 插槽名 | 描述 |
|
||||
| -------------- | ------------------- |
|
||||
| default | 默认插槽 - 弹窗内容 |
|
||||
| prepend-footer | 取消按钮左侧 |
|
||||
| append-footer | 取消按钮右侧 |
|
||||
|
||||
### modalApi
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| --- | --- | --- |
|
||||
| setState | 动态设置弹窗状态属性 | `setState(props) \| setState((prev)=>(props))` |
|
||||
| open | 打开弹窗 | `()=>void` |
|
||||
| close | 关闭弹窗 | `()=>void` |
|
||||
| setData | 设置共享数据 | `<T>(data:T)=>void` |
|
||||
| getData | 获取共享数据 | `<T>()=>T` |
|
||||
| useStore | 获取可响应式状态 | - |
|
||||
@@ -1,542 +0,0 @@
|
||||
---
|
||||
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';
|
||||
|
||||
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'
|
||||
| '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/) | - |
|
||||
|
||||
## Props
|
||||
|
||||
所有属性都可以传入 `useVbenForm` 的第一个参数中。
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| layout | 表单项布局 | `'horizontal' \| 'vertical'` | `horizontal` |
|
||||
| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
|
||||
| wrapperClass | 表单的布局,基于tailwindcss | `any` | - |
|
||||
| actionWrapperClass | 表单操作区域class | `any` | - |
|
||||
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => 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], string?][]` | - |
|
||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
||||
|
||||
### TS 类型说明
|
||||
|
||||
::: details ActionButtonOptions
|
||||
|
||||
```ts
|
||||
export interface ActionButtonOptions {
|
||||
/** 样式 */
|
||||
class?: ClassType;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 是否加载中 */
|
||||
loading?: boolean;
|
||||
/** 按钮大小 */
|
||||
size?: ButtonVariantSize;
|
||||
/** 按钮类型 */
|
||||
variant?: ButtonVariants;
|
||||
/** 是否显示 */
|
||||
show?: boolean;
|
||||
/** 按钮文本 */
|
||||
text?: string;
|
||||
/** 任意属性 */
|
||||
[key: string]: any;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: details FormCommonConfig
|
||||
|
||||
```ts
|
||||
export interface FormCommonConfig {
|
||||
/**
|
||||
* 所有表单项的props
|
||||
*/
|
||||
componentProps?: ComponentProps;
|
||||
/**
|
||||
* 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。
|
||||
* 在有设置校验规则的场景下,建议不要将其设置为true
|
||||
* 默认为false。但用作表格的搜索表单时,默认为true
|
||||
* @default false
|
||||
*/
|
||||
compact?: boolean;
|
||||
/**
|
||||
* 所有表单项的控件样式
|
||||
*/
|
||||
controlClass?: string;
|
||||
/**
|
||||
* 在表单项的Label后显示一个冒号
|
||||
*/
|
||||
colon?: boolean;
|
||||
/**
|
||||
* 所有表单项的禁用状态
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 所有表单项的控件样式
|
||||
* @default {}
|
||||
*/
|
||||
formFieldProps?: Partial<typeof Field>;
|
||||
/**
|
||||
* 所有表单项的栅格布局
|
||||
* @default ""
|
||||
*/
|
||||
formItemClass?: string;
|
||||
/**
|
||||
* 隐藏所有表单项label
|
||||
* @default false
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
* 是否隐藏必填标记
|
||||
* @default false
|
||||
*/
|
||||
hideRequiredMark?: boolean;
|
||||
/**
|
||||
* 所有表单项的label样式
|
||||
* @default ""
|
||||
*/
|
||||
labelClass?: string;
|
||||
/**
|
||||
* 所有表单项的label宽度
|
||||
*/
|
||||
labelWidth?: number;
|
||||
/**
|
||||
* 所有表单项的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?: string;
|
||||
/** 表单项 */
|
||||
label?: string;
|
||||
/** 自定义组件内部渲染 */
|
||||
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(""))
|
||||
}
|
||||
|
||||
// 复杂校验
|
||||
{
|
||||
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`的值将会被忽略。
|
||||
|
||||
:::
|
||||
@@ -1,150 +0,0 @@
|
||||
---
|
||||
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" />
|
||||
|
||||
::: info 注意
|
||||
|
||||
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
// Modal 为弹窗组件
|
||||
// modalApi 为弹窗的方法
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 属性
|
||||
// 事件
|
||||
});
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
所有属性都可以传入 `useVbenModal` 的第一个参数中。
|
||||
|
||||
| 属性名 | 描述 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `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` |
|
||||
|
||||
::: info appendToMain
|
||||
|
||||
`appendToMain`可以指定将弹窗挂载到内容区域,打开这种弹窗时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,弹窗会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。
|
||||
|
||||
:::
|
||||
|
||||
### Event
|
||||
|
||||
以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。
|
||||
|
||||
| 事件名 | 描述 | 类型 | 版本号 |
|
||||
| --- | --- | --- | --- |
|
||||
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | |
|
||||
| onCancel | 点击取消按钮触发 | `()=>void` | |
|
||||
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.4.3 |
|
||||
| onConfirm | 点击确认按钮触发 | `()=>void` | |
|
||||
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | |
|
||||
| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.4.3 |
|
||||
|
||||
### Slots
|
||||
|
||||
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
||||
|
||||
| 插槽名 | 描述 |
|
||||
| -------------- | ------------------- |
|
||||
| default | 默认插槽 - 弹窗内容 |
|
||||
| prepend-footer | 取消按钮左侧 |
|
||||
| append-footer | 取消按钮右侧 |
|
||||
|
||||
### modalApi
|
||||
|
||||
| 事件名 | 描述 | 类型 |
|
||||
| --- | --- | --- |
|
||||
| setState | 动态设置弹窗状态属性 | `setState(props) \| setState((prev)=>(props))` |
|
||||
| open | 打开弹窗 | `()=>void` |
|
||||
| close | 关闭弹窗 | `()=>void` |
|
||||
| setData | 设置共享数据 | `<T>(data:T)=>void` |
|
||||
| getData | 获取共享数据 | `<T>()=>T` |
|
||||
| useStore | 获取可响应式状态 | - |
|
||||
@@ -1,242 +0,0 @@
|
||||
---
|
||||
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`来让表格在工具栏区域显示一个搜索表单控制按钮。
|
||||
|
||||
<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` |
|
||||
@@ -1,15 +0,0 @@
|
||||
# 介绍
|
||||
|
||||
::: info README
|
||||
|
||||
该文档介绍的是框架组件的使用方法、属性、事件等。如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
|
||||
|
||||
:::
|
||||
|
||||
## 布局组件
|
||||
|
||||
布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。
|
||||
|
||||
## 通用组件
|
||||
|
||||
通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
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 | 页面底部 |
|
||||
@@ -1,6 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenCountToAnimator } from '@vben/common-ui';
|
||||
</script>
|
||||
<template>
|
||||
<VbenCountToAnimator :duration="3000" :end-val="30000" :start-val="1" />
|
||||
</template>
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenCountToAnimator } from '@vben/common-ui';
|
||||
</script>
|
||||
<template>
|
||||
<VbenCountToAnimator
|
||||
:duration="3000"
|
||||
:end-val="2000000"
|
||||
:start-val="1"
|
||||
prefix="$"
|
||||
separator="/"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,45 +0,0 @@
|
||||
<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="even:bg-heavy bg-muted flex-center h-[220px] w-full"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
<template #prepend-footer>
|
||||
<VbenButton type="link" @click="handleUpdate(6)">
|
||||
点击更新数据
|
||||
</VbenButton>
|
||||
</template>
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,11 +0,0 @@
|
||||
<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>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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>
|
||||
@@ -1,30 +0,0 @@
|
||||
<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: '外部动态标题' });
|
||||
drawerApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
<VbenButton class="ml-2" type="primary" @click="handleUpdateTitle">
|
||||
从外部修改标题并打开
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
const [Drawer] = useVbenDrawer();
|
||||
</script>
|
||||
<template>
|
||||
<Drawer title="组件抽离示例"> extra drawer content </Drawer>
|
||||
</template>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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',
|
||||
});
|
||||
drawerApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Drawer />
|
||||
|
||||
<VbenButton @click="open">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,236 +0,0 @@
|
||||
<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>
|
||||
@@ -1,231 +0,0 @@
|
||||
<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>
|
||||
@@ -1,68 +0,0 @@
|
||||
<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>
|
||||
@@ -1,168 +0,0 @@
|
||||
<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>
|
||||
@@ -1,94 +0,0 @@
|
||||
<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>
|
||||
@@ -1,189 +0,0 @@
|
||||
<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在同一行
|
||||
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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,45 +0,0 @@
|
||||
<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="even:bg-heavy bg-muted flex-center h-[220px] w-full"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
<template #prepend-footer>
|
||||
<VbenButton type="link" @click="handleUpdate(6)">
|
||||
点击更新数据
|
||||
</VbenButton>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,11 +0,0 @@
|
||||
<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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,10 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
const [Modal] = useVbenModal({
|
||||
draggable: true,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="拖拽示例"> modal content </Modal>
|
||||
</template>
|
||||
@@ -1,30 +0,0 @@
|
||||
<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: '外部动态标题' });
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
<VbenButton class="ml-2" type="primary" @click="handleUpdateTitle">
|
||||
从外部修改标题并打开
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,38 +0,0 @@
|
||||
<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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
const [Modal] = useVbenModal();
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="组件抽离示例"> extra modal content </Modal>
|
||||
</template>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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',
|
||||
});
|
||||
modalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal />
|
||||
|
||||
<VbenButton @click="openModal">Open</VbenButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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>
|
||||
@@ -1,85 +0,0 @@
|
||||
<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>
|
||||
@@ -1,105 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { Button, Image, Switch, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
|
||||
import { getExampleTableApi } from '../mock-api';
|
||||
|
||||
interface RowType {
|
||||
category: string;
|
||||
color: string;
|
||||
id: string;
|
||||
imageUrl: string;
|
||||
open: boolean;
|
||||
price: string;
|
||||
productName: string;
|
||||
releaseDate: string;
|
||||
status: 'error' | 'success' | 'warning';
|
||||
}
|
||||
|
||||
const gridOptions: VxeGridProps<RowType> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: 'name',
|
||||
},
|
||||
columns: [
|
||||
{ title: '序号', type: 'seq', width: 50 },
|
||||
{ field: 'category', title: 'Category', width: 100 },
|
||||
{
|
||||
field: 'imageUrl',
|
||||
slots: { default: 'image-url' },
|
||||
title: 'Image',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
cellRender: { name: 'CellImage' },
|
||||
field: 'imageUrl2',
|
||||
title: 'Render Image',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
field: 'open',
|
||||
slots: { default: 'open' },
|
||||
title: 'Open',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
slots: { default: 'status' },
|
||||
title: 'Status',
|
||||
width: 100,
|
||||
},
|
||||
{ field: 'color', title: 'Color', width: 100 },
|
||||
{ field: 'productName', title: 'Product Name', width: 200 },
|
||||
{ field: 'price', title: 'Price', width: 100 },
|
||||
{
|
||||
field: 'releaseDate',
|
||||
formatter: 'formatDateTime',
|
||||
title: 'Date',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
cellRender: { name: 'CellLink', props: { text: '编辑' } },
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
title: '操作',
|
||||
width: 120,
|
||||
},
|
||||
],
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
return await getExampleTableApi({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [Grid] = useVbenVxeGrid({ gridOptions });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-raw w-full">
|
||||
<Grid>
|
||||
<template #image-url="{ row }">
|
||||
<Image :src="row.imageUrl" height="30" width="30" />
|
||||
</template>
|
||||
<template #open="{ row }">
|
||||
<Switch v-model:checked="row.open" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="row.color">{{ row.status }}</Tag>
|
||||
</template>
|
||||
<template #action>
|
||||
<Button type="link">编辑</Button>
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,55 +0,0 @@
|
||||
<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>
|
||||
@@ -1,92 +0,0 @@
|
||||
<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>
|
||||
@@ -1,67 +0,0 @@
|
||||
<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>
|
||||
@@ -1,127 +0,0 @@
|
||||
<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>
|
||||
@@ -1,36 +0,0 @@
|
||||
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 };
|
||||
@@ -1,112 +0,0 @@
|
||||
<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>
|
||||
@@ -1,384 +0,0 @@
|
||||
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,
|
||||
},
|
||||
];
|
||||
@@ -1,80 +0,0 @@
|
||||
<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>
|
||||
@@ -1,64 +0,0 @@
|
||||
<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>
|
||||
@@ -1,243 +0,0 @@
|
||||
# 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,70 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,188 +0,0 @@
|
||||
# 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 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 husky
|
||||
"prepare": "is-ci || husky",
|
||||
// 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
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
# 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.
|
||||
|
||||
:::
|
||||
@@ -1,78 +0,0 @@
|
||||
# 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>
|
||||
```
|
||||
@@ -1,606 +0,0 @@
|
||||
---
|
||||
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[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
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[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
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[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
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>
|
||||
```
|
||||
@@ -1,356 +0,0 @@
|
||||
# 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(user: UserInfo) {
|
||||
return requestClient.delete<boolean>(`/user/${user.id}`, user);
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
@@ -1,515 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 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 ++]
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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
|
||||
*/
|
||||
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',
|
||||
defaultAvatar:
|
||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
enableRefreshToken: false,
|
||||
isMobile: false,
|
||||
layout: 'sidebar-nav',
|
||||
locale: 'zh-CN',
|
||||
loginExpiredMode: 'modal',
|
||||
name: 'Vben Admin',
|
||||
preferencesButtonPosition: 'auto',
|
||||
watermark: false,
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
fixed: false,
|
||||
},
|
||||
header: {
|
||||
enable: true,
|
||||
hidden: false,
|
||||
mode: 'fixed',
|
||||
},
|
||||
logo: {
|
||||
enable: true,
|
||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
},
|
||||
navigation: {
|
||||
accordion: true,
|
||||
split: true,
|
||||
styleType: 'rounded',
|
||||
},
|
||||
shortcutKeys: {
|
||||
enable: true,
|
||||
globalLockScreen: true,
|
||||
globalLogout: true,
|
||||
globalPreferences: true,
|
||||
globalSearch: true,
|
||||
},
|
||||
sidebar: {
|
||||
autoActivateChild: false,
|
||||
collapsed: false,
|
||||
collapsedShowTitle: false,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
hidden: false,
|
||||
width: 230,
|
||||
},
|
||||
tabbar: {
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
keepAlive: true,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
},
|
||||
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: true,
|
||||
},
|
||||
transition: {
|
||||
enable: true,
|
||||
loading: true,
|
||||
name: 'fade-slide',
|
||||
progress: true,
|
||||
},
|
||||
widget: {
|
||||
fullscreen: true,
|
||||
globalSearch: true,
|
||||
languageToggle: true,
|
||||
lockScreen: true,
|
||||
notification: true,
|
||||
sidebarToggle: true,
|
||||
themeToggle: true,
|
||||
refresh: 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;
|
||||
// /** Default application avatar */
|
||||
defaultAvatar: 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
interface FooterPreferences {
|
||||
/** Whether the footer is visible */
|
||||
enable: boolean;
|
||||
/** Whether the footer is fixed */
|
||||
fixed: boolean;
|
||||
}
|
||||
|
||||
interface HeaderPreferences {
|
||||
/** Whether the header is enabled */
|
||||
enable: boolean;
|
||||
/** Whether the header is hidden, css-hidden */
|
||||
hidden: boolean;
|
||||
/** Header display mode */
|
||||
mode: LayoutHeaderModeType;
|
||||
}
|
||||
|
||||
interface LogoPreferences {
|
||||
/** Whether the logo is visible */
|
||||
enable: boolean;
|
||||
/** Logo URL */
|
||||
source: 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 {
|
||||
/** Whether the sidebar is collapsed */
|
||||
collapsed: boolean;
|
||||
/** Whether to show title when sidebar is collapsed */
|
||||
collapsedShowTitle: boolean;
|
||||
/** Whether the sidebar is visible */
|
||||
enable: boolean;
|
||||
/** Menu auto-expand state */
|
||||
expandOnHover: boolean;
|
||||
/** Whether the sidebar extension area is collapsed */
|
||||
extraCollapse: boolean;
|
||||
/** Whether the sidebar is hidden - css */
|
||||
hidden: boolean;
|
||||
/** 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;
|
||||
/** 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;
|
||||
}
|
||||
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.
|
||||
|
||||
:::
|
||||
@@ -1,106 +0,0 @@
|
||||
# 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).
|
||||
@@ -1,48 +0,0 @@
|
||||
# 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
# 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 +0,0 @@
|
||||
# Layout
|
||||
@@ -1,44 +0,0 @@
|
||||
# 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>
|
||||
```
|
||||
@@ -1,227 +0,0 @@
|
||||
# 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,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1,119 +0,0 @@
|
||||
# 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.
|
||||
|
||||
:::
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,3 +0,0 @@
|
||||
# CHANGE LOG
|
||||
|
||||
TODO
|
||||
@@ -1,95 +0,0 @@
|
||||
---
|
||||
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
|
||||
corepack enable
|
||||
|
||||
# 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.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Roadmap
|
||||
|
||||
TODO:
|
||||
@@ -1,67 +0,0 @@
|
||||
# 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. Here’s 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 doesn’t 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 don’t need demo code, you can simply delete the `playground` folder
|
||||
|
||||
## Documentation Slimming
|
||||
|
||||
If you don’t need documentation, you can delete the `docs` folder.
|
||||
|
||||
## Remove Mock Service
|
||||
|
||||
If you don’t 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 you’ve 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 don’t 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: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"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,49 +0,0 @@
|
||||
# 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.
|
||||
|
||||
:::
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,159 +0,0 @@
|
||||
# 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 is necessary to develop the habit of executing `pnpm install` every time you `git pull` the code, as new dependency packages are often added. The project has already configured automatic execution of `pnpm install` in `.husky/git-merge`, 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;`
|
||||
@@ -1,54 +0,0 @@
|
||||
# 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.
|
||||
|
||||
:::
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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>
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,165 +0,0 @@
|
||||
# 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
|
||||
- [Lint Staged](https://github.com/lint-staged/lint-staged) for running code verification before git commits
|
||||
- [Cspell](https://cspell.org/) for checking spelling errors
|
||||
|
||||
## 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.
|
||||
|
||||
### husky
|
||||
|
||||
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where husky comes in.
|
||||
|
||||
The most effective solution is to perform Lint checks locally before committing. A common practice is to use husky or pre-commit to perform a Lint check before local submission.
|
||||
|
||||
The project defines corresponding hooks inside `.husky`.
|
||||
|
||||
#### How to Disable Husky
|
||||
|
||||
If you want to disable Husky, simply delete the .husky directory.
|
||||
|
||||
### lint-staged
|
||||
|
||||
Used for automatically fixing style issues of committed files. Its configuration file is `.lintstagedrc.mjs`, which can be modified according to project needs.
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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.
|
||||
|
||||
:::
|
||||
@@ -1,33 +0,0 @@
|
||||
# Unit Testing
|
||||
|
||||
The project incorporates [Vitest](https://vitest.dev/) as the unit testing tool. Vitest is a test runner based on Vite, offering a simple API for writing test cases.
|
||||
|
||||
## Writing Test Cases
|
||||
|
||||
Within the project, we follow the convention of naming test files with a `.test.ts` suffix or placing them inside a `__tests__` directory. For example, if you create a `utils.ts` file, then you would create a corresponding `utils.spec.ts` file in the same directory,
|
||||
|
||||
```ts
|
||||
// utils.test.ts
|
||||
import { expect, test } from 'vitest';
|
||||
import { sum } from './sum';
|
||||
|
||||
test('adds 1 + 2 to equal 3', () => {
|
||||
expect(sum(1, 2)).toBe(3);
|
||||
});
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run the tests, execute the following command at the root of the monorepo:
|
||||
|
||||
```bash
|
||||
pnpm test:unit
|
||||
```
|
||||
|
||||
## Existing Unit Tests
|
||||
|
||||
There are already some unit test cases in the project. You can search for files ending with .test.ts to view them. When you make changes to related code, you can run the unit tests to ensure the correctness of your code. It is recommended to maintain the coverage of unit tests during the development process and to run unit tests as part of the CI/CD process to ensure tests pass before deploying the project.
|
||||
|
||||
Existing unit test status:
|
||||
|
||||

|
||||
@@ -1,33 +0,0 @@
|
||||
# Vite Config
|
||||
|
||||
The project encapsulates a layer of Vite configuration and integrates some plugins for easy reuse across multiple packages and applications. The usage is as follows:
|
||||
|
||||
## Application
|
||||
|
||||
```ts
|
||||
// vite.config.mts
|
||||
import { defineConfig } from '@vben/vite-config';
|
||||
|
||||
export default defineConfig(async () => {
|
||||
return {
|
||||
application: {},
|
||||
// Vite configuration, override according to the official Vite documentation
|
||||
vite: {},
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Package
|
||||
|
||||
```ts
|
||||
// vite.config.mts
|
||||
import { defineConfig } from '@vben/vite-config';
|
||||
|
||||
export default defineConfig(async () => {
|
||||
return {
|
||||
library: {},
|
||||
// Vite configuration, override according to the official Vite documentation
|
||||
vite: {},
|
||||
};
|
||||
});
|
||||
```
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
sidebar: false
|
||||
|
||||
hero:
|
||||
name: Vben Admin
|
||||
text: Enterprise-Level Management System Framework
|
||||
tagline: Fully Upgraded, Ready to Use, Simple and Efficient
|
||||
image:
|
||||
src: https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp
|
||||
alt: Vben Admin
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started ->
|
||||
link: /en/guide/introduction/vben
|
||||
- theme: alt
|
||||
text: Live Preview
|
||||
link: https://www.vben.pro
|
||||
- theme: alt
|
||||
text: View on GitHub
|
||||
link: https://github.com/vbenjs/vue-vben-admin
|
||||
|
||||
features:
|
||||
- icon: 🚀
|
||||
title: Latest Technology Stack
|
||||
details: Based on the latest technology stack, including Vue3, Pinia, Vue Router, TypeScript, etc.
|
||||
link: /en/guide/introduction/quick-start
|
||||
linkText: Get Started
|
||||
- icon: 🦄
|
||||
title: Rich Configurations
|
||||
details: An enterprise-level frontend solution for middle and back-end systems, offering a wealth of components, templates, and various preference settings.
|
||||
link: /en/guide/essentials/settings
|
||||
linkText: Configuration Documentation
|
||||
- icon: 🎨
|
||||
title: Theme Customization
|
||||
details: Easily switch between various themes through simple configurations, catering to personalized needs.
|
||||
link: /en/guide/in-depth/theme
|
||||
linkText: Theme Documentation
|
||||
- icon: 🌐
|
||||
title: Internationalization
|
||||
details: Built-in internationalization support with multiple languages to meet global needs.
|
||||
link: /en/guide/in-depth/locale
|
||||
linkText: Internationalization Documentation
|
||||
- icon: 🔐
|
||||
title: Access Control
|
||||
details: Built-in access control solutions supporting various permission management methods to meet different access requirements.
|
||||
link: /en/guide/in-depth/access
|
||||
linkText: Access Documentation
|
||||
- title: Vite
|
||||
icon:
|
||||
src: /logos/vite.svg
|
||||
details: Modern frontend build tool with fast cold start and instant hot updates.
|
||||
link: https://vitejs.dev/
|
||||
linkText: Official Site
|
||||
- title: Shadcn UI
|
||||
icon:
|
||||
src: /logos/shadcn-ui.svg
|
||||
details: Core built on Shadcn UI + Tailwindcss, with business support for any UI framework.
|
||||
link: https://www.shadcn-vue.com/
|
||||
linkText: Official Site
|
||||
- title: Turbo Repo
|
||||
icon:
|
||||
src: /logos/turborepo.svg
|
||||
details: Standardized monorepo architecture using pnpm + monorepo + turbo for enterprise-level development standards.
|
||||
link: https://turbo.build/
|
||||
linkText: Official Site
|
||||
- title: Nitro Mock Server
|
||||
icon:
|
||||
src: /logos/nitro.svg
|
||||
details: Built-in Nitro Mock service makes your mock service more powerful.
|
||||
link: https://nitro.unjs.io/
|
||||
linkText: Official Site
|
||||
---
|
||||
|
||||
<VbenContributors />
|
||||
@@ -1,27 +0,0 @@
|
||||
# 友情链接
|
||||
|
||||
如果您的网站是与 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;"/>
|
||||
|
||||
### 提供资料
|
||||
|
||||
提供您的网站名称、链接、描述、LOGO(可选)等信息,我们会在第一时间添加您的网站。
|
||||
|
||||
### 友情链接
|
||||
|
||||
- 在您的网站上添加我们的友情链接,链接如下:
|
||||
|
||||
- 名称:Vben Admin
|
||||
- 链接:https://www.vben.pro
|
||||
- 描述:Vben Admin 企业级开箱即用的中后台前端解决方案
|
||||
- Logo:https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp
|
||||
|
||||
我们将定期的检查友情链接,如果发现您的网站已经删除了我们的友情链接以及链接地址是否正确。
|
||||
@@ -1,243 +0,0 @@
|
||||
# 构建与部署
|
||||
|
||||
::: tip 前言
|
||||
|
||||
由于是展示项目,所以打包后相对较大,如果项目中没有用到的插件,可以删除对应的文件或者路由,不引用即可,没有引用就不会打包。
|
||||
|
||||
:::
|
||||
|
||||
## 构建
|
||||
|
||||
项目开发完成之后,执行以下命令进行构建:
|
||||
|
||||
**注意:** 请在项目根目录下执行以下命令
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
构建打包成功之后,会在根目录生成对应的应用下的 `dist` 文件夹,里面就是构建打包好的文件,例如: `apps/web-antd/dist/`
|
||||
|
||||
## 预览
|
||||
|
||||
发布之前可以在本地进行预览,有多种方式,这里介绍两种:
|
||||
|
||||
- 使用项目自定的命令进行预览(推荐)
|
||||
|
||||
**注意:** 请在项目根目录下执行以下命令
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
等待构建成功后,访问 `http://localhost:4173` 即可查看效果。
|
||||
|
||||
- 本地服务器预览
|
||||
|
||||
可以在电脑全局安装 `serve` 服务,如 `live-server`,
|
||||
|
||||
```bash
|
||||
npm i -g live-server
|
||||
```
|
||||
|
||||
然后在 `dist` 目录下执行 `live-server` 命令,即可在本地查看效果。
|
||||
|
||||
```bash
|
||||
cd apps/web-antd/dist
|
||||
# 本地预览,默认端口8080
|
||||
live-server
|
||||
# 指定端口
|
||||
live-server --port 9000
|
||||
```
|
||||
|
||||
## 压缩
|
||||
|
||||
### 开启 `gzip` 压缩
|
||||
|
||||
需要在打包的时候更改`.env.production`配置:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=gzip
|
||||
```
|
||||
|
||||
### 开启 `brotli` 压缩
|
||||
|
||||
需要在打包的时候更改`.env.production`配置:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=brotli
|
||||
```
|
||||
|
||||
### 同时开启 `gzip` 和 `brotli` 压缩
|
||||
|
||||
需要在打包的时候更改`.env.production`配置:
|
||||
|
||||
```bash
|
||||
VITE_COMPRESS=gzip,brotli
|
||||
```
|
||||
|
||||
::: tip 提示
|
||||
|
||||
`gzip` 和 `brotli` 都需要安装特定模块才能使用。
|
||||
|
||||
:::
|
||||
|
||||
::: details gzip 与 brotli 在 nginx 内的配置
|
||||
|
||||
```bash
|
||||
http {
|
||||
# 开启gzip
|
||||
gzip on;
|
||||
# 开启gzip_static
|
||||
# gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询
|
||||
# 只有这个开启,vue文件打包的.gz文件才会有效果,否则不需要开启gzip进行打包
|
||||
gzip_static on;
|
||||
gzip_proxied any;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 16k;
|
||||
#如果nginx中使用了多层代理 必须设置这个才可以开启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]\.";
|
||||
|
||||
# 开启 brotli压缩
|
||||
# 需要安装对应的nginx模块,具体安装方式可以自行查询
|
||||
# 可以与gzip共存不会冲突
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 构建分析
|
||||
|
||||
如果你的构建文件很大,可以通过项目内置 [rollup-plugin-analyzer](https://github.com/doesdev/rollup-plugin-analyzer) 插件进行代码体积分析,从而优化你的代码。只需要在`根目录`下执行以下命令:
|
||||
|
||||
```bash
|
||||
pnpm run build:analyze
|
||||
```
|
||||
|
||||
运行之后,在自动打开的页面可以看到具体的体积分布,以分析哪些依赖有问题。
|
||||
|
||||

|
||||
|
||||
## 部署
|
||||
|
||||
简单的部署只需要将最终生成的静态文件,dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。
|
||||
|
||||
例如上传到 nginx 服务器,可以将 dist 文件夹下的文件上传到服务器的 `/srv/www/project/index.html` 目录下,然后访问配置好的域名即可。
|
||||
|
||||
```bash
|
||||
# nginx配置
|
||||
location / {
|
||||
# 不缓存html,防止程序更新后缓存继续生效
|
||||
if ($request_filename ~* .*\.(?:htm|html)$) {
|
||||
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
|
||||
access_log on;
|
||||
}
|
||||
# 这里是vue打包文件dist内的文件的存放路径
|
||||
root /srv/www/project/;
|
||||
index index.html index.htm;
|
||||
}
|
||||
```
|
||||
|
||||
部署时可能会发现资源路径不对,只需要修改`.env.production`文件即可。
|
||||
|
||||
```bash
|
||||
# 根据自己路径来配置更改
|
||||
# 注意需要以 / 开头和结尾
|
||||
VITE_BASE=/
|
||||
VITE_BASE=/xxx/
|
||||
```
|
||||
|
||||
### 前端路由与服务端的结合
|
||||
|
||||
项目前端路由使用的是 vue-router,所以你可以选择两种方式:history 和 hash。
|
||||
|
||||
- `hash` 默认会在 url 后面拼接`#`
|
||||
- `history` 则不会,不过 `history` 需要服务器配合
|
||||
|
||||
可在 `.env.production` 内进行 mode 修改
|
||||
|
||||
```bash
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
```
|
||||
|
||||
### history 路由模式下服务端配置
|
||||
|
||||
开启 `history` 模式需要服务器配置,更多的服务器配置详情可以看 [history-mode](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode)
|
||||
|
||||
这里以 `nginx` 配置为例:
|
||||
|
||||
#### 部署到根目录
|
||||
|
||||
```bash {5}
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
# 用于配合 History 使用
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 部署到非根目录
|
||||
|
||||
- 首先需要在打包的时候更改`.env.production`配置:
|
||||
|
||||
```bash
|
||||
VITE_BASE = /sub/
|
||||
```
|
||||
|
||||
- 然后在 nginx 配置文件中配置
|
||||
|
||||
```bash {8}
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location /sub/ {
|
||||
# 这里是vue打包文件dist内的文件的存放路径
|
||||
alias /srv/www/project/;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /sub/index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 跨域处理
|
||||
|
||||
使用 nginx 处理项目部署后的跨域问题
|
||||
|
||||
1. 配置前端项目接口地址,在项目目录下的``.env.production`文件中配置:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=/api
|
||||
```
|
||||
|
||||
2. 在 nginx 配置请求转发到后台
|
||||
|
||||
```bash {10-11}
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
# 接口代理,用于解决跨域问题
|
||||
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;
|
||||
# 后台接口地址
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,70 +0,0 @@
|
||||
# 基础概念
|
||||
|
||||
新版本中,整体工程进行了重构,现在我们将会介绍一些基础概念,以便于你更好的理解整个文档。请务必仔细阅读这一部分。
|
||||
|
||||
## 大仓
|
||||
|
||||
大仓指的是整个项目的仓库,包含了所有的代码、包、应用、规范、文档、配置等,也就是一整个 `Monorepo` 目录的所有内容。
|
||||
|
||||
## 应用
|
||||
|
||||
应用指的是一个完整的项目,一个项目可以包含多个应用,这些项目可以复用大仓内的代码、包、规范等。应用都被放置在 `apps` 目录下。每个应用都是独立的,可以单独运行、构建、测试、部署,可以引入不同的组件库等等。
|
||||
|
||||
::: tip
|
||||
|
||||
应用不限于前端应用,也可以是后端应用、移动端应用等,例如 `apps/backend-mock`就是一个后端服务。
|
||||
|
||||
:::
|
||||
|
||||
## 包
|
||||
|
||||
包指的是一个独立的模块,可以是一个组件、一个工具、一个库等。包可以被多个应用引用,也可以被其他包引用。包都被放置在 `packages` 目录下。
|
||||
|
||||
对于这些包,你可以把它看作是一个独立的 `npm` 包,使用方式与 `npm` 包一样。
|
||||
|
||||
### 包引入
|
||||
|
||||
在 `package.json` 中引入包:
|
||||
|
||||
```json {3}
|
||||
{
|
||||
"dependencies": {
|
||||
"@vben/utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 包使用
|
||||
|
||||
在代码中引入包:
|
||||
|
||||
```ts
|
||||
import { isString } from '@vben/utils';
|
||||
```
|
||||
|
||||
## 别名
|
||||
|
||||
在项目中,你可以看到一些 `#` 开头的路径,例如: `#/api`、`#/views`, 这些路径都是别名,用于快速定位到某个目录。它不是通过 `vite` 的 `alias` 实现的,而是通过 `Node.js` 本身的 [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) 原理。只需要在 `package.json` 中配置 `imports` 字段即可。
|
||||
|
||||
```json {3}
|
||||
{
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
为了 IDE 能够识别这些别名,我们还需要在`tsconfig.json`内配置:
|
||||
|
||||
```json {5}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"#/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样,你就可以在代码中使用别名了。
|
||||
@@ -1,188 +0,0 @@
|
||||
# 本地开发 {#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",
|
||||
// 单独构建 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",
|
||||
// husky的安装
|
||||
"prepare": "is-ci || husky",
|
||||
// 预览应用
|
||||
"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
|
||||
```
|
||||
|
||||
## 公共静态资源
|
||||
|
||||
项目中需要使用到的公共静态资源,如:图片、静态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。
|
||||
|
||||

|
||||
|
||||
## 本地运行文档
|
||||
|
||||
如需本地运行文档,并进行调整,可以执行以下命令:
|
||||
|
||||
```bash
|
||||
pnpm dev:docs
|
||||
```
|
||||
|
||||
## 问题解决
|
||||
|
||||
如果你在使用过程中遇到依赖相关的问题,可以尝试以下重新安装依赖:
|
||||
|
||||
```bash
|
||||
# 请在项目根目录下执行
|
||||
# 该命令会删除整个仓库所有的 node_modules、yarn.lock、package.lock.json后
|
||||
# 再进行依赖重新安装(安装速度会明显变慢)。
|
||||
pnpm reinstall
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
# 外部模块
|
||||
|
||||
除了项目默认引入的外部模块,有时我们还需要引入其他外部模块。我们以 [ant-design-vue](https://antdv.com/components/overview) 为例:
|
||||
|
||||
## 安装依赖
|
||||
|
||||
::: tip 安装依赖到指定包
|
||||
|
||||
- 由于项目采用了 [pnpm](https://pnpm.io/) 作为包管理工具,所以我们需要使用 `pnpm` 命令来安装依赖。
|
||||
- 通过采用了 Monorepo 模块来管理项目,所以我们需要在指定包下安装依赖。安装依赖前请确保已经进入到指定包目录下。
|
||||
|
||||
:::
|
||||
|
||||
```bash
|
||||
# cd /path/to/your/package
|
||||
pnpm add ant-design-vue
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
### 全局引入
|
||||
|
||||
```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');
|
||||
```
|
||||
|
||||
#### 使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-button>text</a-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 局部引入
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { Button } from 'ant-design-vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button>text</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
::: warning 注意
|
||||
|
||||
- 如果组件有依赖样式,则需要再引入样式文件
|
||||
|
||||
:::
|
||||
@@ -1,78 +0,0 @@
|
||||
# 图标
|
||||
|
||||
::: tip 关于图标的管理
|
||||
|
||||
- 项目的图标主要由`@vben/icons`包提供,建议统一在该包内部管理,以便于统一管理和维护。
|
||||
- 如果你使用的是 `Vscode`,推荐安装 [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) 插件,可以方便的查找和使用图标。
|
||||
|
||||
:::
|
||||
|
||||
项目中有以下多种图标使用方式,可以根据实际情况选择使用:
|
||||
|
||||
## Iconify 图标 <Badge text="推荐" type="tip"/>
|
||||
|
||||
集成了 [iconify](https://github.com/iconify/iconify) 图标库
|
||||
|
||||
### 新增
|
||||
|
||||
可在 `packages/icons/src/iconify` 目录下新增图标:
|
||||
|
||||
```ts
|
||||
// packages/icons/src/iconify/index.ts
|
||||
import { createIconifyIcon } from '@vben-core/icons';
|
||||
|
||||
export const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc');
|
||||
```
|
||||
|
||||
### 使用
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { MdiKeyboardEsc } from '@vben/icons';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 一个宽高为20px的图标 -->
|
||||
<MdiKeyboardEsc class="size-5" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Svg 图标 <Badge text="推荐" type="tip"/>
|
||||
|
||||
没有采用 Svg Sprite 的方式,而是直接引入 Svg 图标,
|
||||
|
||||
### 新增
|
||||
|
||||
可以在 `packages/icons/src/svg/icons` 目录下新增图标文件`test.svg`, 然后在 `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 };
|
||||
```
|
||||
|
||||
### 使用
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { SvgTestIcon } from '@vben/icons';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 一个宽高为20px的图标 -->
|
||||
<SvgTestIcon class="size-5" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Tailwind CSS 图标
|
||||
|
||||
### 使用
|
||||
|
||||
直接添加 Tailwind CSS 的图标类名即可使用,图标类名可查看 [iconify](https://github.com/iconify/iconify) :
|
||||
|
||||
```vue
|
||||
<span class="icon-[mdi--ab-testing]"></span>
|
||||
```
|
||||
@@ -1,600 +0,0 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 路由和菜单
|
||||
|
||||
在项目中,框架提供了一套基础的路由系统,并**根据路由文件自动生成对应的菜单结构**。
|
||||
|
||||
## 路由类型
|
||||
|
||||
路由分为核心路由、静态路由和动态路由,核心路由是框架内置的路由,包含了根路由、登录路由、404路由等;静态路由是在项目启动时就已经确定的路由;动态路由一般是在用户登录后,根据用户的权限动态生成的路由。
|
||||
|
||||
静态路由和动态路由都会走权限控制,可以通过配置路由的 `meta` 属性中的 `authority` 字段来控制权限,可以参考[路由权限控制](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/modules/demos.ts)。
|
||||
|
||||
### 核心路由
|
||||
|
||||
核心路由是框架内置的路由,包含了根路由、登录路由、404路由等,核心路由的配置在应用下 `src/router/routes/core` 目录下
|
||||
|
||||
::: tip
|
||||
|
||||
核心路由主要用于框架的基础功能,因此不建议将业务相关的路由放在核心路由中,推荐将业务相关的路由放在静态路由或动态路由中。
|
||||
|
||||
:::
|
||||
|
||||
### 静态路由
|
||||
|
||||
静态路由的配置在应用下 `src/router/routes/index` 目录下,打开注释的文件内容:
|
||||
|
||||
::: tip
|
||||
|
||||
权限控制是通过路由的 `meta` 属性中的 `authority` 字段来控制的,如果你的页面项目不需要权限控制,可以不设置 `authority` 字段。
|
||||
|
||||
:::
|
||||
|
||||
```ts
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // [!code --]
|
||||
const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); // [!code ++]
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统 */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles) // [!code --]
|
||||
const externalRoutes: RouteRecordRaw[] = []; // [!code --]
|
||||
const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // [!code ++]
|
||||
```
|
||||
|
||||
### 动态路由
|
||||
|
||||
动态路由的配置在对应应用 `src/router/routes/modules` 目录下,这个目录下存放了所有的路由文件。每个文件的内容格式如下,与 Vue Router 的路由配置格式一致,以下为二级路由和多级路由的配置。
|
||||
|
||||
## 路由定义
|
||||
|
||||
静态路由与动态路由的配置方式一致,以下为二级路由和多级路由的配置:
|
||||
|
||||
### 二级路由
|
||||
|
||||
::: details 二级路由示例代码
|
||||
|
||||
```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[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
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;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 多级路由
|
||||
|
||||
::: tip
|
||||
|
||||
- 多级路由的父级路由无需设置 `component` 属性,只需设置 `children` 属性即可。除非你真的需要在父级路由嵌套下显示内容。
|
||||
- 如果没有特殊情况,父级路由的 `redirect` 属性,不需要指定,默认会指向第一个子路由。
|
||||
|
||||
:::
|
||||
|
||||
::: details 多级路由示例代码
|
||||
|
||||
```ts
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('demos.title'),
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
redirect: '/demos/access',
|
||||
children: [
|
||||
// 嵌套菜单
|
||||
{
|
||||
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;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 新增页面
|
||||
|
||||
新增一个页面,你只需要添加一个路由及对应的页面组件即可。
|
||||
|
||||
### 添加路由
|
||||
|
||||
在对应的路由文件中添加一个路由对象,如下:
|
||||
|
||||
```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[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
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;
|
||||
```
|
||||
|
||||
### 添加页面组件
|
||||
|
||||
在`#/views/home/`下,新增一个`index.vue`文件,如下:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<h1>home page</h1>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 验证
|
||||
|
||||
到这里页面已添加完成,访问 `http://localhost:5555/home/index` 出现对应的页面即可。
|
||||
|
||||
## 路由配置
|
||||
|
||||
路由配置项主要在对象路由的 `meta` 属性中,以下为常用的配置项:
|
||||
|
||||
```ts {5-8}
|
||||
const routes = [
|
||||
{
|
||||
name: 'HomeIndex',
|
||||
path: '/home/index',
|
||||
meta: {
|
||||
icon: 'mdi:home',
|
||||
title: $t('page.home.index'),
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
::: details 路由Meta配置类型定义
|
||||
|
||||
```ts
|
||||
interface RouteMeta {
|
||||
/**
|
||||
* 激活图标(菜单)
|
||||
*/
|
||||
activeIcon?: string;
|
||||
/**
|
||||
* 当前激活的菜单,有时候不想激活现有菜单,需要激活父级菜单时使用
|
||||
*/
|
||||
activePath?: string;
|
||||
/**
|
||||
* 是否固定标签页
|
||||
* @default false
|
||||
*/
|
||||
affixTab?: boolean;
|
||||
/**
|
||||
* 固定标签页的顺序
|
||||
* @default 0
|
||||
*/
|
||||
affixTabOrder?: number;
|
||||
/**
|
||||
* 需要特定的角色标识才可以访问
|
||||
* @default []
|
||||
*/
|
||||
authority?: string[];
|
||||
/**
|
||||
* 徽标
|
||||
*/
|
||||
badge?: string;
|
||||
/**
|
||||
* 徽标类型
|
||||
*/
|
||||
badgeType?: 'dot' | 'normal';
|
||||
/**
|
||||
* 徽标颜色
|
||||
*/
|
||||
badgeVariants?:
|
||||
| 'default'
|
||||
| 'destructive'
|
||||
| 'primary'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| string;
|
||||
/**
|
||||
* 当前路由的子级在菜单中不展现
|
||||
* @default false
|
||||
*/
|
||||
hideChildrenInMenu?: boolean;
|
||||
/**
|
||||
* 当前路由在面包屑中不展现
|
||||
* @default false
|
||||
*/
|
||||
hideInBreadcrumb?: boolean;
|
||||
/**
|
||||
* 当前路由在菜单中不展现
|
||||
* @default false
|
||||
*/
|
||||
hideInMenu?: boolean;
|
||||
/**
|
||||
* 当前路由在标签页不展现
|
||||
* @default false
|
||||
*/
|
||||
hideInTab?: boolean;
|
||||
/**
|
||||
* 图标(菜单/tab)
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* iframe 地址
|
||||
*/
|
||||
iframeSrc?: string;
|
||||
/**
|
||||
* 忽略权限,直接可以访问
|
||||
* @default false
|
||||
*/
|
||||
ignoreAccess?: boolean;
|
||||
/**
|
||||
* 开启KeepAlive缓存
|
||||
*/
|
||||
keepAlive?: boolean;
|
||||
/**
|
||||
* 外链-跳转路径
|
||||
*/
|
||||
link?: string;
|
||||
/**
|
||||
* 路由是否已经加载过
|
||||
*/
|
||||
loaded?: boolean;
|
||||
/**
|
||||
* 标签页最大打开数量
|
||||
* @default false
|
||||
*/
|
||||
maxNumOfOpenTab?: number;
|
||||
/**
|
||||
* 菜单可以看到,但是访问会被重定向到403
|
||||
*/
|
||||
menuVisibleWithForbidden?: boolean;
|
||||
/**
|
||||
* 在新窗口打开
|
||||
*/
|
||||
openInNewWindow?: boolean;
|
||||
/**
|
||||
* 用于路由->菜单排序
|
||||
*/
|
||||
order?: number;
|
||||
/**
|
||||
* 菜单所携带的参数
|
||||
*/
|
||||
query?: Recordable;
|
||||
/**
|
||||
* 标题名称
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### title
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置页面的标题,会在菜单和标签页中显示。一般会配合国际化使用。
|
||||
|
||||
### icon
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置页面的图标,会在菜单和标签页中显示。一般会配合图标库使用,如果是`http`链接,会自动加载图片。
|
||||
|
||||
### activeIcon
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置页面的激活图标,会在菜单中显示。一般会配合图标库使用,如果是`http`链接,会自动加载图片。
|
||||
|
||||
### keepAlive
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否开启缓存,开启后页面会缓存,不会重新加载,仅在标签页启用时有效。
|
||||
|
||||
### hideInMenu
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否在菜单中隐藏,隐藏后页面不会在菜单中显示。
|
||||
|
||||
### hideInTab
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否在标签页中隐藏,隐藏后页面不会在标签页中显示。
|
||||
|
||||
### hideInBreadcrumb
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否在面包屑中隐藏,隐藏后页面不会在面包屑中显示。
|
||||
|
||||
### hideChildrenInMenu
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面的子页面是否在菜单中隐藏,隐藏后子页面不会在菜单中显示。
|
||||
|
||||
### authority
|
||||
|
||||
- 类型:`string[]`
|
||||
- 默认值:`[]`
|
||||
|
||||
用于配置页面的权限,只有拥有对应权限的用户才能访问页面,不配置则不需要权限。
|
||||
|
||||
### badge
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置页面的徽标,会在菜单显示。
|
||||
|
||||
### badgeType
|
||||
|
||||
- 类型:`'dot' | 'normal'`
|
||||
- 默认值:`'normal'`
|
||||
|
||||
用于配置页面的徽标类型,`dot` 为小红点,`normal` 为文本。
|
||||
|
||||
### badgeVariants
|
||||
|
||||
- 类型:`'default' | 'destructive' | 'primary' | 'success' | 'warning' | string`
|
||||
- 默认值:`'success'`
|
||||
|
||||
用于配置页面的徽标颜色。
|
||||
|
||||
### activePath
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置当前激活的菜单,有时候页面没有显示在菜单内,需要激活父级菜单时使用。
|
||||
|
||||
### affixTab
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否固定标签页,固定后页面不可关闭。
|
||||
|
||||
### affixTabOrder
|
||||
|
||||
- 类型:`number`
|
||||
- 默认值:`0`
|
||||
|
||||
用于配置页面固定标签页的排序, 采用升序排序。
|
||||
|
||||
### iframeSrc
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置内嵌页面的 `iframe` 地址,设置后会在当前页面内嵌对应的页面。
|
||||
|
||||
### ignoreAccess
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面是否忽略权限,直接可以访问。
|
||||
|
||||
### link
|
||||
|
||||
- 类型:`string`
|
||||
- 默认值:`''`
|
||||
|
||||
用于配置外链跳转路径,会在新窗口打开。
|
||||
|
||||
### maxNumOfOpenTab
|
||||
|
||||
- 类型:`number`
|
||||
- 默认值:`-1`
|
||||
|
||||
用于配置标签页最大打开数量,设置后会在打开新标签页时自动关闭最早打开的标签页(仅在打开同名标签页时生效)。
|
||||
|
||||
### menuVisibleWithForbidden
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
用于配置页面在菜单可以看到,但是访问会被重定向到403。
|
||||
|
||||
### openInNewWindow
|
||||
|
||||
- 类型:`boolean`
|
||||
- 默认值:`false`
|
||||
|
||||
设置为 `true` 时,会在新窗口打开页面。
|
||||
|
||||
### order
|
||||
|
||||
- 类型:`number`
|
||||
- 默认值:`0`
|
||||
|
||||
用于配置页面的排序,用于路由到菜单排序。
|
||||
|
||||
_注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对应的一级菜单中按代码顺序设置。
|
||||
|
||||
### query
|
||||
|
||||
- 类型:`Recordable`
|
||||
- 默认值:`{}`
|
||||
|
||||
用于配置页面的菜单参数,会在菜单中传递给页面。
|
||||
|
||||
## 路由刷新
|
||||
|
||||
路由刷新方式如下:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useRefresh } from '@vben/hooks';
|
||||
|
||||
const { refresh } = useRefresh();
|
||||
|
||||
// 刷新当前路由
|
||||
refresh();
|
||||
</script>
|
||||
```
|
||||
@@ -1,359 +0,0 @@
|
||||
# 服务端交互与数据Mock
|
||||
|
||||
::: tip 说明
|
||||
|
||||
本文档介绍如何在开发环境下使用 Mock 数据和与服务端进行交互,涉及到的技术有:
|
||||
|
||||
- [Nitro](https://nitro.unjs.io/) 轻量级后端服务器,可部署在任何地方,项目用作于 Mock 服务器。
|
||||
- [axios](https://axios-http.com/docs/intro) 用于发送 HTTP 请求与服务端进行交互。
|
||||
|
||||
:::
|
||||
|
||||
## 开发环境交互
|
||||
|
||||
如果前端应用和后端接口服务器没有运行在同一个主机上,你需要在开发环境下将接口请求代理到接口服务器。如果是同一个主机,可以直接请求具体的接口地址。
|
||||
|
||||
### 本地开发跨域配置
|
||||
|
||||
::: tip 提示
|
||||
|
||||
本地开发跨域配置项目已经配置好了,如有其他需求,可以自行增加或者调整配置。
|
||||
|
||||
:::
|
||||
|
||||
#### 配置本地开发接口地址
|
||||
|
||||
在项目根目录下的 `.env.development` 文件中配置接口地址,这里配置为 `/api`:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=/api
|
||||
```
|
||||
|
||||
#### 配置开发服务器代理
|
||||
|
||||
开发环境时候,如果需要处理跨域,接口地址在对应的应用目录下的 `vite.config.mts` 文件中配置:
|
||||
|
||||
```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代理目标地址
|
||||
target: 'http://localhost:5320/api',
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
#### 接口请求
|
||||
|
||||
根据上面的配置,我们可以在前端项目中使用 `/api` 作为接口请求的前缀,例如:
|
||||
|
||||
```ts
|
||||
import axios from 'axios';
|
||||
|
||||
axios.get('/api/user').then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
```
|
||||
|
||||
此时,请求会被代理到 `http://localhost:5320/api/user`。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
从浏览器控制台的 Network 看,请求是 `http://localhost:5555/api/user`, 这是因为 proxy 配置不会改变本地请求的 url。
|
||||
|
||||
:::
|
||||
|
||||
### 没有跨域时的配置
|
||||
|
||||
如果没有跨域问题,可以直接忽略 [配置开发服务器代理](./server.md#配置开发服务器代理) 配置,直接将接口地址设置在 `VITE_GLOB_API_URL`
|
||||
|
||||
在项目根目录下的 `.env.development` 文件中配置接口地址:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
```
|
||||
|
||||
## 生产环境交互
|
||||
|
||||
### 接口地址配置
|
||||
|
||||
在项目根目录下的 `.env.production` 文件中配置接口地址:
|
||||
|
||||
```bash
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
```
|
||||
|
||||
::: tip 打包如何动态修改接口地址
|
||||
|
||||
`.env` 文件内的 `VITE_GLOB_*` 开头的变量会在打包的时候注入 `_app.config.js` 文件内。在 `dist/_app.config.js` 修改相应的接口地址后刷新页面即可,不需要在根据不同环境打包多次,一次打包可以用于多个不同接口环境的部署。
|
||||
|
||||
:::
|
||||
|
||||
### 跨域处理
|
||||
|
||||
生产环境如果出现跨域问题,可以使用 `nginx` 代理接口地址 或者后台开启 `cors` 进行处理即可(可参考mock服务)。
|
||||
|
||||
## 接口请求配置
|
||||
|
||||
项目中默认自带了基于 `axios` 封装的基础的请求配置,核心由 `@vben/request` 包提供。项目没有过多的封装,只是简单的封装了一些常用的配置,如有其他需求,可以自行增加或者调整配置。针对不同的app,可能是用到了不同的组件库以及`store`,所以在应用目录下的`src/api/request.ts`文件夹下,有对应的请求配置文件,如`web-antd`项目下的`src/api/request.ts`文件,可以根据自己的需求进行配置。
|
||||
|
||||
### 请求示例
|
||||
|
||||
#### GET 请求
|
||||
|
||||
```ts
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
```
|
||||
|
||||
#### POST/PUT 请求
|
||||
|
||||
```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,
|
||||
// 或者 PUT
|
||||
method: user.id ? 'PUT' : 'POST',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### DELETE 请求
|
||||
|
||||
```ts
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export async function deleteUserApi(user: UserInfo) {
|
||||
return requestClient.delete<boolean>(`/user/${user.id}`, user);
|
||||
}
|
||||
```
|
||||
|
||||
### 请求配置
|
||||
|
||||
应用内的`src/api/request.ts`可以根据自己应用的情况的需求进行配置:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
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.isAccessChecked
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// 请求头处理
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// response数据解构
|
||||
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 });
|
||||
},
|
||||
});
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
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 });
|
||||
```
|
||||
|
||||
### 多个接口地址
|
||||
|
||||
只需要创建多个 `requestClient` 即可,如:
|
||||
|
||||
```ts
|
||||
const { apiURL, otherApiURL } = useAppConfig(
|
||||
import.meta.env,
|
||||
import.meta.env.PROD,
|
||||
);
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const otherRequestClient = createRequestClient(otherApiURL);
|
||||
```
|
||||
|
||||
## 刷新Token
|
||||
|
||||
项目中默认提供了刷新 Token 的逻辑,只需要按照下面的配置即可开启:
|
||||
|
||||
- 确保当前启用了刷新 Token 的配置
|
||||
|
||||
调整对应应用目录下的`preferences.ts`,确保`enableRefreshToken='true'`。
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
enableRefreshToken: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
在 `src/api/request.ts` 中配置 `doRefreshToken` 方法即可:
|
||||
|
||||
```ts
|
||||
// 这里调整为你的token格式
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
// 这里调整为你的刷新token接口
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
```
|
||||
|
||||
## 数据 Mock
|
||||
|
||||
::: tip 生产环境 Mock
|
||||
|
||||
新版本不再支持生产环境 mock,请使用真实接口。
|
||||
|
||||
:::
|
||||
|
||||
Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发进程所阻塞。
|
||||
|
||||
项目使用 [Nitro](https://nitro.unjs.io/) 来进行本地 mock 数据处理。其原理是本地额外启动一个后端服务,是一个真实的后端服务,可以处理请求,返回数据。
|
||||
|
||||
### Nitro 使用
|
||||
|
||||
Mock 服务代码位于`apps/backend-mock`目录下,无需手动启动,已经集成在项目中,只需要在项目根目录下运行`pnpm dev`即可,运行成功之后,控制台会打印 `http://localhost:5320/api`, 访问该地址即可查看 mock 服务。
|
||||
|
||||
[Nitro](https://nitro.unjs.io/) 语法简单,可以根据自己的需求进行配置及开发,具体配置可以查看 [Nitro 文档](https://nitro.unjs.io/)。
|
||||
|
||||
## 关闭 Mock 服务
|
||||
|
||||
mock的本质是一个真实的后端服务,如果不需要 mock 服务,可以在项目根目录下的 `.env.development` 文件中配置 `VITE_NITRO_MOCK=false` 即可关闭 mock 服务。
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
VITE_NITRO_MOCK=false
|
||||
```
|
||||
@@ -1,541 +0,0 @@
|
||||
# 配置
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
项目的环境变量配置位于应用目录下的 `.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
|
||||
|
||||
# 是否打开 devtools,true 为打开,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 ++]
|
||||
}
|
||||
```
|
||||
|
||||
到这里,就可以在项目内使用 `useAppConfig`方法获取到新增的配置项了。
|
||||
|
||||
```ts
|
||||
const { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
```
|
||||
|
||||
::: warning 注意
|
||||
|
||||
`useAppConfig`方法只能在应用内使用,不要耦合到包内部去使用。这里传入 `import.meta.env`和`import.meta.env.PROD`是为了避免这种情况,一个纯粹的包,应避免使用特定构建工具的变量。
|
||||
|
||||
:::
|
||||
|
||||
## 偏好设置
|
||||
|
||||
项目提供了非常丰富的偏好设置,用于动态配置项目的各种功能:
|
||||
|
||||

|
||||
|
||||
如果你找不到文档说明,可以尝试自己配置好以后,点击`复制偏好设置`,覆盖项目默认即可。配置文件位于应用目录下的`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',
|
||||
defaultAvatar:
|
||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
enableRefreshToken: false,
|
||||
isMobile: false,
|
||||
layout: 'sidebar-nav',
|
||||
locale: 'zh-CN',
|
||||
loginExpiredMode: 'modal',
|
||||
name: 'Vben Admin',
|
||||
preferencesButtonPosition: 'auto',
|
||||
watermark: false,
|
||||
},
|
||||
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: '',
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
fixed: false,
|
||||
},
|
||||
header: {
|
||||
enable: true,
|
||||
hidden: false,
|
||||
mode: 'fixed',
|
||||
},
|
||||
logo: {
|
||||
enable: true,
|
||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
},
|
||||
navigation: {
|
||||
accordion: true,
|
||||
split: true,
|
||||
styleType: 'rounded',
|
||||
},
|
||||
shortcutKeys: {
|
||||
enable: true,
|
||||
globalLockScreen: true,
|
||||
globalLogout: true,
|
||||
globalPreferences: true,
|
||||
globalSearch: true,
|
||||
},
|
||||
sidebar: {
|
||||
autoActivateChild: false,
|
||||
collapsed: false,
|
||||
collapsedShowTitle: false,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
hidden: false,
|
||||
width: 230,
|
||||
},
|
||||
tabbar: {
|
||||
draggable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
keepAlive: true,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
},
|
||||
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: true,
|
||||
},
|
||||
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;
|
||||
// /** 应用默认头像 */
|
||||
defaultAvatar: 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface FooterPreferences {
|
||||
/** 底栏是否可见 */
|
||||
enable: boolean;
|
||||
/** 底栏是否固定 */
|
||||
fixed: boolean;
|
||||
}
|
||||
|
||||
interface HeaderPreferences {
|
||||
/** 顶栏是否启用 */
|
||||
enable: boolean;
|
||||
/** 顶栏是否隐藏,css-隐藏 */
|
||||
hidden: boolean;
|
||||
/** header显示模式 */
|
||||
mode: LayoutHeaderModeType;
|
||||
}
|
||||
|
||||
interface LogoPreferences {
|
||||
/** logo是否可见 */
|
||||
enable: boolean;
|
||||
/** logo地址 */
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface NavigationPreferences {
|
||||
/** 导航菜单手风琴模式 */
|
||||
accordion: boolean;
|
||||
/** 导航菜单是否切割,只在 layout=mixed-nav 生效 */
|
||||
split: boolean;
|
||||
/** 导航菜单风格 */
|
||||
styleType: NavigationStyleType;
|
||||
}
|
||||
|
||||
interface SidebarPreferences {
|
||||
/** 侧边栏是否折叠 */
|
||||
collapsed: boolean;
|
||||
/** 侧边栏折叠时,是否显示title */
|
||||
collapsedShowTitle: boolean;
|
||||
/** 侧边栏是否可见 */
|
||||
enable: boolean;
|
||||
/** 菜单自动展开状态 */
|
||||
expandOnHover: boolean;
|
||||
/** 侧边栏扩展区域是否折叠 */
|
||||
extraCollapse: boolean;
|
||||
/** 侧边栏是否隐藏 - css */
|
||||
hidden: boolean;
|
||||
/** 侧边栏宽度 */
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ShortcutKeyPreferences {
|
||||
/** 是否启用快捷键-全局 */
|
||||
enable: boolean;
|
||||
/** 是否启用全局锁屏快捷键 */
|
||||
globalLockScreen: boolean;
|
||||
/** 是否启用全局注销快捷键 */
|
||||
globalLogout: boolean;
|
||||
/** 是否启用全局偏好设置快捷键 */
|
||||
globalPreferences: boolean;
|
||||
/** 是否启用全局搜索快捷键 */
|
||||
globalSearch: boolean;
|
||||
}
|
||||
|
||||
interface TabbarPreferences {
|
||||
/** 是否开启多标签页拖拽 */
|
||||
draggable: boolean;
|
||||
/** 是否开启多标签页 */
|
||||
enable: boolean;
|
||||
/** 标签页高度 */
|
||||
height: number;
|
||||
/** 开启标签页缓存功能 */
|
||||
keepAlive: boolean;
|
||||
/** 是否持久化标签 */
|
||||
persist: boolean;
|
||||
/** 是否开启多标签页图标 */
|
||||
showIcon: boolean;
|
||||
/** 显示最大化按钮 */
|
||||
showMaximize: boolean;
|
||||
/** 显示更多按钮 */
|
||||
showMore: boolean;
|
||||
/** 标签页风格 */
|
||||
styleType: TabsStyleType;
|
||||
}
|
||||
|
||||
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`方法内覆盖即可,不要修改默认配置文件。
|
||||
- 更改配置后请清空缓存,否则可能不生效。:::
|
||||
@@ -1,106 +0,0 @@
|
||||
# 样式
|
||||
|
||||
::: tip 前言
|
||||
|
||||
对于 vue 项目,[官方文档](https://vuejs.org/api/sfc-css-features.html#deep-selectors) 对语法已经有比较详细的介绍,这里主要是介绍项目中的样式文件结构和使用。
|
||||
|
||||
:::
|
||||
|
||||
## 项目结构
|
||||
|
||||
项目中的样式文件存放在 `@vben/styles`,包含一些全局样式,如重置样式、全局变量等,它继承了 `@vben-core/design` 的样式和能力,可以根据项目需求进行覆盖。
|
||||
|
||||
## Scss
|
||||
|
||||
项目中使用 `scss` 作为样式预处理器,可以在项目中使用 `scss` 的特性,如变量、函数、混合等。
|
||||
|
||||
```vue
|
||||
<style lang="scss" scoped>
|
||||
$font-size: 30px;
|
||||
|
||||
.box {
|
||||
.title {
|
||||
color: green;
|
||||
font-size: $font-size;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Postcss
|
||||
|
||||
如果你不习惯使用 `scss`,也可以使用 `postcss`,它是一个更加强大的样式处理器,可以使用更多的插件,项目内置了 [postcss-nested](https://github.com/postcss/postcss-nested) 插件,配置 `Css Variables`,完全可以取代 `scss`。
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
.box {
|
||||
--font-size: 30px;
|
||||
.title {
|
||||
color: green;
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
项目中集成了 [Tailwind CSS](https://tailwindcss.com/),可以在项目中使用 `tailwindcss` 的类名,快速构建页面。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="bg-white p-4">
|
||||
<p class="text-green">hello world</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## BEM 规范
|
||||
|
||||
样式冲突的另一种选择,是使用 `BEM` 规范。如果选择 `scss` ,建议使用 `BEM` 命名规范,可以更好的管理样式。项目默认提供了`useNamespace`函数,可以方便的生成命名空间。
|
||||
|
||||
```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>
|
||||
// 如果你在应用内使用,这行代码可以省略,已经在所有的应用内全局引入了
|
||||
@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
|
||||
|
||||
针对样式冲突问题,还有一种方案是使用 `CSS Modules` 模块化方案。使用方式如下。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<p :class="$style.red">This should be red</p>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
更多用法可以见 [CSS Modules 官方文档](https://vuejs.org/api/sfc-css-features.html#css-modules)。
|
||||
@@ -1,48 +0,0 @@
|
||||
# 检查更新
|
||||
|
||||
## 介绍
|
||||
|
||||
当网站有更新时,您可能需要检查更新。框架提供了这一功能,通过定时检查更新,您可以在应用的 preferences.ts 文件中配置 `checkUpdatesInterval`和 `enableCheckUpdates` 字段,以开启和设置检查更新的时间间隔(单位:分钟)。
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
// 是否开启检查更新
|
||||
enableCheckUpdates: true,
|
||||
// 检查更新的时间间隔,单位为分钟
|
||||
checkUpdatesInterval: 1,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 效果
|
||||
|
||||
检测到更新时,会弹出提示框,询问用户是否刷新页面:
|
||||
|
||||

|
||||
|
||||
## 替换为其他检查更新方式
|
||||
|
||||
如果需要通过其他方式检查更新,例如通过接口来更灵活地控制更新逻辑(如强制刷新、显示更新内容等),你可以通过修改 `@vben/layouts` 下面的 `src/widgets/check-updates/check-updates.vue`文件来实现。
|
||||
|
||||
```ts
|
||||
// 这里可以替换为你的检查更新逻辑
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
# 常用功能
|
||||
|
||||
一些常用的功能合集。
|
||||
|
||||
## 登录认证过期
|
||||
|
||||
当接口返回`401`状态码时,框架会认为登录认证过期,登录超时会跳转到登录页或者打开登录弹窗。在应用目录下的`preferences.ts`可以配置:
|
||||
|
||||
### 跳转登录页面
|
||||
|
||||
登录超时会跳转到登录页
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'page',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 打开登录弹窗
|
||||
|
||||
登录超时会打开登录弹窗
|
||||
|
||||

|
||||
|
||||
配置:
|
||||
|
||||
```ts
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'modal',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 动态标题
|
||||
|
||||
- 默认值:`true`
|
||||
|
||||
开启后网页标题随着路由的`title`而变化。在应用目录下的`preferences.ts`,开启或者关闭即可。
|
||||
|
||||
```ts
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
dynamicTitle: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 页面水印
|
||||
|
||||
- 默认值:`false`
|
||||
|
||||
开启后网页会显示水印,在应用目录下的`preferences.ts`,开启或者关闭即可。
|
||||
|
||||
```ts
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
watermark: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
如果你想更新水印的内容,可以这么做,参数可以参考 [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/):
|
||||
|
||||
```ts
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
|
||||
await updateWatermark({
|
||||
// 水印内容
|
||||
content: 'hello my watermark',
|
||||
});
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user