2026-06-05 20:05:26 +08:00
|
|
|
|
# @apigo.cc/base - AI 逻辑操作说明书 (示例驱动)
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-08 22:16:57 +08:00
|
|
|
|
本库是基于 `@apigo.cc/state` 增强的 UI 原子库,提供遵循 Bootstrap 5.3 规范的高阶业务组件。本包采用完全的全局注入模式。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 全局变量与核心 API 清单
|
|
|
|
|
|
引入 `base.js` 后,以下对象将直接挂载到 `window` / `globalThis` 上,可随时随地调用:
|
|
|
|
|
|
|
|
|
|
|
|
| 全局变量名 | 用途说明 |
|
|
|
|
|
|
| :--- | :--- |
|
|
|
|
|
|
| **`HTTP`** | 异步请求工具 (`HTTP.get`, `HTTP.post`, `HTTP.request` 等)。 |
|
|
|
|
|
|
| **`UI`** | 交互组件触发器 (`UI.toast`, `UI.alert`, `UI.confirm`, `UI.showDialog`)。 |
|
|
|
|
|
|
| **`AutoForm`** | 表单组件扩展注册器 (`AutoForm.register`) 和自定义控件库 (`AutoForm.customTypes`)。 |
|
|
|
|
|
|
| **`MouseMover`** | 拖拽辅助工具 (`MouseMover.start`)。 |
|
|
|
|
|
|
| **`VirtualScroll`** / **`List`** | 虚拟滚动列表核心算法类。 |
|
|
|
|
|
|
|
|
|
|
|
|
*(注意:底层状态库 `@apigo.cc/state` 暴露的 `NewState`, `Hash`, `LocalStorage`, `State`, `$`, `$$`, `Component` 以及危险的高级 API `_unsafeRefreshState` 也在全局可用。严禁随意调用 `_unsafeRefreshState`。)*
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
## 1. 快速集成 (Quick Start)
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-06 22:30:12 +08:00
|
|
|
|
将脚本放置在 `<head>` 中,确保地基在 DOM 解析前就绪:
|
2026-06-05 20:05:26 +08:00
|
|
|
|
```html
|
2026-06-06 22:30:12 +08:00
|
|
|
|
<!-- 1. 基础状态机 (地基) -->
|
|
|
|
|
|
<script src="dist/lib/state.js"></script>
|
|
|
|
|
|
<!-- 2. Bootstrap 适配层 -->
|
|
|
|
|
|
<script src="dist/lib/bootstrap.js"></script>
|
2026-06-08 22:16:57 +08:00
|
|
|
|
<!-- 3. 本业务组件库 (原生注入) -->
|
2026-06-06 22:30:12 +08:00
|
|
|
|
<script src="dist/lib/base.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 4. 数据先行 (在 body 解析前定义)
|
|
|
|
|
|
window.brand = { label: 'My App' };
|
|
|
|
|
|
</script>
|
2026-06-05 20:05:26 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 组件超能力示例 (Mega Examples)
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 声明式数据组件 (`<API>`)
|
2026-05-14 20:04:31 +08:00
|
|
|
|
```html
|
2026-06-05 20:05:26 +08:00
|
|
|
|
<API id="userSearchApi" auto
|
|
|
|
|
|
$request='{
|
|
|
|
|
|
"url": "/api/users/search",
|
|
|
|
|
|
"method": "POST",
|
|
|
|
|
|
"data": { "keyword": State.searchKey }
|
|
|
|
|
|
}'
|
|
|
|
|
|
$onresponse="console.log('加载成功,响应对象:', event.detail); State.userList = event.detail.result"
|
|
|
|
|
|
$onerror="UI.toast('加载失败:' + event.detail.message, { type: 'danger' })">
|
|
|
|
|
|
</API>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 手动调用 API 示例 (适合动态触发/事后赋值场景)
|
|
|
|
|
|
async function forceReload() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// do() 返回 Promise<ResponseObject>。传入 noui: true 屏蔽全局 UI 弹窗报错。
|
|
|
|
|
|
const resp = await userSearchApi.do({ noui: true });
|
|
|
|
|
|
console.log("手动加载完成,结果:", resp.result);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("请求失败", err);
|
|
|
|
|
|
}
|
2026-05-14 20:04:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
2026-06-05 20:05:26 +08:00
|
|
|
|
* **AI 核心要点**:
|
|
|
|
|
|
* **请求响应结构 (ResponseObject)**:
|
|
|
|
|
|
`{ ok: Boolean, status: Number, headers: Object, responseType: 'json'|'binary'|'text', result: Any, error: String|null }`
|
|
|
|
|
|
* 深度监听 `request` 任何字段的改变自动触发请求(微任务防抖)。`response` 事件为 `bubbles: false`,`error` 为 `bubbles: true`。
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 万能表单与内嵌控件 (`<AutoForm>`)
|
|
|
|
|
|
```html
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 1. 预先确定表单 Schema (合并了所有扩展控件配置)
|
|
|
|
|
|
const employeeFormSchema = [
|
|
|
|
|
|
{ name: 'name', label: '姓名', type: 'text', setting: { required: true } },
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'joinDate',
|
|
|
|
|
|
label: '入职周期',
|
|
|
|
|
|
type: 'DatePicker', // 内置日期范围选择控件
|
|
|
|
|
|
setting: { rangeEnd: 'leaveDate' } // 影子字段绑定为 leaveDate
|
|
|
|
|
|
},
|
|
|
|
|
|
{ name: 'color', label: '工位颜色', type: 'ColorPicker' }, // 内置颜色控件
|
|
|
|
|
|
{ name: 'avatarIcon', label: '头像图标', type: 'IconPicker' }, // 内置图标控件
|
|
|
|
|
|
{ name: 'skills', label: '专业技能', type: 'TagsInput' }, // 内置多标签控件
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'role',
|
|
|
|
|
|
label: '角色',
|
|
|
|
|
|
type: 'select',
|
|
|
|
|
|
options: [{ label: '管理员', value: 'admin' }, { label: '员工', value: 'staff' }],
|
|
|
|
|
|
placeholder: '请选择角色...'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'status',
|
|
|
|
|
|
label: '状态',
|
|
|
|
|
|
type: 'checkbox',
|
|
|
|
|
|
options: ['在职', '离职'],
|
|
|
|
|
|
if: "this.data.age > 18" // 仅在 age > 18 时渲染,this 访问 AutoForm 实例
|
|
|
|
|
|
},
|
|
|
|
|
|
{ name: 'notify', label: '启用通知', type: 'switch' }
|
|
|
|
|
|
];
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 2. HTML 声明式属性绑定 schema 状态,且绑定接口 API 实例 -->
|
|
|
|
|
|
<AutoForm id="employeeForm"
|
|
|
|
|
|
vertical
|
2026-06-08 21:57:18 +08:00
|
|
|
|
$.state.schema="employeeFormSchema"
|
2026-06-05 20:05:26 +08:00
|
|
|
|
$api="saveEmployeeApi"
|
|
|
|
|
|
$onresponse="UI.toast('保存成功!')">
|
|
|
|
|
|
</AutoForm>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 3. 数据层回显/事后赋值操作 (使用 Object.assign 保证绑定的 Proxy 响应式链路不丢失)
|
|
|
|
|
|
Object.assign(employeeForm.state.data, {
|
|
|
|
|
|
name: '张三',
|
|
|
|
|
|
joinDate: '2026-06-01',
|
|
|
|
|
|
leaveDate: '2026-06-30',
|
|
|
|
|
|
color: '#ff0000',
|
|
|
|
|
|
avatarIcon: 'person',
|
|
|
|
|
|
skills: ['Vue', 'React'],
|
|
|
|
|
|
role: 'staff',
|
|
|
|
|
|
notify: true
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
* **AI 核心要点**:
|
|
|
|
|
|
* 容器属性:`vertical` (垂直排列)、`inline` (无边框流式行内布局)、`nobutton` (隐藏底部默认按钮)。
|
|
|
|
|
|
* **表单数据操作红线**:禁止对 `form.state.data` 或 `form.data` 执行覆盖式重新赋值,必须使用 `Object.assign`。
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
### 2.3 增强列表组件 (`<List>`)
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
列表有三类布局模式,分别有不同的数据结构关联,均推荐在 HTML 中声明绑定:
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
#### 模式一:普通扁平列表 (`mode="normal"`)
|
|
|
|
|
|
* **关联逻辑**:直接渲染一维数组项。
|
|
|
|
|
|
* **数据结构**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const userList = [
|
|
|
|
|
|
{ id: '1', label: '小明', summary: '前端开发' },
|
|
|
|
|
|
{ id: '2', label: '小红', summary: '产品经理' }
|
|
|
|
|
|
];
|
|
|
|
|
|
```
|
|
|
|
|
|
* **HTML 写法**:
|
|
|
|
|
|
```html
|
|
|
|
|
|
<List mode="normal" $list="userList" $onitemclick="console.log(event.detail.item)"></List>
|
|
|
|
|
|
```
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
#### 模式二:分组列表 (`mode="group"`)
|
|
|
|
|
|
* **关联逻辑**:`list` 的项通过 `group` 字段去匹配 `groups` 数组中的 `id` 字段。
|
|
|
|
|
|
* **数据结构**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const deptGroups = [
|
|
|
|
|
|
{ id: 'devGroup', label: '研发中心', summary: '共 5 人' },
|
|
|
|
|
|
{ id: 'hrGroup', label: '人力资源', summary: '共 2 人' }
|
|
|
|
|
|
];
|
|
|
|
|
|
const groupUserList = [
|
|
|
|
|
|
{ id: '1', label: '小明', group: 'devGroup' },
|
|
|
|
|
|
{ id: '2', label: '小红', group: 'hrGroup' }
|
|
|
|
|
|
];
|
|
|
|
|
|
```
|
|
|
|
|
|
* **HTML 写法**:
|
|
|
|
|
|
```html
|
2026-06-08 21:57:18 +08:00
|
|
|
|
<List mode="group" $list="groupUserList" $.state.groups="deptGroups"></List>
|
2026-06-05 20:05:26 +08:00
|
|
|
|
```
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
#### 模式三:树形可折叠列表 (`mode="tree"`) —— 超级示例
|
|
|
|
|
|
* **关联逻辑**:通过每项的 `parent` 关联父项的 `id` 属性,根项的 `parent` 设为空字符串 `''`。
|
|
|
|
|
|
* **数据结构**(内置 `_itemHeight` 优化虚拟滚动高度测量):
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const deptTreeList = [
|
|
|
|
|
|
{ id: '1', label: '总经办', parent: '', _itemHeight: 40 },
|
|
|
|
|
|
{ id: '2', label: '研发部', parent: '1', _itemHeight: 40 },
|
|
|
|
|
|
{ id: '3', label: '开发组', parent: '2', _itemHeight: 40 }
|
|
|
|
|
|
];
|
|
|
|
|
|
```
|
|
|
|
|
|
* **HTML 写法**:
|
|
|
|
|
|
```html
|
|
|
|
|
|
<List id="orgTreeList"
|
|
|
|
|
|
mode="tree"
|
|
|
|
|
|
fast
|
|
|
|
|
|
collapsible
|
|
|
|
|
|
auto-select
|
|
|
|
|
|
class="overflow-auto border"
|
|
|
|
|
|
style="height: 400px;"
|
|
|
|
|
|
$list="deptTreeList"
|
|
|
|
|
|
$onitemclick="console.log('点击节点:', event.detail.item, '全局绝对索引:', event.detail.index)">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 自定义单项右侧动作区域 -->
|
|
|
|
|
|
<template slot-id="item">
|
|
|
|
|
|
<span class="fw-semibold text-primary" $text="item.label"></span>
|
|
|
|
|
|
<div slot-id="item-actions">
|
|
|
|
|
|
<button class="btn btn-sm btn-link py-0 bi bi-plus-circle" onclick="addNode(item)"></button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</List>
|
|
|
|
|
|
```
|
|
|
|
|
|
* **AI 核心要点**:
|
|
|
|
|
|
* **属性默认值**:当数据结构符合默认字段命名(`id`, `label`, `summary`, `parent`, `group`)时,**无须填写**任何映射属性(如 `idfield`, `labelfield`, `parentfield` 等),实际编写代码时应当将其省略。
|
|
|
|
|
|
* 虚拟滚动 (`fast`) 强制容器必须声明为 `overflow-auto` 类。
|
2026-05-27 23:02:25 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
---
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
### 2.4 导航组件 (`<Nav>`)
|
|
|
|
|
|
```html
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 1. 预先确定品牌与菜单数据
|
|
|
|
|
|
const myBrand = { icon: 'shield-lock', label: '安全控制台' };
|
|
|
|
|
|
const myNavList = [
|
|
|
|
|
|
{ type: 'button', name: 'dashboard', label: '仪表盘', icon: 'speedometer' },
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'dropdown',
|
|
|
|
|
|
name: 'settings',
|
|
|
|
|
|
label: '系统设置',
|
|
|
|
|
|
icon: 'gear',
|
|
|
|
|
|
list: [
|
|
|
|
|
|
{ type: 'button', name: 'profile', label: '个人信息', icon: 'person' },
|
|
|
|
|
|
{ type: 'switch', name: 'darkMode', label: '暗黑模式', icon: 'moon', bind: LocalStorage, name: 'darkMode' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{ type: 'fill' },
|
|
|
|
|
|
{ type: 'button', name: 'logout', label: '退出登录', icon: 'box-arrow-right', noselect: true }
|
|
|
|
|
|
];
|
|
|
|
|
|
</script>
|
2026-05-29 22:45:56 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
<!-- 2. HTML 声明式属性绑定 -->
|
|
|
|
|
|
<Nav id="sidebarNav"
|
|
|
|
|
|
vertical
|
2026-06-08 21:57:18 +08:00
|
|
|
|
$.state.brand="myBrand"
|
|
|
|
|
|
$.state.list="myNavList"
|
2026-06-05 20:05:26 +08:00
|
|
|
|
$onnav="console.log('导航点击:', event.detail.item)">
|
|
|
|
|
|
</Nav>
|
|
|
|
|
|
```
|
|
|
|
|
|
* **AI 核心要点**:
|
|
|
|
|
|
* 常规导航项被点击时(且 `noselect` 不为 true),会自动更新全局 `Hash.nav = item.name`。
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
---
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
### 2.5 拖拽改变大小组件 (`<Resizer>`)
|
2026-05-14 20:04:31 +08:00
|
|
|
|
```html
|
2026-06-05 20:05:26 +08:00
|
|
|
|
<div class="d-flex" style="height: 300px;">
|
|
|
|
|
|
<div id="leftPanel" style="width: 200px;" class="bg-light">侧边栏</div>
|
|
|
|
|
|
<!-- 拖拽调节器,默认 target 为前一个兄弟节点 -->
|
|
|
|
|
|
<Resizer target="leftPanel"
|
|
|
|
|
|
min="100"
|
|
|
|
|
|
max="400"
|
|
|
|
|
|
$onresizing="console.log('拖动尺寸:', event.detail.newSize)"
|
|
|
|
|
|
$change="State.leftPanelWidth">
|
|
|
|
|
|
</Resizer>
|
|
|
|
|
|
<div class="flex-fill">内容区</div>
|
|
|
|
|
|
</div>
|
2026-05-14 20:04:31 +08:00
|
|
|
|
```
|
2026-06-05 20:05:26 +08:00
|
|
|
|
* **AI 核心要点**:
|
|
|
|
|
|
* `resizing` 与 `resize` 事件的 `detail` 结构均为 `{ oldSize: Number, newSize: Number }`;`change` 事件的 `detail` 为 `newSize` 像素数字。
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
## 3. 网络与交互工具集 (`HTTP` & `UI`)
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 HTTP 请求工具 (`HTTP`)
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 支持 HTTP.get, HTTP.post, HTTP.put, HTTP.delete, HTTP.head
|
|
|
|
|
|
const resp = await HTTP.post({
|
|
|
|
|
|
url: '/api/user/save',
|
|
|
|
|
|
data: { name: 'Alice', file: avatarFile },
|
|
|
|
|
|
timeout: 5000
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
* **返回值 `ResponseObject` 结构**:
|
|
|
|
|
|
* `ok` (Boolean): 状态码是否在 200-299。
|
|
|
|
|
|
* `status` (Number): HTTP 状态码。
|
|
|
|
|
|
* `headers` (Object): 响应头键值对。
|
|
|
|
|
|
* `responseType` (String): `'json' | 'binary' | 'text'`。
|
|
|
|
|
|
* `result` (Any): 成功后的解析结果。
|
|
|
|
|
|
* `error` (String | null): 失败的错误描述。
|
|
|
|
|
|
* **数据自动转换**:若 `data` 内包含 `File`/`Blob`,自动转为 `FormData` 且清除 Content-Type;普通对象自动序列化为 JSON 并添加 `application/json`。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 交互工具 (`UI`)
|
|
|
|
|
|
* **`UI.showDialog({ title, message, buttons, type })`**
|
|
|
|
|
|
* **返回值**:`Promise<Number>`。点击按钮返回其索引值 (从 `1` 开始),点击关闭或取消返回 `0`。
|
|
|
|
|
|
* **参数**:`type` 可以为 `'primary'|'danger'|'warning'|'success'` 控制主题色;`message` 支持 HTML。
|
|
|
|
|
|
* **`UI.alert(message, options)`**
|
|
|
|
|
|
* **返回值**:`Promise<Boolean>`(点击关闭按钮返回 `false`)。
|
|
|
|
|
|
* **`UI.confirm(message, options)`**
|
|
|
|
|
|
* **返回值**:`Promise<Boolean>`。点击“确认”返回 `true`,点击“取消”或关闭返回 `false`。
|
|
|
|
|
|
* **`UI.toast(message, options)`**
|
|
|
|
|
|
* **返回值**:`void`。`options.delay` 为自动消失延迟毫秒(`0` 代表不消失)。可以通过传入 `buttons: ['选项1']` 按钮提供交互,点击后在实例 `result` 上保存索引。
|
|
|
|
|
|
* **`UI.toastConfirm(message, options)`**
|
|
|
|
|
|
* **返回值**:`Promise<Boolean>`。在 Toast 中提供单确认按钮,点击确认返回 `true`,否则返回 `false`。
|
2026-05-14 20:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 20:05:26 +08:00
|
|
|
|
## 4. 开发红线 (Constraints)
|
|
|
|
|
|
|
|
|
|
|
|
1. **表单数据操作红线**:严禁直接覆盖表单的 `state.data` 或 `data` 对象(如 `form.data = {}`)。这会切断与内部 Proxy 的响应式链路。必须使用 `Object.assign(form.state.data, newData)`。
|
2026-06-08 21:57:18 +08:00
|
|
|
|
2. **结构化指令红线**:所有 `$if` 与 `$each` 指令 **必须** 显式作用于 `<template>` 标签,且严禁在同一个 `<template>` 上同时使用两者(必须嵌套)。
|
|
|
|
|
|
3. **列表布局红线**:开启虚拟滚动(`fast`)时,容器必须包含 `overflow-auto` 类。
|
|
|
|
|
|
4. **指令 DOM 保护**:严禁使用原生 DOM API 直接修改由 `$each`、`$if` 或组件渲染指令生成的 DOM 节点。所有 DOM 状态的变化应当完全通过修改与之绑定的底层 `State` 属性驱动。
|
|
|
|
|
|
5. **退出拦截约束**:在全局配置有 `State.exitBlocks > 0` 时,框架将强行拦截并警告任何刷新/关闭页面的行为。
|