PVQ-UI 组件库开发方案
状态:草案(待评审定稿) 作者:溟远 适用仓库:
pvq-ui(独立组件库) 关联插件:pvq-wolai-sync、pvq-threads-sync(后续迁移);参考pvq-wp的 FontAwesome 统一管理模式
1. 背景与目标
1.1 真实需求(一句话)
写一次组件库,以后写插件直接调用它,不再单独写组件;存量插件逐步替换成它,实现全站样式统一。
1.2 当前痛点(已核实代码)
pvq-wolai-sync后台已实现一套完整的.pvq-*通用组件 + 全套--pvq-*设计令牌(assets/css/admin-settings.css:11-62)。pvq-threads-sync另起一套.pvq-ts-*命名空间,并*复制了同一套 `--pvq-令牌**(assets/css/admin.css:8-43),连 FontAwesome 都硬编码了自己的 handle(includes/class-threads-sync.php:234的threads-sync-fa`)。- 结果:同一套设计语言被重复实现、各自演进,风格必然漂移,且每次新插件都要重写一遍通用组件。
1.3 目标
pvq-ui成为所有 PVQ 插件的「界面样式唯一真源」。- 新插件从第一天起直接调用
pvq-ui,零通用组件副本。 - 存量插件(wolai → threads)分阶段替换,统一视觉语言。
- 设计令牌(颜色 / 圆角 / 阴影 / 动画)全站一致。
2. 设计原则(精简版,已修正草案矛盾)
- 单一真源:通用组件只存在于
pvq-ui,插件不再自带副本。 - CDN 单一源:构建产物上传固定 URL,所有插件引用同一 URL(零本地副本、零 build 同步)。
- 自给自足:不依赖
pvq-wp,也不强制安装任何基座插件。收敛逻辑随库自带(见 §3.3),而非各插件重写。 - BEM 命名:
pvq-{block}/pvq-{block}__{element}/pvq-{block}--{modifier}。 - 渐进完善:先落地最高频的后台设置页 + 通用组件,前端 / 编辑器组件随后补充。
修正说明:原草案 §2.3「纯自给自足」与 §3.3「每个消费插件挂载 override 收敛」自相矛盾——若每个插件各自重写收敛逻辑,既不单一真源又易出 bug。本方案改为:收敛逻辑内置于库自带的
pvq-ui-loader.php单一文件,由各插件复制引用,而非重写(见 §3.3)。
3. 架构与运行机制
3.1 产物(CDN 固定路径)
构建后上传到 https://<CDN_BASE>/pvq-ui/<版本>/ 下的固定文件名:
| 文件 | 场景 | WordPress handle | 依赖 |
|---|---|---|---|
pvq-ui.css |
全场景 base(令牌 + 通用组件) | pvq-ui |
无 |
pvq-ui-admin.css |
后台 | pvq-ui-admin |
pvq-ui, font-awesome |
pvq-ui-editor.css |
块编辑器 | pvq-ui-editor |
pvq-ui |
pvq-ui-frontend.css |
前端 | pvq-ui-frontend |
pvq-ui |
pvq-ui.js |
全场景交互(原生 JS) | pvq-ui |
无 |
组件视觉大量使用 FontAwesome 图标(按钮 / 卡片图标 / 连接状态等),故
pvq-ui-admin必须声明对font-awesome句柄的依赖;loader 会一并保证 FA 已注册(见 §3.3)。
3.2 常量 + 句柄守卫(平移 FontAwesome 模式)
先定义者生效、同句柄不重复注册:
if ( ! defined( 'PVQ_UI_URL' ) ) define( 'PVQ_UI_URL', 'https://<CDN_BASE>/pvq-ui/<ver>/pvq-ui.css' );
if ( ! defined( 'PVQ_UI_ADMIN_URL' ) )define( 'PVQ_UI_ADMIN_URL','https://<CDN_BASE>/pvq-ui/<ver>/pvq-ui-admin.css' );
if ( ! defined( 'PVQ_UI_EDITOR_URL' ) )define('PVQ_UI_EDITOR_URL','https://<CDN_BASE>/pvq-ui/<ver>/pvq-ui-editor.css' );
if ( ! defined( 'PVQ_UI_FRONT_URL' ) )define( 'PVQ_UI_FRONT_URL','https://<CDN_BASE>/pvq-ui/<ver>/pvq-ui-frontend.css' );
if ( ! defined( 'PVQ_UI_VERSION' ) ) define( 'PVQ_UI_VERSION', '1.0.0' );
foreach ( array( 'pvq-ui' => PVQ_UI_URL, 'pvq-ui-admin' => PVQ_UI_ADMIN_URL, 'pvq-ui-editor' => PVQ_UI_EDITOR_URL, 'pvq-ui-frontend' => PVQ_UI_FRONT_URL ) as $h => $url ) {
if ( ! wp_style_is( $h, 'registered' ) ) {
$deps = ( 'pvq-ui' === $h ) ? array() : array( 'pvq-ui' );
if ( 'pvq-ui-admin' === $h ) $deps[] = 'font-awesome';
wp_register_style( $h, $url, $deps, PVQ_UI_VERSION );
}
}
3.3 pvq-ui-loader.php(单一收敛点,随库分发)
库随附一个自包含的 pvq-ui-loader.php(约 60 行)。消费方复制到自身 includes/ 后 require_once 并调用助手函数即可:
require_once __DIR__ . '/pvq-ui-loader.php';
PVQ_UI_Loader::enqueue( array( 'admin' ) ); // 后台场景;'editor' / 'frontend' 同理
该文件集中负责:
- 定义 §3.2 的 4 个常量(均
defined()守卫); - 注册 4 个句柄(
wp_style_is守卫); - 提供
PVQ_UI_Loader::enqueue( $scenes )助手; - 版本收敛(override)内置于此,挂载
wp_print_styles(999),把所有pvq-ui*句柄的src/ver收敛到对应常量 URL,防止某插件硬编码旧版。
收敛逻辑用 did_action('pvq_ui_override_registered') 守卫,多副本只实际挂载一次、幂等;默认开启,可被常量 PVQ_UI_NO_CONVERGE 关闭。
这样「自给自足」与「统一收敛」不再矛盾:单一真源是库文件,不是各插件重写;且不依赖
pvq-wp。
3.4 句柄 / 常量集中
全部常量在 pvq-ui-loader.php 顶部集中定义;消费方只通过 defined() 保护做覆盖(便于内网站点把 URL 指向本地副本)。
3.5 组件清单 JSON 唯一真源 + 官网画廊数据驱动
组件清单长期存在两份漂移隐患:①doc/模块文档/设计令牌与组件清单.md(人类可读);②site/inc/gallery.php(官网画廊手抄 HTML)。本方案收敛为单一机器可读真源 site/inc/components.json:
- 结构:
{ "version", "groups":[ { "title", "items":[ { "id","name","icon","stability","layer","desc","demos":[ { "title","html" } ] } ] } ] }。demos[].html即画廊"预览"与"代码示例"共用的同一份 HTML——只写一次,预览和代码块都从它来。 - 官网画廊数据驱动:
gallery.php遍历 JSON 自动生成侧边栏导航、每个组件的<section>预览区与代码块。新增组件只需在 JSON 的items加一条,画廊侧边栏 + 预览 + 代码全部自动出现,杜绝手抄三遍。 - 双轨部署(本地即时 + 线上跟版本):
- 本地:
gallery.php直接读仓库内site/inc/components.json,改 JSON 刷新即见(无需部署)。 - 线上:
deploy.sh把components.json一并同步到 COSpvq-ui/v{ver}/components.json,gallery.php运行时读该同版本 JSON(与库 CSS 同目录同版本,天然一致)。
- 本地:
- 与
.md的关系:.md降级为"人类可读 API 说明",引用 JSON 条目、不再手抄示例;令牌表(:root全集)仍留.md(令牌不进 JSON)。凡"有无某组件"以 JSON 为准。
该 JSON 随库版本一起发布到 COS,库一升级,线上画廊自动跟随多组件、变稳定性标签。
4. 文件结构与组件清单
标注 [提取] = 直接来自现有
pvq-wolai-sync/assets/css/admin-settings.css;[新增] = 库首次提供。
4.1 pvq-ui.css(base,全场景 enqueue)
- 设计令牌:
:root全套--pvq-*(以 wolaiadmin-settings.css:11-62的:root为基线,原样保留命名)。 - 按钮 [提取]:
.pvq-btn.pvq-btn--primary.pvq-btn--secondary.pvq-btn--danger.pvq-btn--warning.pvq-btn-group - 卡片 [提取]:
.pvq-card.pvq-card__header.pvq-card__icon.pvq-card__icon--{primary,success,warning,info,danger}.pvq-card__title.pvq-card__desc.pvq-card__action - 表单 [提取]:
.pvq-field.pvq-field__label.pvq-field__help.pvq-input.pvq-input__wrap.pvq-input__wrap--has-icon.pvq-input__icon.pvq-input__toggle - 开关 [提取]:
.pvq-switch.pvq-switch--active.pvq-switch-row.pvq-switch-row__label.pvq-switch-row__desc - 状态/徽标 [提取+新增]:
.pvq-status.pvq-status--{success,warning,error}.pvq-status__dot.pvq-badge[新增] - 其它 [提取]:
.pvq-divider.pvq-spinner.pvq-help.pvq-help__icon.pvq-help__title.pvq-help__text.pvq-banner.pvq-banner--{success,danger,warning,info}.pvq-alert.pvq-alert--{success,error,loading}.pvq-stat-grid.pvq-stat.pvq-stat__value.pvq-stat__label.pvq-meta-list.pvq-meta-list__row.pvq-meta-list__key.pvq-meta-list__value.pvq-meta-list__value--{ok,bad}[新增 meta-list] - 动画:全部
@keyframes保留pvq-前缀(含.pvq-spin/.pvq-pulse/.pvq-shimmer等现有动画)。
4.2 pvq-ui-admin.css(后台 enqueue)
- 设置页骨架 [提取]:
.pvq-settings.pvq-settings__{header,logo,title-group,title,subtitle,version-badge,footer,footer-info,footer-links,footer-link,footer-link--disabled} - 选项卡 + 磁吸滑块 [提取]:
.pvq-tabs.pvq-tabs__slider.pvq-tab.pvq-tab--active.pvq-tab__icon.pvq-tab-content.pvq-tab-content--active - 滑块 + 数字 [提取]:
.pvq-range.pvq-range__group.pvq-range__value.pvq-range__labels - 系统信息 [提取]:
.pvq-meta-list(见 4.1)
4.3 pvq-ui-editor.css(块编辑器 enqueue)
- 通用块骨架 [提取自 wolai 专有抽象]:
.pvq-block-placeholder.pvq-block-loading.pvq-block-empty.pvq-block-preview.pvq-block-frame
4.4 pvq-ui-frontend.css(前端 enqueue)
- 通用内容组件 [提取自 wolai 专有抽象]:
.pvq-callout.pvq-callout__{icon,content}.pvq-quote.pvq-prose
不进库、留在 wolai-sync 的强绑定笔记数据模型的样式:
.pvq-wolai-text/-heading/-code-block/-todo/-toggle/-image/-video/-bookmark/-table等,但改用--pvq-*令牌保证风格一致。.pvq-token-info、.pvq-detect-bar、.pvq-status-available/-unavailable等 wolai 专有类也留在 wolai。
5. 设计令牌(节选,命名保持不变)
主色 --pvq-primary 系列、语义色 --pvq-{success,danger,warning,info} 及 -light、中性色 --pvq-{bg,surface,border,text,text-secondary,text-muted}、圆角 --pvq-radius{,-sm,-lg}、阴影 --pvq-shadow{,-sm,-md,-lg,-primary}、缓动 --pvq-ease*、--pvq-transition*。完整清单以 wolai admin-settings.css:11-62 为基线,由库统一维护。
6. BEM 命名规范与旧→新映射(修正版)
重要:旧类多为双类组合(如
pvq-btn pvq-btn-primary),并非单类,迁移时勿当单类搜索替换。完整映射表写入本文档附录,供迁移对照。
| 旧(真实代码) | 新(pvq-ui BEM) | 备注 |
|---|---|---|
pvq-btn + pvq-btn-primary |
pvq-btn + pvq-btn--primary |
双类 |
pvq-btn-secondary / pvq-btn-danger / pvq-btn-warning |
pvq-btn--secondary / pvq-btn--danger / pvq-btn--warning |
|
pvq-card-icon + primary 等 |
pvq-card__icon + pvq-card__icon--primary |
双类 |
pvq-label |
pvq-field__label |
|
pvq-field-help |
pvq-field__help |
草案误写为 .pvq-label-hint |
pvq-input-wrap + has-icon |
pvq-input__wrap + pvq-input__wrap--has-icon |
双类 |
pvq-switch + active |
pvq-switch + pvq-switch--active |
双类 |
pvq-switch-label / pvq-switch-desc |
pvq-switch-row__label / pvq-switch-row__desc |
|
pvq-status-indicator + success/warning/error |
pvq-status + pvq-status--success/warning/error |
|
pvq-connection-status + connected/disconnected/unconfigured |
pvq-banner + pvq-banner--success/danger/warning |
修饰符值需变更 |
pvq-test-result + .show + .success/.error/.loading |
pvq-alert + 显隐类 + pvq-alert--success/error/loading |
需新增显隐类 |
pvq-cache-stats / pvq-cache-stat / pvq-cache-stat-value / pvq-cache-stat-label |
pvq-stat-grid / pvq-stat / pvq-stat__value / pvq-stat__label |
中间层 pvq-cache-stat 勿漏 |
pvq-help-card (+ -inner / -icon / -title / -text) |
pvq-help (+ __icon / __title / __text) |
|
pvq-settings-app / pvq-settings-header / pvq-settings-logo / pvq-settings-title / pvq-settings-subtitle / pvq-settings-footer / pvq-footer-link / pvq-footer-link--disabled |
pvq-settings / pvq-settings__header / __logo / __title / __subtitle / __footer / __footer-link / __footer-link--disabled |
|
pvq-tab-slider / pvq-tab + active / pvq-tab-content + active |
pvq-tabs__slider / pvq-tab + pvq-tab--active / pvq-tab-content + pvq-tab-content--active |
双类 |
pvq-range-group / pvq-range / pvq-range-value / pvq-range-labels |
pvq-range__group / pvq-range / pvq-range__value / pvq-range__labels |
|
pvq-system-info / pvq-system-info-row |
pvq-meta-list / pvq-meta-list__row |
|
pvq-wolai-callout (+ -icon / -content) |
pvq-callout (+ __icon / __content) |
进 frontend |
pvq-wolai-sync-placeholder |
pvq-block-placeholder |
进 editor |
7. JS 交互(pvq-ui.js,原生 JS + 事件委托)
- tab 切换 + 磁吸滑块:委托
.pvq-tab点击 → 切.pvq-tab--active+.pvq-tab-content--active+ 移动.pvq-tabs__slider。 - switch 开关:委托
.pvq-switch点击切.pvq-switch--active,并dispatchEvent自定义事件pvq-switch:change(detail 含 checked / data-target),插件监听后自行同步隐藏域与提交。 - range 拖动:同步
.pvq-range__value。
迁移关键(草案低估的部分):现状 wolai 后台用 jQuery + 大量 inline
onclick="pvqWolaiSwitchTab('general')"/onkeydown=...(class-pvq-wolai-sync-settings.php:104,275,490)。迁移到pvq-ui.js必须剥离所有 inline handler,改为data-*属性 + 委托监听;switch 的「视觉 toggle」与「表单值同步」要重新划边界(库管视觉 + 派发事件,插件管隐藏域)。否则迁移后 JS 报错。
8. 消费方接入(PHP 样板 = 复制 loader + 一行调用)
// 每个插件只需:复制 pvq-ui-loader.php 到 includes/,然后:
require_once __DIR__ . '/pvq-ui-loader.php';
PVQ_UI_Loader::enqueue( array( 'admin' ) ); // 后台场景
// 插件自身 CSS:声明依赖 pvq-ui / pvq-ui-<场景>
wp_enqueue_style( 'my-plugin-admin', ..., array( 'pvq-ui', 'pvq-ui-admin' ), $ver );
相比原草案 §8:不再要求每个插件内联重复注册与 override 逻辑,全部收敛进 pvq-ui-loader.php。
9. 迁移步骤(以 wolai-sync 先行)
pvq-ui仓库完成 base / admin / editor / frontend +pvq-ui.js+pvq-ui-loader.php,构建上传 CDN 固定路径。- wolai 三个 enqueue 点(settings admin / frontend / editor)改为复制
pvq-ui-loader.php并调用PVQ_UI_Loader::enqueue();自身 CSS 加pvq-ui/pvq-ui-admin依赖。 - 删除
admin-settings.css中已进库的通用组件 +:root令牌;保留 wolai 专有类(.pvq-token-info、.pvq-wolai-*等)。 - 设置页 HTML/PHP 旧 class 全量改新 BEM 名(对照 §6 映射表);并剥离所有 inline
onclick/onkeydown(§7)。 frontend.css/editor.css中 wolai 专有.pvq-wolai-*保留,硬编码颜色改为--pvq-*令牌;callout / 块骨架等抽象部分移交pvq-ui。- 迁移完整性校验:grep 确认已进库的类(如
.pvq-card、.pvq-btn--primary)在 wolai 自有 CSS/HTML 中零残留,避免与库双定义冲突。 - 同步 docs / readme.md / CHANGELOG(版本号三处同步)。
- threads-sync 后续按同流程迁移(注意其
.pvq-ts-*命名空间需整体重命名为.pvq-*)。
10. 新插件接入清单
- 复制
pvq-ui-loader.php到插件includes/。 - 调用
PVQ_UI_Loader::enqueue( array( 'admin' / 'editor' / 'frontend' ) )。 - 自身 CSS
wp_enqueue_style(..., ['pvq-ui','pvq-ui-<场景>'])。 - 只写插件专有样式,通用组件直接用
.pvq-*BEM 类。 - 不自带任何通用组件副本(纯 CDN 引用;内网站点可常量覆盖为本地副本)。
11. 风险与权衡
- CDN 单点:UI(含 JS 交互)CDN 挂 → 设置页布局/交互崩。缓解:复用已在用域名
cdn-buke.pvq.ink;JS 交互保留 CSS 兜底(无 JS 也能看);内网站点用PVQ_UI_URL常量覆盖为本地副本。 - 版本冲突:靠 §3.3 loader 内置 override 收敛(幂等),确保全站同一版本。
- FA 依赖:组件视觉依赖 FontAwesome,loader 一并保证
font-awesome句柄已注册。 - 重命名成本:BEM 重命名需批量改所有插件 DOM 引用 + 文档/演示 HTML 硬编码类;一次性工程,靠 §6 映射表 + §9 第 6 步 grep 校验降低出错率。
- 命名空间残留:wolai 后台通用类现用
.pvq-*前缀,迁移须确保彻底删除,否则与库双定义。
12. 文档同步(铁律)
本方案定稿后写入 doc/开发文档/PVQ-UI组件库开发方案.md(即本文件,作为唯一真源)。
后续:
pvq-ui仓库内维护doc/模块文档/设计令牌与组件清单.md(令牌 + 组件 API)。- 更新现有文档(如
数据库块编辑器工具栏和FontAwesome方案.md、各插件readme.md/CHANGELOG)。 - 迁移盲区提醒:
docs/演示/*.html中大量硬编码旧 handle / CDN / 设置项,须随迁移一并更新。 - 遵循项目铁律:先文档 → 再代码 → 最后同步 CHANGELOG 与 readme(除非用户要求不更新版本号)。
13. 关联文档(单一真源拆分)
本方案为总纲,下列文档与之互为引用、共同构成唯一真源:
组件提取对照表.md:三套现有组件(wolai / pvq-wp / threads)→ 库单一实现的逐组件映射与择优;迁移实施直接对照。库演进治理方案.md:库如何随插件开发持续演进——版本化 CDN、文档驱动增量贡献、组件分级(stable/experimental)、分层文件、防回潮。PVQ-UI项目规则.md:最高优先级权威规则,收敛散落各处的铁律(文档/单一真源/命名/版本化/分级/分层/接入/纪律);凡与本文冲突以它为准。PVQ-UI组件库调用手册.md:插件开发者实操指南——复制 loader、一行 enqueue、直接写.pvq-*类、场景对照、常用组件速查、内网覆盖、迁移步骤、自检清单。
版本化细化:§3.1 的 CDN 路径已含
<版本>,各插件通过PVQ_UI_Loader::enqueue( $scenes, $version )锁定版本;§11 的 override 收敛默认只解决"同版本冲突",不强制跨版本降级——从而支持"存量不动、新插件用最新"的渐进统一,匹配"随开发慢慢完善"的诉求。