PVQ-UI 组件库调用手册(插件实操)
状态:使用指南(插件开发者速查) 适用:任何要调用
pvq-ui的 PVQ 插件(新插件 / 存量迁移) 关联:《PVQ-UI项目规则.md》(铁律)、《PVQ-UI组件库开发方案.md》(架构)、《库演进治理方案.md》(版本化)
1. 一句话流程
*复制 loader → 一行 enqueue → 直接写 `.pvq-` BEM 类 → 自身 CSS 只写插件专有样式。**
不要自己注册库句柄、不要复制库 CSS、不要自造通用组件。
2. 接入步骤(新插件)
2.1 复制 loader
把 pvq-ui 仓库里的 pvq-ui-loader.php 复制到插件 includes/ 目录(或任意合适位置)。这是唯一需要"复制"的文件,其余全走 CDN。
2.2 一行 enqueue
在插件的 admin_enqueue_scripts / enqueue_block_editor_assets / wp_enqueue_scripts 钩子里:
require_once __DIR__ . '/includes/pvq-ui-loader.php';
// 后台场景(最常用)
PVQ_UI_Loader::enqueue( array( 'admin' ) );
// 块编辑器场景
PVQ_UI_Loader::enqueue( array( 'editor' ) );
// 前端场景
PVQ_UI_Loader::enqueue( array( 'frontend' ) );
// 多场景一次性
PVQ_UI_Loader::enqueue( array( 'admin', 'frontend' ) );
第二参数
$version推荐留空,由站点级PVQ_UI_VERSION统一决定(见 §2.3);仅在需要"某插件单独锁版本"时才显式传,且须理解其先到先得语义。 版本下限:选择器 Select / 删除二次确认需 ≥1.2.0、Search 需 ≥1.2.8、Table/DataGrid/Weight 需 ≥1.2.9、Modal/Range 可编辑需 ≥1.2.10、Toast/Popover 需 ≥1.3.0(逻辑随pvq-ui.js发布,低于该版本不生效)。
- 第一个参数
$scenes:需要哪些层就传哪些(admin/editor/frontend)。base 层pvq-ui会自动随依赖加载,无需手动传。 - 第二个参数
$version:锁定库版本。推荐做法(v1.4.0 起):站点级统一定义一次,调用处不传版本。
2.3 版本定义:全站一处(推荐,v1.4.0 起)
loader 里的 PVQ_UI_VERSION 是 defined() 守卫的先到先得常量,且 converge() 会按它把所有 pvq-ui* 句柄的 URL 对齐过去——也就是说全站实际生效的库版本 = 第一次调用 enqueue() 时那个插件传的版本(不传则用它自己副本的 DEFAULT_VERSION)。这带来两个隐患:
- 谁先加载不确定 → 某个插件把版本钉旧(如硬编码
'1.2.10'),全站都被拉回旧版,其他插件即使副本已升版也无效; - 插件副本的
DEFAULT_VERSION只有在"该插件恰好第一个调用"时才起作用,升级副本常常是空转的。
⚠️ 还有更隐蔽的一层(v1.4.2 补充,务必知道):每份副本的类定义都被
if ( ! class_exists( 'PVQ_UI_Loader' ) )包裹,所以最先requireloader 的那个插件的副本决定了整个类——其后所有副本的整份文件被跳过,它们的DEFAULT_VERSION/register()/converge()从未执行。此时所有调用点都"没传版本"、看起来完全合规,但全站版本其实是最先加载的那份副本说了算。若那份副本恰好落后,就会出现「新版插件里.pvq-popover/.pvq-toast等组件"不存在"、浮层打不开、操作无反馈」这类静默故障,排查时完全想不到是组件库版本问题。因此:所有 PVQ 插件的 loader 副本必须保持同版本,只升其中一个是不够的;并且强烈建议同时在wp-config.php定义PVQ_UI_VERSION——否则"不传版本"等于把版本交给加载顺序。v1.4.2 起 loader 自带兜底:未定义站点常量时在文件作用域按「各副本自述版本的最高者」就地定义
PVQ_UI_VERSION(因此即便最先加载的旧副本赢得了类归属、其类代码被执行,也会沿用这个正确值——旧副本再也无法把全站拉回旧版);并且当站点生效版本 < 最高副本版本时一次性告警(_doing_it_wrong()+error_log,列出各副本版本、落后项与修复命令)。健康站点零输出。但这只是防御,正常做法仍是"先定义站点常量 + 同步全部副本"。
因此推荐在站点级定义一次,插件调用处一律不传版本:
// wp-config.php(在 /* That's all, stop editing! */ 之前)
define( 'PVQ_UI_VERSION', '1.4.3' ); // 全站 PVQ-UI 版本唯一真源
// 各插件:不再传版本
PVQ_UI_Loader::enqueue( array( 'admin' ) );
- 好处:版本只改一处、与插件加载顺序无关、插件副本的
DEFAULT_VERSION不再影响全站(两层"先到先得"同时被钉死)。 - 需要"某插件单独锁旧版"时(临时隔离),才在调用处显式传版本;但注意上述先到先得语义——只有最先加载者有话语权,故这种方式不适合长期共存多版本。插件不要写版本字面量(如
enqueue(['frontend'], '1.2.10')),那是最典型的钉版本来源。 - 升版后别忘了同步站点常量:站点常量低于最高副本版本时 loader 会告警(提示常量落后)。
- 站点若因内网/离线需要指向本地副本,仍用
PVQ_UI_URL等常量覆盖(见 §6)。
2.4 loader 副本同步(升版必做,已脚本化)
pvq-ui-loader.php 是复制分发的(每个插件 cp 一份放自己 includes/),因此库升版或 loader 逻辑改动后,插件侧副本会过期。库仓库提供两个脚本(scripts/ 下):
php scripts/check-loader-copies.php # 只读体检:列出全部副本的版本/漂移/调用点,发现落后则退出码非 0
php scripts/sync-loaders.php # 干跑(默认):打印将要做的事
php scripts/sync-loaders.php --apply # 实际同步(留 .bak、逐个 php -l)
判定规则(与《项目规则》§4.4 一致,脚本已固化):
| 差异性质 | 处置 |
|---|---|
| 与真源逐字节一致 | 跳过 |
仅注释 / DEFAULT_VERSION 差异 |
用真源 cp 覆盖(插件侧副本应以真源为准) |
衍生副本(自有文档头、if (...): 语法等) |
最小侵入:只改 DEFAULT_VERSION 与其行前注释,保留本地内容 |
.bak 约定:脚本写前留 .bak(便于即时回退),验证通过后请清除(find <插件目录> -name '*.bak*' -delete);.bak 不入库、不长期堆积。
当前全站副本(2026-09-19 二次核实,4 份):
| 副本 | 性质 | 处置 |
|---|---|---|
/home/muyu/pvq-weixinyuedu/includes/ |
与真源逐字节一致(安装处是软链;该目录非 git 仓库) | cp 覆盖 |
/www/wwwroot/wordpress/wp-content/plugins/pvq-wp/includes/ |
与真源逐字节一致 | cp 覆盖 |
/www/wwwroot/wordpress/wp-content/plugins/pvq-threads-sync/includes/ |
衍生副本(自有文档头 / 冒号语法),依赖逻辑已核实与真源一致 | 只改版本行 |
/mnt/c/Users/muyu/Documents/code/Gastronomy/wordpress/pvq-workfolio/includes/ |
安装处是软链;独立 git 仓库 oh-af/pvq-workfolio。1.4.2 前为 1.2.10 且缺 font-awesome-frontend 逻辑 |
头部是标准头 → 整份 cp |
⚠️ 找副本必须跟随符号链接:默认
find不跟随符号链接,会整份漏掉以软链安装的插件——pvq-workfolio那份陈旧副本就是这样长期未被发现的(它指向/mnt/c/...,不在任何扫描根下)。请用:find -L /www/wwwroot/wordpress/wp-content /home/muyu -maxdepth 8 -name 'pvq-ui-loader.php'体检脚本已不依赖 find:它直接遍历
wp-content/plugins/*并realpath()解析,因此软链插件也能被发现。新增插件后重跑体检,看是否多出第 5 份。库自身的site/pvq-ui-loader.php必须与仓库根真源逐字节一致(体检一并核对)。
2.5 自身 CSS 声明依赖
插件自己的样式要"站在库肩上",声明依赖即可:
wp_enqueue_style(
'my-plugin-admin',
plugins_url( 'assets/css/admin.css', __FILE__ ),
array( 'pvq-ui', 'pvq-ui-admin' ), // 依赖库,不复制库样式
MY_PLUGIN_VER
);
2.6 直接写组件
在 PHP 渲染的 HTML / 模板里,直接用库提供的 .pvq-* 类,无需任何额外注册:
<div class="pvq-card">
<div class="pvq-card__header">
<span class="pvq-card__icon pvq-card__icon--primary"><i class="fas fa-cog"></i></span>
<div>
<h3 class="pvq-card__title">连接设置</h3>
<p class="pvq-card__desc">配置你的账号信息</p>
</div>
</div>
<div class="pvq-card__action">
<button class="pvq-btn pvq-btn--primary">保存</button>
</div>
</div>
图标用 FontAwesome class(如 fas fa-cog)。loader 在 admin 与 frontend 两层均声明对 font-awesome 句柄的依赖并保证其已注册,故这两层直接写 fas 图标即可;base / editor 层若需 FA 图标,请额外 enqueue(['admin']) 或手动注册 font-awesome。
3. 场景与层对照
| 你想用在哪 | enqueue 传参 | 自动加载 | 典型组件 |
|---|---|---|---|
| 后台设置页 | ['admin'] |
pvq-ui + pvq-ui-admin |
卡片 / 表单 / 开关 / 勾选框 / 单选 / 选项卡 / 横幅 / 统计 / 帮助 |
| 块编辑器 | ['editor'] |
pvq-ui + pvq-ui-editor |
块骨架 placeholder / loading / preview |
| 前端页面 | ['frontend'] |
pvq-ui + pvq-ui-frontend |
callout / quote / prose / 进度条 / 书签 / 卡片化表格 / 数据表格(DataGrid) / 权重输入框(Weight) |
| 全场景基础 | (不需要单独传,base 随依赖带) | pvq-ui |
按钮 / 令牌 / 动画 |
4. 常用组件速查(仅列类名,完整 API 见 doc/模块文档/设计令牌与组件清单.md;机器真源为 site/inc/components.json)
完整清单与示例以
doc/模块文档/设计令牌与组件清单.md为准。
-
按钮:
.pvq-btn.pvq-btn--primary/--secondary/--danger/--warning/--ghost.pvq-btn--sm/--icon.pvq-btn-group(破坏性操作加data-pvq-confirm二次确认,见 §4.2) -
卡片:
.pvq-card.pvq-card__header/__icon/__icon--primary/__title/__desc/__action -
表单:
.pvq-field.pvq-field__label.pvq-field__help.pvq-field__error(红字错误文案槽).pvq-field__success(绿字成功文案槽,与 error 对称).pvq-input.pvq-input__wrap.pvq-input__wrap--has-icon.pvq-input__icon.pvq-input__toggle;校验态:.pvq-field--error(红框+红辉光).pvq-field--success(绿框+绿辉光).pvq-field__label--required(必填*);变体:前后缀.pvq-affix-group/.pvq-affix/--suffix、输入组.pvq-input-group/.pvq-input-group__btn(--ghost)、清除.pvq-input__clear+.pvq-input__wrap--has-clear、密码显隐切换.pvq-input__toggle+.pvq-input__wrap--has-toggle(由pvq-ui.js自动初始化) -
开关(语义化,纯 CSS):
<label class="pvq-switch"> <input type="checkbox" class="pvq-switch__input" checked> <span class="pvq-switch__slider"></span> </label> -
选项卡 + 磁吸滑块(胶囊 pill):
.pvq-tabs.pvq-tabs__slider.pvq-tab.pvq-tab--active.pvq-tab__icon.pvq-tab-content.pvq-tab-content--active(容器/tab/滑块全圆角999px,width:fit-content紧凑;形态细节见《设计令牌与组件清单.md》Tabs 条目) -
状态横幅:
.pvq-banner.pvq-banner--success/danger/warning/info.pvq-banner__dot -
状态指示:
.pvq-status.pvq-status--success/warning/error.pvq-status__dot -
统计:
.pvq-stat-grid.pvq-stat.pvq-stat__value.pvq-stat__label -
帮助:
.pvq-help.pvq-help__icon.pvq-help__title.pvq-help__text -
范围:
.pvq-range.pvq-range__group.pvq-range__value.pvq-range__labels(v1.2.10:.pvq-range__value用<input type="number">即可直接在框内改数字,回写滑块,clamp + step 对齐;用<span>保持只读) -
二次确认弹窗(v1.2.10 新增,
stable,含 JS):.pvq-modal[data-pvq-modal]>.pvq-modal__backdrop+.pvq-modal__panel(__close/__icon/__title/__desc/可选__list/__actions>__cancel+__confirm)。弹窗式二次确认,与就地确认data-pvq-confirm互为补充。颜色由--pvq-modal-accent(默认--pvq-danger红)驱动,变体--warning/--primary只换令牌。确认按钮为实色 accent(须用 (0,4,1) 特度压过库button.pvq-btn.pvq-btn)。库initConfirms()自动接管:触发器[data-pvq-modal-open="<id>"]、关闭[data-pvq-modal-close]、确认[data-pvq-modal-confirm](派发pvq-modal:confirm,detail:{id, modal, trigger},插件监听执行业务);异步加[data-pvq-modal-async]转 spinner 并禁用,业务完成后调modal.closeModal()。含焦点陷阱、默认聚焦取消、Esc、焦点归还、body 滚动锁。 -
其它:
.pvq-divider.pvq-spinner.pvq-alert.pvq-alert--success/error/loading.pvq-meta-list.pvq-meta-list__row/__key/__value -
选择器 Select(1.1.2 新增,
experimental):<div class="pvq-select"> <select name="threads_sync_settings[mode]"> <option value="auto">自动同步</option> <option value="manual" selected>手动同步</option> <option value="off" disabled>关闭(维护中)</option> </select> </div>详见下方 §4.1 完整调用接口。
- 变体
--sm(紧凑):高度 30px、字号 13px、解除库默认min-width:160px,适配 WP 后台密集行(tablenav / bulkactions / 媒体工具栏)。纯 CSS,JS 无需改;加在外层.pvq-select--sm即可。 - 变体
--glass(玻璃磨砂下拉,见 §4.1.9):容器加pvq-select--glass,下拉面板半透明白 +backdrop-filterblur 磨砂;JS 自动把容器状态带到 body 级面板。
- 变体
-
搜索 Search(1.2.8 新增,
stable):.pvq-search.pvq-search__icon.pvq-search__input.pvq-search__clear(清除 ✕).pvq-search__submit(提交).pvq-search__spinner(加载态);功能变体:--with-btn(提交按钮,joined)--with-filter(复用.pvq-select作筛选段)--loading(图标位转 spinner)--sm(高 34px);形状变体:--pill/--underline(无框仅底线)/--ghost(填底无边框)/--glass(半透明 + blur);颜色令牌--pvq-search-accent(默认--pvq-primary绿,hover 用color-mix压暗,插件可覆盖任意色)。库initSearches()自动初始化,提交/筛选 change 派发pvq-search事件。详见下方 §4.3 完整调用接口。
-
进度条 Progress(1.2.9 并入,
stable):.pvq-progress.pvq-progress__fill(设width控制进度).pvq-progress__text;变体.pvq-progress--lg(大号).pvq-progress--danger(危险红,走--pvq-danger)。颜色令牌--pvq-progress-accent(默认--pvq-primary绿)。 -
书签 Bookmark(1.2.9 并入,
stable):.pvq-bookmark.pvq-bookmark__image(左图).pvq-bookmark__content.pvq-bookmark__url.pvq-bookmark__favicon.pvq-bookmark__title.pvq-bookmark__description(右文);去掉__image即纯文字链接卡。纯 CSS。 -
勾选框 Checkbox(base 层,1.2.9 并入;末次修订由 frontend 层提升至 base,故
enqueue(['admin'])的后台也可用):结构label.pvq-cb-item> 隐藏<input type=checkbox>+span.pvq-custom-check(.pvq-cb-box+span.pvq-cb-mark✓)+span.pvq-cb-text,容器.pvq-cb-list;三变体pvq-cb-variant-strike(勾选删除线)/pvq-cb-variant-ghost(透明底圆角)/pvq-cb-variant-ripple(圆形+波纹);尺寸--sm(24px)/默认(32px)/--lg(40px);支持:indeterminate(列表页"全选"主框)。纯 CSS,颜色全走--pvq-primary。 -
单选按钮 Radio(base 层,1.2.9 并入;末次修订由 frontend 层提升至 base):结构
label.pvq-rb-item> 隐藏<input type=radio>+span.pvq-custom-radio(.pvq-rb-outer+span.pvq-rb-dot)+span.pvq-rb-label,容器.pvq-rb-group;四变体pvq-rb-variant-card(选中高亮整行)/pvq-rb-variant-ghost/pvq-rb-variant-ripple/pvq-rb-variant-solid(纯色填充);尺寸--sm/--lg。纯 CSS,颜色全走--pvq-primary。 -
卡片化表格 Table(1.2.9 并入,
stable):.pvq-table(配.pvq-table-wrap加[data-pvq-table]触发入场动画)即原生table/thead/tbody;表头绿底、单元格独立圆角卡片;--striped(斑马纹)、__col--right/center(列对齐)、caption。普通.pvq-table不加data则完全可见、无动画。 -
数据表格 DataGrid(1.2.9 并入,
stable,含 JS):.pvq-datagrid[data-pvq-datagrid]>.pvq-datagrid__toolbar(搜索/排序/筛选/重置)+.pvq-datagrid__content>.pvq-datagrid__table+.pvq-datagrid__empty。搜索/筛选为自包含可展开胶囊(点图标原地展开,点栏外空白收起);排序三态循环(点头列/按钮);重置清空全部。库initDataGrids()自动接管,筛选下拉按th[data-col]与单元格值自动生成。 -
权重输入框 Weight(v1.2.10 新增,随 1.2.11 固化发布,
stable,含 JS):.pvq-weight[data-pvq-weight]>.pvq-weight__list>.pvq-weight__item(label / input-wrap / slider-wrap 三段横向,窄屏flex-wrap)。数字框.pvq-weight__input(input[type="number"],CSS 写input.pvq-weight__input压过 WP 核心)+ 单位.pvq-weight__unit(绝对定位右下);滑块.pvq-weight__slider(input[type="range"])。底部.pvq-weight__action含总和.pvq-weight__total-val与「重置 / 归一化」按钮(data-pvq-weight-reset/data-pvq-weight-normalize,按钮复用库.pvq-btn--primary/.pvq-btn--ghost+.pvq-weight__btn-pill胶囊)。库initWeights()自动接管:输入↔滑块双向同步、实时总和、归一化(总和归 100,差值补首行)并派发pvq-weight:normalized(detail.weights)供插件持久化,重置派发pvq-weight:reset。设计原则:形状归库、颜色归令牌——组件定义--pvq-weight-accent(默认--pvq-primary绿),聚焦/滑块/徽章全走它,零硬编码;变体.pvq-weight--sm(紧凑)/.pvq-weight--glass(玻璃拟态)。详见下方 §4.4 完整调用接口。
-
锚点浮层 Popover(v1.3.1 新增,
experimental,含 JS,回流自 pvq-weixinyuedu):.pvq-popover.pvq-popover__body.pvq-popover__foot.pvq-popover__arrow,修饰符--open(打开态,JS 加)/--above(JS 判定的上方位置)。以元素为锚、脱离文档流、按需显隐(术语解释 / 作者卡 / 预览 / 迷你详情)——card是常驻容器(无定位/开合)、collapse是原地展开会撑高文档流、confirm-modal是模态阻断、toast固定角落不锚定,四者都不能替代。开关只有两态:hidden属性(彻底不显示)+--open(淡入显示)。position:fixed实时定位(不依赖祖先position),视口边缘自动翻转并水平/垂直双向夹取;桌面悬停(移开延时 160ms 关闭)、触屏点按开合、键盘聚焦 +Escape(关闭并交还焦点)。JS 双入口:PVQUI.popover(anchor, opts)与声明式[data-pvq-popover="<浮层 id>"],initPopovers()随initAll自动接管且幂等;事件pvq-popover:open/pvq-popover:close。详见下方 §4.7 完整调用接口。
5. JS 交互(无需你写,库已委托)
pvq-ui.js 已用事件委托处理以下交互,插件只监听自定义事件即可:
| 交互 | 库行为 | 插件要做的 |
|---|---|---|
| 选项卡切换 | 点击 .pvq-tab → 自动切 .pvq-tab--active + 移动 .pvq-tabs__slider |
无需处理(纯视觉) |
| 开关 | 真实 checkbox,纯 CSS 切换,无需 JS | 监听 change 事件或不监听(表单原生提交即带值) |
| range 拖动 | 同步 .pvq-range__value(<span> 写 text;<input> 写 value) |
如需存值,监听 input 事件 |
| range 数值框编辑(v1.2.10) | .pvq-range__value 为 <input type="number"> 时可直接改数字 → 实时回写滑块(clamp min/max + step 对齐),并对滑块派发 input/change。双向实时联动互不失效:拖滑块同步框内、改框内同步滑块;输入过程中不改写框内文本,回车/失焦才校正显示 |
无需处理;监听滑块 input/change 即可拿到值 |
| 搜索 | 输入回车 / 提交按钮 → 派发 pvq-search CustomEvent(detail: { query, filter, element });筛选 <select> change(仅当有词)同 |
监听 pvq-search 调后端检索;异步期间自行加/去 .pvq-search--loading |
| 卡片化表格 | [data-pvq-table] 由 IntersectionObserver 触发入场动画(pvq-is-visible),尊重 prefers-reduced-motion |
无需处理(纯视觉);普通 .pvq-table 不加 data-pvq-table 则完全可见 |
| 数据表格 DataGrid | [data-pvq-datagrid] 由库 initDataGrids() 接管:搜索实时过滤行、排序重排(数值/中文)、筛选按列+值多选、重置恢复、入场动画;筛选下拉按 th[data-col] + 单元格值自动生成 |
如需把排序/筛选结果回传后端,可监听工具栏按钮(.pvq-datagrid__btn--sort/--reset)的 click;默认纯前端过滤 |
| 二次确认弹窗 Confirm Modal(v1.2.10) | [data-pvq-modal-open="<id>"] 打开、[data-pvq-modal-close] 关闭(X/取消/点遮罩)、[data-pvq-modal-confirm] 确认并派发 pvq-modal:confirm(detail: {id, modal, trigger});含焦点陷阱、默认聚焦取消、Esc、焦点归还、滚动锁(<html> 加 .pvq-scroll-locked,关闭 / pagehide 释放,bfcache 恢复自愈),库 initConfirms() 自动接管 |
监听 pvq-modal:confirm 执行真实业务(删除请求等);异步场景给确认按钮加 [data-pvq-modal-async](转 spinner 并禁用防重复提交),业务完成后调 e.detail.modal.closeModal() |
严禁在插件里写 inline
onclick/onkeydown调库组件;一律用data-*+ 委托 / 原生事件。
4.1 选择器 Select 调用接口(1.1.2 新增 · experimental)
完整 API 以
doc/模块文档/设计令牌与组件清单.md「选择器 Select」条目为准。下方是插件调用的实操契约。
4.1.1 调用前提
- 版本必须 ≥ 1.1.2(选择器逻辑随
pvq-ui.js发布,旧版无此功能):PVQ_UI_Loader::enqueue( array( 'admin' ), '1.2.0' ); // 后台场景;selector 逻辑随 base 自动加载pvq-ui.js在所有场景的enqueue()里都会被wp_enqueue_script('pvq-ui')带出,无需单独 enqueue JS。 - 插件不复制、不重写任何下拉 JS/CSS;选择器纯靠写 HTML 类自动生效。
4.1.2 HTML 契约(必须这样写)
<div class="pvq-select">
<select name="your_form[field]"> <!-- ① 原生 select 必须保留,且 name 照常 -->
<option value="a">选项 A</option>
<option value="b" selected>选项 B</option> <!-- ② selected 决定初始显示 -->
<option value="c" disabled>选项 C(禁用)</option> <!-- ③ disabled 自动识别 -->
</select>
</div>
- 类名唯一要求:外层包一个
.pvq-select,里面放一个原生<select>。 - 原生
<select>仅视觉隐藏、DOM 仍在 → 表单照常提交,WP 里$_POST['your_form']['field']正常拿到值。.pvq-select可放进.pvq-field内做行布局(见展示页)。 - 不要手写
.pvq-select__trigger/.pvq-select__dropdown/.pvq-select__option—— 这些由库 JS 自动生成(触发器生成在.pvq-select内,下拉面板挂在<body>)。
4.1.3 自动初始化(无需你调用)
pvq-ui.js在DOMContentLoaded时自动扫描文档内所有.pvq-select并增强;已初始化的打data-pvqSelectInit="1"标记,重复扫描不会重复渲染(幂等)。- 因此静态 HTML 写完即生效,你不需要在插件 JS 里调任何初始化函数。
4.1.4 取值方式(两种,任选)
- 表单原生提交:
name已挂在原生 select 上,随<form>提交自动带值(与旧 jQuery 版完全一致)。 - JS 监听变更:监听原生 select 的
change事件即可(库在选中时会dispatchEvent(new Event('change'))):document.querySelector('select[name="your_form[field]"]') .addEventListener('change', function (e) { console.log('当前值:', e.target.value); });
4.1.5 动态内容(AJAX 注入后重挂)
若 select 是 AJAX 后来插入 DOM 的,注入后调一次即可:
if (window.PVQUI && window.PVQUI.initSelects) { window.PVQUI.initSelects(); }
(已初始化的会跳过,未初始化的会被接管。)
4.1.6 交互能力清单(库已内置,无需插件写)
开/关、箭头翻转、空间不足自动向上弹(flip)、右边界贴合、滚动/缩放重定位、键盘(Enter/Space 切换、ArrowDown 打开、方向键移动高亮、Enter 选中、Esc 关闭)、点击外部关闭、同时只开一个、选中同步原生 select 并派发 change、禁用项不可选。
4.1.7 可访问性现状(experimental 边界,知悉即可)
已含 role="button"/aria-haspopup/aria-expanded/aria-selected + 键盘高亮 --active。完整 listbox/option 方向键语义化聚焦留待 stable,不影响键盘可用性与表单提交。
4.1.8 threads 迁移配方(存量替换 .pvq-ts-*)
旧 threads 用 pvqSelect($sel)(jQuery)把原生 select 改成 .pvq-ts-* 结构。改用库后:
| 步骤 | 旧(jQuery) | 新(库,原生) |
|---|---|---|
| 1. 调库 | wp_enqueue_script 自己的 admin.js + FontAwesome |
PVQ_UI_Loader::enqueue(['admin'], '1.2.0')(自带 FA + pvq-ui.js) |
| 2. 初始化 | PHP 渲染 <select> + JS 调 pvqSelect($sel) |
删掉 pvqSelect(...) 调用;HTML 改为 .pvq-select > select(§4.1.2) |
| 3. 结构类 | .pvq-ts-wrap .pvq-ts-trigger .pvq-ts-dropdown .pvq-ts-option |
自动生成(无需手写) |
| 4. 取值 | 旧 change 监听照常 |
原生 select change 监听照常(name 不变) |
Before(旧,勿保留):
<!-- PHP 渲染原生 select -->
<select name="threads_sync_settings[mode]">…</select>
// admin.js(删)
$('select[name="threads_sync_settings[mode]"]').each(function(){ pvqSelect($(this)); });
After(新):
<div class="pvq-select">
<select name="threads_sync_settings[mode]">…</select>
</div>
// functions.php / 加载点:仅一行 enqueue,无自定义下拉 JS
require_once __DIR__ . '/includes/pvq-ui-loader.php';
PVQ_UI_Loader::enqueue( array( 'admin' ), '1.2.0' );
旧
.pvq-ts-*的 CSS/JS 建议先保留(改名或注释),迁移出问题时可即时切回;确认无回归后再删。另见《库演进治理方案》防回潮。
4.1.9 玻璃磨砂变体 --glass(装饰性,可选)
下拉面板默认是不透明纸面(--pvq-surface)。想让下拉背景变半透明磨砂,只需在容器上加一个类,trigger 与表单行为不变:
<div class="pvq-select pvq-select--glass">
<select name="your_form[field]">
<option value="a">选项 A</option>
<option value="b" selected>选项 B</option>
</select>
</div>
- 效果:下拉面板半透明白(
rgba(255,255,255,.52))+backdrop-filter: blur(20px) saturate(1.4),背后内容被磨砂成柔和色斑。背后需有彩色/图片内容才明显;白底上仅轻微通透。 - 兼容:不支持
backdrop-filter的环境自动退回rgba(255,255,255,.72)高不透明度,文字始终可读。无需插件额外 JS。 - 说明:下拉面板由库 JS 渲染并挂
<body>(position:fixed),容器上的类不会自动落到面板——库在打开时检测pvq-select--glass并把状态带到面板(.pvq-select__dropdown--glass),插件无需关心。 - 与
--sm可叠加:<div class="pvq-select pvq-select--sm pvq-select--glass">。
4.2 删除按钮二次确认(1.2.0 新增 · stable)
完整 API 以
doc/模块文档/设计令牌与组件清单.md「删除按钮二次确认」条目为准。下方是插件调用的实操契约。
4.2.1 调用前提
- 版本必须 ≥ 1.2.0(二次确认逻辑随
pvq-ui.js发布):PVQ_UI_Loader::enqueue( array( 'admin' ), '1.2.0' );pvq-ui.js在所有场景的enqueue()里都会被wp_enqueue_script('pvq-ui')带出,无需单独 enqueue JS。 - 删除按钮视觉复用
.pvq-btn--danger+fa-trash;二次确认是*纯 `data-` 增强**,无需插件写任何 JS。
4.2.2 HTML 契约
<button class="pvq-btn pvq-btn--danger" data-pvq-confirm="确定删除该项?">
<i class="fas fa-trash"></i> 删除
</button>
- 仅多一个
data-pvq-confirm属性(值为确认态显示的文案);其余按普通按钮写。 - 图标-only / 小号变体同样可用:
.pvq-btn--danger.pvq-btn--icon[data-pvq-confirm="确定?"]、.pvq-btn--danger.pvq-btn--sm。
4.2.3 行为契约(库已内置,无需插件写)
- 首次点击:仅进入确认态——按钮变实色危险红(
.pvq-btn--armed)+ 文案替换为确认语,不触发任何原行为(不提交表单、不跑插件 handler)。 - 确认态内再次点击:恢复外观并放行原行为(表单提交 / 插件自有
click监听照常)。 - 超时(3s)/
Esc/ 点击页面其它处:自动取消恢复原样。 - WordPress:按钮在
<form>内(如upload.php)时,库在捕获阶段preventDefault,不会整页重载;第二次点击才放行。
4.2.4 插件要做的
- 无需监听、无需初始化;把真正删除逻辑挂在按钮原本的
click/ 表单提交上即可(二次确认由库透明拦截首次点击)。 - 严禁为二次确认写 inline
onclick;一律靠data-pvq-confirm+ 库委托。
4.3 搜索 Search 调用接口(1.2.8 新增 · stable)
完整 API 以
doc/模块文档/设计令牌与组件清单.md「搜索组件 Search」条目为准。下方是插件调用的实操契约。
4.3.1 调用前提
- 版本必须 ≥ 1.2.8(搜索逻辑随
pvq-ui.js发布):PVQ_UI_Loader::enqueue( array( 'admin' ), '1.2.8' );,JS 随 base 自动加载,无需单独 enqueue。 - 插件不复制、不重写任何搜索 JS/CSS;纯靠写 HTML 类自动生效。
4.3.2 HTML 契约(必须这样写)
<!-- 基础:图标 + 输入 + 清除 -->
<div class="pvq-search" data-pvq-search>
<i class="pvq-search__icon fas fa-magnifying-glass"></i>
<input class="pvq-search__input" type="text" placeholder="搜索文章、用户、标签…">
<button class="pvq-search__clear" type="button" aria-label="清除"><i class="fas fa-xmark"></i></button>
</div>
<!-- 带提交按钮 -->
<div class="pvq-search pvq-search--with-btn" data-pvq-search>
<i class="pvq-search__icon fas fa-magnifying-glass"></i>
<input class="pvq-search__input" type="text" placeholder="输入关键词后点搜索">
<button class="pvq-search__submit" type="button"><i class="fas fa-magnifying-glass"></i> 搜索</button>
</div>
<!-- 筛选前置(复用库 .pvq-select) -->
<div class="pvq-search pvq-search--with-filter" data-pvq-search>
<div class="pvq-select pvq-search__filter">
<select name="pvq_search_scope">
<option>全部</option><option>标题</option><option>正文</option><option>作者</option>
</select>
</div>
<i class="pvq-search__icon fas fa-magnifying-glass"></i>
<input class="pvq-search__input" type="text" placeholder="在所选范围内搜索…">
</div>
- 类名唯一要求:容器
.pvq-search(加data-pvq-search让库接管),内部<input class="pvq-search__input">;可选.pvq-search__clear/.pvq-search__submit;筛选段用.pvq-select.pvq-search__filter包裹原生<select>,库自动增强。 --with-filter几何(v1.4.1 修正):筛选段固定110px且是流内 flex 子项,故输入文字用元素自身坐标的padding-left: 40px(= 容器目标 150px − 段宽 110px);插件无需也无法自定义这两个数值(改库 CSS 即可,或提缺陷;见《项目规则》§2 通用组件只在库)。- 暗色不是变体:暗色由库
--pvq-surface/--pvq-text/--pvq-border/--pvq-primary令牌主题切换,.pvq-search自动适配。
4.3.3 自动初始化(无需你调用)
pvq-ui.js 的 initSearches()(initAll 自动调用,幂等)会:① 清除按钮显隐/清空;② 回车或点提交按钮 → 读取词与(若有)筛选值;③ 筛选 <select> 改选(仅当有词)即时重搜;④ 派发 pvq-search CustomEvent(bubbles,detail: { query, filter, element })。
4.3.4 插件要做的事(接检索后端)
document.addEventListener('pvq-search', function (e) {
var q = e.detail.query, scope = e.detail.filter; // scope 为 --with-filter 时的范围值,无则 undefined
var box = e.target.closest('[data-pvq-search]');
// 调真实检索接口;异步期间自行加/去 .pvq-search--loading 显示 spinner
box.classList.add('pvq-search--loading');
fetch('/search?q=' + encodeURIComponent(q) + (scope ? '&scope=' + scope : ''))
.then(function (r) { return r.json(); })
.finally(function () { box.classList.remove('pvq-search--loading'); });
});
- 库不假设后端、不自动切换 loading;loading 态由插件控制(加
.pvq-search--loading即显示 spinner 并隐藏放大镜)。
4.4 卡片化表格 Table / 数据表格 DataGrid 调用接口(1.2.9 并入 · stable)
完整 API 以
doc/模块文档/设计令牌与组件清单.md「卡片化表格 Table / 数据表格 DataGrid」两条目为准。下方是插件调用的实操契约。
4.4.1 调用前提
- 版本必须 ≥ 1.2.9(表格逻辑随
pvq-ui.js发布):PVQ_UI_Loader::enqueue( array( 'frontend' ), '1.2.9' );,JS 随 base 自动加载,无需单独 enqueue。 - 前端表格纯靠写 HTML 类自动生效;DataGrid 的搜索/排序/筛选/重置由库
initDataGrids()(initAll自动调用、幂等)接管。
4.4.2 简单表格 HTML 契约
<!-- 卡片化表格:加 [data-pvq-table] 触发入场动画;纯展示可省略 data -->
<div class="pvq-table-wrap" data-pvq-table>
<table class="pvq-table pvq-table--striped">
<caption>语言速览</caption>
<thead>
<tr><th>语言</th><th class="pvq-table__col--right">诞生年</th></tr>
</thead>
<tbody>
<tr><td>PHP</td><td class="pvq-table__col--right">1995</td></tr>
<tr><td>Python</td><td class="pvq-table__col--right">1991</td></tr>
</tbody>
</table>
</div>
- 单元格独立圆角卡片、表头绿底、悬停微放大;
--striped加斑马纹;__col--right/center仅做列级对齐。
4.4.3 DataGrid HTML 契约(必须这样写)
<div class="pvq-datagrid" data-pvq-datagrid>
<div class="pvq-datagrid__toolbar">
<!-- 搜索:自包含胶囊,折叠为圆图标,点击原地展开 -->
<div class="pvq-datagrid__search">
<button class="pvq-datagrid__toggle pvq-datagrid__toggle--search" type="button"><i class="fas fa-magnifying-glass"></i></button>
<input class="pvq-datagrid__search-input" type="text" placeholder="搜索…">
<button class="pvq-datagrid__search-clear" type="button" aria-label="清除"><i class="fas fa-xmark"></i></button>
</div>
<!-- 排序:点头列或此按钮三态循环 无/升/降 -->
<button class="pvq-datagrid__btn pvq-datagrid__btn--sort" type="button"><i class="fa-solid fa-up-down"></i></button>
<!-- 筛选:自包含胶囊,展开为「列 + 值」双下拉,菜单由库自动生成 -->
<div class="pvq-datagrid__filter">
<button class="pvq-datagrid__toggle pvq-datagrid__toggle--filter" type="button"><i class="fas fa-filter"></i></button>
<div class="pvq-dd" data-pvq-dd="col"><button class="pvq-dd__trigger" type="button"><span class="pvq-dd__text">选择列</span><i class="pvq-dd__arrow fas fa-chevron-down"></i></button><div class="pvq-dd__menu"></div></div>
<div class="pvq-dd" data-pvq-dd="val"><button class="pvq-dd__trigger" type="button"><span class="pvq-dd__text">选择值</span><i class="pvq-dd__arrow fas fa-chevron-down"></i></button><div class="pvq-dd__menu"></div></div>
</div>
<!-- 重置:清空搜索/筛选/排序 -->
<button class="pvq-datagrid__btn pvq-datagrid__btn--reset" type="button"><i class="fas fa-rotate-left"></i></button>
</div>
<div class="pvq-datagrid__content">
<table class="pvq-datagrid__table">
<thead>
<!-- th 加 data-col 供筛选列菜单识别;排序图标槽 .pvq-sort-icon 可省略(库 JS 自动注入每个表头) -->
<tr><th data-col="项目">项目<i class="pvq-sort-icon"></i></th><th data-col="数量" class="pvq-table__col--right">数量<i class="pvq-sort-icon"></i></th></tr>
</thead>
<tbody>
<tr><td>示例 A</td><td class="pvq-table__col--right">12</td></tr>
<tr><td>示例 B</td><td class="pvq-table__col--right">3</td></tr>
</tbody>
</table>
<div class="pvq-datagrid__empty">无匹配结果</div>
</div>
</div>
- 类名唯一要求:容器
.pvq-datagrid[data-pvq-datagrid];工具栏四件套齐全;表格用.pvq-datagrid__table,表头th[data-col]给筛选列菜单用。 - 筛选下拉菜单由库自动生成:写空
.pvq-dd__menu即可,库按th[data-col]与单元格值填充列/值;无需手写<option>。 - 搜索/筛选点击栏外空白自动收起;排序点头列或排序按钮三态循环;重置恢复全部。
4.4.4 自动初始化(无需你调用)
pvq-ui.js 的 initTables() / initDataGrids()(initAll 自动调用、幂等)会:① 简单表格 IntersectionObserver 入场动画;② DataGrid 搜索实时过滤、排序重排(数值按大小、中文按 localeCompare)、筛选按列+值多选、重置恢复、入场动画。已初始化的打 data-pvqTableInit="1" / data-pvqDgInit="1",重复扫描不重复渲染。
4.4.5 动态内容(AJAX 注入后重挂)
若表格是 AJAX 后来插入 DOM 的,注入后调一次即可:
if (window.PVQUI && window.PVQUI.initTables) { window.PVQUI.initTables(); }
if (window.PVQUI && window.PVQUI.initDataGrids) { window.PVQUI.initDataGrids(); }
4.5 二次确认弹窗 Confirm Modal(v1.2.10 新增)
破坏性 / 重要操作的弹窗式二次确认。与 §4.x 的 data-pvq-confirm(按钮就地二次确认)互为补充:就地确认适合表格行内删除等轻量场景;弹窗适合需要说明后果、列出影响对象、异步执行的重量级场景。
最简用法(三属性即可)
<!-- 1) 触发按钮:指向弹窗 id -->
<button class="pvq-btn pvq-btn--danger" data-pvq-modal-open="del-modal">删除</button>
<!-- 2) 弹窗本体(放在页面任意位置,推荐 body 末尾) -->
<div class="pvq-modal" id="del-modal" data-pvq-modal role="dialog"
aria-modal="true" aria-labelledby="del-t">
<div class="pvq-modal__backdrop" data-pvq-modal-close></div>
<div class="pvq-modal__panel">
<button class="pvq-modal__close" data-pvq-modal-close aria-label="关闭">✕</button>
<div class="pvq-modal__icon"><i class="fas fa-triangle-exclamation"></i></div>
<h3 class="pvq-modal__title" id="del-t">确认删除?</h3>
<p class="pvq-modal__desc">此操作将永久删除 <strong>“项目报告 Q3”</strong>,且<strong>无法恢复</strong>。</p>
<div class="pvq-modal__actions">
<button type="button" class="pvq-btn pvq-modal__cancel" data-pvq-modal-close>取消</button>
<button type="button" class="pvq-btn pvq-modal__confirm" data-pvq-modal-confirm>确认删除</button>
</div>
</div>
</div>
插件要做的:监听 confirm 执行业务
document.addEventListener('pvq-modal:confirm', function (e) {
if (e.detail.id !== 'del-modal') { return; }
// e.detail = { id, modal, trigger }
// …在这里发删除请求;库只管 UI,不碰业务
});
异步场景(防重复提交)
给确认按钮加 data-pvq-modal-async,点击后按钮转 spinner 并禁用,库不会自动关闭,由业务完成后调 closeModal():
<button type="button" class="pvq-btn pvq-modal__confirm"
data-pvq-modal-confirm data-pvq-modal-async>
<span class="pvq-modal__spinner"></span>
<span class="pvq-modal__confirm-text">开始同步</span>
</button>
document.addEventListener('pvq-modal:confirm', function (e) {
if (e.detail.id !== 'sync-modal') { return; }
fetch('/api/sync', { method: 'POST' })
.then(function () { e.detail.modal.closeModal(); }) // 成功后关
.catch(function () { e.detail.modal.closeModal(); }); // 失败也要关,或自行恢复按钮
});
语义变体(只换一个令牌)
<div class="pvq-modal pvq-modal--warning" …> <!-- 黄:覆盖 / 重置 -->
<div class="pvq-modal pvq-modal--primary" …> <!-- 森林绿:发布等非破坏性 -->
<div class="pvq-modal" …> <!-- 默认红:删除等破坏性 -->
也可自行覆盖:style="--pvq-modal-accent:#8b5cf6"(图标、清单圆点、确认按钮、投影同步变化)。
带影响对象清单
在 __desc 后插入即可,圆点自动用 accent 色:
<ul class="pvq-modal__list">
<li>设计令牌与组件清单.md</li>
<li>PVQ-UI 组件库开发方案.md</li>
</ul>
类名与行为契约
| 属性 / 类 | 作用 |
|---|---|
.pvq-modal[data-pvq-modal] + id |
弹窗容器(默认 display:none,库加 --open 显形) |
[data-pvq-modal-open="<id>"] |
触发器,值为目标弹窗 id |
[data-pvq-modal-close] |
关闭:右上角 X / 取消按钮 / 遮罩均用它 |
[data-pvq-modal-confirm] |
确认:派发 pvq-modal:confirm 后关闭 |
[data-pvq-modal-async] |
确认后转 spinner + 禁用,等业务调 closeModal() |
.pvq-modal--busy |
异步进行态(库自动加/去),显示 spinner |
事件:pvq-modal:open / pvq-modal:close / pvq-modal:confirm(均 bubbles,detail:{id, modal, trigger})。
无障碍(库已内置,插件无需处理):role="dialog" + aria-modal + aria-labelledby;打开时默认聚焦取消按钮(防回车误触发破坏性操作);焦点陷阱 Tab/Shift+Tab 锁在弹窗内;Esc 关闭;关闭后焦点归还触发元素;滚动锁定(库在 <html> 上加 .pvq-scroll-locked,关闭与 pagehide 时释放,bfcache 恢复时自愈复位);prefers-reduced-motion 降级。
⚠️ 改写按钮颜色的插件注意:库基础
button.pvq-btn.pvq-btn(0,3,1)会把background刷成--pvq-surface白色,覆盖按钮色必须用 ≥(0,4,1) 的选择器(如button.pvq-btn.pvq-modal__confirm.pvq-modal__confirm),否则会出现"只有彩色阴影、按钮本体是白的"。
4.6 浮层通知 Toast 与可关闭横幅 Banner __close(experimental)
4.6.1 先选对组件(反馈分级)
| 场景 | 用什么 |
|---|---|
| 一次操作的即时结论(保存成功 / 连接失败 / 删除完成) | .pvq-toast(本次新增) |
| 结果内联在表单旁(加上 ImGui? 校验结论) | .pvq-alert(已有) |
| 常驻的待办 / 提醒(未配置 Token、升级提示) | .pvq-banner + __close(本次新增关闭能力) |
| 结果是一组多行数据 | 页面内常驻区(如 .pvq-meta-list),toast 只给一句结论 |
| 长任务进行中 | 内联 .pvq-alert--loading,不要用 toast |
4.6.2 调用前提
PVQ_UI_Loader::enqueue(['base'])(或 ['admin'])后自动生效——initToasts() / initBanners() 已并入 initAll,页面上无需写初始化代码。
4.6.3 Toast:命令式
PVQUI.toast({ type: 'success', title: '设置已保存' });
PVQUI.toast({ type: 'error', title: '连接失败', desc: 'Token 无效或已过期', duration: 0 }); // 0 = 不自动关闭
| 字段 | 默认 | 说明 |
|---|---|---|
type |
info |
success / error / warning / info |
title |
必填 | 主文案 |
desc |
— | 次要文案 |
icon |
按 type | FontAwesome 类名,如 fas fa-circle-check |
duration |
success/info 3000、warning 5000、error 8000 |
毫秒;0 = 常驻直到手动关闭 |
返回值即实例:{ el, type, duration, close(reason) },可自行 inst.close()。
⚠️ 文案走
textContent,不接受 HTML——库刻意不支持富文本,避免插件把后端返回拼进 toast 造成 XSS。
4.6.4 Toast:声明式(后端渲染友好,无 inline handler)
<button type="button" class="pvq-btn pvq-btn--primary"
data-pvq-toast data-pvq-toast-type="success"
data-pvq-toast-title="已同步 12 条" data-pvq-toast-duration="3000">同步</button>
data-pvq-toast-title 省略时取元素文本;duration 支持 0。
4.6.5 让开 WP admin bar(后台必做)
库默认值 --pvq-toast-offset-y: 20px 是中性值(前台不留多余空白)。后台需要在 admin 样式里覆写:
body.wp-admin { --pvq-toast-offset-y: 44px; } /* admin bar 32px */
@media (max-width: 782px) { body.wp-admin { --pvq-toast-offset-y: 50px; } } /* admin bar 46px */
4.6.6 事件与幂等
document.addEventListener('pvq-toast:close', function (e) {
// e.detail: { toast, el, type, reason } reason: 'timer' | 'close' | 'displace'
});
- 同屏最多 4 条,超出挤走最旧一条(
reason: 'displace');悬停暂停倒计时、移开继续。 - 幂等提醒:
initAll()同时绑在DOMContentLoaded与window resize上,故initToasts()只确保容器存在、绝不清理正在显示的 toast。插件若要"清场",请自行遍历.pvq-toast调inst.close(),不要试图复位库状态。 - AJAX 注入横幅后,调一次
PVQUI.initBanners()重挂(幂等,重复调用安全)。
4.6.7 可关闭横幅 + 关闭记忆
<div class="pvq-banner pvq-banner--warning" data-pvq-banner-key="setup-todo">
<span class="pvq-banner__dot"></span>
<div><div class="pvq-banner__title">尚未配置 Token</div><div class="pvq-banner__text">前往「设置 → 连接」完成授权。</div></div>
<button type="button" class="pvq-banner__close" data-pvq-banner-close aria-label="关闭">✕</button>
</div>
// 库只负责 UI:播离场、移除节点、派发事件;记忆策略归插件
document.addEventListener('pvq-banner:close', function (e) {
var key = e.detail.banner.dataset.pvqBannerKey;
if (key) { fetch(ajaxurl, { method: 'POST', body: new URLSearchParams({ action: 'pvq_dismiss_banner', key: key }) }); }
});
库不读写 storage(localStorage / user_meta 都不碰),以免跨插件猜测键名。不含
__close的横幅行为完全不变,向后兼容。
4.7 锚点浮层 Popover 调用接口(v1.3.1 新增 · experimental · 回流自 pvq-weixinyuedu)
4.7.1 先选对组件
「要显示一块内容」有五种常见诉求,选错会导致布局位移或过度阻断:
| 诉求 | 用哪个 |
|---|---|
| 常驻内容块(不点不消失) | .pvq-card |
| 原地展开(内容撑开文档流) | .pvq-collapse |
| 强阻断、必须做决定 | .pvq-modal(见 §4.5) |
| 操作结果的即时结论(角落、自动消失) | .pvq-toast(见 §4.6) |
| 以某元素为锚、脱离文档流、按需显隐(术语解释 / 作者卡 / 预览 / 迷你详情) | .pvq-popover(本节) |
4.7.2 调用前提
- 已 enqueue
base层(popover 在 base)。 - 必须有
pvq-ui.js:定位与开合全靠库 JS。缺 JS 时浮层保持不可见(库默认即浮层态);如需兜底请在服务端另给静态呈现(如<noscript>块)。
4.7.3 HTML 契约(必须这样写)
<!-- 锚点:任意可聚焦元素。data-pvq-popover 指向浮层 id;aria-describedby 供读屏关联 -->
<button type="button" tabindex="0"
data-pvq-popover="pvq-reading-note-1" aria-describedby="pvq-reading-note-1">引自原文</button>
<!-- 浮层本体:位置随意(库不移动 DOM,靠 fixed 定位),但不要放在带 transform 的容器内 -->
<div class="pvq-popover" id="pvq-reading-note-1" role="note" hidden>
<div class="pvq-popover__body">…点评内容…</div>
<div class="pvq-popover__foot">…出处 / 时间…</div>
<span class="pvq-popover__arrow"></span>
</div>
hidden属性必须写(初始不显示);打开/关闭由库 JS 增删--open与hidden。role="note"(不要写tooltip:本组件允许浮层内含链接等富内容,而tooltip语义要求纯说明文本)。__arrow可选;写了则由库自动对准锚点中心。- 一个浮层配一个锚点;多个锚点请各写一份。
4.7.4 自动初始化(无需你调用)
initPopovers() 随 initAll()(DOMContentLoaded + resize)自动执行:扫描 [data-pvq-popover] → 取值为浮层 id(值为空时回退读 aria-describedby)→ 目标必须存在且带 .pvq-popover 类(避免误绑普通 aria-describedby 说明文本)→ 建立关联。幂等:同一浮层只初始化一次,document/window 级监听只绑一次。
4.7.5 交互清单(库已内置,无需插件写)
| 操作 | 行为 |
|---|---|
| 桌面悬停锚点 | 打开;移开后延时 160ms 关闭(给鼠标移入浮层的时间) |
| 触屏点按锚点 | 开合切换(库用 matchMedia('(hover: hover)') 判定,不信任伪悬停) |
键盘 Tab 聚焦锚点 |
打开;焦点离开浮层与锚点 → 关闭 |
Escape |
关闭并把焦点交还锚点 |
| 点击浮层与锚点之外 | 关闭 |
| 锚点滚动 / 窗口缩放 | 打开态重新定位(捕获阶段监听,任意滚动容器均跟随) |
| 打开 / 关闭动画 | 淡入淡出 180ms;prefers-reduced-motion 下降级为瞬时 |
4.7.6 事件(插件可选监听)
| 事件 | 时机 | detail |
|---|---|---|
pvq-popover:open |
打开后 | { popover, el, anchor, reason } |
pvq-popover:close |
关闭时(元素仍在,过渡结束后才 hidden) |
{ popover, el, anchor, reason } |
reason ∈ pointer(悬停)/ focus(聚焦)/ click(触屏点按)/ escape / outside(点外部)/ api(代码调用)。
4.7.7 命令式(浮层内容动态生成时)
var pop = PVQUI.popover(anchorEl, {
content: '<p>…</p>', // 或传 DOM 节点
placement: 'auto', // auto | below | above
gap: 8,
closeDelay: 160,
onOpen: function (inst) {},
onClose: function (inst) {}
});
pop.open();
pop.destroy(); // 解绑(库创建的浮层节点一并移除;destroy 后可重新 popover() 挂载)
⚠️
content传字符串时按 HTML 插入(点评可能含格式)。不得拼接未转义的用户输入,只处理可信内容时建议改传 DOM 节点。
4.7.8 插件要做的
- HTML 按 §4.7.3 写(
hidden+role="note"+aria-describedby齐全)。 - 无需写任何交互 JS;动态注入(AJAX)后如需重挂,再调一次
PVQUI.initPopovers()即可(幂等)。 - 浮层内容由后端渲染时,不要放在带
transform/filter/backdrop-filter/will-change的容器内(会成为 fixed 包含块致坐标偏移),必要时把浮层挪到该容器之外。
4.7.9 迁移配方(pvq-weixinyuedu 想法卡片)
| 插件现状 | 收敛为 |
|---|---|
.pvq-wr-thought 外壳(fixed / --open / 定位全在插件 CSS 与 thought.js) |
外壳改 .pvq-popover,只保留业务内容样式(引文、点评体、时间) |
.pvq-wr-js 分支(head 同步脚本加类消 FOUC) |
全部删除——库默认即浮层态,thought-boot.js 可删 |
data-pvq-thought-trigger + aria-describedby 绑定 |
data-pvq-popover="<浮层 id>"(aria-describedby 仍被兼容) |
thought.js 的 place() / 翻转 / CLOSE_DELAY / EXIT_MS |
删除,改由库 initPopovers() 接管 |
6. 内网站点 / 本地副本覆盖
若站点无法访问公共 CDN,用常量把库指向本地副本(在 wp-config.php 或插件最早执行处定义):
define( 'PVQ_UI_URL', 'https://内网域名/pvq-ui/v1.2.0/pvq-ui.css' );
define( 'PVQ_UI_ADMIN_URL', 'https://内网域名/pvq-ui/v1.2.0/pvq-ui-admin.css' );
define( 'PVQ_UI_EDITOR_URL', 'https://内网域名/pvq-ui/v1.2.0/pvq-ui-editor.css' );
define( 'PVQ_UI_FRONT_URL', 'https://内网域名/pvq-ui/v1.2.0/pvq-ui-frontend.css' );
define( 'PVQ_UI_VERSION', '1.2.0' );
loader 用 defined() 守卫,仅当未定义时才用内置默认值——所以站点的覆盖优先。
如需完全关闭 loader 的版本收敛,可 define('PVQ_UI_NO_CONVERGE', true);。
7. 存量插件迁移(wolai / pvq-wp / threads)
对照《PVQ-UI组件库开发方案.md》§9 与《组件提取对照表.md》§6 映射表:
- 复制
pvq-ui-loader.php,改三个 enqueue 点为PVQ_UI_Loader::enqueue(...)。 - 删插件自有 CSS 中已进库的通用类 +
:root令牌;保留插件专有类(如.pvq-wolai-*),硬编码色值改为--pvq-*。 - HTML 旧类全量改新 BEM 名(注意旧类多为双类组合,勿当单类替换)。
- 剥离所有 inline handler(§5)。
- grep 校验:已进库的类在插件 CSS/HTML 中零残留定义(防回潮)。
- 同步
CHANGELOG/readme/site/inc/components.json与模块文档的稳定性标记(版本七处同步见《PVQ-UI项目规则.md》§1.3)。
8. 自检清单(提交插件前)
- [ ] 只复制了
pvq-ui-loader.php,没有复制任何库 CSS/JS - [ ] 调用了
PVQ_UI_Loader::enqueue(...)且显式传了版本 - [ ] 自身 CSS 声明了对
pvq-ui/pvq-ui-<场景>的依赖 - [ ] HTML 只用
.pvq-*BEM 类,没有自建通用组件 - [ ] 无 inline
onclick/onkeydown - [ ] 颜色 / 圆角 / 阴影均用
--pvq-*令牌,无硬编码 - [ ] 迁移插件已完成 grep 防回潮校验