@apigo.cc/base
v1.0.23
Published
`@apigo.cc/base` 是基于 `@apigo.cc/state` 和 Bootstrap 5.3 的同步组件层。页面只需要按顺序引入脚本,就能在首屏完成 DOM 处理和组件渲染。
Readme
@apigo.cc/base
@apigo.cc/base 是基于 @apigo.cc/state 和 Bootstrap 5.3 的同步组件层。页面只需要按顺序引入脚本,就能在首屏完成 DOM 处理和组件渲染。
目录
- 依赖加载 - 同步加载顺序和来源说明
- 全局变量 - 本包直接挂到
window的变量 - HTTP - 请求工具
- API - 声明式请求节点
- AutoForm - 自动表单节点
- 表单扩展控件 -
DatePicker/ColorPicker/IconPicker/TagsInput - List - 普通、分组、树形和虚拟滚动列表
- Nav - 水平导航栏或垂直侧边栏
- Modal / Dialog - 弹窗容器和确认弹窗
- Resizer - 拖拽分隔条
- UI - toast 和对话框工具
- VirtualScroll / MouseMover - 虚拟滚动和鼠标拖拽辅助
依赖加载
按顺序同步加载:
@apigo.cc/state -> @apigo.cc/bootstrap -> @apigo.cc/base
base 会直接使用来自 @apigo.cc/state 的全局对象 State、Hash、LocalStorage。
推荐加载顺序
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/state.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/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/jsondata是FormData时,不手动设置Content-Type,让浏览器生成带 boundary 的 multipart 请求头- 如果你在
headers里手动传了Content-Type,普通对象请求会按你的设置发送;FormData仍然以浏览器生成的请求头为准
返回值
{
ok,
status,
headers,
responseType,
result,
error
}推荐示例
<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 默认结构
{
url: '',
method: 'GET',
headers: {},
data: null,
timeout: 10000,
responseType: ''
}response 默认结构
{
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 写法
['Admin', 'Editor']
[{ label: 'Admin', value: 'admin' }]事件
| 事件 | 触发时机 | detail |
|---|---|---|
| submit | 表单校验通过、提交前 | 当前表单数据 |
| response | 提交成功 | 完整响应对象 |
| error | 提交失败 | Error 对象 |
插槽
| 插槽 | 位置 | 用途 |
|---|---|---|
| actions | 默认提交按钮左侧 | 自定义动作按钮 |
推荐示例
<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> 示例
<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> 单独使用
<script>
window.birthDate = '2026-06-01'
</script>
<DatePicker $bind="window.birthDate"></DatePicker><ColorPicker>
| 项目 | 说明 |
|---|---|
| state.value | 颜色值,默认 #000000 |
| value | 可读写,映射到 state.value |
| change | 颜色变化时触发,detail 为颜色字符串 |
<ColorPicker> 示例
<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> 示例
<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> 示例
<script>
window.tagForm = {}
window.tagsSchema = [
{ name: 'tags', label: 'Tags', type: 'TagsInput' }
]
</script>
<AutoForm $.state.schema="window.tagsSchema" $.state.data="window.tagForm"></AutoForm><TagsInput> 单独使用
<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
<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>分组列表
<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>树形列表
<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>虚拟滚动
<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和summaryfilter是对象时,按字段模糊匹配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 } |
推荐示例
<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> 示例
<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 示例
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 | 拖动结束触发,适合写回状态 |
推荐示例
默认用法
<div id="sidebar" style="width: 280px"></div>
<Resizer min="180" max="420"></Resizer>指定 target
<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)
await UI.alert('Saved')UI.confirm(message, options)
const ok = await UI.confirm('Delete it?')UI.showDialog(options)
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)
MouseMover.start(event, {
onmousemove: ({ w, h }) => {},
onmouseup: ({ w, h }) => {}
})说明
VirtualScroll主要给List和DataTable用MouseMover主要给Resizer用
