mirror of
https://github.com/KwiTsukasa/kt-template-admin.git
synced 2026-05-27 16:35:47 +08:00
feat: skills
This commit is contained in:
parent
6ecada0d3d
commit
d0e212a283
297
skills/SKILL.md
Normal file
297
skills/SKILL.md
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
---
|
||||||
|
name: vben
|
||||||
|
description: Vben Admin 5.0 前端框架开发技能。用于开发基于 Vue3、Vite、TypeScript 的中后台管理系统。
|
||||||
|
TRIGGER when: 编写/修改 Vben Admin 项目代码、配置路由菜单、设置权限控制、主题定制、组件开发、API对接、状态管理、国际化配置。
|
||||||
|
DO NOT trigger when: 编写后端代码、纯前端通用开发(与 Vben 框架无关)。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben Admin 开发技能
|
||||||
|
|
||||||
|
基于 Vben Admin 5.0 文档的专业开发指导技能,帮助快速开发中后台管理系统。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
采用 Monorepo 架构,核心目录:
|
||||||
|
|
||||||
|
```
|
||||||
|
├── apps/ # 应用目录
|
||||||
|
│ ├── web-antd/ # Ant Design Vue 应用
|
||||||
|
│ ├── web-ele/ # Element Plus 应用
|
||||||
|
│ ├── web-naive/ # Naive UI 应用
|
||||||
|
│ └── backend-mock/ # Mock 后端服务
|
||||||
|
├── packages/ # 共享包
|
||||||
|
│ ├── @core/ # 核心包(UI组件、布局等)
|
||||||
|
│ ├── effects/ # 副作用包(权限、请求、hooks等)
|
||||||
|
│ ├── stores/ # 状态管理
|
||||||
|
│ ├── locales/ # 国际化
|
||||||
|
│ └── utils/ # 工具函数
|
||||||
|
└── internal/ # 内部工具配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开发
|
||||||
|
pnpm dev:antd # 启动 Ant Design 应用
|
||||||
|
pnpm dev:ele # 启动 Element Plus 应用
|
||||||
|
pnpm dev:naive # 启动 Naive UI 应用
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
pnpm build # 构建所有应用
|
||||||
|
pnpm build:antd # 构建指定应用
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
pnpm lint # 代码检查
|
||||||
|
pnpm check:type # 类型检查
|
||||||
|
pnpm reinstall # 重新安装依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心开发指南
|
||||||
|
|
||||||
|
### 路由与菜单
|
||||||
|
|
||||||
|
路由配置位于 `src/router/routes/modules/` 目录:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:home',
|
||||||
|
title: $t('page.home.title'),
|
||||||
|
authority: ['admin'], // 权限控制
|
||||||
|
order: 1000, // 菜单排序
|
||||||
|
keepAlive: true, // 开启缓存
|
||||||
|
hideInMenu: false, // 菜单中隐藏
|
||||||
|
hideInTab: false, // 标签页中隐藏
|
||||||
|
},
|
||||||
|
name: 'Home',
|
||||||
|
path: '/home',
|
||||||
|
component: () => import('#/views/home/index.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 权限控制
|
||||||
|
|
||||||
|
三种模式在 `preferences.ts` 中配置:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
accessMode: 'frontend', // 'frontend' | 'backend' | 'mixed'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
按钮级权限:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { AccessControl, useAccess } from '@vben/access';
|
||||||
|
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 权限码方式 -->
|
||||||
|
<AccessControl :codes="['AC_100100']" type="code">
|
||||||
|
<Button>有权限可见</Button>
|
||||||
|
</AccessControl>
|
||||||
|
|
||||||
|
<!-- 角色方式 -->
|
||||||
|
<Button v-if="hasAccessByRoles(['admin'])">管理员可见</Button>
|
||||||
|
|
||||||
|
<!-- 指令方式 -->
|
||||||
|
<Button v-access:code="'AC_100100'">有权限可见</Button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 偏好设置配置
|
||||||
|
|
||||||
|
在应用目录的 `preferences.ts` 中配置:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
layout: 'sidebar-nav', // 布局方式
|
||||||
|
locale: 'zh-CN', // 语言
|
||||||
|
dynamicTitle: true, // 动态标题
|
||||||
|
watermark: false, // 水印
|
||||||
|
loginExpiredMode: 'page', // 登录过期模式
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
mode: 'dark', // 主题模式
|
||||||
|
builtinType: 'default', // 内置主题
|
||||||
|
colorPrimary: 'hsl(212 100% 45%)', // 主题色
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
collapsed: false, // 侧边栏折叠
|
||||||
|
width: 224, // 侧边栏宽度
|
||||||
|
},
|
||||||
|
tabbar: {
|
||||||
|
enable: true, // 标签页
|
||||||
|
keepAlive: true, // 缓存
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 请求
|
||||||
|
|
||||||
|
请求配置在 `src/api/request.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
// GET 请求
|
||||||
|
export async function getUserInfoApi() {
|
||||||
|
return requestClient.get<UserInfo>('/user/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST 请求
|
||||||
|
export async function saveUserApi(user: UserInfo) {
|
||||||
|
return requestClient.post<UserInfo>('/user', user);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
代理配置在 `vite.config.mts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig(async () => {
|
||||||
|
return {
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:5320/api',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 国际化
|
||||||
|
|
||||||
|
使用 `$t()` 函数:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
title: $t('page.home.title'),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
语言文件在 `packages/locales/` 目录。
|
||||||
|
|
||||||
|
### 主题定制
|
||||||
|
|
||||||
|
CSS 变量覆盖:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--primary: 212 100% 45%;
|
||||||
|
--sidebar: 0 0% 100%;
|
||||||
|
--header: 0 0% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.34deg 10.43% 12.27%;
|
||||||
|
--sidebar: 222.34deg 10.43% 12.27%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 登录接口对接
|
||||||
|
|
||||||
|
需要的接口:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/core/auth.ts
|
||||||
|
export async function loginApi(data: LoginParams) {
|
||||||
|
return requestClient.post<LoginResult>('/auth/login', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/api/core/user.ts
|
||||||
|
export async function getUserInfoApi() {
|
||||||
|
return requestClient.get<UserInfo>('/user/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/api/core/auth.ts (可选)
|
||||||
|
export async function getAccessCodesApi() {
|
||||||
|
return requestClient.get<string[]>('/auth/codes');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.development
|
||||||
|
VITE_PORT=5555
|
||||||
|
VITE_GLOB_API_URL=/api
|
||||||
|
VITE_NITRO_MOCK=true
|
||||||
|
|
||||||
|
# .env.production
|
||||||
|
VITE_GLOB_API_URL=https://api.example.com
|
||||||
|
VITE_COMPRESS=gzip
|
||||||
|
VITE_ROUTER_HISTORY=hash
|
||||||
|
```
|
||||||
|
|
||||||
|
## 别名配置
|
||||||
|
|
||||||
|
使用 `#` 开头的路径别名:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"#/*": "./src/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
import { useAuthStore } from '#/store';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 详细参考文档
|
||||||
|
|
||||||
|
需要更多细节时,查阅以下参考文件:
|
||||||
|
|
||||||
|
### 核心功能 (references/core/)
|
||||||
|
- **路由菜单**: `references/core/route.md` - 路由配置、Meta属性、多级菜单
|
||||||
|
- **权限控制**: `references/core/access.md` - 三种权限模式、按钮级权限
|
||||||
|
- **偏好设置**: `references/core/preferences.md` - 完整配置项说明
|
||||||
|
- **主题定制**: `references/core/theme.md` - CSS变量、内置主题、自定义主题
|
||||||
|
- **API请求**: `references/core/api.md` - 请求配置、拦截器、多接口地址
|
||||||
|
- **国际化**: `references/core/locale.md` - 语言配置、新增语言包
|
||||||
|
- **登录对接**: `references/core/login.md` - 登录接口、Token刷新
|
||||||
|
- **图标使用**: `references/core/icons.md` - Iconify图标、SVG图标
|
||||||
|
|
||||||
|
### 业务组件 (references/components/business/)
|
||||||
|
- **Page页面**: `references/components/business/page.md` - 页面布局容器、标题区、内容区
|
||||||
|
- **表单组件**: `references/components/business/form.md` - Vben Form表单配置、校验、联动
|
||||||
|
- **表格组件**: `references/components/business/table.md` - Vben Vxe Table表格配置、搜索、远程加载
|
||||||
|
- **模态框**: `references/components/business/modal.md` - Vben Modal配置、拖拽、全屏
|
||||||
|
- **抽屉**: `references/components/business/drawer.md` - Vben Drawer配置、组件抽离
|
||||||
|
- **轻量提示框**: `references/components/business/alert.md` - alert、confirm、prompt调用
|
||||||
|
- **API组件包装器**: `references/components/business/api-component.md` - 远程数据自动加载
|
||||||
|
|
||||||
|
### 通用组件 (references/components/common/)
|
||||||
|
- **数字动画**: `references/components/common/count-to.md` - CountToAnimator数字滚动动画
|
||||||
|
- **省略文本**: `references/components/common/ellipsis-text.md` - EllipsisText文本省略展开
|
||||||
|
|
||||||
|
### 功能配置 (references/features/)
|
||||||
|
- **常用功能**: `references/features/features.md` - 水印、缓存、动态标题等
|
||||||
|
|
||||||
|
### 构建部署 (references/deployment/)
|
||||||
|
- **构建部署**: `references/deployment/deploy.md` - 构建配置、Nginx、Docker
|
||||||
|
- **常见问题**: `references/deployment/faq.md` - 依赖安装、打包部署、错误排查
|
||||||
150
skills/references/components/business/alert.md
Normal file
150
skills/references/components/business/alert.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Vben Alert 轻量提示框
|
||||||
|
|
||||||
|
提供纯 JavaScript 调用的轻量提示框,适合快速创建 `alert`、`confirm`、`prompt` 这类简单交互。
|
||||||
|
|
||||||
|
## Alert 提示框
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { alert } from '@vben/common-ui';
|
||||||
|
|
||||||
|
// 基础用法
|
||||||
|
await alert('操作成功');
|
||||||
|
|
||||||
|
// 带标题
|
||||||
|
await alert('操作成功', '提示');
|
||||||
|
|
||||||
|
// 完整配置
|
||||||
|
await alert({
|
||||||
|
title: '提示',
|
||||||
|
content: '操作成功',
|
||||||
|
icon: 'success', // 'error' | 'info' | 'question' | 'success' | 'warning'
|
||||||
|
confirmText: '确定',
|
||||||
|
centered: true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Confirm 确认框
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { confirm } from '@vben/common-ui';
|
||||||
|
|
||||||
|
// 基础用法
|
||||||
|
const result = await confirm('确定要删除吗?');
|
||||||
|
if (result) {
|
||||||
|
// 用户点击确认
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整配置
|
||||||
|
const result = await confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '删除后数据无法恢复,确定要删除吗?',
|
||||||
|
icon: 'warning',
|
||||||
|
confirmText: '删除',
|
||||||
|
cancelText: '取消',
|
||||||
|
beforeClose: async ({ isConfirm }) => {
|
||||||
|
if (isConfirm) {
|
||||||
|
// 返回 false 阻止关闭
|
||||||
|
return await doDelete();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompt 输入框
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { prompt } from '@vben/common-ui';
|
||||||
|
|
||||||
|
// 基础用法
|
||||||
|
const value = await prompt('请输入名称:');
|
||||||
|
if (value) {
|
||||||
|
console.log('用户输入:', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带默认值
|
||||||
|
const value = await prompt({
|
||||||
|
title: '请输入名称',
|
||||||
|
defaultValue: '默认名称',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自定义输入组件
|
||||||
|
const value = await prompt({
|
||||||
|
title: '请选择类型',
|
||||||
|
component: Select,
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '类型A', value: 'a' },
|
||||||
|
{ label: '类型B', value: 'b' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
defaultValue: 'a',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## useAlertContext
|
||||||
|
|
||||||
|
在自定义组件内获取弹窗上下文:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAlertContext } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const { doConfirm, doCancel } = useAlertContext();
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
// 触发确认操作
|
||||||
|
doConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
// 触发取消操作
|
||||||
|
doCancel();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
|
interface AlertProps {
|
||||||
|
title?: string;
|
||||||
|
content: Component | string;
|
||||||
|
icon?: Component | IconType;
|
||||||
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
showCancel?: boolean;
|
||||||
|
centered?: boolean;
|
||||||
|
bordered?: boolean;
|
||||||
|
buttonAlign?: 'center' | 'end' | 'start';
|
||||||
|
overlayBlur?: number;
|
||||||
|
beforeClose?: (scope: { isConfirm: boolean }) => boolean | Promise<boolean>;
|
||||||
|
footer?: Component | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromptProps<T = any> extends AlertProps {
|
||||||
|
component?: Component;
|
||||||
|
componentProps?: Record<string, any>;
|
||||||
|
defaultValue?: T;
|
||||||
|
modelPropName?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
- 简单的确认提示
|
||||||
|
- 删除操作确认
|
||||||
|
- 快速输入收集
|
||||||
|
- 不需要复杂布局的弹窗
|
||||||
|
|
||||||
|
## 与 Modal 的区别
|
||||||
|
|
||||||
|
| 特性 | Alert | Modal |
|
||||||
|
|------|-------|-------|
|
||||||
|
| 调用方式 | 纯JS调用 | 组件式 |
|
||||||
|
| 复杂度 | 简单 | 可复杂 |
|
||||||
|
| 自定义内容 | 有限 | 完全自定义 |
|
||||||
|
| 表单支持 | prompt有限 | 完整支持 |
|
||||||
|
| 适用场景 | 快速确认/提示 | 复杂弹窗业务 |
|
||||||
205
skills/references/components/business/api-component.md
Normal file
205
skills/references/components/business/api-component.md
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# Vben ApiComponent API组件包装器
|
||||||
|
|
||||||
|
用于包装其它组件,为目标组件提供自动获取远程数据的能力。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
包装 Select 组件,自动获取远程选项:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ApiComponent } from '@vben/common-ui';
|
||||||
|
import { Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
async function fetchOptions() {
|
||||||
|
const res = await getUserListApi();
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
v-model="selectedValue"
|
||||||
|
:api="fetchOptions"
|
||||||
|
:component="Select"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 包装级联选择器
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ApiComponent } from '@vben/common-ui';
|
||||||
|
import { Cascader } from 'ant-design-vue';
|
||||||
|
|
||||||
|
async function fetchTreeData() {
|
||||||
|
const res = await getRegionTreeApi();
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
v-model="selectedRegion"
|
||||||
|
:api="fetchTreeData"
|
||||||
|
:component="Cascader"
|
||||||
|
:immediate="false"
|
||||||
|
children-field="children"
|
||||||
|
loading-slot="suffixIcon"
|
||||||
|
visible-event="onDropdownVisibleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 请求参数
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const params = ref({ type: 'user' });
|
||||||
|
|
||||||
|
async function fetchOptions(params) {
|
||||||
|
const res = await getOptionsApi(params);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
v-model="value"
|
||||||
|
:api="fetchOptions"
|
||||||
|
:params="params"
|
||||||
|
:component="Select"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 请求前后处理
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
async function beforeFetch(params) {
|
||||||
|
// 请求前处理参数
|
||||||
|
return { ...params, status: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function afterFetch(data) {
|
||||||
|
// 请求后处理数据
|
||||||
|
return data.map(item => ({
|
||||||
|
...item,
|
||||||
|
label: `${item.name} (${item.code})`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
v-model="value"
|
||||||
|
:api="fetchOptions"
|
||||||
|
:component="Select"
|
||||||
|
:before-fetch="beforeFetch"
|
||||||
|
:after-fetch="afterFetch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自动选择选项
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 自动选择第一个选项 -->
|
||||||
|
<ApiComponent
|
||||||
|
v-model="value"
|
||||||
|
:api="fetchOptions"
|
||||||
|
:component="Select"
|
||||||
|
auto-select="first"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 有且仅有一个选项时自动选择 -->
|
||||||
|
<ApiComponent
|
||||||
|
v-model="value"
|
||||||
|
:api="fetchOptions"
|
||||||
|
:component="Select"
|
||||||
|
auto-select="one"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| modelValue | 当前值 | `any` | - |
|
||||||
|
| component | 目标组件 | `Component` | - |
|
||||||
|
| api | 获取数据的函数 | `(arg?) => Promise<any>` | - |
|
||||||
|
| params | 传递给api的参数 | `object` | - |
|
||||||
|
| resultField | 从结果中提取数组的字段名 | `string` | - |
|
||||||
|
| labelField | label字段名 | `string` | `label` |
|
||||||
|
| valueField | value字段名 | `string` | `value` |
|
||||||
|
| childrenField | 子级数据字段名 | `string` | - |
|
||||||
|
| optionsPropName | 目标组件接收options的属性名 | `string` | `options` |
|
||||||
|
| modelPropName | 目标组件的双向绑定属性名 | `string` | `modelValue` |
|
||||||
|
| immediate | 是否立即调用api | `boolean` | `true` |
|
||||||
|
| alwaysLoad | 每次显示时重新请求 | `boolean` | `false` |
|
||||||
|
| beforeFetch | 请求前的回调 | `(params) => any` | - |
|
||||||
|
| afterFetch | 请求后的回调 | `(data) => any` | - |
|
||||||
|
| options | 直接传入选项数据 | `OptionsItem[]` | - |
|
||||||
|
| visibleEvent | 触发请求的事件名 | `string` | - |
|
||||||
|
| loadingSlot | 显示loading的插槽名 | `string` | - |
|
||||||
|
| numberToString | 将value从数字转为string | `boolean` | `false` |
|
||||||
|
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'` | `false` |
|
||||||
|
|
||||||
|
## Methods 方法
|
||||||
|
|
||||||
|
| 方法 | 描述 | 类型 |
|
||||||
|
|------|------|------|
|
||||||
|
| getComponentRef | 获取被包装组件的实例 | `() => T` |
|
||||||
|
| updateParam | 设置接口请求参数 | `(params) => void` |
|
||||||
|
| getOptions | 获取已加载的选项数据 | `() => OptionsItem[]` |
|
||||||
|
| getValue | 获取当前值 | `() => any` |
|
||||||
|
|
||||||
|
## 并发和缓存
|
||||||
|
|
||||||
|
使用 Tanstack Query 包装接口请求,实现并发控制和缓存:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useQuery } from '@tanstack/vue-query';
|
||||||
|
|
||||||
|
function useUserOptions() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['user-options'],
|
||||||
|
queryFn: () => getUserListApi(),
|
||||||
|
staleTime: 5 * 60 * 1000, // 5分钟缓存
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 适配器配置
|
||||||
|
|
||||||
|
在应用适配器中预包装组件:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/adapter/component.ts
|
||||||
|
import { ApiComponent } from '@vben/common-ui';
|
||||||
|
import { Select, TreeSelect } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(ApiComponent, {
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: Select,
|
||||||
|
}, slots);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(ApiComponent, {
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
childrenField: 'children',
|
||||||
|
}, slots);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
224
skills/references/components/business/drawer.md
Normal file
224
skills/references/components/business/drawer.md
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
# Vben Drawer 抽屉
|
||||||
|
|
||||||
|
框架提供的抽屉组件,支持自动高度、loading等功能。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenDrawer } from '#/adapter';
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
title: '标题',
|
||||||
|
onConfirm: () => {
|
||||||
|
console.log('确认');
|
||||||
|
drawerApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button @click="drawerApi.open()">打开</Button>
|
||||||
|
<Drawer>
|
||||||
|
抽屉内容
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件抽离
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- parent.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenDrawer } from '#/adapter';
|
||||||
|
import ChildForm from './child-form.vue';
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
connectedComponent: ChildForm,
|
||||||
|
onConfirm: () => {
|
||||||
|
const data = drawerApi.getData();
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button @click="drawerApi.open()">打开</Button>
|
||||||
|
<Drawer />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- child-form.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenDrawer } from '#/adapter';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({...});
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onOpenChange: (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = drawerApi.getData();
|
||||||
|
formApi.setValues(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
const values = await formApi.validateAndSubmitForm();
|
||||||
|
drawerApi.setData(values);
|
||||||
|
drawerApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 弹出位置
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 左侧弹出
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
placement: 'left',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 右侧弹出(默认)
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
placement: 'right',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 顶部弹出
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
placement: 'top',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 底部弹出
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
placement: 'bottom',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading 状态
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onConfirm: async () => {
|
||||||
|
drawerApi.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
await saveData();
|
||||||
|
drawerApi.close();
|
||||||
|
} finally {
|
||||||
|
drawerApi.setState({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lock 锁定状态
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onConfirm: async () => {
|
||||||
|
drawerApi.lock();
|
||||||
|
try {
|
||||||
|
await saveData();
|
||||||
|
drawerApi.close();
|
||||||
|
} finally {
|
||||||
|
drawerApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 挂载到内容区域
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
appendToMain: true, // 挂载到内容区域,不遮挡导航菜单
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 需要设置 auto-content-height -->
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Drawer />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| title | 标题 | `string` | - |
|
||||||
|
| titleTooltip | 标题提示 | `string` | - |
|
||||||
|
| description | 描述信息 | `string` | - |
|
||||||
|
| isOpen | 打开状态 | `boolean` | `false` |
|
||||||
|
| loading | 加载状态 | `boolean` | `false` |
|
||||||
|
| closable | 显示关闭按钮 | `boolean` | `true` |
|
||||||
|
| closeIconPlacement | 关闭按钮位置 | `'left' \| 'right'` | `right` |
|
||||||
|
| modal | 显示遮罩 | `boolean` | `true` |
|
||||||
|
| header | 显示header | `boolean` | `true` |
|
||||||
|
| footer | 显示footer | `boolean` | `true` |
|
||||||
|
| confirmLoading | 确认按钮loading | `boolean` | `false` |
|
||||||
|
| closeOnClickModal | 点击遮罩关闭 | `boolean` | `true` |
|
||||||
|
| closeOnPressEscape | ESC关闭 | `boolean` | `true` |
|
||||||
|
| confirmText | 确认按钮文本 | `string` | `确认` |
|
||||||
|
| cancelText | 取消按钮文本 | `string` | `取消` |
|
||||||
|
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||||
|
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
|
||||||
|
| placement | 弹出位置 | `'left' \| 'right' \| 'top' \| 'bottom'` | `right` |
|
||||||
|
| class | drawer的class | `string` | - |
|
||||||
|
| zIndex | ZIndex层级 | `number` | `1000` |
|
||||||
|
| overlayBlur | 遮罩模糊度 | `number` | - |
|
||||||
|
| connectedComponent | 连接组件 | `Component` | - |
|
||||||
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
|
| appendToMain | 挂载到内容区 | `boolean` | `false` |
|
||||||
|
|
||||||
|
## Event 事件
|
||||||
|
|
||||||
|
| 事件名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| onBeforeClose | 关闭前触发 | `() => boolean \| Promise<boolean>` |
|
||||||
|
| onCancel | 取消按钮触发 | `() => void` |
|
||||||
|
| onConfirm | 确认按钮触发 | `() => void` |
|
||||||
|
| onOpenChange | 打开/关闭时触发 | `(isOpen: boolean) => void` |
|
||||||
|
| onOpened | 打开动画完毕 | `() => void` |
|
||||||
|
| onClosed | 关闭动画完毕 | `() => void` |
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| default | 抽屉内容 |
|
||||||
|
| prepend-footer | 取消按钮左侧 |
|
||||||
|
| center-footer | 取消和确认中间 |
|
||||||
|
| append-footer | 确认按钮右侧 |
|
||||||
|
| close-icon | 关闭按钮图标 |
|
||||||
|
| extra | 额外内容(标题右侧) |
|
||||||
|
|
||||||
|
## drawerApi 方法
|
||||||
|
|
||||||
|
| 方法 | 描述 | 类型 |
|
||||||
|
|------|------|------|
|
||||||
|
| open | 打开抽屉 | `() => void` |
|
||||||
|
| close | 关闭抽屉 | `() => void` |
|
||||||
|
| setState | 设置状态 | `(state) => drawerApi` |
|
||||||
|
| setData | 设置共享数据 | `<T>(data: T) => drawerApi` |
|
||||||
|
| getData | 获取共享数据 | `<T>() => T` |
|
||||||
|
| useStore | 获取响应式状态 | - |
|
||||||
|
| lock | 锁定抽屉 | `(isLock?: boolean) => drawerApi` |
|
||||||
|
| unlock | 解锁抽屉 | `() => drawerApi` |
|
||||||
|
|
||||||
|
## 设置默认属性
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// apps/<app>/src/bootstrap.ts
|
||||||
|
import { setDefaultDrawerProps } from '@vben/common-ui';
|
||||||
|
|
||||||
|
setDefaultDrawerProps({
|
||||||
|
zIndex: 2000,
|
||||||
|
placement: 'left',
|
||||||
|
});
|
||||||
|
```
|
||||||
361
skills/references/components/business/form.md
Normal file
361
skills/references/components/business/form.md
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
# Vben Form 表单
|
||||||
|
|
||||||
|
框架提供的表单组件,基于 [vee-validate](https://vee-validate.logaretm.com/v4/) 进行表单验证,支持多UI框架适配。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '姓名',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'age',
|
||||||
|
label: '年龄',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: 1 },
|
||||||
|
{ label: '禁用', value: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 表单提交
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: [...],
|
||||||
|
handleSubmit: async (values) => {
|
||||||
|
console.log('提交数据:', values);
|
||||||
|
// 调用API保存数据
|
||||||
|
},
|
||||||
|
handleReset: () => {
|
||||||
|
console.log('重置表单');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手动提交
|
||||||
|
async function submit() {
|
||||||
|
const values = await formApi.validateAndSubmitForm();
|
||||||
|
console.log(values);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 表单校验
|
||||||
|
|
||||||
|
### 预定义规则
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const schema = [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '姓名',
|
||||||
|
rules: 'required', // 必填
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'type',
|
||||||
|
label: '类型',
|
||||||
|
rules: 'selectRequired', // 下拉必选
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zod 校验
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
|
||||||
|
const schema = [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
rules: z.string().email({ message: '请输入正确的邮箱' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'password',
|
||||||
|
label: '密码',
|
||||||
|
rules: z.string().min(6, { message: '密码至少6位' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'phone',
|
||||||
|
label: '手机号',
|
||||||
|
rules: z.string().regex(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 表单联动
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const schema = [
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'type',
|
||||||
|
label: '类型',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '个人', value: 'personal' },
|
||||||
|
{ label: '企业', value: 'company' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'companyName',
|
||||||
|
label: '企业名称',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['type'],
|
||||||
|
// 显示条件
|
||||||
|
show: (values) => values.type === 'company',
|
||||||
|
// 必填条件
|
||||||
|
required: (values) => values.type === 'company',
|
||||||
|
// 动态组件参数
|
||||||
|
componentProps: (values) => ({
|
||||||
|
placeholder: values.type === 'company' ? '请输入企业名称' : '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查询表单
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: [...],
|
||||||
|
// 查询表单不触发验证
|
||||||
|
handleSubmit: (values) => {
|
||||||
|
emit('search', values);
|
||||||
|
},
|
||||||
|
// 字段变化时提交(防抖)
|
||||||
|
submitOnChange: true,
|
||||||
|
// 显示折叠按钮
|
||||||
|
showCollapseButton: true,
|
||||||
|
collapsedRows: 1,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 表单操作
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Form, formApi] = useVbenForm({...});
|
||||||
|
|
||||||
|
// 获取表单值
|
||||||
|
async function getValues() {
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
console.log(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置表单值
|
||||||
|
async function setValues() {
|
||||||
|
await formApi.setValues({
|
||||||
|
name: '张三',
|
||||||
|
age: 18,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置单个字段值
|
||||||
|
formApi.setFieldValue('name', '李四');
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
formApi.resetForm();
|
||||||
|
|
||||||
|
// 验证表单
|
||||||
|
try {
|
||||||
|
await formApi.validate();
|
||||||
|
} catch (errors) {
|
||||||
|
console.log('验证失败:', errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新schema
|
||||||
|
formApi.updateSchema([
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '新标签',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 获取字段组件实例
|
||||||
|
const inputRef = formApi.getFieldComponentRef('name');
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| layout | 表单布局 | `'horizontal' \| 'vertical' \| 'inline'` | `horizontal` |
|
||||||
|
| schema | 表单配置 | `FormSchema[]` | - |
|
||||||
|
| commonConfig | 通用配置 | `FormCommonConfig` | - |
|
||||||
|
| showDefaultActions | 显示默认操作按钮 | `boolean` | `true` |
|
||||||
|
| showCollapseButton | 显示折叠按钮 | `boolean` | `false` |
|
||||||
|
| collapsedRows | 折叠时显示的行数 | `number` | `1` |
|
||||||
|
| handleSubmit | 提交回调 | `(values) => void` | - |
|
||||||
|
| handleReset | 重置回调 | `() => void` | - |
|
||||||
|
| handleValuesChange | 值变化回调 | `(values, fieldsChanged) => void` | - |
|
||||||
|
| submitOnEnter | 回车提交 | `boolean` | `false` |
|
||||||
|
| submitOnChange | 字段变化提交 | `boolean` | `false` |
|
||||||
|
|
||||||
|
## FormSchema 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface FormSchema {
|
||||||
|
component: Component | string; // 组件
|
||||||
|
componentProps?: object; // 组件参数
|
||||||
|
defaultValue?: any; // 默认值
|
||||||
|
dependencies?: FormItemDependencies; // 依赖联动
|
||||||
|
description?: string; // 描述
|
||||||
|
fieldName: string; // 字段名
|
||||||
|
help?: string; // 帮助信息
|
||||||
|
hide?: boolean; // 隐藏
|
||||||
|
label?: string; // 标签
|
||||||
|
rules?: string | ZodSchema; // 校验规则
|
||||||
|
suffix?: string; // 后缀
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ComponentType =
|
||||||
|
| 'Input' // 输入框
|
||||||
|
| 'InputNumber' // 数字输入框
|
||||||
|
| 'InputPassword' // 密码输入框
|
||||||
|
| 'Textarea' // 文本域
|
||||||
|
| 'Select' // 下拉选择
|
||||||
|
| 'TreeSelect' // 树选择
|
||||||
|
| 'RadioGroup' // 单选组
|
||||||
|
| 'CheckboxGroup' // 多选组
|
||||||
|
| 'Checkbox' // 复选框
|
||||||
|
| 'Switch' // 开关
|
||||||
|
| 'DatePicker' // 日期选择
|
||||||
|
| 'RangePicker' // 日期范围
|
||||||
|
| 'TimePicker' // 时间选择
|
||||||
|
| 'Upload' // 上传
|
||||||
|
| 'Rate' // 评分
|
||||||
|
| 'AutoComplete' // 自动完成
|
||||||
|
| 'Divider' // 分割线
|
||||||
|
| 'Space'; // 间距
|
||||||
|
```
|
||||||
|
|
||||||
|
## 时间字段映射
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'RangePicker',
|
||||||
|
fieldName: 'timeRange',
|
||||||
|
label: '时间范围',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 将 timeRange 映射到 startTime 和 endTime
|
||||||
|
fieldMappingTime: [
|
||||||
|
['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| reset-before | 重置按钮之前 |
|
||||||
|
| submit-before | 提交按钮之前 |
|
||||||
|
| expand-before | 展开按钮之前 |
|
||||||
|
| expand-after | 展开按钮之后 |
|
||||||
|
| {fieldName} | 字段自定义插槽 |
|
||||||
|
|
||||||
|
## 自定义组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MyCustomComponent from './MyCustomComponent.vue';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: MyCustomComponent,
|
||||||
|
fieldName: 'custom',
|
||||||
|
label: '自定义组件',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form>
|
||||||
|
<template #custom="slotProps">
|
||||||
|
<MyCustomComponent v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 适配器配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/adapter/form.ts
|
||||||
|
import { setupVbenForm, useVbenForm as useForm } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
setupVbenForm({
|
||||||
|
config: {
|
||||||
|
baseModelPropName: 'value',
|
||||||
|
emptyStateValue: null,
|
||||||
|
modelPropNameMap: {
|
||||||
|
Checkbox: '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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useVbenForm = useForm;
|
||||||
|
```
|
||||||
252
skills/references/components/business/modal.md
Normal file
252
skills/references/components/business/modal.md
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
# Vben Modal 模态框
|
||||||
|
|
||||||
|
框架提供的模态框组件,支持拖拽、全屏、自动高度、loading等功能。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenModal } from '#/adapter';
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
title: '标题',
|
||||||
|
onConfirm: () => {
|
||||||
|
console.log('确认');
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button @click="modalApi.open()">打开</Button>
|
||||||
|
<Modal>
|
||||||
|
弹窗内容
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件抽离
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- parent.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenModal } from '#/adapter';
|
||||||
|
import ChildForm from './child-form.vue';
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
connectedComponent: ChildForm,
|
||||||
|
onConfirm: () => {
|
||||||
|
const data = modalApi.getData();
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button @click="modalApi.open()">打开</Button>
|
||||||
|
<Modal />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- child-form.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenModal } from '#/adapter';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({...});
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onOpenChange: (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData();
|
||||||
|
formApi.setValues(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
const values = await formApi.validateAndSubmitForm();
|
||||||
|
modalApi.setData(values);
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 拖拽功能
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全屏功能
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreen: true, // 默认全屏
|
||||||
|
fullscreenButton: true, // 显示全屏按钮
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading 状态
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onConfirm: async () => {
|
||||||
|
modalApi.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
await saveData();
|
||||||
|
modalApi.close();
|
||||||
|
} finally {
|
||||||
|
modalApi.setState({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lock 锁定状态
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onConfirm: async () => {
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
await saveData();
|
||||||
|
modalApi.close();
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 动画类型
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 滑动动画(默认)
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
animationType: 'slide',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 缩放动画
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
animationType: 'scale',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 挂载到内容区域
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
appendToMain: true, // 挂载到内容区域,不遮挡导航菜单
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 需要设置 auto-content-height -->
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Modal />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| title | 标题 | `string` | - |
|
||||||
|
| titleTooltip | 标题提示 | `string` | - |
|
||||||
|
| description | 描述信息 | `string` | - |
|
||||||
|
| 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` | `true` |
|
||||||
|
| confirmLoading | 确认按钮loading | `boolean` | `false` |
|
||||||
|
| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |
|
||||||
|
| closeOnClickModal | 点击遮罩关闭 | `boolean` | `true` |
|
||||||
|
| closeOnPressEscape | ESC关闭 | `boolean` | `true` |
|
||||||
|
| confirmText | 确认按钮文本 | `string` | `确认` |
|
||||||
|
| cancelText | 取消按钮文本 | `string` | `取消` |
|
||||||
|
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||||
|
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
|
||||||
|
| class | modal的class(宽度) | `string` | - |
|
||||||
|
| contentClass | 内容区class | `string` | - |
|
||||||
|
| footerClass | 底部区class | `string` | - |
|
||||||
|
| headerClass | 顶部区class | `string` | - |
|
||||||
|
| bordered | 显示border | `boolean` | `false` |
|
||||||
|
| zIndex | ZIndex层级 | `number` | `1000` |
|
||||||
|
| overlayBlur | 遮罩模糊度 | `number` | - |
|
||||||
|
| animationType | 动画类型 | `'slide' \| 'scale'` | `slide` |
|
||||||
|
| connectedComponent | 连接组件 | `Component` | - |
|
||||||
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
|
| appendToMain | 挂载到内容区 | `boolean` | `false` |
|
||||||
|
|
||||||
|
## Event 事件
|
||||||
|
|
||||||
|
| 事件名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| onBeforeClose | 关闭前触发 | `() => boolean \| Promise<boolean>` |
|
||||||
|
| onCancel | 取消按钮触发 | `() => void` |
|
||||||
|
| onConfirm | 确认按钮触发 | `() => void` |
|
||||||
|
| onOpenChange | 打开/关闭时触发 | `(isOpen: boolean) => void` |
|
||||||
|
| onOpened | 打开动画完毕 | `() => void` |
|
||||||
|
| onClosed | 关闭动画完毕 | `() => void` |
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| default | 弹窗内容 |
|
||||||
|
| prepend-footer | 取消按钮左侧 |
|
||||||
|
| center-footer | 取消和确认中间 |
|
||||||
|
| append-footer | 确认按钮右侧 |
|
||||||
|
|
||||||
|
## modalApi 方法
|
||||||
|
|
||||||
|
| 方法 | 描述 | 类型 |
|
||||||
|
|------|------|------|
|
||||||
|
| open | 打开弹窗 | `() => void` |
|
||||||
|
| close | 关闭弹窗 | `() => void` |
|
||||||
|
| setState | 设置状态 | `(state) => modalApi` |
|
||||||
|
| setData | 设置共享数据 | `<T>(data: T) => modalApi` |
|
||||||
|
| getData | 获取共享数据 | `<T>() => T` |
|
||||||
|
| useStore | 获取响应式状态 | - |
|
||||||
|
| lock | 锁定弹窗 | `(isLock?: boolean) => modalApi` |
|
||||||
|
| unlock | 解锁弹窗 | `() => modalApi` |
|
||||||
|
|
||||||
|
## 设置默认属性
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// apps/<app>/src/bootstrap.ts
|
||||||
|
import { setDefaultModalProps } from '@vben/common-ui';
|
||||||
|
|
||||||
|
setDefaultModalProps({
|
||||||
|
zIndex: 2000,
|
||||||
|
draggable: true,
|
||||||
|
fullscreenButton: false,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设置宽度
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
class: 'w-[600px]', // Tailwind CSS
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
87
skills/references/components/business/page.md
Normal file
87
skills/references/components/business/page.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# Page 页面组件
|
||||||
|
|
||||||
|
`Page` 是页面内容区最常用的顶层布局容器,内置了标题区、内容区和底部区三部分结构。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page title="页面标题" description="页面描述">
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自动高度
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 开启自动高度计算,内容区会自动扣减头部和底部高度 -->
|
||||||
|
<Page title="页面标题" auto-content-height>
|
||||||
|
<div class="h-full overflow-auto">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
title="用户管理"
|
||||||
|
description="管理系统用户信息"
|
||||||
|
auto-content-height
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<Button type="primary">新增用户</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<div class="p-4">
|
||||||
|
<Table />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<Pagination />
|
||||||
|
</template>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| title | 页面标题 | `string` | - |
|
||||||
|
| description | 页面描述 | `string` | - |
|
||||||
|
| contentClass | 内容区域的class | `string` | - |
|
||||||
|
| headerClass | 头部区域的class | `string` | - |
|
||||||
|
| footerClass | 底部区域的class | `string` | - |
|
||||||
|
| autoContentHeight | 自动计算内容区高度 | `boolean` | `false` |
|
||||||
|
| heightOffset | 额外扣减的高度偏移量 | `number` | `0` |
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| default | 页面内容 |
|
||||||
|
| title | 页面标题 |
|
||||||
|
| description | 页面描述 |
|
||||||
|
| extra | 页面头部右侧内容 |
|
||||||
|
| footer | 页面底部内容 |
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 如果 `title`、`description`、`extra` 三者都没有提供有效内容,头部区域不会渲染
|
||||||
|
- 开启 `autoContentHeight` 时,内容区需要设置 `overflow-auto` 来处理滚动
|
||||||
|
- 配合 Modal/Drawer 的 `appendToMain` 属性使用时,需要开启 `autoContentHeight`
|
||||||
266
skills/references/components/business/table.md
Normal file
266
skills/references/components/business/table.md
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
# Vben Vxe Table 表格
|
||||||
|
|
||||||
|
基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 和 `Vben Form` 做了二次封装,用于构建带搜索表单的列表页面。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [
|
||||||
|
{ type: 'seq', width: 50 },
|
||||||
|
{ field: 'name', title: '名称' },
|
||||||
|
{ field: 'age', title: '年龄' },
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
{ name: '张三', age: 18 },
|
||||||
|
{ name: '李四', age: 20 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Grid />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 远程加载
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getUserListApi } from '#/api';
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [
|
||||||
|
{ type: 'seq', width: 50 },
|
||||||
|
{ field: 'name', title: '名称' },
|
||||||
|
{ field: 'age', title: '年龄' },
|
||||||
|
],
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }) => {
|
||||||
|
const res = await getUserListApi({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
items: res.data.list,
|
||||||
|
total: res.data.total,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 搜索表单
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: 1 },
|
||||||
|
{ label: '禁用', value: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
toolbarConfig: {
|
||||||
|
search: true, // 显示搜索面板开关按钮
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
const res = await getUserListApi({
|
||||||
|
...formValues,
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: [...],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 树形表格
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [...],
|
||||||
|
treeConfig: {
|
||||||
|
transform: true,
|
||||||
|
parentField: 'parentId',
|
||||||
|
rowField: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 固定列
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const columns = [
|
||||||
|
{ field: 'name', title: '名称', fixed: 'left', width: 100 },
|
||||||
|
{ field: 'age', title: '年龄' },
|
||||||
|
{ field: 'address', title: '地址' },
|
||||||
|
{ field: 'action', title: '操作', fixed: 'right', width: 100 },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 单元格编辑
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
editConfig: {
|
||||||
|
mode: 'cell', // 或 'row'
|
||||||
|
trigger: 'click',
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '名称',
|
||||||
|
editRender: { name: 'input' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义渲染器
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 适配器配置
|
||||||
|
import { h } from 'vue';
|
||||||
|
import { Image, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
vxeUI.renderer.add('CellImage', {
|
||||||
|
renderTableDefault(_renderOpts, params) {
|
||||||
|
const { column, row } = params;
|
||||||
|
return h(Image, { src: row[column.field] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vxeUI.renderer.add('CellLink', {
|
||||||
|
renderTableDefault(renderOpts) {
|
||||||
|
const { props } = renderOpts;
|
||||||
|
return h(Button, { size: 'small', type: 'link' }, {
|
||||||
|
default: () => props?.text,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
field: 'avatar',
|
||||||
|
title: '头像',
|
||||||
|
cellRender: { name: 'CellImage' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'link',
|
||||||
|
title: '链接',
|
||||||
|
cellRender: { name: 'CellLink', props: { text: '查看' } },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## GridApi 方法
|
||||||
|
|
||||||
|
| 方法名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| setLoading | 设置loading状态 | `(loading: boolean) => void` |
|
||||||
|
| setGridOptions | 更新gridOptions | `(options) => void` |
|
||||||
|
| reload | 重新加载,重置分页 | `(params?) => void` |
|
||||||
|
| query | 重新查询,保留分页 | `(params?) => void` |
|
||||||
|
| grid | vxe-grid实例 | `VxeGridInstance` |
|
||||||
|
| formApi | 搜索表单API | `FormApi` |
|
||||||
|
| toggleSearchForm | 切换搜索表单状态 | `(show?: boolean) => boolean` |
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| tableTitle | 表格标题 | `string` |
|
||||||
|
| tableTitleHelp | 表格标题帮助信息 | `string` |
|
||||||
|
| class | 外层容器的class | `string` |
|
||||||
|
| gridClass | vxe-grid的class | `string` |
|
||||||
|
| gridOptions | vxe-grid配置 | `VxeTableGridOptions` |
|
||||||
|
| gridEvents | vxe-grid事件 | `VxeGridListeners` |
|
||||||
|
| formOptions | 搜索表单配置 | `VbenFormProps` |
|
||||||
|
| showSearchForm | 是否显示搜索表单 | `boolean` |
|
||||||
|
| separator | 搜索表单与表格的分隔条 | `boolean \| SeparatorOptions` |
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| toolbar-actions | 工具栏左侧区域 |
|
||||||
|
| toolbar-tools | 工具栏右侧区域 |
|
||||||
|
| table-title | 自定义表格标题 |
|
||||||
|
| form-* | 搜索表单插槽转发 |
|
||||||
|
|
||||||
|
## 适配器配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/adapter/vxe-table.ts
|
||||||
|
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||||
|
import { useVbenForm } from './form';
|
||||||
|
|
||||||
|
setupVbenVxeTable({
|
||||||
|
configVxeTable: (vxeUI) => {
|
||||||
|
vxeUI.setConfig({
|
||||||
|
grid: {
|
||||||
|
align: 'center',
|
||||||
|
border: false,
|
||||||
|
columnConfig: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
minHeight: 180,
|
||||||
|
proxyConfig: {
|
||||||
|
autoLoad: true,
|
||||||
|
response: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
list: 'items',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showOverflow: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
useVbenForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { useVbenVxeGrid };
|
||||||
|
```
|
||||||
128
skills/references/components/common/count-to.md
Normal file
128
skills/references/components/common/count-to.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Vben CountToAnimator 数字动画
|
||||||
|
|
||||||
|
用于展示数字滚动动画效果。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CountToAnimator } from '@vben/common-ui';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CountToAnimator :start-val="0" :end-val="2024" :duration="1500" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义格式
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 带前缀和后缀 -->
|
||||||
|
<CountToAnimator
|
||||||
|
:start-val="0"
|
||||||
|
:end-val="9999"
|
||||||
|
prefix="¥"
|
||||||
|
suffix="元"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 带小数位 -->
|
||||||
|
<CountToAnimator
|
||||||
|
:start-val="0"
|
||||||
|
:end-val="99.99"
|
||||||
|
:decimals="2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 自定义分隔符 -->
|
||||||
|
<CountToAnimator
|
||||||
|
:start-val="0"
|
||||||
|
:end-val="1000000"
|
||||||
|
separator=","
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 手动控制
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { CountToAnimator } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const countRef = ref();
|
||||||
|
|
||||||
|
function handleStart() {
|
||||||
|
countRef.value?.reset();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CountToAnimator
|
||||||
|
ref="countRef"
|
||||||
|
:start-val="0"
|
||||||
|
:end-val="9999"
|
||||||
|
:autoplay="false"
|
||||||
|
/>
|
||||||
|
<Button @click="handleStart">开始动画</Button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| startVal | 起始值 | `number` | `0` |
|
||||||
|
| endVal | 结束值 | `number` | `2021` |
|
||||||
|
| duration | 动画持续时间(ms) | `number` | `1500` |
|
||||||
|
| autoplay | 是否自动播放 | `boolean` | `true` |
|
||||||
|
| prefix | 前缀 | `string` | `''` |
|
||||||
|
| suffix | 后缀 | `string` | `''` |
|
||||||
|
| separator | 千分位分隔符 | `string` | `','` |
|
||||||
|
| decimal | 小数点分隔符 | `string` | `'.'` |
|
||||||
|
| decimals | 保留小数位数 | `number` | `0` |
|
||||||
|
| color | 文本颜色 | `string` | `''` |
|
||||||
|
| useEasing | 是否启用过渡预设 | `boolean` | `true` |
|
||||||
|
| transition | 过渡预设名称 | `string` | `'linear'` |
|
||||||
|
|
||||||
|
## Events 事件
|
||||||
|
|
||||||
|
| 事件名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| started | 动画开始时触发 | `() => void` |
|
||||||
|
| finished | 动画结束时触发 | `() => void` |
|
||||||
|
|
||||||
|
## Methods 方法
|
||||||
|
|
||||||
|
| 方法名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| reset | 重置并重新执行动画 | `() => void` |
|
||||||
|
|
||||||
|
## 过渡预设
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<CountToAnimator
|
||||||
|
:end-val="1000"
|
||||||
|
transition="easeOutQuart"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
可用的过渡预设:
|
||||||
|
- `linear`
|
||||||
|
- `easeInQuad`
|
||||||
|
- `easeOutQuad`
|
||||||
|
- `easeInOutQuad`
|
||||||
|
- `easeInCubic`
|
||||||
|
- `easeOutCubic`
|
||||||
|
- `easeInOutCubic`
|
||||||
|
- `easeOutQuart`
|
||||||
|
- `easeOutExpo`
|
||||||
|
- 等等...
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
- 统计数据展示
|
||||||
|
- 仪表盘数字
|
||||||
|
- 倒计时效果
|
||||||
|
- 金融数字展示
|
||||||
113
skills/references/components/common/ellipsis-text.md
Normal file
113
skills/references/components/common/ellipsis-text.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# Vben EllipsisText 省略文本
|
||||||
|
|
||||||
|
用于展示超长文本,支持省略、Tooltip 提示以及点击展开收起。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<EllipsisText :max-width="200">
|
||||||
|
这是一段很长的文本内容,超出部分会被省略显示...
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可折叠文本
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<EllipsisText :line="2" expand>
|
||||||
|
这是一段很长的文本内容,默认显示2行,点击可以展开查看全部内容。
|
||||||
|
展开后再次点击可以收起。
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义 Tooltip
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<EllipsisText>
|
||||||
|
这是一段文本
|
||||||
|
<template #tooltip>
|
||||||
|
<div>自定义提示内容</div>
|
||||||
|
</template>
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 仅省略时显示 Tooltip
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 只有文本被截断时才显示 Tooltip -->
|
||||||
|
<EllipsisText tooltip-when-ellipsis>
|
||||||
|
这是一段文本
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 属性
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| expand | 是否支持点击展开/收起 | `boolean` | `false` |
|
||||||
|
| line | 文本最大显示行数 | `number` | `1` |
|
||||||
|
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
||||||
|
| placement | 提示浮层位置 | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` |
|
||||||
|
| tooltip | 是否启用文本提示 | `boolean` | `true` |
|
||||||
|
| tooltipWhenEllipsis | 是否仅在文本被截断时显示提示 | `boolean` | `false` |
|
||||||
|
| ellipsisThreshold | 文本截断检测阈值 | `number` | `3` |
|
||||||
|
| tooltipBackgroundColor | 提示背景色 | `string` | `''` |
|
||||||
|
| tooltipColor | 提示文字颜色 | `string` | `''` |
|
||||||
|
| tooltipFontSize | 提示文字大小(px) | `number` | `14` |
|
||||||
|
| tooltipMaxWidth | 提示内容最大宽度(px) | `number` | - |
|
||||||
|
| tooltipOverlayStyle | 提示内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
|
||||||
|
|
||||||
|
## Events 事件
|
||||||
|
|
||||||
|
| 事件名 | 描述 | 类型 |
|
||||||
|
|--------|------|------|
|
||||||
|
| expandChange | 展开状态变化时触发 | `(isExpand: boolean) => void` |
|
||||||
|
|
||||||
|
## 插槽
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
|--------|------|
|
||||||
|
| default | 文本内容 |
|
||||||
|
| tooltip | 自定义提示内容 |
|
||||||
|
|
||||||
|
## 表格中使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
title: '描述',
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
return h(EllipsisText, {
|
||||||
|
maxWidth: 200,
|
||||||
|
line: 2,
|
||||||
|
expand: true,
|
||||||
|
}, () => row.description);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
- 表格长文本列
|
||||||
|
- 列表项描述
|
||||||
|
- 评论内容展示
|
||||||
|
- 日志信息展示
|
||||||
158
skills/references/core/access.md
Normal file
158
skills/references/core/access.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# 权限控制详细配置
|
||||||
|
|
||||||
|
## 权限模式
|
||||||
|
|
||||||
|
### 前端访问控制(frontend)
|
||||||
|
|
||||||
|
路由权限在前端固定配置,适合角色较固定的系统。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
accessMode: 'frontend',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路由配置
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
authority: ['super', 'admin'], // 指定角色
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录时设置用户角色
|
||||||
|
authStore.setUserInfo({
|
||||||
|
...userInfo,
|
||||||
|
roles: ['super', 'admin'], // 必须是数组
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 后端访问控制(backend)
|
||||||
|
|
||||||
|
通过接口动态生成路由表。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
accessMode: 'backend',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/router/access.ts
|
||||||
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
|
return await generateAccessible(preferences.app.accessMode, {
|
||||||
|
fetchMenuListAsync: async () => {
|
||||||
|
return await getAllMenus(); // 后端返回菜单数据
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
后端菜单数据格式:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const menus = [
|
||||||
|
{
|
||||||
|
name: 'Dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: '/dashboard/index', // 视图路径
|
||||||
|
meta: {
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: 'mdi:view-dashboard',
|
||||||
|
noBasicLayout: false, // 是否不使用基础布局
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 混合访问控制(mixed)
|
||||||
|
|
||||||
|
同时使用前端和后端权限控制。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
accessMode: 'mixed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按钮权限控制
|
||||||
|
|
||||||
|
### 获取权限码
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/store/auth.ts
|
||||||
|
const accessCodes = await getAccessCodes();
|
||||||
|
accessStore.setAccessCodes(accessCodes);
|
||||||
|
// 返回格式: ['AC_100100', 'AC_100110', 'AC_100120']
|
||||||
|
```
|
||||||
|
|
||||||
|
### 组件方式
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { AccessControl } from '@vben/access';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 权限码方式 -->
|
||||||
|
<AccessControl :codes="['AC_100100']" type="code">
|
||||||
|
<Button>有权限可见</Button>
|
||||||
|
</AccessControl>
|
||||||
|
|
||||||
|
<!-- 角色方式 -->
|
||||||
|
<AccessControl :codes="['super', 'admin']">
|
||||||
|
<Button>管理员可见</Button>
|
||||||
|
</AccessControl>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 方式
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
|
|
||||||
|
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 权限码判断 -->
|
||||||
|
<Button v-if="hasAccessByCodes(['AC_100100'])">有权限可见</Button>
|
||||||
|
|
||||||
|
<!-- 角色判断 -->
|
||||||
|
<Button v-if="hasAccessByRoles(['admin'])">管理员可见</Button>
|
||||||
|
|
||||||
|
<!-- 多权限满足其一 -->
|
||||||
|
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_100110'])">
|
||||||
|
任一权限可见
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 指令方式
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 权限码指令 -->
|
||||||
|
<Button v-access:code="'AC_100100'">单个权限码</Button>
|
||||||
|
<Button v-access:code="['AC_100100', 'AC_100110']">多个权限码</Button>
|
||||||
|
|
||||||
|
<!-- 角色指令 -->
|
||||||
|
<Button v-access:role="'super'">单个角色</Button>
|
||||||
|
<Button v-access:role="['super', 'admin']">多个角色</Button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 菜单可见但禁止访问
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
menuVisibleWithForbidden: true, // 菜单可见,访问跳转403
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
182
skills/references/core/api.md
Normal file
182
skills/references/core/api.md
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# API 请求与服务端交互
|
||||||
|
|
||||||
|
## 请求客户端配置
|
||||||
|
|
||||||
|
配置文件位于 `src/api/request.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { RequestClient } from '@vben/request';
|
||||||
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
|
function createRequestClient(baseURL: string) {
|
||||||
|
const client = new RequestClient({ baseURL });
|
||||||
|
|
||||||
|
// 请求拦截器 - 添加 Token
|
||||||
|
client.addRequestInterceptor({
|
||||||
|
fulfilled: async (config) => {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
config.headers.Authorization = `Bearer ${accessStore.accessToken}`;
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 响应拦截器 - 处理返回数据
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
defaultResponseInterceptor({
|
||||||
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
|
successCode: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器 - Token 过期处理
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
authenticateResponseInterceptor({
|
||||||
|
client,
|
||||||
|
doReAuthenticate,
|
||||||
|
doRefreshToken,
|
||||||
|
enableRefreshToken: true,
|
||||||
|
formatToken: (token) => token ? `Bearer ${token}` : null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器 - 错误处理
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
errorMessageResponseInterceptor((msg, error) => {
|
||||||
|
message.error(msg);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestClient = createRequestClient(apiURL);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 定义示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/user.ts
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
interface UserInfo {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
realName: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET 请求
|
||||||
|
export async function getUserInfoApi() {
|
||||||
|
return requestClient.get<UserInfo>('/user/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST 请求
|
||||||
|
export async function loginApi(data: { username: string; password: string }) {
|
||||||
|
return requestClient.post<{ accessToken: string }>('/auth/login', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT 请求
|
||||||
|
export async function updateUserApi(user: Partial<UserInfo>) {
|
||||||
|
return requestClient.put<UserInfo>(`/user/${user.id}`, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE 请求
|
||||||
|
export async function deleteUserApi(id: number) {
|
||||||
|
return requestClient.delete(`/user/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带参数的 GET 请求
|
||||||
|
export async function getUserListApi(params: { page: number; size: number }) {
|
||||||
|
return requestClient.get<{ list: UserInfo[]; total: number }>('/user/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ExtendOptions = {
|
||||||
|
// 参数序列化方式
|
||||||
|
paramsSerializer?: 'brackets' | 'comma' | 'indices' | 'repeat';
|
||||||
|
|
||||||
|
// 响应返回方式
|
||||||
|
// 'raw' - 原始 AxiosResponse
|
||||||
|
// 'body' - 响应体(不检查 code)
|
||||||
|
// 'data' - 解构后的 data 字段(默认)
|
||||||
|
responseReturn?: 'body' | 'data' | 'raw';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多接口地址
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { apiURL, otherApiURL } = useAppConfig(
|
||||||
|
import.meta.env,
|
||||||
|
import.meta.env.PROD,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const requestClient = createRequestClient(apiURL);
|
||||||
|
export const otherRequestClient = createRequestClient(otherApiURL);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代理配置
|
||||||
|
|
||||||
|
开发环境代理配置在 `vite.config.mts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig(async () => {
|
||||||
|
return {
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:5320/api',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 刷新 Token
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
enableRefreshToken: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/api/request.ts
|
||||||
|
async function doRefreshToken() {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const resp = await refreshTokenApi();
|
||||||
|
const newToken = resp.data;
|
||||||
|
accessStore.setAccessToken(newToken);
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 接口返回格式
|
||||||
|
|
||||||
|
默认接口返回格式:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface HttpResponse<T = any> {
|
||||||
|
code: number; // 0 表示成功
|
||||||
|
data: T;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如需自定义,修改 `defaultResponseInterceptor` 配置。
|
||||||
126
skills/references/core/icons.md
Normal file
126
skills/references/core/icons.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# 图标使用
|
||||||
|
|
||||||
|
框架支持多种图标使用方式。
|
||||||
|
|
||||||
|
## Iconify 图标
|
||||||
|
|
||||||
|
推荐使用 [Iconify](https://iconify.design/),支持100+图标集。
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon } from '@vben/icons';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 使用 Iconify 图标 -->
|
||||||
|
<Icon icon="mdi:home" />
|
||||||
|
<Icon icon="carbon:user" />
|
||||||
|
<Icon icon="ant-design:setting" />
|
||||||
|
<Icon icon="lucide:search" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图标大小
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<Icon icon="mdi:home" class="size-4" />
|
||||||
|
<Icon icon="mdi:home" class="size-6" />
|
||||||
|
<Icon icon="mdi:home" class="size-8" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图标颜色
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<Icon icon="mdi:home" class="text-primary" />
|
||||||
|
<Icon icon="mdi:home" class="text-red-500" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由菜单图标
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:home',
|
||||||
|
title: '首页',
|
||||||
|
},
|
||||||
|
name: 'Home',
|
||||||
|
path: '/home',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:user',
|
||||||
|
title: '用户管理',
|
||||||
|
},
|
||||||
|
name: 'User',
|
||||||
|
path: '/user',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用图标集
|
||||||
|
|
||||||
|
| 图标集 | 前缀 | 示例 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Material Design | `mdi:` | `mdi:home` |
|
||||||
|
| Carbon | `carbon:` | `carbon:user` |
|
||||||
|
| Ant Design | `ant-design:` | `ant-design:setting` |
|
||||||
|
| Lucide | `lucide:` | `lucide:search` |
|
||||||
|
| Font Awesome | `fa:` | `fa:home` |
|
||||||
|
| Remix Icon | `ri:` | `ri:home-line` |
|
||||||
|
|
||||||
|
## SVG 图标
|
||||||
|
|
||||||
|
### 全局注册 SVG 图标
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 将 SVG 文件放入 src/assets/icons/ 目录
|
||||||
|
// 文件名即为图标名
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<svg-icon name="custom-icon" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tailwind CSS 图标
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="i-mdi-home"></div>
|
||||||
|
<div class="i-carbon-user text-xl"></div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 图标搜索
|
||||||
|
|
||||||
|
- [Iconify 图标搜索](https://icon-sets.iconify.design/)
|
||||||
|
- [Material Design Icons](https://pictogrammers.com/library/mdi/)
|
||||||
|
- [Lucide Icons](https://lucide.dev/icons/)
|
||||||
|
|
||||||
|
## 自定义图标组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon } from '@vben/icons';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
icon: string;
|
||||||
|
size?: number;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Icon
|
||||||
|
:icon="icon"
|
||||||
|
:class="size ? `size-${size}` : 'size-5'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
141
skills/references/core/locale.md
Normal file
141
skills/references/core/locale.md
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# 国际化
|
||||||
|
|
||||||
|
项目使用 [Vue i18n](https://kazupon.github.io/vue-i18n/) 进行国际化处理。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 路由配置中使用
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: $t('page.home.title'),
|
||||||
|
},
|
||||||
|
name: 'Home',
|
||||||
|
path: '/home',
|
||||||
|
component: () => import('#/views/home/index.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 组件中使用 -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>{{ $t('common.confirm') }}</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 语言配置
|
||||||
|
|
||||||
|
在 `preferences.ts` 中设置默认语言:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
locale: 'zh-CN', // 'en-US' | 'zh-CN' | ...
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的语言
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type SupportedLanguagesType =
|
||||||
|
| 'en-US'
|
||||||
|
| 'zh-CN'
|
||||||
|
| 'zh-TW'
|
||||||
|
| 'ko-KR'
|
||||||
|
| 'ru-RU'
|
||||||
|
| 'ja-JP';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 语言包位置
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/locales/
|
||||||
|
├── langs/ # 语言包文件
|
||||||
|
│ ├── en-US.json
|
||||||
|
│ ├── zh-CN.json
|
||||||
|
│ └── ...
|
||||||
|
└── src/ # 国际化相关代码
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增语言包
|
||||||
|
|
||||||
|
1. 在 `packages/locales/langs/` 目录下新建语言文件,如 `ja-JP.json`
|
||||||
|
2. 在 `packages/types/src.ts` 中添加类型定义
|
||||||
|
3. 在 `preferences.ts` 中配置 `locale: 'ja-JP'`
|
||||||
|
|
||||||
|
## 语言包结构示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"confirm": "确认",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"search": "搜索",
|
||||||
|
"reset": "重置"
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"home": {
|
||||||
|
"title": "首页",
|
||||||
|
"welcome": "欢迎"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"formRules": {
|
||||||
|
"required": "请输入{0}",
|
||||||
|
"selectRequired": "请选择{0}"
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"input": "请输入",
|
||||||
|
"select": "请选择"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 远程加载语言包
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 支持从远程服务器加载语言包
|
||||||
|
import { setI18nLanguage } from '@vben/locales';
|
||||||
|
|
||||||
|
async function loadLocaleMessages(locale: string) {
|
||||||
|
const messages = await fetch(`/locales/${locale}.json`).then(res => res.json());
|
||||||
|
setI18nLanguage(locale, messages);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 切换语言
|
||||||
|
|
||||||
|
框架内置了语言切换组件,可通过偏好设置开启:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
languageToggle: true, // 显示语言切换按钮
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 在代码中切换
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useLocale } from '@vben/locales';
|
||||||
|
|
||||||
|
const { changeLocale } = useLocale();
|
||||||
|
|
||||||
|
// 切换到英文
|
||||||
|
changeLocale('en-US');
|
||||||
|
```
|
||||||
185
skills/references/core/login.md
Normal file
185
skills/references/core/login.md
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# 登录对接
|
||||||
|
|
||||||
|
对接自定义后端登录接口。
|
||||||
|
|
||||||
|
## 需要实现的接口
|
||||||
|
|
||||||
|
### 1. 登录接口
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/core/auth.ts
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export interface LoginParams {
|
||||||
|
password: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResult {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginApi(data: LoginParams) {
|
||||||
|
return requestClient.post<LoginResult>('/auth/login', data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 获取用户信息接口
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/core/user.ts
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
realName: string;
|
||||||
|
avatar: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserInfoApi() {
|
||||||
|
return requestClient.get<UserInfo>('/user/info');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 获取权限码接口(可选)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/core/auth.ts
|
||||||
|
export async function getAccessCodesApi() {
|
||||||
|
return requestClient.get<string[]>('/auth/codes');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 刷新Token接口(可选)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/core/auth.ts
|
||||||
|
export async function refreshTokenApi() {
|
||||||
|
return requestClient.post<{ accessToken: string }>('/auth/refresh-token');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录页配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/router/routes/core.ts
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: 'Login',
|
||||||
|
},
|
||||||
|
name: 'Login',
|
||||||
|
path: '/login',
|
||||||
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 权限模式配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
// 前端模式:路由权限在前端定义
|
||||||
|
// 后端模式:路由从后端获取
|
||||||
|
// 混合模式:前端定义路由,后端返回权限码
|
||||||
|
accessMode: 'frontend',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录过期处理
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
// 'page' - 跳转到登录页
|
||||||
|
// 'modal' - 显示登录过期弹窗
|
||||||
|
loginExpiredMode: 'page',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Token刷新配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
enableRefreshToken: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/api/request.ts
|
||||||
|
async function doRefreshToken() {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const resp = await refreshTokenApi();
|
||||||
|
const newToken = resp.data.accessToken;
|
||||||
|
accessStore.setAccessToken(newToken);
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义登录页布局
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
// 'panel-left' | 'panel-right' | 'panel-top'
|
||||||
|
authPageLayout: 'panel-right',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录表单配置
|
||||||
|
|
||||||
|
登录表单在 `packages/@core/layouts/src/authentication/login.vue` 中定义,可以通过覆盖组件或修改适配器来自定义:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 自定义登录表单字段
|
||||||
|
const loginFormSchema: VbenFormSchema[] = [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入用户名',
|
||||||
|
},
|
||||||
|
fieldName: 'username',
|
||||||
|
label: '用户名',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入密码',
|
||||||
|
},
|
||||||
|
fieldName: 'password',
|
||||||
|
label: '密码',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第三方登录
|
||||||
|
|
||||||
|
如需对接第三方登录,可在登录页添加第三方登录按钮:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="third-party-login">
|
||||||
|
<Button @click="handleWechatLogin">微信登录</Button>
|
||||||
|
<Button @click="handleGithubLogin">GitHub登录</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
function handleWechatLogin() {
|
||||||
|
// 跳转到微信扫码页或打开微信登录弹窗
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
207
skills/references/core/preferences.md
Normal file
207
skills/references/core/preferences.md
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
# 偏好设置完整配置
|
||||||
|
|
||||||
|
## App 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface AppPreferences {
|
||||||
|
accessMode: 'frontend' | 'backend' | 'mixed'; // 权限模式
|
||||||
|
authPageLayout: AuthPageLayoutType; // 登录页布局
|
||||||
|
checkUpdatesInterval: number; // 检查更新间隔
|
||||||
|
colorGrayMode: boolean; // 灰色模式
|
||||||
|
colorWeakMode: boolean; // 色弱模式
|
||||||
|
compact: boolean; // 紧凑模式
|
||||||
|
contentCompact: 'wide' | 'full'; // 内容紧凑模式
|
||||||
|
contentCompactWidth: number; // 内容宽度
|
||||||
|
contentPadding: number; // 内容内边距
|
||||||
|
defaultAvatar: string; // 默认头像
|
||||||
|
defaultHomePath: string; // 默认首页路径
|
||||||
|
dynamicTitle: boolean; // 动态标题
|
||||||
|
enableCheckUpdates: boolean; // 检查更新
|
||||||
|
enablePreferences: boolean; // 显示偏好设置
|
||||||
|
enableCopyPreferences: boolean; // 复制偏好设置按钮
|
||||||
|
enableRefreshToken: boolean; // 刷新Token
|
||||||
|
isMobile: boolean; // 移动端模式
|
||||||
|
layout: LayoutType; // 布局方式
|
||||||
|
locale: 'zh-CN' | 'en-US'; // 语言
|
||||||
|
loginExpiredMode: 'page' | 'modal'; // 登录过期模式
|
||||||
|
name: string; // 应用名
|
||||||
|
preferencesButtonPosition: string; // 偏好设置按钮位置
|
||||||
|
watermark: boolean; // 水印
|
||||||
|
zIndex: number; // z-index
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ThemePreferences {
|
||||||
|
builtinType: BuiltinThemeType; // 内置主题
|
||||||
|
colorDestructive: string; // 错误色
|
||||||
|
colorPrimary: string; // 主题色
|
||||||
|
colorSuccess: string; // 成功色
|
||||||
|
colorWarning: string; // 警告色
|
||||||
|
mode: 'light' | 'dark'; // 主题模式
|
||||||
|
radius: string; // 圆角
|
||||||
|
semiDarkHeader: boolean; // 半深色顶栏
|
||||||
|
semiDarkSidebar: boolean; // 半深色侧边栏
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
内置主题列表:
|
||||||
|
- `default`, `violet`, `pink`, `rose`, `sky-blue`, `deep-blue`
|
||||||
|
- `green`, `deep-green`, `orange`, `yellow`
|
||||||
|
- `zinc`, `neutral`, `slate`, `gray`, `custom`
|
||||||
|
|
||||||
|
## Sidebar 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface SidebarPreferences {
|
||||||
|
autoActivateChild: boolean; // 点击目录自动激活子菜单
|
||||||
|
collapsed: boolean; // 折叠状态
|
||||||
|
collapsedButton: boolean; // 折叠按钮可见
|
||||||
|
collapsedShowTitle: boolean; // 折叠时显示标题
|
||||||
|
collapseWidth: number; // 折叠宽度
|
||||||
|
enable: boolean; // 启用侧边栏
|
||||||
|
expandOnHover: boolean; // 悬停展开
|
||||||
|
extraCollapse: boolean; // 扩展区域折叠
|
||||||
|
extraCollapsedWidth: number; // 扩展区域折叠宽度
|
||||||
|
fixedButton: boolean; // 固定按钮
|
||||||
|
hidden: boolean; // 隐藏侧边栏
|
||||||
|
mixedWidth: number; // 混合布局宽度
|
||||||
|
width: number; // 侧边栏宽度
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tabbar 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface TabbarPreferences {
|
||||||
|
draggable: boolean; // 拖拽
|
||||||
|
enable: boolean; // 启用标签页
|
||||||
|
height: number; // 高度
|
||||||
|
keepAlive: boolean; // 缓存
|
||||||
|
maxCount: number; // 最大数量
|
||||||
|
middleClickToClose: boolean; // 中键关闭
|
||||||
|
persist: boolean; // 持久化
|
||||||
|
showIcon: boolean; // 显示图标
|
||||||
|
showMaximize: boolean; // 最大化按钮
|
||||||
|
showMore: boolean; // 更多按钮
|
||||||
|
styleType: TabsStyleType; // 样式类型
|
||||||
|
wheelable: boolean; // 滚轮响应
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Header 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface HeaderPreferences {
|
||||||
|
enable: boolean; // 启用顶栏
|
||||||
|
height: number; // 高度
|
||||||
|
hidden: boolean; // 隐藏
|
||||||
|
menuAlign: 'start' | 'center' | 'end'; // 菜单对齐
|
||||||
|
mode: 'fixed' | 'static'; // 显示模式
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breadcrumb 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BreadcrumbPreferences {
|
||||||
|
enable: boolean; // 启用面包屑
|
||||||
|
hideOnlyOne: boolean; // 仅一个时隐藏
|
||||||
|
showHome: boolean; // 显示首页
|
||||||
|
showIcon: boolean; // 显示图标
|
||||||
|
styleType: 'normal' | 'background'; // 样式
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface NavigationPreferences {
|
||||||
|
accordion: boolean; // 手风琴模式
|
||||||
|
split: boolean; // 分割(mixed-nav布局)
|
||||||
|
styleType: 'rounded' | 'plain'; // 样式
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Widget 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface WidgetPreferences {
|
||||||
|
fullscreen: boolean; // 全屏按钮
|
||||||
|
globalSearch: boolean; // 全局搜索
|
||||||
|
languageToggle: boolean; // 语言切换
|
||||||
|
lockScreen: boolean; // 锁屏
|
||||||
|
notification: boolean; // 通知
|
||||||
|
refresh: boolean; // 刷新按钮
|
||||||
|
sidebarToggle: boolean; // 侧边栏切换
|
||||||
|
themeToggle: boolean; // 主题切换
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Copyright 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CopyrightPreferences {
|
||||||
|
companyName: string; // 公司名
|
||||||
|
companySiteLink: string; // 公司链接
|
||||||
|
date: string; // 日期
|
||||||
|
enable: boolean; // 启用版权
|
||||||
|
icp: string; // 备案号
|
||||||
|
icpLink: string; // 备案链接
|
||||||
|
settingShow: boolean; // 设置面板显示
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transition 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface TransitionPreferences {
|
||||||
|
enable: boolean; // 启用动画
|
||||||
|
loading: boolean; // 加载动画
|
||||||
|
name: PageTransitionType; // 动画名称
|
||||||
|
progress: boolean; // 进度条
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
layout: 'sidebar-nav',
|
||||||
|
locale: 'zh-CN',
|
||||||
|
dynamicTitle: true,
|
||||||
|
accessMode: 'frontend',
|
||||||
|
defaultHomePath: '/dashboard',
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
mode: 'light',
|
||||||
|
builtinType: 'default',
|
||||||
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
collapsed: false,
|
||||||
|
width: 224,
|
||||||
|
},
|
||||||
|
tabbar: {
|
||||||
|
enable: true,
|
||||||
|
keepAlive: true,
|
||||||
|
styleType: 'chrome',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
enable: true,
|
||||||
|
height: 50,
|
||||||
|
},
|
||||||
|
widget: {
|
||||||
|
fullscreen: true,
|
||||||
|
refresh: true,
|
||||||
|
themeToggle: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:修改配置后需清空浏览器缓存才能生效。
|
||||||
202
skills/references/core/route.md
Normal file
202
skills/references/core/route.md
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# 路由与菜单详细配置
|
||||||
|
|
||||||
|
## 路由类型
|
||||||
|
|
||||||
|
### 核心路由
|
||||||
|
框架内置路由,位于 `src/router/routes/core/`,包含根路由、登录路由、404路由等。
|
||||||
|
|
||||||
|
### 静态路由
|
||||||
|
位于 `src/router/routes/index/`,项目启动时已确定的路由。
|
||||||
|
|
||||||
|
### 动态路由
|
||||||
|
位于 `src/router/routes/modules/`,根据用户权限动态生成。
|
||||||
|
|
||||||
|
## 路由 Meta 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface RouteMeta {
|
||||||
|
// 页面标题(必填)
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
// 菜单/标签页图标
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
// 激活图标
|
||||||
|
activeIcon?: string;
|
||||||
|
|
||||||
|
// 菜单排序(仅一级菜单有效)
|
||||||
|
order?: number;
|
||||||
|
|
||||||
|
// 开启 KeepAlive 缓存
|
||||||
|
keepAlive?: boolean;
|
||||||
|
|
||||||
|
// 在菜单中隐藏
|
||||||
|
hideInMenu?: boolean;
|
||||||
|
|
||||||
|
// 在标签页中隐藏
|
||||||
|
hideInTab?: boolean;
|
||||||
|
|
||||||
|
// 在面包屑中隐藏
|
||||||
|
hideInBreadcrumb?: boolean;
|
||||||
|
|
||||||
|
// 子菜单在菜单中隐藏
|
||||||
|
hideChildrenInMenu?: boolean;
|
||||||
|
|
||||||
|
// 权限控制
|
||||||
|
authority?: string[];
|
||||||
|
|
||||||
|
// 忽略权限,直接访问
|
||||||
|
ignoreAccess?: boolean;
|
||||||
|
|
||||||
|
// 菜单可见但禁止访问(跳转403)
|
||||||
|
menuVisibleWithForbidden?: boolean;
|
||||||
|
|
||||||
|
// 固定标签页
|
||||||
|
affixTab?: boolean;
|
||||||
|
|
||||||
|
// 固定标签页顺序
|
||||||
|
affixTabOrder?: number;
|
||||||
|
|
||||||
|
// 标签页最大打开数量
|
||||||
|
maxNumOfOpenTab?: number;
|
||||||
|
|
||||||
|
// 徽标
|
||||||
|
badge?: string;
|
||||||
|
|
||||||
|
// 徽标类型 'dot' | 'normal'
|
||||||
|
badgeType?: 'dot' | 'normal';
|
||||||
|
|
||||||
|
// 徽标颜色
|
||||||
|
badgeVariants?: 'default' | 'destructive' | 'primary' | 'success' | 'warning';
|
||||||
|
|
||||||
|
// 外链跳转路径
|
||||||
|
link?: string;
|
||||||
|
|
||||||
|
// 在新窗口打开
|
||||||
|
openInNewWindow?: boolean;
|
||||||
|
|
||||||
|
// iframe 地址
|
||||||
|
iframeSrc?: string;
|
||||||
|
|
||||||
|
// 激活指定菜单路径
|
||||||
|
activePath?: string;
|
||||||
|
|
||||||
|
// 不使用基础布局
|
||||||
|
noBasicLayout?: boolean;
|
||||||
|
|
||||||
|
// 完整路径作为 tab key
|
||||||
|
fullPathKey?: boolean;
|
||||||
|
|
||||||
|
// DOM 缓存(解决复杂页面切换卡顿)
|
||||||
|
domCached?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增页面示例
|
||||||
|
|
||||||
|
1. 添加路由文件 `src/router/routes/modules/home.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:home',
|
||||||
|
title: $t('page.home.title'),
|
||||||
|
order: 1000,
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 添加页面组件 `src/views/home/index.vue`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Home Page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多级路由示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'ic:baseline-view-in-ar',
|
||||||
|
title: '多级菜单',
|
||||||
|
},
|
||||||
|
name: 'Nested',
|
||||||
|
path: '/nested',
|
||||||
|
redirect: '/nested/menu1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Menu1',
|
||||||
|
path: '/nested/menu1',
|
||||||
|
component: () => import('#/views/nested/menu1.vue'),
|
||||||
|
meta: { title: '菜单1' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Menu2',
|
||||||
|
path: '/nested/menu2',
|
||||||
|
meta: { title: '菜单2' },
|
||||||
|
redirect: '/nested/menu2/menu2-1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Menu21',
|
||||||
|
path: '/nested/menu2/menu2-1',
|
||||||
|
component: () => import('#/views/nested/menu2-1.vue'),
|
||||||
|
meta: { title: '菜单2-1' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由刷新
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useRefresh } from '@vben/hooks';
|
||||||
|
|
||||||
|
const { refresh } = useRefresh();
|
||||||
|
refresh(); // 刷新当前路由
|
||||||
|
```
|
||||||
|
|
||||||
|
## 标签页控制
|
||||||
|
|
||||||
|
标签页使用唯一 key 标识,优先级:
|
||||||
|
|
||||||
|
1. 路由 query 参数 `pageKey`
|
||||||
|
2. 路由完整路径(`fullPathKey` 不为 false 时)
|
||||||
|
3. 路由 path(`fullPathKey` 为 false 时)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 使用 pageKey 打开多个标签页
|
||||||
|
router.push({
|
||||||
|
path: '/detail',
|
||||||
|
query: { pageKey: 'unique-id' },
|
||||||
|
});
|
||||||
|
```
|
||||||
236
skills/references/core/theme.md
Normal file
236
skills/references/core/theme.md
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
# 主题定制详细配置
|
||||||
|
|
||||||
|
## CSS 变量
|
||||||
|
|
||||||
|
框架使用 CSS 变量实现主题定制,所有颜色使用 HSL 格式。
|
||||||
|
|
||||||
|
### 核心变量
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
/* 基础背景色 */
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--background-deep: 216 20.11% 95.47%;
|
||||||
|
--foreground: 210 6% 21%;
|
||||||
|
|
||||||
|
/* 卡片 */
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
/* 弹出层 */
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
/* 静默状态 */
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
|
/* 主题色 */
|
||||||
|
--primary: 212 100% 45%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 错误色 */
|
||||||
|
--destructive: 0 78% 68%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 成功色 */
|
||||||
|
--success: 144 57% 58%;
|
||||||
|
--success-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 警告色 */
|
||||||
|
--warning: 42 84% 61%;
|
||||||
|
--warning-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 次要色 */
|
||||||
|
--secondary: 240 5% 96%;
|
||||||
|
--secondary-foreground: 240 6% 10%;
|
||||||
|
|
||||||
|
/* 强调色 */
|
||||||
|
--accent: 240 5% 96%;
|
||||||
|
--accent-hover: 200deg 10% 90%;
|
||||||
|
--accent-foreground: 240 6% 10%;
|
||||||
|
|
||||||
|
/* 边框 */
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
|
||||||
|
/* 输入框 */
|
||||||
|
--input: 240deg 5.88% 90%;
|
||||||
|
--input-placeholder: 217 10.6% 65%;
|
||||||
|
--input-background: 0 0% 100%;
|
||||||
|
|
||||||
|
/* 圆角 */
|
||||||
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
/* 遮罩 */
|
||||||
|
--overlay: 0deg 0% 0% / 30%;
|
||||||
|
|
||||||
|
/* 侧边栏 */
|
||||||
|
--sidebar: 0 0% 100%;
|
||||||
|
--sidebar-deep: 216 20.11% 95.47%;
|
||||||
|
|
||||||
|
/* 顶栏 */
|
||||||
|
--header: 0 0% 100%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 暗色模式变量
|
||||||
|
|
||||||
|
```css
|
||||||
|
.dark {
|
||||||
|
--background: 222.34deg 10.43% 12.27%;
|
||||||
|
--background-deep: 220deg 13.06% 9%;
|
||||||
|
--foreground: 0 0% 95%;
|
||||||
|
|
||||||
|
--card: 222.34deg 10.43% 12.27%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--sidebar: 222.34deg 10.43% 12.27%;
|
||||||
|
--sidebar-deep: 220deg 13.06% 9%;
|
||||||
|
|
||||||
|
--header: 222.34deg 10.43% 12.27%;
|
||||||
|
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 0deg 0% 100% / 10%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修改主题色
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
theme: {
|
||||||
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
|
colorWarning: 'hsl(42 84% 61%)',
|
||||||
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 切换暗色模式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
theme: {
|
||||||
|
mode: 'dark', // 'light' | 'dark'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内置主题
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
theme: {
|
||||||
|
builtinType: 'violet', // 使用紫色主题
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
可用主题:
|
||||||
|
- `default` - 默认蓝色
|
||||||
|
- `violet` - 紫色
|
||||||
|
- `pink` - 粉色
|
||||||
|
- `rose` - 玫瑰色
|
||||||
|
- `sky-blue` - 天蓝色
|
||||||
|
- `deep-blue` - 深蓝色
|
||||||
|
- `green` - 绿色
|
||||||
|
- `deep-green` - 深绿色
|
||||||
|
- `orange` - 橙色
|
||||||
|
- `yellow` - 黄色
|
||||||
|
- `zinc` - 锌灰色
|
||||||
|
- `neutral` - 中性色
|
||||||
|
- `slate` - 石板色
|
||||||
|
- `gray` - 灰色
|
||||||
|
- `custom` - 自定义
|
||||||
|
|
||||||
|
## 自定义主题
|
||||||
|
|
||||||
|
1. 在 `preferences.ts` 设置:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
theme: {
|
||||||
|
builtinType: 'my-theme',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 CSS 文件中定义变量:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* light 模式 */
|
||||||
|
[data-theme='my-theme'] {
|
||||||
|
--primary: 262.1 83.3% 57.8%;
|
||||||
|
--primary-foreground: 210 20% 98%;
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 224 71.4% 4.1%;
|
||||||
|
/* ... 其他变量 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dark 模式 */
|
||||||
|
.dark[data-theme='my-theme'],
|
||||||
|
[data-theme='my-theme'] .dark {
|
||||||
|
--primary-foreground: 210 20% 98%;
|
||||||
|
--background: 224 71.4% 4.1%;
|
||||||
|
--foreground: 210 20% 98%;
|
||||||
|
/* ... 其他变量 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特殊模式
|
||||||
|
|
||||||
|
### 灰色模式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
colorGrayMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 色弱模式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
colorWeakMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义侧边栏/顶栏颜色
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--sidebar: 0 0% 100%;
|
||||||
|
--header: 0 0% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--sidebar: 222.34deg 10.43% 12.27%;
|
||||||
|
--header: 222.34deg 10.43% 12.27%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 水印功能
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
watermark: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 动态更新水印内容
|
||||||
|
import { useWatermark } from '@vben/hooks';
|
||||||
|
|
||||||
|
const { updateWatermark } = useWatermark();
|
||||||
|
await updateWatermark({
|
||||||
|
content: 'hello watermark',
|
||||||
|
});
|
||||||
|
```
|
||||||
186
skills/references/deployment/deploy.md
Normal file
186
skills/references/deployment/deploy.md
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
# 构建与部署
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
### 构建命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建所有应用
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 构建指定应用
|
||||||
|
pnpm build:antd
|
||||||
|
pnpm build:ele
|
||||||
|
pnpm build:naive
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.production
|
||||||
|
VITE_APP_TITLE=Vben Admin
|
||||||
|
VITE_APP_NAMESPACE=vben-web-antd
|
||||||
|
VITE_BASE=/
|
||||||
|
VITE_GLOB_API_URL=https://api.example.com
|
||||||
|
VITE_COMPRESS=gzip # 压缩方式: none, brotli, gzip
|
||||||
|
VITE_PWA=false # PWA支持
|
||||||
|
VITE_ROUTER_HISTORY=hash # 路由模式: hash, history
|
||||||
|
VITE_INJECT_APP_LOADING=true # 注入全局loading
|
||||||
|
VITE_ARCHIVER=true # 生成dist.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 预览构建结果
|
||||||
|
pnpm preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 压缩
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# gzip压缩
|
||||||
|
VITE_COMPRESS=gzip
|
||||||
|
|
||||||
|
# brotli压缩
|
||||||
|
VITE_COMPRESS=brotli
|
||||||
|
|
||||||
|
# 不压缩
|
||||||
|
VITE_COMPRESS=none
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分析构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 分析构建产物
|
||||||
|
pnpm analyze
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
### Nginx 配置
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name example.com;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# 开启gzip压缩
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
|
||||||
|
# hash路由配置
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# history路由配置
|
||||||
|
# location / {
|
||||||
|
# try_files $uri $uri/ /index.html;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# API代理
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker 部署
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Dockerfile
|
||||||
|
FROM node:20-alpine as builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
RUN npm install -g pnpm && pnpm install --frozen-lockfile
|
||||||
|
COPY . .
|
||||||
|
RUN pnpm build:antd
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
backend:
|
||||||
|
image: your-backend-image
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 动态配置
|
||||||
|
|
||||||
|
打包后可通过修改 `_app.config.js` 动态修改配置:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// dist/_app.config.js
|
||||||
|
window._VBEN_ADMIN_PRO_APP_CONF_ = {
|
||||||
|
VITE_GLOB_API_URL: 'https://api.example.com',
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## PWA 支持
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开启PWA
|
||||||
|
VITE_PWA=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# hash模式(默认)
|
||||||
|
VITE_ROUTER_HISTORY=hash
|
||||||
|
|
||||||
|
# history模式(需要服务器配置)
|
||||||
|
VITE_ROUTER_HISTORY=history
|
||||||
|
```
|
||||||
|
|
||||||
|
## CDN 部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置公共资源路径
|
||||||
|
VITE_BASE=https://cdn.example.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 构建内存溢出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 增加Node内存
|
||||||
|
NODE_OPTIONS=--max_old_space_size=4096 pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 静态资源404
|
||||||
|
|
||||||
|
- 检查 `VITE_BASE` 配置是否正确
|
||||||
|
- 确保Nginx配置了正确的root路径
|
||||||
|
|
||||||
|
### 3. 跨域问题
|
||||||
|
|
||||||
|
- 开发环境:配置vite proxy
|
||||||
|
- 生产环境:配置Nginx代理或后端开启CORS
|
||||||
209
skills/references/deployment/faq.md
Normal file
209
skills/references/deployment/faq.md
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# 常见问题
|
||||||
|
|
||||||
|
## 问题查找渠道
|
||||||
|
|
||||||
|
1. 对应模块的 GitHub 仓库 [issue](https://github.com/vbenjs/vue-vben-admin/issues) 搜索
|
||||||
|
2. 从 [Google](https://www.google.com) 搜索问题
|
||||||
|
3. 从 [百度](https://www.baidu.com) 搜索问题
|
||||||
|
4. 在列表找不到问题可以到 [issues](https://github.com/vbenjs/vue-vben-admin/issues) 提问
|
||||||
|
5. 需要讨论的问题到 [discussions](https://github.com/vbenjs/vue-vben-admin/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖问题
|
||||||
|
|
||||||
|
### git pull 后依赖更新
|
||||||
|
|
||||||
|
在 Monorepo 项目下,需要养成每次 `git pull` 后执行 `pnpm install` 的习惯,因为经常会有新的依赖包加入。项目在 `lefthook.yml` 已配置自动执行,但有时会出现问题,建议手动执行。
|
||||||
|
|
||||||
|
### 依赖安装失败
|
||||||
|
|
||||||
|
- 尝试执行 `pnpm run reinstall`
|
||||||
|
- 切换手机热点进行依赖安装
|
||||||
|
- 配置国内镜像,在项目根目录创建 `.npmrc` 文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .npmrc
|
||||||
|
registry = https://registry.npmmirror.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 缓存更新问题
|
||||||
|
|
||||||
|
项目配置默认缓存在 `localStorage` 内,版本更新后可能有些配置没改变。
|
||||||
|
|
||||||
|
**解决方式:** 每次更新代码时修改 `package.json` 内的 `version` 版本号。因为 localStorage 的 key 是根据版本号来的,更新后版本不同前面的配置会失效,重新登录即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修改配置文件问题
|
||||||
|
|
||||||
|
修改 `.env` 等环境文件以及 `vite.config.ts` 文件时,vite 会自动重启服务。自动重启有几率出现问题,请重新运行项目即可解决。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本地运行报错
|
||||||
|
|
||||||
|
由于 vite 在本地没有转换代码,且代码中用到了可选链等比较新的语法,本地开发需要使用版本较高的浏览器(**Chrome 90+**)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 页面切换后空白
|
||||||
|
|
||||||
|
开启路由切换动画,且页面组件存在多个根节点会导致此问题。
|
||||||
|
|
||||||
|
**错误示例:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 注释也算一个节点 -->
|
||||||
|
<h1>text h1</h1>
|
||||||
|
<h2>text h2</h2>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
**正确示例:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>text h1</h1>
|
||||||
|
<h2>text h2</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **提示:**
|
||||||
|
> - 如果想使用多个根标签,可以禁用路由切换动画
|
||||||
|
> - template 下面的根注释节点也算一个节点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本地开发正常,打包后不行
|
||||||
|
|
||||||
|
排查是否使用了 `ctx` 变量:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ 错误用法 - ctx 未暴露在实例类型内,Vue 官方不推荐使用
|
||||||
|
import { getCurrentInstance } from 'vue';
|
||||||
|
getCurrentInstance().ctx.xxxx;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 打包文件过大
|
||||||
|
|
||||||
|
- 使用精简版进行开发,完整版引用了较多库文件
|
||||||
|
- 开启 gzip,体积约为原先 1/3
|
||||||
|
- 可同时开启 brotli 压缩,比 gzip 更好
|
||||||
|
|
||||||
|
**注意:**
|
||||||
|
|
||||||
|
- `gzip_static` 模块需要 nginx 另外安装,默认未安装
|
||||||
|
- 开启 `brotli` 也需要 nginx 另外安装模块
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 运行错误 - 路径问题
|
||||||
|
|
||||||
|
如果出现类似以下错误,请检查项目全路径(包含所有父级路径)**不能出现中文、日文、韩文**:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 控制台路由警告
|
||||||
|
|
||||||
|
如果页面能正常打开,以下警告可忽略:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
[Vue Router warn]: No match found for location with path "xxxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
后续 `vue-router` 可能会提供配置项来关闭警告。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 启动报错 - Node.js 版本
|
||||||
|
|
||||||
|
出现以下错误时,检查 Node.js 版本是否符合要求:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TypeError: str.matchAll is not a function
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## nginx 部署 MIME 类型问题
|
||||||
|
|
||||||
|
部署到 nginx 后,可能出现以下错误:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream".
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方式一:** nginx 配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http {
|
||||||
|
# 如果有此项配置需要注释掉
|
||||||
|
# include mime.types;
|
||||||
|
|
||||||
|
types {
|
||||||
|
application/javascript js mjs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方式二:** 修改 nginx 的 `mime.types` 文件,将 `application/javascript js;` 改为 `application/javascript js mjs;`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目更新
|
||||||
|
|
||||||
|
### 无法像 npm 插件一样更新
|
||||||
|
|
||||||
|
项目是完整的项目模版,不是插件或安装包,无法像插件一样更新。需要根据业务需求二次开发,自行手动合并升级。
|
||||||
|
|
||||||
|
### 更新建议
|
||||||
|
|
||||||
|
项目采用 Monorepo 方式管理,核心代码如 `packages/@core`、`packages/effects` 已抽离。只要业务代码没有修改这部分代码,可以直接拉取最新代码合并。
|
||||||
|
|
||||||
|
**建议:** 关注仓库动态积极合并,不要长时间积累,否则合并冲突过多。
|
||||||
|
|
||||||
|
### Git 更新流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 添加公司 git 源地址
|
||||||
|
git remote add up gitUrl;
|
||||||
|
|
||||||
|
# 2. 提交代码到公司
|
||||||
|
git push up main
|
||||||
|
|
||||||
|
# 3. 同步公司代码
|
||||||
|
git pull up main
|
||||||
|
|
||||||
|
# 4. 同步开源最新代码
|
||||||
|
git pull origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 移除百度统计代码
|
||||||
|
|
||||||
|
在对应应用的 `index.html` 文件中,删除以下代码:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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>
|
||||||
|
```
|
||||||
297
skills/references/features/features.md
Normal file
297
skills/references/features/features.md
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
# 常用功能
|
||||||
|
|
||||||
|
## 动态标题
|
||||||
|
|
||||||
|
根据页面内容动态更新浏览器标题。
|
||||||
|
|
||||||
|
### 开启动态标题
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
dynamicTitle: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路由配置标题
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: '用户管理',
|
||||||
|
},
|
||||||
|
name: 'User',
|
||||||
|
path: '/user',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 页面内设置标题
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useTabbar } from '@vben/tabbar';
|
||||||
|
|
||||||
|
const { setTitle } = useTabbar();
|
||||||
|
|
||||||
|
// 动态设置标题
|
||||||
|
setTitle('用户详情 - ID: 123');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 水印
|
||||||
|
|
||||||
|
为页面添加水印保护。
|
||||||
|
|
||||||
|
### 开启水印
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
watermark: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义水印内容
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useWatermark } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const { setWatermark } = useWatermark();
|
||||||
|
|
||||||
|
setWatermark({
|
||||||
|
content: '机密文档',
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页面缓存
|
||||||
|
|
||||||
|
保持页面状态,切换路由时不销毁组件。
|
||||||
|
|
||||||
|
### 路由级别缓存
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
keepAlive: true, // 开启缓存
|
||||||
|
},
|
||||||
|
name: 'UserList',
|
||||||
|
path: '/user/list',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 全局配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
tabbar: {
|
||||||
|
enable: true,
|
||||||
|
keepAlive: true, // 全局开启缓存
|
||||||
|
persist: true, // 持久化标签页
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页面加载进度条
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
transition: {
|
||||||
|
progress: true, // 显示页面加载进度条
|
||||||
|
loading: true, // 显示页面加载动画
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页面过渡动画
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
transition: {
|
||||||
|
enable: true,
|
||||||
|
name: 'fade-slide', // 动画名称
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可选动画
|
||||||
|
|
||||||
|
- `fade` - 淡入淡出
|
||||||
|
- `fade-slide` - 滑动淡入淡出
|
||||||
|
- `fade-bottom` - 底部滑入
|
||||||
|
- `fade-scale` - 缩放淡入
|
||||||
|
|
||||||
|
## 面包屑
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
breadcrumb: {
|
||||||
|
enable: true, // 显示面包屑
|
||||||
|
showHome: false, // 显示首页图标
|
||||||
|
showIcon: true, // 显示图标
|
||||||
|
hideOnlyOne: false, // 只有一个时隐藏
|
||||||
|
styleType: 'normal', // 样式类型
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 标签页
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
tabbar: {
|
||||||
|
enable: true, // 显示标签页
|
||||||
|
height: 38, // 标签页高度
|
||||||
|
showIcon: true, // 显示图标
|
||||||
|
showMore: true, // 显示更多按钮
|
||||||
|
showMaximize: true, // 显示最大化按钮
|
||||||
|
draggable: true, // 可拖拽
|
||||||
|
wheelable: true, // 滚轮切换
|
||||||
|
persist: true, // 持久化
|
||||||
|
keepAlive: true, // 缓存
|
||||||
|
maxCount: 0, // 最大数量(0不限制)
|
||||||
|
styleType: 'chrome', // 样式:chrome | plain | card
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页脚版权
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
footer: {
|
||||||
|
enable: true, // 显示页脚
|
||||||
|
fixed: false, // 固定在底部
|
||||||
|
height: 32, // 高度
|
||||||
|
},
|
||||||
|
copyright: {
|
||||||
|
enable: true,
|
||||||
|
companyName: 'My Company',
|
||||||
|
companySiteLink: 'https://example.com',
|
||||||
|
date: '2024',
|
||||||
|
icp: '备案号',
|
||||||
|
icpLink: 'https://beian.miit.gov.cn',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 锁屏
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
lockScreen: true, // 显示锁屏按钮
|
||||||
|
},
|
||||||
|
shortcutKeys: {
|
||||||
|
globalLockScreen: true, // 锁屏快捷键
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全局搜索
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
globalSearch: true, // 显示搜索按钮
|
||||||
|
},
|
||||||
|
shortcutKeys: {
|
||||||
|
globalSearch: true, // 搜索快捷键
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通知中心
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
notification: true, // 显示通知图标
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全屏
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
fullscreen: true, // 显示全屏按钮
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 刷新按钮
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
refresh: true, // 显示刷新按钮
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主题切换
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
widget: {
|
||||||
|
themeToggle: true, // 显示主题切换按钮
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 检查更新
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
enableCheckUpdates: true, // 开启检查更新
|
||||||
|
checkUpdatesInterval: 1, // 检查间隔(小时)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 色弱模式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
colorWeakMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 灰色模式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// preferences.ts
|
||||||
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
|
app: {
|
||||||
|
colorGrayMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user