更新规则和uipro
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
李琦
2026-08-10 13:10:42 +08:00
parent 6705094406
commit f8813888d7
4 changed files with 234 additions and 21785 deletions

View File

@@ -84,8 +84,8 @@ alwaysApply: true
```ts
export const STATUS_OPTIONS = [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 },
{ label: '启用', value: 0 },
{ label: '禁用', value: 1 },
];
```

View File

@@ -0,0 +1,231 @@
---
description: nl-admin-view Vben 官方组件用法规范Page/Form/Modal/Drawer/VxeTable 等)
globs: apps/web-antd/**/*.{vue,ts}
alwaysApply: false
---
# Vben 组件规则(对齐官方文档 + 本仓库约定)
文档来源https://doc.vben.pro/components/
业务页统一在 `apps/web-antd`;表单/表格必须走本仓库适配器,不要直接依赖底层实现细节。
## 选型速查
| 场景 | 用什么 | 不要用 |
|---|---|---|
| 页面外壳 | `Page``@vben/common-ui` | 自写页面头/底容器 |
| 列表+搜索 | `useVbenVxeGrid``#/adapter/vxe-table` | 直接 `new VxeGrid` / antd `Table` |
| 表单 | `useVbenForm``#/adapter/form` | antd `Form` / 手写校验 |
| 复杂弹窗(表单/多步) | `useVbenModal` | antd `Modal` |
| 侧滑面板 | `useVbenDrawer` | antd `Drawer` |
| 轻量确认/提示/单输入 | `alert` / `confirm` / `prompt` | 为简单确认再开 Modal |
| 表格行/工具栏按钮 | `#/components/table-action` 的 `TableAction` | 散落一排 `Button` |
| 远程下拉/树选 | 适配器里的 `ApiSelect` / `ApiTreeSelect`(底层 `ApiComponent` | 页面里手写 options 拉取逻辑 |
| 超长文本 | `EllipsisText` | CSS 省略后无 Tooltip |
| 只读详情字段组 | `VbenDescriptions` | 手写 label-value 网格 |
| 头像/固定比例裁剪 | `VCropper` | 无裁剪直接上传原图(需要裁剪时) |
| 富文本 | `VbenTiptap``@vben/plugins/tiptap` | 再引一套编辑器 |
---
## Page
- 业务页根节点用 `<Page>`;列表页常见:`<Page auto-content-height title="xxx">`
- `title` / `description` / `#extra` 都没有内容时,头部不渲染
- `autoContentHeight`:内容区按可视高度自动扣减头/底;额外偏移用 `heightOffset`px
- `appendToMain` 的 Modal/Drawer 挂到内容区时,**Page 必须开 `auto-content-height`**,否则高度计算不对
- 插槽:`default` / `title` / `description` / `extra` / `footer`
---
## Modal`useVbenModal`
```ts
const [Modal, modalApi] = useVbenModal({
connectedComponent: ExtraModal, // 复杂内容抽离子组件
draggable: true,
// overflow: true, // 需要拖出可视区时再开
});
```
强制约定:
- 复杂弹窗用 `connectedComponent`;内外共享 `modalApi`
- **严禁覆盖 `#footer`**(会丢掉默认取消/确定与 loading
- 扩展底部只用:`prepend-footer` / `center-footer` / `append-footer`
- 提交防重复:`modalApi.lock()` / `unlock()`(或 `setState({ confirmLoading: true })`
- 回填:`modalApi.setData(data).open()`;内部 `onOpenChange` 里 `modalApi.getData()`
- 参数优先级:`slot` > `props` > `state``connectedComponent` 时同名事件**以内侧为准**`onOpenChange` 内外都会触发)
- 默认属性可在 `apps/web-antd/src/bootstrap.ts` 的 `setDefaultModalProps` 统一改
- 动画:`animationType: 'slide' | 'scale'`(默认 slide
常用 API`open` / `close` / `setState` / `setData` / `getData` / `lock` / `unlock`
---
## Drawer`useVbenDrawer`
- API 形态与 Modal 基本一致:`connectedComponent`、`setData/getData`、`lock/unlock`、footer 三插槽
- **同样严禁覆盖 `#footer`**
- `placement`: `left | right | top | bottom`(默认 `right`
- 关闭前校验用 `onBeforeClose`(支持 Promise需要重置内部状态可开 `destroyOnClose`
- 挂内容区同样要配合 Page 的 `auto-content-height`
- 默认属性:`setDefaultDrawerProps`
---
## Alert轻量提示
来自 `@vben/common-ui``alert` / `confirm` / `prompt` / `useAlertContext`
- **简单确认、提示、单字段输入**用这套;复杂表单/多控件仍用 Modal
- `confirm().then/catch`;异步关闭用 `beforeClose`,返回 `false` 可阻止关闭
- `prompt` 可传 `component` + `componentProps`antd Select 需 `popupClassName: 'pointer-events-auto'`
- 自定义内容里需要主动确认/取消:`useAlertContext().doConfirm/doCancel`(仅 setup/函数组件)
- 动态创建的 Alert **不支持 HMR**,改代码后需关掉重开
---
## Form`useVbenForm` from `#/adapter/form`
```ts
const [Form, formApi] = useVbenForm({
schema: [
{
fieldName: 'nick_name', // 与后端 snake_case 对齐
label: '昵称',
component: 'Input',
rules: 'required', // 选择类用 selectRequired
componentProps: { placeholder: '请输入' },
},
],
});
```
强制约定:
- 只从 `#/adapter/form` 引入,不直接玩 TanStack Form 实例
- 动态改字段:`formApi.updateSchema([...])`**没有** `setComponentProps`
- 重置:`formApi.resetForm()`**禁止** `resetFields()`antd API会报错
- 取值/赋值:`getValues()` / `setValues()`;需要提交编解码时用 `codec` / `getRawValues()`
- 校验:`formApi.validate().then(e => { if (e.valid) ... })` 或 `validateAndSubmitForm()`
- 字段自定义 slot控件绑定用 `v-bind="slotProps.componentProps"`(不要再 `v-bind="slotProps"`
- 联动优先 `dependencies.resolve(context)` 一次返回 `show/disabled/rules/componentProps...`
- 远程 options打开时拉取后 `updateSchema` 注入;或用 `ApiSelect`/`ApiTreeSelect`
- 密码字段用 `InputPassword`(适配器组件名),不要 `Input` + `type="password"`
---
## Vxe Table`useVbenVxeGrid` from `#/adapter/vxe-table`
```ts
const [Grid, gridApi] = useVbenVxeGrid({
formOptions, // 搜索表单Vben Form
gridOptions: {
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
// 返回适配器约定结构
return { items: [], total: 0 };
},
},
},
toolbarConfig: { search: true },
},
gridEvents,
});
```
强制约定:
- 远程列表走 `proxyConfig.ajax.query`,禁止自己 fetch 再赋 `data`
- 响应结构对齐适配器:`{ items, total }`(见 `apps/web-antd/src/adapter/vxe-table.ts`
- 分页从 `{ page }` 取 `currentPage` / `pageSize`
- 刷新:`gridApi.query()` 保留页;`gridApi.reload()` 回第一页
- loading`gridApi.setLoading(bool)`
- 工具栏插槽:`toolbar-actions`(左)/ `toolbar-tools`(右)
- 操作列:列上 `slots: { default: 'action' }`,页面 `<template #action="{ row }">`**禁止**列 render 写 JSX
- 需要原生能力时用 `gridApi.grid`(如树展开 `setAllTreeExpand`
---
## ApiComponent / ApiSelect
- 一般不直接用 `ApiComponent`;优先用适配器包装好的 `ApiSelect` / `ApiTreeSelect`
- 关键映射:`api` / `params` / `resultField` / `labelField` / `valueField` / `childrenField`
- `immediate: false` + `visibleEvent`:展开时再请求
- 同页多实例同数据源:用 TanStack Query/`useQuery` 包一层再传 `api`,避免 N 次请求
- `autoSelect`: `'first' | 'last' | 'one' | fn | false`(不要用在多选)
---
## TableAction本仓库
本仓库列表页已统一用本地封装:
```ts
import { TableAction } from '#/components/table-action';
```
- 字段习惯:`label`(不是官方 demo 的 `text`)、`drop-down-actions`、`popConfirm`、`auth`、`ifShow`
- 放在 `#toolbar-actions` 与 `#action` 插槽中
- 若改用官方 `VbenTableAction``@vben/common-ui` / `#/adapter/vxe-table`),注意其字段为 `text` / `dropdownActions`,并优先走适配器以自动注入权限
---
## EllipsisText
```vue
<EllipsisText :max-width="240" :line="2" :tooltip-when-ellipsis="true">
{{ text }}
</EllipsisText>
```
- 表格/卡片超长文案优先用它;需要展开收起开 `expand`
- `tooltipWhenEllipsis`:仅截断时显示提示,避免短文本也弹层
---
## VbenDescriptions
- 详情/预览优先 `:items="[{ label, content, span }]"` 数据驱动
- `span: 'filled'` 占满当前行剩余;`column` 可传断点对象
- `bordered` + `title` + `#extra` 做带操作的信息块
- `layout="vertical"` 标签在上;也可用 `VbenDescriptionsItem` 子组件(`items` 优先)
---
## VCropper
```ts
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
```
- Props`img`(必填)、`width`/`height`、`aspectRatio`(如 `'1:1'` `'16:9'`
- blob URL 用完/`onBeforeUnmount` 要 `URL.revokeObjectURL`,防泄漏
- 网络图导出需 CORS头像场景常用固定比例 + 指定 `targetWidth/targetHeight`
---
## VbenTiptap`@vben/plugins/tiptap`
```vue
<VbenTiptap v-model="html" :image-upload="imageUpload" />
<VbenTiptapPreview :content="html" />
```
- 内容为 HTML 字符串;只读展示用 `VbenTiptapPreview`
- 上传必须配 `imageUpload.upload(file, onProgress) => Promise<url>`,走本项目上传接口
- 上传中不要拿 `getHTML()` 去保存(可能是临时 blob URL
- 自定义 `extensions` 会覆盖默认扩展,且图片上传能力不可用——非必要不要自定义
---
## 与本仓库踩坑清单(必守)
1. Form 无 `setComponentProps` / 无 `resetFields`
2. Modal/Drawer 禁止覆盖 `#footer`;提交用 `lock/unlock`
3. 表格返回必须是 `{ items, total }`;刷新分清 `query` vs `reload`
4. 字段名 `fieldName` 用后端 snake_case如 `created_at`、`nick_name`
5. 状态枚举与后端一致:**0 正常 / 1 禁用**
6. 暗色主题:业务样式用 CSS 变量,禁止写死亮色色值(见 `Code-Standards.mdc`

View File

@@ -89,7 +89,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.12.0"
},
"packageManager": "pnpm@10.10.0",
"packageManager": "pnpm@10.2.0",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

21782
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff