840 lines
23 KiB
Markdown
840 lines
23 KiB
Markdown
# @apigo.cc/base
|
||
|
||
`@apigo.cc/base` 是基于 `@apigo.cc/state` 和 Bootstrap 5.3 的同步组件层。页面只需要按顺序引入脚本,就能在首屏完成 DOM 处理和组件渲染。
|
||
|
||
## 目录
|
||
|
||
- [依赖加载](#依赖加载) - 同步加载顺序和来源说明
|
||
- [全局变量](#全局变量) - 本包直接挂到 `window` 的变量
|
||
- [HTTP](#http) - 请求工具
|
||
- [API](#api) - 声明式请求节点
|
||
- [AutoForm](#autoform) - 自动表单节点
|
||
- [表单扩展控件](#表单扩展控件) - `DatePicker` / `ColorPicker` / `IconPicker` / `TagsInput`
|
||
- [List](#list) - 普通、分组、树形和虚拟滚动列表
|
||
- [Nav](#nav) - 水平导航栏或垂直侧边栏
|
||
- [Modal / Dialog](#modal--dialog) - 弹窗容器和确认弹窗
|
||
- [Resizer](#resizer) - 拖拽分隔条
|
||
- [UI](#ui) - toast 和对话框工具
|
||
- [VirtualScroll / MouseMover](#virtualscroll--mousemover) - 虚拟滚动和鼠标拖拽辅助
|
||
|
||
## 依赖加载
|
||
|
||
按顺序同步加载:
|
||
|
||
`@apigo.cc/state` -> `@apigo.cc/bootstrap` -> `@apigo.cc/base`
|
||
|
||
`base` 会直接使用来自 `@apigo.cc/state` 的全局对象 `State`、`Hash`、`LocalStorage`。
|
||
|
||
- [state 文档](https://apigo.cc/web/state)
|
||
|
||
### 推荐加载顺序
|
||
|
||
```html
|
||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.23/dist/state.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.9/dist/bootstrap.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.23/dist/base.min.js"></script>
|
||
```
|
||
|
||
### 全局变量
|
||
|
||
| 变量 | 说明 |
|
||
|---|---|
|
||
| `HTTP` | 请求工具 |
|
||
| `UI` | 提示、确认、对话框工具 |
|
||
| `AutoForm` | 自动表单容器和扩展注册器 |
|
||
| `VirtualScroll` | 虚拟滚动工具 |
|
||
| `MouseMover` | 鼠标拖拽位移工具 |
|
||
|
||
## HTTP
|
||
|
||
`HTTP` 只处理请求,不处理 UI。
|
||
|
||
### 方法
|
||
|
||
| 方法 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `HTTP.request(options)` | `await HTTP.request({ url: '/api/user', method: 'POST', data: { id: 1 } })` | 通用请求入口 |
|
||
| `HTTP.get(...)` | `await HTTP.get({ url: '/api/user/1' })` | `GET` |
|
||
| `HTTP.post(...)` | `await HTTP.post({ url: '/api/user', data: form })` | `POST` |
|
||
| `HTTP.put(...)` | `await HTTP.put({ url: '/api/user/1', data: patch })` | `PUT` |
|
||
| `HTTP.delete(...)` | `await HTTP.delete({ url: '/api/user/1' })` | `DELETE` |
|
||
| `HTTP.head(...)` | `await HTTP.head({ url: '/api/user/1' })` | `HEAD` |
|
||
|
||
### `HTTP.request` 参数
|
||
|
||
| 参数 | 类型 | 写法示例 | 说明 |
|
||
|---|---|---|---|
|
||
| `url` | `string` | `url: '/api/user'` | 必填 |
|
||
| `method` | `string` | `method: 'POST'` | 默认 `POST`,内部会转大写 |
|
||
| `data` | `any` | `data: { name: 'Alice' }` | 请求体 |
|
||
| `headers` | `object` | `headers: { Authorization: 'Bearer xxx' }` | 请求头 |
|
||
| `responseType` | `string` | `responseType: 'json'` | 可手动指定 |
|
||
| `timeout` | `number` | `timeout: 5000` | 默认 `10000` ms |
|
||
|
||
### `HTTP` 对象属性
|
||
|
||
| 属性 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `keepHeaders` | `HTTP.keepHeaders = ['Session-Id']` | 需要跨请求保留的响应头名 |
|
||
|
||
### Content-Type 规则
|
||
|
||
- `data` 是普通对象时,默认按 JSON 发送,并自动补 `Content-Type: application/json`
|
||
- `data` 是 `FormData` 时,不手动设置 `Content-Type`,让浏览器生成带 boundary 的 multipart 请求头
|
||
- 如果你在 `headers` 里手动传了 `Content-Type`,普通对象请求会按你的设置发送;`FormData` 仍然以浏览器生成的请求头为准
|
||
|
||
### 返回值
|
||
|
||
```js
|
||
{
|
||
ok,
|
||
status,
|
||
headers,
|
||
responseType,
|
||
result,
|
||
error
|
||
}
|
||
```
|
||
|
||
### 推荐示例
|
||
|
||
```html
|
||
<script>
|
||
window.userRequest = {
|
||
url: '/api/users/1',
|
||
method: 'GET'
|
||
}
|
||
window.apiResponse = null
|
||
window.apiResult = null
|
||
window.user = null
|
||
</script>
|
||
|
||
<API id="userApi"
|
||
auto
|
||
$.request="window.userRequest"
|
||
$onresponse="window.user = event.detail.result"
|
||
$onerror="UI.toast(event.detail.message, { type: 'danger' })">
|
||
</API>
|
||
|
||
<button class="btn btn-primary" onclick="userApi.do({ noui: true })">Reload</button>
|
||
```
|
||
|
||
## API
|
||
|
||
`<API>` 是把请求封装成页面节点的方式。
|
||
|
||
### 属性和状态
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `auto` | `<API auto>` | 监听 `request` 变化后自动请求 |
|
||
| `request` | `$.request="window.userRequest"` | 请求配置对象 |
|
||
| `response` | `$.response="window.apiResponse"` | 最近一次请求状态 |
|
||
| `result` | `$.result="window.apiResult"` | 响应结果缓存 |
|
||
|
||
### `request` 默认结构
|
||
|
||
```js
|
||
{
|
||
url: '',
|
||
method: 'GET',
|
||
headers: {},
|
||
data: null,
|
||
timeout: 10000,
|
||
responseType: ''
|
||
}
|
||
```
|
||
|
||
### `response` 默认结构
|
||
|
||
```js
|
||
{
|
||
loading: false,
|
||
ok: null,
|
||
status: null,
|
||
error: null,
|
||
headers: {},
|
||
responseType: '',
|
||
result: null
|
||
}
|
||
```
|
||
|
||
### 方法
|
||
|
||
| 方法 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `do(opt = {})` | `await userApi.do()` | 手动请求 |
|
||
| `opt.noui` | `await userApi.do({ noui: true })` | 失败时不弹 toast |
|
||
|
||
### 事件
|
||
|
||
| 事件 | 触发时机 | `detail` |
|
||
|---|---|---|
|
||
| `response` | 请求成功 | 完整响应对象 |
|
||
| `error` | 请求失败或返回错误 | `Error` 对象 |
|
||
|
||
### 备注
|
||
|
||
- `auto` 只在 `request.url` 存在时生效
|
||
- `request` 深度变化会在微任务里触发一次请求,避免同一轮重复请求
|
||
|
||
## AutoForm
|
||
|
||
`<AutoForm>` 通过 `schema` 生成表单,适合和 `API`、`window` 上的页面数据直接联动。
|
||
|
||
### 属性
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `vertical` | `<AutoForm vertical>` | 垂直布局 |
|
||
| `horizontal` | `<AutoForm horizontal>` | 强制左右两列布局 |
|
||
| `inline` | `<AutoForm inline>` | 行内紧凑布局 |
|
||
| `nobutton` | `<AutoForm nobutton>` | 隐藏底部提交按钮 |
|
||
| `api` | `$.api="saveApi"` | 绑定 `<API>` 实例或其引用 |
|
||
| `submitlabel` | `submitlabel="Search"` | 提交按钮文案,默认 `{#Submit#}` |
|
||
|
||
### 状态和方法
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `data` | `$.state.data="window.profile"` | 表单数据,建议用 `Object.assign` 更新 |
|
||
| `schema` | `$.state.schema="window.profileSchema"` | 表单定义 |
|
||
| `request` | `$.request="{ method: 'POST' }"` | 默认提交配置 |
|
||
| `response` | `$.response="window.formResponse"` | 最近一次提交结果 |
|
||
| `result` | `$.result="window.formResult"` | 最近一次提交的 `response.result` |
|
||
| `submit(opt = {})` | `form.submit()` | 主动提交 |
|
||
|
||
### `schema` 字段
|
||
|
||
| 字段 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `name` | `name: 'role'` | 必填,数据键名 |
|
||
| `label` | `label: 'Role'` | 表单项标题 |
|
||
| `type` | `type: 'select'` | 控件类型 |
|
||
| `setting` | `setting: { required: true }` | 透传给控件的属性 |
|
||
| `options` | `options: ['A', 'B']` | `select` / `checkbox` / `radio` 选项 |
|
||
| `placeholder` | `placeholder: 'Please choose'` | `select` 的空值占位文案 |
|
||
| `if` | `if: 'this.data.enable'` | 条件渲染表达式 |
|
||
|
||
### `if` 说明
|
||
|
||
- `if` 不是字符串模板拼接,而是表达式
|
||
- 表达式执行时可直接访问 `this`、`State`、`Hash`、`LocalStorage`、`item`、`data`
|
||
- 这类条件是表单级动态渲染,适合做联动显隐
|
||
|
||
### 支持的 `type`
|
||
|
||
| `type` | 用途 |
|
||
|---|---|
|
||
| `text` | 单行文本 |
|
||
| `password` | 密码 |
|
||
| `email` | 邮箱 |
|
||
| `number` | 数字 |
|
||
| `date` | 日期 |
|
||
| `datetime` | 日期时间 |
|
||
| `file` | 文件 |
|
||
| `select` | 下拉单选 |
|
||
| `checkbox` | 多选 |
|
||
| `radio` | 单选按钮组 |
|
||
| `switch` | 开关 |
|
||
| `textarea` | 多行文本 |
|
||
| `DatePicker` | 扩展日期控件 |
|
||
| `ColorPicker` | 扩展颜色控件 |
|
||
| `IconPicker` | 扩展图标控件 |
|
||
| `TagsInput` | 扩展标签控件 |
|
||
|
||
### `options` 写法
|
||
|
||
```js
|
||
['Admin', 'Editor']
|
||
[{ label: 'Admin', value: 'admin' }]
|
||
```
|
||
|
||
### 事件
|
||
|
||
| 事件 | 触发时机 | `detail` |
|
||
|---|---|---|
|
||
| `submit` | 表单校验通过、提交前 | 当前表单数据 |
|
||
| `response` | 提交成功 | 完整响应对象 |
|
||
| `error` | 提交失败 | `Error` 对象 |
|
||
|
||
### 插槽
|
||
|
||
| 插槽 | 位置 | 用途 |
|
||
|---|---|---|
|
||
| `actions` | 默认提交按钮左侧 | 自定义动作按钮 |
|
||
|
||
### 推荐示例
|
||
|
||
```html
|
||
<script>
|
||
window.profile = { name: 'Alice' }
|
||
window.profileSchema = [
|
||
{ name: 'name', label: 'Name', type: 'text', setting: { required: true, placeholder: 'Enter name' } },
|
||
{ name: 'role', label: 'Role', type: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] },
|
||
{ name: 'active', label: 'Active', type: 'switch' },
|
||
{ name: 'birthday', label: 'Birthday', type: 'DatePicker', setting: { rangeEnd: 'birthdayEnd' } },
|
||
{ name: 'birthdayEnd', label: 'Birthday End', type: 'date' },
|
||
{ name: 'theme', label: 'Theme', type: 'ColorPicker' },
|
||
{ name: 'icon', label: 'Icon', type: 'IconPicker' },
|
||
{ name: 'tags', label: 'Tags', type: 'TagsInput' },
|
||
{ name: 'remark', label: 'Remark', type: 'textarea', if: 'this.data.active' }
|
||
]
|
||
</script>
|
||
|
||
<AutoForm vertical
|
||
$.state.schema="window.profileSchema"
|
||
$.state.data="window.profile"
|
||
$onsubmit="UI.toast('submit: ' + JSON.stringify(event.detail))">
|
||
<template slot="actions">
|
||
<button type="button" class="btn btn-outline-secondary" onclick="Object.assign(window.profile, { name: 'Alice' })">Reset</button>
|
||
</template>
|
||
</AutoForm>
|
||
```
|
||
|
||
### 编程注意
|
||
|
||
- 不要直接整体覆盖 `form.data` 或 `form.state.data`
|
||
- 需要更新时,优先用 `Object.assign(form.data, nextData)`
|
||
|
||
### `AutoForm.register(name, typeName = '')`
|
||
|
||
注册可被 `schema.type` 识别的自定义控件。
|
||
|
||
| 参数 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `name` | `AutoForm.register('MyPicker')` | 组件标签名 |
|
||
| `typeName` | `AutoForm.register('MyPicker', 'picker')` | 默认 `''`。只有当标签名和 `schema.type` 不同,或需要复用同一个标签名匹配另一个 `type` 时才传 |
|
||
|
||
### 什么时候传 `typeName`
|
||
|
||
- 你想让 `<MyPicker>` 处理 `schema.type === 'picker'`
|
||
- 你想把一个标签名映射成不同的 `type`
|
||
- 如果 `schema.type` 和标签名一致,直接省略第二个参数
|
||
|
||
## 表单扩展控件
|
||
|
||
这些控件既可以在 `AutoForm` 里作为 `schema.type` 使用,也可以单独放到页面上。单独使用时,直接绑定 `value`,再监听 `change` 即可。
|
||
|
||
### `<DatePicker>`
|
||
|
||
| 项目 | 说明 |
|
||
|---|---|
|
||
| `state.start` | 起始日期 |
|
||
| `state.end` | 结束日期 |
|
||
| `value` | 映射到 `state.start` |
|
||
| `isRange` | 是否启用范围模式 |
|
||
| `setting.rangeEnd` | 范围结束字段名 |
|
||
| `rangeEnd` | 组件属性形式的范围结束字段名 |
|
||
| `change` | 开始日期变化时触发,`detail` 为开始日期字符串 |
|
||
|
||
### `<DatePicker>` 示例
|
||
|
||
```html
|
||
<script>
|
||
window.rangeForm = {}
|
||
window.rangeSchema = [
|
||
{ name: 'rangeStart', label: 'Range', type: 'DatePicker', setting: { rangeEnd: 'rangeEnd' } },
|
||
{ name: 'rangeEnd', label: 'Range End', type: 'date' }
|
||
]
|
||
</script>
|
||
|
||
<AutoForm $.state.schema="window.rangeSchema" $.state.data="window.rangeForm"></AutoForm>
|
||
```
|
||
|
||
### `<DatePicker>` 单独使用
|
||
|
||
```html
|
||
<script>
|
||
window.birthDate = '2026-06-01'
|
||
</script>
|
||
|
||
<DatePicker $bind="window.birthDate"></DatePicker>
|
||
```
|
||
|
||
### `<ColorPicker>`
|
||
|
||
| 项目 | 说明 |
|
||
|---|---|
|
||
| `state.value` | 颜色值,默认 `#000000` |
|
||
| `value` | 可读写,映射到 `state.value` |
|
||
| `change` | 颜色变化时触发,`detail` 为颜色字符串 |
|
||
|
||
### `<ColorPicker>` 示例
|
||
|
||
```html
|
||
<script>
|
||
window.colorForm = {}
|
||
window.colorSchema = [
|
||
{ name: 'theme', label: 'Theme', type: 'ColorPicker' }
|
||
]
|
||
</script>
|
||
|
||
<AutoForm $.state.schema="window.colorSchema" $.state.data="window.colorForm"></AutoForm>
|
||
```
|
||
|
||
### `<IconPicker>`
|
||
|
||
| 项目 | 说明 |
|
||
|---|---|
|
||
| `state.value` | 当前图标名 |
|
||
| `state.search` | 搜索词 |
|
||
| `state.open` | 下拉是否展开 |
|
||
| `filteredIcons` | 过滤后的图标列表 |
|
||
| `value` | 可读写,映射到 `state.value` |
|
||
| `change` | 选择图标时触发,`detail` 为图标名 |
|
||
|
||
### `<IconPicker>` 示例
|
||
|
||
```html
|
||
<script>
|
||
window.iconForm = {}
|
||
window.iconSchema = [
|
||
{ name: 'icon', label: 'Icon', type: 'IconPicker' }
|
||
]
|
||
</script>
|
||
|
||
<AutoForm $.state.schema="window.iconSchema" $.state.data="window.iconForm"></AutoForm>
|
||
```
|
||
|
||
### `<TagsInput>`
|
||
|
||
| 项目 | 说明 |
|
||
|---|---|
|
||
| `state.tags` | 标签数组 |
|
||
| `change` | 标签变更时触发,`detail` 为最新数组 |
|
||
|
||
### `<TagsInput>` 示例
|
||
|
||
```html
|
||
<script>
|
||
window.tagForm = {}
|
||
window.tagsSchema = [
|
||
{ name: 'tags', label: 'Tags', type: 'TagsInput' }
|
||
]
|
||
</script>
|
||
|
||
<AutoForm $.state.schema="window.tagsSchema" $.state.data="window.tagForm"></AutoForm>
|
||
```
|
||
|
||
### `<TagsInput>` 单独使用
|
||
|
||
```html
|
||
<script>
|
||
window.tags = ['Vue', 'AI']
|
||
</script>
|
||
|
||
<TagsInput $bind="window.tags"></TagsInput>
|
||
```
|
||
|
||
## List
|
||
|
||
`<List>` 支持普通、分组、树形和虚拟滚动。它的重点不是“能渲染”,而是“能直接拿来绑定状态”。
|
||
|
||
### 属性
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `mode` | `<List mode="group">` | `normal` / `group` / `tree`,默认 `normal` |
|
||
| `fast` | `<List fast>` | 开启虚拟滚动。任意模式都能用 |
|
||
| `collapsible` | `<List collapsible>` | 树形模式下允许折叠 |
|
||
| `auto-select` | `<List auto-select>` | 点击项后把当前项 id 写回 `selectedItem` |
|
||
| `auto-select-group` | `<List auto-select-group>` | 点击分组后把当前分组 id 写回 `selectedGroup` |
|
||
|
||
### 状态
|
||
|
||
| 名称 | 说明 |
|
||
|---|---|
|
||
| `list` | 原始列表 |
|
||
| `groups` | 分组列表,`group` 模式使用 |
|
||
| `filter` | 字符串或对象筛选条件 |
|
||
| `order` | 排序规则,例如 `name`、`-name`、`name desc` |
|
||
| `selectedItem` | 当前选中的项 id |
|
||
| `selectedGroup` | 当前选中的分组 id |
|
||
| `renderedList` | 当前实际渲染的列表片段 |
|
||
| `flatList` | 逻辑处理后的扁平列表 |
|
||
| `collapsed` | 树形折叠状态 |
|
||
|
||
### 实例属性
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `filterFunc` | `list.filterFunc = item => item.active` | 自定义过滤函数 |
|
||
| `orderFunc` | `list.orderFunc = (a, b) => a.name.localeCompare(b.name)` | 自定义排序函数 |
|
||
|
||
### 事件
|
||
|
||
| 事件 | 触发时机 | `detail` |
|
||
|---|---|---|
|
||
| `change` | 选中项变化 | `selectedItem` |
|
||
| `itemclick` | 点击普通项 | `{ item, index }` |
|
||
| `groupclick` | 点击分组项 | `{ item, index }` |
|
||
|
||
### 绑定习惯
|
||
|
||
- 推荐配合 `$bind` 保存当前选中 id,比如 `$bind="window.currentUserId"`
|
||
- 如果你只关心点击事件,也可以不绑,直接监听 `itemclick` / `groupclick`
|
||
|
||
### 插槽
|
||
|
||
| 插槽 | 说明 |
|
||
|---|---|
|
||
| `item` | 单项自定义内容 |
|
||
| `item-actions` | 单项右侧动作区 |
|
||
| `group-actions` | 分组右侧动作区 |
|
||
|
||
### 字段默认映射
|
||
|
||
不写映射属性时,默认字段名就是这些:
|
||
|
||
| 字段 | 默认值 |
|
||
|---|---|
|
||
| `idfield` | `id` |
|
||
| `labelfield` | `label` |
|
||
| `summaryfield` | `summary` |
|
||
| `groupidfield` | `id` |
|
||
| `grouplabelfield` | `label` |
|
||
| `groupsummaryfield` | `summary` |
|
||
| `groupfield` | `group` |
|
||
| `parentfield` | `parent` |
|
||
| `groupicon` | `folder` |
|
||
| `itemicon` | `file` |
|
||
|
||
### 推荐示例
|
||
|
||
#### 普通列表 + `$bind`
|
||
|
||
```html
|
||
<script>
|
||
window.currentUserId = null
|
||
window.userList = [
|
||
{ id: '1', label: 'Alice', summary: 'Admin' },
|
||
{ id: '2', label: 'Bob', summary: 'Editor' }
|
||
]
|
||
</script>
|
||
|
||
<List class="h-100 overflow-auto" auto-select
|
||
$.state.list="window.userList"
|
||
$bind="window.currentUserId"
|
||
$onitemclick="UI.toast('item: ' + event.detail.item.label)">
|
||
<template slot="item">
|
||
<span $text="item.label"></span>
|
||
<span class="ms-auto text-muted small" $text="item.summary"></span>
|
||
</template>
|
||
</List>
|
||
```
|
||
|
||
#### 分组列表
|
||
|
||
```html
|
||
<script>
|
||
window.groups = [
|
||
{ id: 'team-a', label: 'Team A', summary: '2 people' },
|
||
{ id: 'team-b', label: 'Team B', summary: '1 person' }
|
||
]
|
||
|
||
window.members = [
|
||
{ id: '1', label: 'Alice', summary: 'Admin', group: 'team-a' },
|
||
{ id: '2', label: 'Bob', summary: 'Editor', group: 'team-b' }
|
||
]
|
||
</script>
|
||
|
||
<List mode="group" class="h-100 overflow-auto" auto-select-group
|
||
$.state.list="window.members"
|
||
$.state.groups="window.groups"
|
||
$ongroupclick="UI.toast('group: ' + event.detail.item.label)">
|
||
<template slot="item">
|
||
<span $text="item.label"></span>
|
||
<span class="ms-auto text-muted small" $text="item.summary"></span>
|
||
</template>
|
||
</List>
|
||
```
|
||
|
||
#### 树形列表
|
||
|
||
```html
|
||
<script>
|
||
window.treeList = [
|
||
{ id: '1', label: 'Root', parent: '' },
|
||
{ id: '2', label: 'Child A', parent: '1' },
|
||
{ id: '3', label: 'Child B', parent: '1' }
|
||
]
|
||
</script>
|
||
|
||
<List mode="tree" collapsible class="h-100 overflow-auto"
|
||
$.state.list="window.treeList">
|
||
</List>
|
||
```
|
||
|
||
#### 虚拟滚动
|
||
|
||
```html
|
||
<script>
|
||
window.bigList = Array.from({ length: 2000 }, (_, i) => ({
|
||
id: String(i + 1),
|
||
label: 'Row ' + (i + 1),
|
||
summary: 'virtual row'
|
||
}))
|
||
</script>
|
||
|
||
<List fast class="h-100 overflow-auto"
|
||
$.state.list="window.bigList">
|
||
</List>
|
||
```
|
||
|
||
### 行为说明
|
||
|
||
- `filter` 是字符串时,同时匹配 `label` 和 `summary`
|
||
- `filter` 是对象时,按字段模糊匹配
|
||
- `tree` 模式会自动保留命中的祖先链
|
||
- `fast` 模式要求外层容器有 `overflow-auto`
|
||
- 数据项可加 `_itemHeight` 优化虚拟滚动测量
|
||
|
||
## Nav
|
||
|
||
`<Nav>` 默认是水平导航。需要侧边栏时才加 `vertical`。
|
||
|
||
### 属性
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `vertical` | `<Nav vertical>` | 垂直侧边栏 |
|
||
|
||
### 状态
|
||
|
||
| 名称 | 说明 |
|
||
|---|---|
|
||
| `brand` | `{ image, icon, label }` |
|
||
| `list` | 导航项数组 |
|
||
| `value` | 当前选中项 |
|
||
| `openName` | 当前展开的下拉项 |
|
||
|
||
### 导航项结构
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `type` | `button` / `dropdown` / `label` / `fill` |
|
||
| `name` | 导航项标识 |
|
||
| `label` | 文本 |
|
||
| `icon` | Bootstrap Icons 名称 |
|
||
| `class` | 额外 class |
|
||
| `noselect` | 选中时不写回 `value` |
|
||
| `width` | 仅 `dropdown` 在水平模式下控制下拉宽度 |
|
||
| `list` | `dropdown` 的子项 |
|
||
|
||
### 子项结构
|
||
|
||
`dropdown.list` 内的子项支持:
|
||
|
||
- `type: 'button'`
|
||
- `type: 'label'`
|
||
- `type: 'switch'`
|
||
|
||
`switch` 子项需要:
|
||
|
||
- `bind`: 一个状态对象
|
||
- `name`: 写回的字段名
|
||
|
||
### 事件
|
||
|
||
| 事件 | 触发时机 | `detail` |
|
||
|---|---|---|
|
||
| `change` | 选中项变化 | 当前 `name` |
|
||
| `nav` | 点击或展开时 | `{ item }` 或 `{ item, open }` |
|
||
|
||
### 推荐示例
|
||
|
||
```html
|
||
<script>
|
||
window.currentNav = 'users'
|
||
window.brand = { icon: 'cpu', label: 'Admin' }
|
||
window.navSettings = { darkMode: false }
|
||
window.navList = [
|
||
{ type: 'button', name: 'users', label: 'Users', icon: 'people' },
|
||
{ type: 'dropdown', name: 'settings', label: 'Settings', icon: 'gear', list: [
|
||
{ type: 'button', name: 'profile', label: 'Profile', icon: 'person' },
|
||
{ type: 'switch', name: 'darkMode', label: 'Dark Mode', icon: 'moon', bind: window.navSettings }
|
||
]}
|
||
]
|
||
</script>
|
||
|
||
<Nav class="border-bottom"
|
||
$.state.brand="window.brand"
|
||
$.state.list="window.navList"
|
||
$bind="window.currentNav"
|
||
$onnav="UI.toast(event.detail.item.label)">
|
||
</Nav>
|
||
```
|
||
|
||
### 备注
|
||
|
||
- 水平模式是默认推荐形态
|
||
- 需要左侧栏时再加 `vertical`
|
||
- `list` 推荐在 JS 里单独定义,不要硬塞成一长串 HTML 字面量
|
||
- 如果需要同步当前路由或菜单名,可以再加 `$bind`
|
||
|
||
## Modal / Dialog
|
||
|
||
### `<Modal>`
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `show()` | `editModal.show()` | 打开弹窗 |
|
||
| `hide()` | `editModal.hide()` | 关闭弹窗 |
|
||
| `change` | `@change` | 关闭时派发 `false` |
|
||
|
||
### `<Modal>` 的插槽
|
||
|
||
| 插槽 | 说明 |
|
||
|---|---|
|
||
| `header` | 弹窗头部 |
|
||
| `body` | 弹窗主体 |
|
||
| `footer` | 弹窗底部 |
|
||
|
||
### `<Modal>` 示例
|
||
|
||
```html
|
||
<Modal id="editModal">
|
||
<div slot="header">Edit User</div>
|
||
<div slot="body">
|
||
<AutoForm nobutton inline $.state.schema="window.profileSchema" $.state.data="window.profile"></AutoForm>
|
||
</div>
|
||
<div slot="footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||
</div>
|
||
</Modal>
|
||
|
||
<button class="btn btn-primary" onclick="editModal.show()">Open</button>
|
||
```
|
||
|
||
### `<Dialog>`
|
||
|
||
`<Dialog>` 是基于 `Modal` 的消息确认弹窗,平时直接用 `UI.showDialog` 更快。
|
||
|
||
### `UI.showDialog`
|
||
|
||
| 参数 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `title` | `title: 'Delete'` | 标题 |
|
||
| `message` | `message: 'Are you sure?'` | 内容 |
|
||
| `buttons` | `buttons: ['Cancel', 'Confirm']` | 按钮文本数组 |
|
||
| `type` | `type: 'danger'` | 样式类型 |
|
||
|
||
### `UI.showDialog` 示例
|
||
|
||
```js
|
||
const ok = await UI.confirm('Delete it?', { type: 'danger' })
|
||
if (ok) {
|
||
UI.toast('deleted', { type: 'success' })
|
||
}
|
||
```
|
||
|
||
## Resizer
|
||
|
||
`<Resizer>` 默认调整前一个元素的宽度。需要别的目标时再传 `target`。
|
||
|
||
### 属性
|
||
|
||
| 名称 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `vertical` | `<Resizer vertical>` | 调整高度,不是宽度 |
|
||
| `min` | `min="180"` | 最小像素,默认 `10` |
|
||
| `max` | `max="420"` | 最大像素,默认 `1000` |
|
||
| `target` | `target="sidebar"` | 要调整的元素;不写时默认前一个兄弟节点 |
|
||
|
||
### 事件
|
||
|
||
| 事件 | `detail` | 说明 |
|
||
|---|---|---|
|
||
| `resizing` | `{ oldSize, newSize }` | 拖动中持续触发 |
|
||
| `resize` | `{ oldSize, newSize }` | 拖动结束触发 |
|
||
| `change` | `newSize` | 拖动结束触发,适合写回状态 |
|
||
|
||
### 推荐示例
|
||
|
||
#### 默认用法
|
||
|
||
```html
|
||
<div id="sidebar" style="width: 280px"></div>
|
||
<Resizer min="180" max="420"></Resizer>
|
||
```
|
||
|
||
#### 指定 target
|
||
|
||
```html
|
||
<div id="sidebar" style="width: 280px"></div>
|
||
<Resizer target="sidebar" min="180" max="420"></Resizer>
|
||
```
|
||
|
||
## UI
|
||
|
||
### `UI.toast(message, options)`
|
||
|
||
| 参数 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `message` | `UI.toast('Saved')` | 提示文案 |
|
||
| `type` | `UI.toast('Saved', { type: 'success' })` | 主题类型 |
|
||
| `delay` | `UI.toast('Saved', { delay: 2000 })` | 自动关闭延迟 |
|
||
| `buttons` | `UI.toast('Confirm?', { buttons: ['Cancel', 'Confirm'] })` | 底部按钮 |
|
||
| `container` | `UI.toast('...', { container: 'right-top' })` | 自定义容器 |
|
||
|
||
### `UI.toastConfirm(message, options)`
|
||
|
||
返回 `Promise<boolean>`。
|
||
|
||
### `UI.alert(message, options)`
|
||
|
||
```js
|
||
await UI.alert('Saved')
|
||
```
|
||
|
||
### `UI.confirm(message, options)`
|
||
|
||
```js
|
||
const ok = await UI.confirm('Delete it?')
|
||
```
|
||
|
||
### `UI.showDialog(options)`
|
||
|
||
```js
|
||
await UI.showDialog({
|
||
title: 'Confirm',
|
||
message: 'Do you want to continue?',
|
||
buttons: ['Cancel', 'Confirm'],
|
||
type: 'primary'
|
||
})
|
||
```
|
||
|
||
## VirtualScroll / MouseMover
|
||
|
||
### `VirtualScroll(options = {})`
|
||
|
||
参数:
|
||
|
||
| 参数 | 写法示例 | 说明 |
|
||
|---|---|---|
|
||
| `itemHeight` | `VirtualScroll({ itemHeight: 40 })` | 默认行高预估值 |
|
||
|
||
返回值:
|
||
|
||
| 方法 | 说明 |
|
||
|---|---|
|
||
| `reset(list, container)` | 重置缓存并返回初始可见列表 |
|
||
| `init(list, refreshCallback)` | 初始化高度缓存 |
|
||
| `update(index, node)` | 记录节点实际高度 |
|
||
| `calc(container, list)` | 计算当前需要渲染的片段 |
|
||
|
||
### `MouseMover.start(event, handlers)`
|
||
|
||
```js
|
||
MouseMover.start(event, {
|
||
onmousemove: ({ w, h }) => {},
|
||
onmouseup: ({ w, h }) => {}
|
||
})
|
||
```
|
||
|
||
### 说明
|
||
|
||
- `VirtualScroll` 主要给 `List` 和 `DataTable` 用
|
||
- `MouseMover` 主要给 `Resizer` 用
|