@agile-team/wl-skills-ui 1.6.14 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +35 -2
- package/bin/wl-ui.js +45 -3
- package/docs/compat-matrix.md +68 -0
- package/docs/team-intro.md +168 -0
- package/mcp/server.js +76 -0
- package/package.json +4 -1
- package/runtime/vite/check.mjs +130 -0
- package/scanner/integration.mjs +45 -0
- package/scripts/check-docs.mjs +22 -0
- package/skills/_flows/legacy-skin-align.md +14 -0
- package/skills/_meta/_compat/loader.mjs +96 -0
- package/skills/_meta/_compat/vendors.json +24 -1
- package/skills/vendors/jh-components/SKILL.md +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to **@agile-team/wl-skills-ui** will be documented in this f
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.7.1] - 2026-05-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- 新增 Vite 插件 `@agile-team/wl-skills-ui/vite`:消费方在 `vite.config.ts` 加一行 `wlSkillsCheck()` 即可在每次 `dev/build` 启动期自动校验 vendor 版本配对,偏离推荐组合时彩色打印警告与一键修复片段(`enforce: 'warn' | 'error' | 'silent'`)。
|
|
12
|
+
- 新增 `npx wl-ui doctor --print-overrides` 子命令:检测到偏离时直接输出 pnpm/npm/yarn `overrides` JSON 片段,复制即可修复。
|
|
13
|
+
- `skills/_meta/_compat/loader.mjs` 抽出共享 compat 加载器,统一 `evaluateVendor` / `buildOverridesSnippet` 语义,scanner、MCP、Vite、CLI 单源共用。
|
|
14
|
+
- `vendors.json` 的 `compat` 升级为结构化 schema(`peers / gatingPeer / conflictsWith / domAssumptions`),同时保留旧平铺字段兜底;未来新增 vendor 配对无需改读取方代码。
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- scanner `I005` 改为遍历全部声明 `compat` 的 vendor,输出按 vendor 拆分的子检查项 `I005:<id>`,更易定位。
|
|
19
|
+
- MCP `wl_ui_detect_skin` 返回结构升级:`vendors[].verdict`、`fixSnippet`、`summary` 统一暴露,AI 一次拿全多 vendor 评估结果。
|
|
20
|
+
- `package.json` `files` 字段加入 `runtime/vite`,确保 Vite 插件随包发布。
|
|
21
|
+
|
|
22
|
+
## [1.7.0] - 2026-05-12
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- 新增 `docs/compat-matrix.md`:项目集群推荐版本与 wl-skills-ui 的适配矩阵单一事实源(`element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`)。
|
|
27
|
+
- `skills/_meta/_compat/vendors.json` 在 `jh.compat` 字段钉死推荐 EP/jh-ui 版本与 EP 2.2 vs 2.3 DOM 差异说明。
|
|
28
|
+
- scanner 接入完整性新增 `I005`:从 `vendors.json` 读取推荐版本,校验消费方 `package.json` 是否命中推荐组合。
|
|
29
|
+
- MCP 新增 `wl_ui_detect_skin` 工具:读取项目 `package.json` 返回 `verdict (match / mismatch / no-jh-ui)` 与推荐 SCSS 列表。
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `legacy-skin-align` flow 增加 Phase 0.5 强约束:写样式前必须先识别 jh-ui ↔ EP 版本配对。
|
|
34
|
+
- `skills/vendors/jh-components/SKILL.md` 补全反例(`.el-input__wrapper.is-focus` 在 EP 2.2 永远命中不到等),并显式钉死推荐组合。
|
|
35
|
+
- `scripts/check-docs.mjs` 扩展:`docs/compat-matrix.md` 的 EP/jh-ui 版本必须与 `vendors.json` 一致。
|
|
36
|
+
- README 新增「项目-依赖适配矩阵速查」章节,引导消费方一键判断推荐组合是否命中。
|
|
37
|
+
|
|
7
38
|
## [1.6.14] - 2026-05-12
|
|
8
39
|
|
|
9
40
|
### Added
|
package/README.md
CHANGED
|
@@ -191,11 +191,44 @@ yarn add @agile-team/wl-skills-ui
|
|
|
191
191
|
|
|
192
192
|
---
|
|
193
193
|
|
|
194
|
+
## 项目-依赖适配矩阵速查
|
|
195
|
+
|
|
196
|
+
集团项目集群推荐组合(**单一事实源**:`docs/compat-matrix.md` + `skills/_meta/_compat/vendors.json` 的 `jh.compat`):
|
|
197
|
+
|
|
198
|
+
| 依赖 | 推荐版本 | 备注 |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `element-plus` | **`2.2.6-prod.3`** | 集团 jh- 定制版;EP 2.3.0 起引入 `.el-input__wrapper`,与 jh-ui 3.x 不兼容 |
|
|
201
|
+
| `@jhlc/jh-ui` | **`3.1.0`** | SCSS 皮肤包,`.com-text` label 包裹 + `.has-colon` 冒号注入 |
|
|
202
|
+
| `@agile-team/wl-skills-ui` | `^1.7.0` | 已对齐上述组合的 DOM 假设 |
|
|
203
|
+
|
|
204
|
+
三种识别方式,任选其一:
|
|
205
|
+
|
|
206
|
+
- **启动期自动**:`vite.config.ts` 加一行 `import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite'; export default defineConfig({ plugins: [wlSkillsCheck()] })`,每次 `dev/build` 偏离推荐组合时彩色打印警告(支持 `enforce: 'error'` 阻断)。
|
|
207
|
+
- **手动 CLI**:`npx wl-ui check --project .` 看 `I005:<vendor>`;偏离时执行 `npx wl-ui doctor --print-overrides` 拿到可直接复制的 `pnpm.overrides` 修复片段。
|
|
208
|
+
- **AI 协作**:MCP 工具 `wl_ui_detect_skin` 一次返回多 vendor 评估结果(`vendors[].verdict / fixSnippet / summary`)。
|
|
209
|
+
|
|
210
|
+
完整版本-项目实测表见 `docs/compat-matrix.md`。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
194
214
|
## 版本亮点
|
|
195
215
|
|
|
196
|
-
当前 v1.
|
|
216
|
+
当前 v1.7.1 版本在 v1.7.0「单一事实源」基础上,把"识别"升级为"启动期自动 + 一键修复":
|
|
217
|
+
|
|
218
|
+
- 新增 Vite 插件 `@agile-team/wl-skills-ui/vite`:消费方一行配置即可在每次启动 dev/build 时自动跑版本配对校验,偏离时彩色提示 + 修复片段
|
|
219
|
+
- 新增 `npx wl-ui doctor --print-overrides`:检测到偏离直接输出 pnpm/npm/yarn overrides JSON,复制粘贴即可修复
|
|
220
|
+
- `vendors.json` `compat` 升级为 `peers / gatingPeer / conflictsWith / domAssumptions` 结构化 schema,未来新增 vendor 配对零代码改动
|
|
221
|
+
- scanner `I005` 拆为 `I005:<vendor>`,MCP `wl_ui_detect_skin` 一次返回多 vendor 结果
|
|
222
|
+
- 抽出 `skills/_meta/_compat/loader.mjs` 共享加载器,scanner/MCP/Vite/CLI 单源共用
|
|
223
|
+
|
|
224
|
+
历史亮点(v1.7.0 起):
|
|
197
225
|
|
|
198
|
-
- 新增
|
|
226
|
+
- 新增 `docs/compat-matrix.md` 作为项目集群推荐版本与 wl-skills-ui 的适配矩阵单一事实源;`skills/_meta/_compat/vendors.json` 在 `jh.compat` 字段钉死 `element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`
|
|
227
|
+
- scanner 接入完整性新增 `I005`:基于 `vendors.json` 校验消费方 `package.json` 是否锚定推荐组合,偏离时给出明确建议
|
|
228
|
+
- MCP 新增 `wl_ui_detect_skin` 工具,AI 写样式前可一键拿到 `verdict: match | mismatch | no-jh-ui` 与推荐 SCSS 列表
|
|
229
|
+
- `legacy-skin-align` flow 增加 Phase 0.5 强约束:写样式前必须先识别版本配对;`jh-components` SKILL 补全反例(`.el-input__wrapper.is-focus` 在 EP 2.2 永远命中不到等)
|
|
230
|
+
- `docs:check` 扩展校验:`docs/compat-matrix.md` 中的版本号必须与 `vendors.json` 同步,避免事实源漂移
|
|
231
|
+
- 沿用 v1.6.14 的 `_jh-ui.scss` 适配层(`.com-text` label / EP 2.2.x `.el-input__inner` focus + error / 必填星号)
|
|
199
232
|
- 兼容 Element Plus 2.2.x(`.el-input__inner` 直挂结构)下输入、选择、日期控件的圆角 / focus / error 三态,jh-select 与 jh-date-picker focus 行为稳定
|
|
200
233
|
- 补强输入、选择、日期、文本域 focus 品牌色边框和必填星号红色显示规则,兼容 Element Plus 定制版本差异
|
|
201
234
|
- 修复表单必填星号、错误 label、错误提示和错误边框红色态,避免校验反馈被通用 label/input 样式覆盖
|
package/bin/wl-ui.js
CHANGED
|
@@ -207,9 +207,16 @@ if (subcommand === "clean") {
|
|
|
207
207
|
if (subcommand === "doctor") {
|
|
208
208
|
const { values } = parseArgs({
|
|
209
209
|
args: rawArgs,
|
|
210
|
-
options: {
|
|
210
|
+
options: {
|
|
211
|
+
project: { type: "string", default: "." },
|
|
212
|
+
"print-overrides": { type: "boolean", default: false },
|
|
213
|
+
},
|
|
211
214
|
strict: false,
|
|
212
215
|
});
|
|
216
|
+
if (values["print-overrides"]) {
|
|
217
|
+
await printOverrides(resolve(values.project));
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}
|
|
213
220
|
runDoctor(resolve(values.project));
|
|
214
221
|
process.exit(0);
|
|
215
222
|
}
|
|
@@ -895,6 +902,40 @@ function runClean(projectRoot, dryRun) {
|
|
|
895
902
|
);
|
|
896
903
|
}
|
|
897
904
|
|
|
905
|
+
async function printOverrides(projectRoot) {
|
|
906
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
907
|
+
if (!existsSync(pkgPath)) {
|
|
908
|
+
console.error(`[wl-ui doctor] 未找到 ${pkgPath}`);
|
|
909
|
+
process.exit(1);
|
|
910
|
+
}
|
|
911
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
912
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
913
|
+
const loader = await import("../skills/_meta/_compat/loader.mjs");
|
|
914
|
+
const vendors = loader.listCompatVendors();
|
|
915
|
+
const evaluations = vendors
|
|
916
|
+
.map((c) => loader.evaluateVendor(c, deps))
|
|
917
|
+
.filter((e) => e.verdict !== "not-applicable");
|
|
918
|
+
if (evaluations.length === 0) {
|
|
919
|
+
console.log(
|
|
920
|
+
"[wl-ui doctor] 当前项目未命中任何 vendor 适配矩阵,无需 overrides",
|
|
921
|
+
);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const snippet = loader.buildOverridesSnippet(evaluations);
|
|
925
|
+
if (!snippet) {
|
|
926
|
+
console.log("[wl-ui doctor] 当前项目所有 vendor 配对已命中推荐组合 ✓");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
console.log(
|
|
930
|
+
"\n[wl-ui doctor --print-overrides] 检测到 vendor 版本偏离,复制以下片段到 package.json:\n",
|
|
931
|
+
);
|
|
932
|
+
console.log("// pnpm");
|
|
933
|
+
console.log(JSON.stringify(snippet.pnpm, null, 2));
|
|
934
|
+
console.log("\n// npm / yarn");
|
|
935
|
+
console.log(JSON.stringify(snippet.npmYarn, null, 2));
|
|
936
|
+
console.log("\n复制后执行:pnpm install(或对应包管理器的 install 命令)\n");
|
|
937
|
+
}
|
|
938
|
+
|
|
898
939
|
function runDoctor(projectRoot) {
|
|
899
940
|
const pkgPath = join(projectRoot, "package.json");
|
|
900
941
|
let pkg = null;
|
|
@@ -973,8 +1014,9 @@ wl-ui — @agile-team/wl-skills-ui 统一 CLI v${PKG.version}
|
|
|
973
1014
|
对比已安装文件与 manifest
|
|
974
1015
|
wl-ui clean [--project <path>] [--dry-run]
|
|
975
1016
|
清理 wl-skills-ui 安装文件
|
|
976
|
-
wl-ui doctor [--project <path>]
|
|
977
|
-
检查安装状态 / MCP / 桥接 /
|
|
1017
|
+
wl-ui doctor [--project <path>] [--print-overrides]
|
|
1018
|
+
检查安装状态 / MCP / 桥接 / 规范插件;
|
|
1019
|
+
--print-overrides 时输出 vendor 版本偏离的 pnpm/npm/yarn overrides 修复片段
|
|
978
1020
|
wl-ui prompts
|
|
979
1021
|
打印 AI 触发提示词
|
|
980
1022
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 项目-依赖适配矩阵
|
|
2
|
+
|
|
3
|
+
> 单一事实源,集团项目集群推荐版本与 wl-skills-ui 适配关系。
|
|
4
|
+
> scanner / MCP / Skill 文档统一从本表派生。
|
|
5
|
+
|
|
6
|
+
## 推荐版本(集团内推荐组合)
|
|
7
|
+
|
|
8
|
+
| 依赖 | 推荐版本 | 说明 |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `vue` | `~3.2.25` 或 `^3.2.25` | 与项目集群保持一致 |
|
|
11
|
+
| `element-plus` | **`2.2.6-prod.3`** | 集团 jh- 定制版,搭配 `@jhlc/jh-ui` 使用,DOM 仍为 `.el-input > .el-input__inner` 直挂结构(**EP 2.3.0 起才引入 `.el-input__wrapper`**) |
|
|
12
|
+
| `@jhlc/jh-ui` | **`3.1.0`** | SCSS 皮肤包,`.com-text` label 包裹、`.has-colon ::after` 冒号注入、`.el-form-item--default { margin-bottom: 24px }` 等强约束 |
|
|
13
|
+
| `@jhlc/common-core` | `3.1.0` 或 `3.1.0-prod.x` | 基础 util/types,`@jhlc/jh-ui` 间接依赖 |
|
|
14
|
+
| `@agile-team/wl-skills-ui` | `^1.7.0` | 已对齐 jh-ui 3.1.0 + EP 2.2.6-prod.3 的 DOM 假设 |
|
|
15
|
+
|
|
16
|
+
## 项目集群当前实测
|
|
17
|
+
|
|
18
|
+
| 项目 | element-plus | @jhlc/jh-ui | 适配状态 |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `wl-ui-sale` | `2.2.6-prod.3` | `3.1.0` | ✅ 推荐组合 |
|
|
21
|
+
| `wl-ui-public` | `2.2.6-prod.3` | `3.1.0` | ✅ 推荐组合 |
|
|
22
|
+
| `wl-ui-safe` | `2.2.6-prod.3` | `3.1.0` | ✅ 推荐组合 |
|
|
23
|
+
| `wl-ui-security` | `2.2.6-prod.3` | `3.1.0` | ✅ 推荐组合 |
|
|
24
|
+
| `wl-mdata` | `2.2.6-prod.3` | — | ⚠️ 仅 `@jhlc/common-core`,未装 jh-ui,本表 jh-ui 相关规则不生效 |
|
|
25
|
+
|
|
26
|
+
## EP 2.2.x vs EP 2.3+ DOM 差异(适配关键依据)
|
|
27
|
+
|
|
28
|
+
| 维度 | EP 2.2.x(jh-ui 配套) | EP 2.3+(社区主流) |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| 输入控件 | `.el-input > .el-input__inner` | `.el-input > .el-input__wrapper > .el-input__inner` |
|
|
31
|
+
| focus 标志 | `.el-input.is-focus` / `.el-input__inner:focus` | `.el-input__wrapper.is-focus` / `:focus-within` |
|
|
32
|
+
| select | `.el-select .el-input > .el-input__inner` | `.el-select__wrapper` |
|
|
33
|
+
| 错误态 | `.el-form-item.is-error .el-input__inner` | `.el-form-item.is-error .el-input__wrapper` |
|
|
34
|
+
| 表单 label | jh-ui 注入 `<span class="com-text">` 包裹文本 | EP 默认直接渲染文本节点 |
|
|
35
|
+
|
|
36
|
+
## 适配建议
|
|
37
|
+
|
|
38
|
+
- 集团内项目:**统一锚定 `element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`**,与 wl-skills-ui v1.7.0 三方对齐。
|
|
39
|
+
- 升级 `element-plus` 到 2.3+ 前,必须同步升级 `@jhlc/jh-ui` 到对应支持版本,否则 `.com-text` / `.el-input__inner` DOM 与 EP `__wrapper` 体系不兼容。
|
|
40
|
+
- 新项目接入:见 README 「快速接入」章节,默认会按本表声明 peerDependency 范围。
|
|
41
|
+
|
|
42
|
+
## 维护流程
|
|
43
|
+
|
|
44
|
+
- 推荐版本字段统一在 `skills/_meta/_compat/vendors.json` 的 `vendors[id=jh].compat`(结构化 schema:`peers / gatingPeer / conflictsWith / domAssumptions`)。
|
|
45
|
+
- 共享加载器 `skills/_meta/_compat/loader.mjs` 暴露 `listCompatVendors / evaluateVendor / buildOverridesSnippet`,scanner、MCP、Vite 插件、CLI 单源共用。
|
|
46
|
+
- 本文档由 `scripts/check-docs.mjs` 校验:版本号必须与 `vendors.json` 一致,避免漂移。
|
|
47
|
+
- scanner `I005:<vendor>` 接入完整性检查会按本表校验消费方项目是否使用推荐组合。
|
|
48
|
+
- MCP 工具 `wl_ui_detect_skin` 直接读取消费方 `package.json` 给 AI 返回结构化结果。
|
|
49
|
+
- Vite 插件 `@agile-team/wl-skills-ui/vite` 在启动期自动校验,无需手动调用。
|
|
50
|
+
- `npx wl-ui doctor --print-overrides` 在检测到偏离时输出可直接复制的 pnpm/npm/yarn `overrides` 片段。
|
|
51
|
+
|
|
52
|
+
## 启动期自动校验(推荐)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// vite.config.ts
|
|
56
|
+
import { defineConfig } from 'vite';
|
|
57
|
+
import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite';
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
plugins: [
|
|
61
|
+
wlSkillsCheck({
|
|
62
|
+
// enforce: 'warn' | 'error' | 'silent' 默认 'warn'
|
|
63
|
+
// includeVendors: ['jh'] 只校验指定 vendor
|
|
64
|
+
// verbose: true 额外打印 match 项
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @agile-team/wl-skills-ui — 团队接入指南
|
|
2
|
+
|
|
3
|
+
> 版本:v1.6.0 · 适用:所有基于 Vue 3 + Element Plus 的业务子应用
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 我们现在有什么问题?
|
|
8
|
+
|
|
9
|
+
各个子系统独自维护样式,导致:
|
|
10
|
+
|
|
11
|
+
- 同一个按钮,各系统颜色/大小/间距都不一样
|
|
12
|
+
- 弹窗、表格、分页、标签——每个系统长一个样
|
|
13
|
+
- 模块联邦把各系统挂在同一个平台,视觉割裂感很强
|
|
14
|
+
- 改一处要在多个仓库重复改,越改越乱
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 这个包解决什么?
|
|
19
|
+
|
|
20
|
+
**一套 CSS + 一行导入,所有子系统统一视觉。**
|
|
21
|
+
|
|
22
|
+
核心做三件事:
|
|
23
|
+
|
|
24
|
+
| 能力 | 说明 |
|
|
25
|
+
|------|------|
|
|
26
|
+
| **设计令牌** | 统一定义颜色、按钮尺寸、间距、圆角、阴影。改一个变量,全系统生效 |
|
|
27
|
+
| **样式化妆层** | 不改业务代码,直接覆盖 Element Plus 原生控件 + 内部封装组件(`Base*` / `jh-*` / `C_*` / `c_*` / AG Grid) |
|
|
28
|
+
| **扫描 & 修复** | 自动扫描项目里哪些地方写死了颜色、不合规范,一键 dry-run 预览修复内容,确认后批量修复 |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 两种接入方式,选一种就行
|
|
33
|
+
|
|
34
|
+
### 方式 A — 化妆模式(推荐老项目,不动业务代码)
|
|
35
|
+
|
|
36
|
+
适合:**项目已有大量业务代码,不想动**。一行样式导入,立刻对齐视觉。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm add @agile-team/wl-skills-ui
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```scss
|
|
43
|
+
// src/styles/index.scss(或项目全局样式入口)
|
|
44
|
+
@use "@agile-team/wl-skills-ui/styles/presets/skin" as *;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
完成。刷新页面,按钮、表格、分页、弹窗、标签全部对齐统一风格。
|
|
48
|
+
|
|
49
|
+
> **可以回退**:把这一行 `@use` 删掉,样式立刻恢复原状,零风险。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 方式 B — 原生模式(推荐新项目 / 可以重构的项目)
|
|
54
|
+
|
|
55
|
+
适合:**新项目,或者想进一步把业务写法也规范化**。
|
|
56
|
+
|
|
57
|
+
```scss
|
|
58
|
+
// src/styles/index.scss
|
|
59
|
+
@use "@agile-team/wl-skills-ui/styles" as *;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// src/main.ts
|
|
64
|
+
import { installCommonPreset } from "@agile-team/wl-skills-ui/runtime/common-preset";
|
|
65
|
+
installCommonPreset();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
额外获得:
|
|
69
|
+
- 状态字段(启用/禁用/审核/已验证)自动渲染为 Tag,不用手写模板
|
|
70
|
+
- 操作列(查看/编辑/删除)按统一规范生成,不用每个页面重复写
|
|
71
|
+
- 页面骨架(列表页/左树右表/弹窗表单/详情页)有现成模板
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 现在就能看效果吗?
|
|
76
|
+
|
|
77
|
+
可以,3 分钟验证:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. 安装
|
|
81
|
+
pnpm add @agile-team/wl-skills-ui
|
|
82
|
+
|
|
83
|
+
# 2. 在全局样式入口加一行
|
|
84
|
+
# @use "@agile-team/wl-skills-ui/styles/presets/skin" as *;
|
|
85
|
+
|
|
86
|
+
# 3. 启动项目,浏览器看效果
|
|
87
|
+
pnpm dev
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
如果效果不对 / 有冲突,删掉那一行即可完全还原。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 想知道项目哪里不符合规范?先扫描
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 扫描 src 目录,输出报告
|
|
98
|
+
npx wl-ui scan --target src --outFile ui-audit.md
|
|
99
|
+
|
|
100
|
+
# 预览修复内容(不实际写入)
|
|
101
|
+
npx wl-ui fix --target src --dry-run
|
|
102
|
+
|
|
103
|
+
# 确认没问题,执行修复
|
|
104
|
+
npx wl-ui fix --target src
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
扫描报告会告诉你:哪些文件写死了颜色、哪些按钮不合尺寸规范、哪些封装组件需要覆盖样式。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 能回退吗?
|
|
112
|
+
|
|
113
|
+
**完全可以,三个层面都支持回退:**
|
|
114
|
+
|
|
115
|
+
| 操作 | 回退方式 |
|
|
116
|
+
|------|----------|
|
|
117
|
+
| 样式导入 | 删除 `@use` 那一行,立即恢复 |
|
|
118
|
+
| 扫描修复 | 修复前有 `--dry-run` 预览;git 可随时 `revert` |
|
|
119
|
+
| CLI 工具安装 | `npx wl-ui clean --project .` 一键卸载所有注入文件 |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 各系统哪些组件会被统一?
|
|
124
|
+
|
|
125
|
+
接入后,以下内容会自动对齐视觉(无需任何额外配置):
|
|
126
|
+
|
|
127
|
+
**Element Plus 原生控件**
|
|
128
|
+
- 按钮颜色、尺寸、间距
|
|
129
|
+
- 表格(行高、边框、斑马纹、操作列)
|
|
130
|
+
- 表单(Label 宽度、输入框尺寸)
|
|
131
|
+
- 弹窗(标题栏、底部按钮位置)
|
|
132
|
+
- 分页(对齐方式、按钮样式)
|
|
133
|
+
- Tag(颜色语义:成功/警告/危险/信息)
|
|
134
|
+
- Card、Tabs、Tree、Drawer、Upload、Steps、Tooltip、Dropdown...
|
|
135
|
+
|
|
136
|
+
**内部封装组件(有源码没源码都能覆盖)**
|
|
137
|
+
- `BaseTable` / `BaseQuery` / `BaseToolbar` / `BaseDialog`
|
|
138
|
+
- `jh-tree` / `jh-pagination` / `jh-drag-col`
|
|
139
|
+
- `C_*` / `c_*` 前缀组件
|
|
140
|
+
- AG Grid 表格主题
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 如果用了 AI 编辑器(Copilot / Cursor / Cline 等)
|
|
145
|
+
|
|
146
|
+
可以一句话触发 AI 自动帮你把整个项目的样式对齐:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
用 wl-ui 的 legacy-skin-align 流程对当前项目做老项目化妆对齐
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
AI 会按照 6 个阶段依次执行:接入 tokens → 引入 skin preset → 修复各类封装组件样式 → 修复 Element Plus 控件 → 修复硬编码颜色 → 全程不动业务逻辑。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 安装一览
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pnpm add @agile-team/wl-skills-ui
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
要求:Node ≥ 18,Vue ≥ 3.2,Element Plus ≥ 2.2
|
|
163
|
+
|
|
164
|
+
NPM:[@agile-team/wl-skills-ui](https://www.npmjs.com/package/@agile-team/wl-skills-ui)
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
*有问题找杨晨誉,或直接群里说。*
|
package/mcp/server.js
CHANGED
|
@@ -83,6 +83,21 @@ const TOOLS = [
|
|
|
83
83
|
required: ["text"],
|
|
84
84
|
},
|
|
85
85
|
},
|
|
86
|
+
{
|
|
87
|
+
name: "wl_ui_detect_skin",
|
|
88
|
+
description:
|
|
89
|
+
"读取目标项目 package.json,识别 @jhlc/jh-ui 与 element-plus 版本配对,返回适配矩阵建议。",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
project: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "项目根目录,默认 WL_PROJECT_ROOT 或当前目录",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: [],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
86
101
|
{
|
|
87
102
|
name: "wl_ui_recommend_flow",
|
|
88
103
|
description:
|
|
@@ -113,6 +128,60 @@ function projectRoot(args = {}) {
|
|
|
113
128
|
return resolve(args.project || process.env.WL_PROJECT_ROOT || process.cwd());
|
|
114
129
|
}
|
|
115
130
|
|
|
131
|
+
async function detectSkin(args = {}) {
|
|
132
|
+
const root = projectRoot(args);
|
|
133
|
+
const pkgPath = join(root, "package.json");
|
|
134
|
+
const fs = require("node:fs");
|
|
135
|
+
if (!fs.existsSync(pkgPath)) {
|
|
136
|
+
return { ok: false, reason: `未找到 ${pkgPath}` };
|
|
137
|
+
}
|
|
138
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
139
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
140
|
+
const loader = await import("../skills/_meta/_compat/loader.mjs");
|
|
141
|
+
const vendors = loader.listCompatVendors();
|
|
142
|
+
const evaluations = vendors.map((c) => ({
|
|
143
|
+
compat: c,
|
|
144
|
+
evaluation: loader.evaluateVendor(c, deps),
|
|
145
|
+
}));
|
|
146
|
+
const applicable = evaluations.filter(
|
|
147
|
+
(e) => e.evaluation.verdict !== "not-applicable",
|
|
148
|
+
);
|
|
149
|
+
const overrides = loader.buildOverridesSnippet(
|
|
150
|
+
applicable.map((e) => e.evaluation),
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
project: pkg.name,
|
|
155
|
+
vendors: evaluations.map(({ compat, evaluation }) => ({
|
|
156
|
+
vendorId: compat.vendorId,
|
|
157
|
+
vendorLabel: compat.vendorLabel,
|
|
158
|
+
gatingPeer: compat.gatingPeer,
|
|
159
|
+
verdict: evaluation.verdict,
|
|
160
|
+
peers: evaluation.peers,
|
|
161
|
+
conflictsWith: compat.conflictsWith,
|
|
162
|
+
domAssumptions: compat.domAssumptions,
|
|
163
|
+
note: compat.note,
|
|
164
|
+
})),
|
|
165
|
+
summary:
|
|
166
|
+
applicable.length === 0
|
|
167
|
+
? "no-applicable-vendor"
|
|
168
|
+
: applicable.every((e) => e.evaluation.verdict === "match")
|
|
169
|
+
? "all-match"
|
|
170
|
+
: "has-mismatch",
|
|
171
|
+
fixSnippet: overrides,
|
|
172
|
+
recommendedScss: applicable.some(
|
|
173
|
+
({ compat }) => compat.vendorId === "jh" && deps["@jhlc/jh-ui"],
|
|
174
|
+
)
|
|
175
|
+
? [
|
|
176
|
+
"styles/vendors/_jh-ui.scss",
|
|
177
|
+
"styles/vendors/_jh-tree.scss",
|
|
178
|
+
"styles/vendors/_jh-pagination.scss",
|
|
179
|
+
"styles/vendors/_jh-drag-col.scss",
|
|
180
|
+
]
|
|
181
|
+
: ["styles/vendors/_base-components.scss"],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
116
185
|
function runScanner(command, args = {}) {
|
|
117
186
|
const root = projectRoot(args);
|
|
118
187
|
const scanner = join(PKG_ROOT, "scanner", "index.mjs");
|
|
@@ -333,6 +402,13 @@ async function dispatchTool(id, name, args) {
|
|
|
333
402
|
});
|
|
334
403
|
return;
|
|
335
404
|
}
|
|
405
|
+
if (name === "wl_ui_detect_skin") {
|
|
406
|
+
const result = await detectSkin(args);
|
|
407
|
+
sendResult(id, {
|
|
408
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
336
412
|
if (name === "wl_ui_recommend_flow") {
|
|
337
413
|
sendResult(id, {
|
|
338
414
|
content: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "企业级 UI 风格对齐框架 — Vue + Element Plus 项目通用化妆/原生双模式(tokens / element / vendors / layouts / runtime / scanner / fixer / skills)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./es/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"types": "./es/presets/security.d.ts"
|
|
20
20
|
},
|
|
21
21
|
"./runtime/presets/*": "./runtime/presets/*",
|
|
22
|
+
"./vite": "./runtime/vite/check.mjs",
|
|
22
23
|
"./styles": "./styles/index.scss",
|
|
23
24
|
"./styles/*": "./styles/*",
|
|
24
25
|
"./design/tokens": "./design/tokens/index.css",
|
|
@@ -52,10 +53,12 @@
|
|
|
52
53
|
"files": [
|
|
53
54
|
"dist",
|
|
54
55
|
"es",
|
|
56
|
+
"runtime/vite",
|
|
55
57
|
"scanner",
|
|
56
58
|
"standards",
|
|
57
59
|
"styles",
|
|
58
60
|
"design",
|
|
61
|
+
"docs",
|
|
59
62
|
"skills",
|
|
60
63
|
"templates",
|
|
61
64
|
"bin",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2
|
+
// @agile-team/wl-skills-ui/vite — 启动期自检 Vite 插件
|
|
3
|
+
//
|
|
4
|
+
// 用法(消费方 vite.config.ts):
|
|
5
|
+
// import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite';
|
|
6
|
+
// export default defineConfig({ plugins: [wlSkillsCheck()] });
|
|
7
|
+
//
|
|
8
|
+
// 选项:
|
|
9
|
+
// enforce: 'warn' | 'error' | 'silent' 默认 'warn'
|
|
10
|
+
// includeVendors: string[] 只校验指定 vendor id,默认全部
|
|
11
|
+
// verbose: boolean 默认 false,true 时也打印 match 项
|
|
12
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
listCompatVendors,
|
|
17
|
+
evaluateVendor,
|
|
18
|
+
buildOverridesSnippet,
|
|
19
|
+
} from "../../skills/_meta/_compat/loader.mjs";
|
|
20
|
+
|
|
21
|
+
const C_RESET = "\x1b[0m";
|
|
22
|
+
const C_YELLOW = "\x1b[33m";
|
|
23
|
+
const C_RED = "\x1b[31m";
|
|
24
|
+
const C_GREEN = "\x1b[32m";
|
|
25
|
+
const C_DIM = "\x1b[2m";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Vite 插件:启动期校验项目依赖是否锚定 wl-skills-ui 推荐组合
|
|
29
|
+
* @param {{enforce?: 'warn'|'error'|'silent', includeVendors?: string[], verbose?: boolean}} options
|
|
30
|
+
*/
|
|
31
|
+
export function wlSkillsCheck(options = {}) {
|
|
32
|
+
const enforce = options.enforce ?? "warn";
|
|
33
|
+
const includeVendors = options.includeVendors ?? null;
|
|
34
|
+
const verbose = options.verbose ?? false;
|
|
35
|
+
let hasRun = false;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
name: "wl-skills-ui:check",
|
|
39
|
+
configResolved(config) {
|
|
40
|
+
if (hasRun || enforce === "silent") return;
|
|
41
|
+
hasRun = true;
|
|
42
|
+
runCheck({
|
|
43
|
+
projectRoot: config.root,
|
|
44
|
+
enforce,
|
|
45
|
+
includeVendors,
|
|
46
|
+
verbose,
|
|
47
|
+
logger: config.logger,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runCheck({ projectRoot, enforce, includeVendors, verbose, logger }) {
|
|
54
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
55
|
+
if (!existsSync(pkgPath)) return;
|
|
56
|
+
let pkg;
|
|
57
|
+
try {
|
|
58
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
63
|
+
|
|
64
|
+
const vendors = listCompatVendors().filter(
|
|
65
|
+
(v) => !includeVendors || includeVendors.includes(v.vendorId),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const mismatches = [];
|
|
69
|
+
const matches = [];
|
|
70
|
+
const evaluations = [];
|
|
71
|
+
for (const compat of vendors) {
|
|
72
|
+
const ev = evaluateVendor(compat, deps);
|
|
73
|
+
evaluations.push(ev);
|
|
74
|
+
if (ev.verdict === "match") matches.push({ compat, ev });
|
|
75
|
+
else if (ev.verdict === "mismatch") mismatches.push({ compat, ev });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (verbose && matches.length > 0) {
|
|
79
|
+
const lines = matches.map(
|
|
80
|
+
({ compat, ev }) =>
|
|
81
|
+
` ${C_GREEN}✓${C_RESET} ${compat.vendorId}: ${ev.peers
|
|
82
|
+
.map((p) => `${p.name}@${p.actual}`)
|
|
83
|
+
.join(" + ")}`,
|
|
84
|
+
);
|
|
85
|
+
print(logger, "info", `${C_DIM}[wl-skills-ui] 适配矩阵命中:${C_RESET}\n${lines.join("\n")}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (mismatches.length === 0) return;
|
|
89
|
+
|
|
90
|
+
const overrides = buildOverridesSnippet(evaluations);
|
|
91
|
+
const header =
|
|
92
|
+
enforce === "error"
|
|
93
|
+
? `${C_RED}[wl-skills-ui] 启动期版本配对失败(enforce=error)${C_RESET}`
|
|
94
|
+
: `${C_YELLOW}[wl-skills-ui] 启动期版本配对警告${C_RESET}`;
|
|
95
|
+
|
|
96
|
+
const detail = mismatches
|
|
97
|
+
.map(({ compat, ev }) => {
|
|
98
|
+
const peers = ev.peers
|
|
99
|
+
.map(
|
|
100
|
+
(p) =>
|
|
101
|
+
` - ${p.name}: 实际 ${p.actual || "未安装"} / 推荐 ${p.expected} ${p.ok ? `${C_GREEN}✓${C_RESET}` : `${C_RED}✗${C_RESET}`}`,
|
|
102
|
+
)
|
|
103
|
+
.join("\n");
|
|
104
|
+
return ` vendor=${compat.vendorId}\n${peers}\n ${C_DIM}→ ${compat.note || ""}${C_RESET}`;
|
|
105
|
+
})
|
|
106
|
+
.join("\n\n");
|
|
107
|
+
|
|
108
|
+
const fix = overrides
|
|
109
|
+
? `\n${C_DIM}修复建议(复制到 package.json):${C_RESET}\n${C_DIM}${JSON.stringify(overrides.pnpm, null, 2)}${C_RESET}\n或执行:${C_DIM}npx wl-ui doctor --print-overrides${C_RESET}`
|
|
110
|
+
: "";
|
|
111
|
+
|
|
112
|
+
const message = `${header}\n${detail}${fix}`;
|
|
113
|
+
|
|
114
|
+
if (enforce === "error") {
|
|
115
|
+
print(logger, "error", message);
|
|
116
|
+
throw new Error("[wl-skills-ui] vendor 版本配对偏离推荐组合,详见上方提示");
|
|
117
|
+
} else {
|
|
118
|
+
print(logger, "warn", message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function print(logger, level, msg) {
|
|
123
|
+
if (logger && typeof logger[level] === "function") {
|
|
124
|
+
logger[level](msg);
|
|
125
|
+
} else {
|
|
126
|
+
console[level === "error" ? "error" : "warn"](msg);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default wlSkillsCheck;
|
package/scanner/integration.mjs
CHANGED
|
@@ -7,9 +7,18 @@
|
|
|
7
7
|
* I002 — main.scss / 全局入口是否引入 @agile-team/wl-skills-ui/styles 或 shared/index.scss
|
|
8
8
|
* I003 — src/util/ 是否有 ag-cell-renders.ts 或者 main.ts 中安装了 runtime
|
|
9
9
|
* I004 — element-plus 是否在 dependencies 中(peer 兼容)
|
|
10
|
+
* I005 — @jhlc/jh-ui ↔ element-plus 版本配对是否符合推荐组合(vendors.json 单一事实源)
|
|
10
11
|
*/
|
|
11
12
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
12
13
|
import { join, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import {
|
|
16
|
+
listCompatVendors,
|
|
17
|
+
evaluateVendor,
|
|
18
|
+
} from "../skills/_meta/_compat/loader.mjs";
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* @param {string} projectRoot — 项目根目录(包含 index.html / src / package.json)
|
|
@@ -140,6 +149,42 @@ export function checkIntegration(projectRoot) {
|
|
|
140
149
|
: `peer 依赖缺失:${!hasVue ? "vue " : ""}${!hasEp ? "element-plus" : ""}`,
|
|
141
150
|
suggestion: hasEp && hasVue ? "" : "pnpm add vue element-plus",
|
|
142
151
|
});
|
|
152
|
+
|
|
153
|
+
// ── I005: vendor 配对(jh-ui / 未来 vendor 都走这条) ─────────────
|
|
154
|
+
const vendors = listCompatVendors();
|
|
155
|
+
let anyEvaluated = false;
|
|
156
|
+
for (const compat of vendors) {
|
|
157
|
+
const ev = evaluateVendor(compat, deps);
|
|
158
|
+
if (ev.verdict === "not-applicable") continue;
|
|
159
|
+
anyEvaluated = true;
|
|
160
|
+
const ok = ev.verdict === "match";
|
|
161
|
+
const actuals = ev.peers
|
|
162
|
+
.map((p) => `${p.name} ${p.actual || "未安装"}`)
|
|
163
|
+
.join(" + ");
|
|
164
|
+
const expecteds = ev.peers
|
|
165
|
+
.map((p) => `${p.name}@${p.expected}`)
|
|
166
|
+
.join(" + ");
|
|
167
|
+
checks.push({
|
|
168
|
+
id: `I005:${compat.vendorId}`,
|
|
169
|
+
severity: ok ? "info" : "warning",
|
|
170
|
+
ok,
|
|
171
|
+
description: ok
|
|
172
|
+
? `${compat.vendorId} 推荐组合命中(${actuals})`
|
|
173
|
+
: `${compat.vendorId} 版本配对偏离推荐(实际 ${actuals})`,
|
|
174
|
+
suggestion: ok
|
|
175
|
+
? ""
|
|
176
|
+
: `推荐组合:${expecteds}(详见 docs/compat-matrix.md,或执行 npx wl-ui doctor --print-overrides)`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (!anyEvaluated) {
|
|
180
|
+
checks.push({
|
|
181
|
+
id: "I005",
|
|
182
|
+
severity: "info",
|
|
183
|
+
ok: true,
|
|
184
|
+
description: "未命中任何已声明 compat 的 vendor,跳过配对校验",
|
|
185
|
+
suggestion: "",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
143
188
|
} catch {
|
|
144
189
|
checks.push({
|
|
145
190
|
id: "I004",
|
package/scripts/check-docs.mjs
CHANGED
|
@@ -97,6 +97,28 @@ if (existsSync(jhSkill)) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// compat-matrix.md ↔ vendors.json 一致性
|
|
101
|
+
const vendorsJson = JSON.parse(
|
|
102
|
+
readFileSync(join(root, "skills", "_meta", "_compat", "vendors.json"), "utf8"),
|
|
103
|
+
);
|
|
104
|
+
const jhCompat = vendorsJson.vendors.find((v) => v.id === "jh")?.compat || {};
|
|
105
|
+
const compatMatrixPath = join(root, "docs", "compat-matrix.md");
|
|
106
|
+
if (existsSync(compatMatrixPath)) {
|
|
107
|
+
const matrix = readFileSync(compatMatrixPath, "utf8");
|
|
108
|
+
if (jhCompat.elementPlus && !matrix.includes(jhCompat.elementPlus)) {
|
|
109
|
+
errors.push(
|
|
110
|
+
`docs/compat-matrix.md: 缺少推荐 element-plus 版本 ${jhCompat.elementPlus}(与 vendors.json jh.compat 不一致)`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (jhCompat.jhUi && !matrix.includes(jhCompat.jhUi)) {
|
|
114
|
+
errors.push(
|
|
115
|
+
`docs/compat-matrix.md: 缺少推荐 @jhlc/jh-ui 版本 ${jhCompat.jhUi}(与 vendors.json jh.compat 不一致)`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
errors.push("docs/compat-matrix.md: 文件不存在,无法校验适配矩阵");
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
if (!readme.includes(`当前 v${pkg.version}`)) {
|
|
101
123
|
errors.push(
|
|
102
124
|
`README.md: 当前版本文案未同步 package.json version ${pkg.version}`,
|
|
@@ -27,6 +27,20 @@ npx wl-ui doctor --project .
|
|
|
27
27
|
|
|
28
28
|
(AI 严格按序)
|
|
29
29
|
|
|
30
|
+
### Phase 0.5 — 识别 jh-ui ↔ Element Plus 版本配对(强约束)
|
|
31
|
+
|
|
32
|
+
在写任何样式前,**必须先**通过下列任一方式确认推荐组合命中情况:
|
|
33
|
+
|
|
34
|
+
- MCP:调用 `wl_ui_detect_skin`,读取项目 `package.json` 返回 `verdict`(`match` / `mismatch` / `no-jh-ui`)。
|
|
35
|
+
- CLI:执行 `npx wl-ui check --project .`,关注 `I005` 项。
|
|
36
|
+
- 文档:对照 `docs/compat-matrix.md` 的「推荐版本」表。
|
|
37
|
+
|
|
38
|
+
判定规则:
|
|
39
|
+
|
|
40
|
+
- `verdict === "match"`(推荐组合):`_jh-ui.scss` 等 EP 2.2.x 适配规则可全量启用。
|
|
41
|
+
- `verdict === "mismatch"`(版本偏离):禁止套用 EP 2.6+ `.el-input__wrapper` 规则;先与项目方对齐版本再继续 Phase 1。
|
|
42
|
+
- `verdict === "no-jh-ui"`:无需启用 `_jh-ui.scss`,按 EP 默认 DOM 处理。
|
|
43
|
+
|
|
30
44
|
### Phase 1 — 接入 tokens
|
|
31
45
|
1. 检查 `index.html`,在 `<head>` 内追加:
|
|
32
46
|
```html
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2
|
+
// vendors.json 适配矩阵共享加载器
|
|
3
|
+
//
|
|
4
|
+
// 所有读取方(scanner I005 / MCP wl_ui_detect_skin / Vite 插件 / bin doctor /
|
|
5
|
+
// scripts/check-docs.mjs)统一从这里取数据,避免逻辑漂移。
|
|
6
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const VENDORS_JSON_PATH = join(__dirname, "vendors.json");
|
|
13
|
+
|
|
14
|
+
let _cache = null;
|
|
15
|
+
|
|
16
|
+
/** 加载 vendors.json(带进程内缓存) */
|
|
17
|
+
export function loadVendorsJson() {
|
|
18
|
+
if (_cache) return _cache;
|
|
19
|
+
_cache = JSON.parse(readFileSync(VENDORS_JSON_PATH, "utf8"));
|
|
20
|
+
return _cache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 规范化单个 vendor 的 compat(兼容旧平铺字段) */
|
|
24
|
+
export function normalizeCompat(vendor) {
|
|
25
|
+
const c = vendor?.compat;
|
|
26
|
+
if (!c) return null;
|
|
27
|
+
const peers = c.peers || {};
|
|
28
|
+
// 旧平铺字段兜底
|
|
29
|
+
if (!peers["element-plus"] && c.elementPlus) {
|
|
30
|
+
peers["element-plus"] = c.elementPlus;
|
|
31
|
+
}
|
|
32
|
+
if (!peers["@jhlc/jh-ui"] && c.jhUi) {
|
|
33
|
+
peers["@jhlc/jh-ui"] = c.jhUi;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
vendorId: vendor.id,
|
|
37
|
+
vendorLabel: vendor.label,
|
|
38
|
+
peers,
|
|
39
|
+
gatingPeer: c.gatingPeer || Object.keys(peers).find((k) => k.startsWith("@")) || null,
|
|
40
|
+
conflictsWith: c.conflictsWith || [],
|
|
41
|
+
domAssumptions: c.domAssumptions || [],
|
|
42
|
+
note: c.note || "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 返回所有声明了 compat 的 vendor */
|
|
47
|
+
export function listCompatVendors() {
|
|
48
|
+
const { vendors = [] } = loadVendorsJson();
|
|
49
|
+
return vendors.map(normalizeCompat).filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 针对一个项目的 dependencies + devDependencies 判定 vendor 配对状态
|
|
54
|
+
* @returns {{vendorId, gatingInstalled, peers: Array<{name, expected, actual, ok}>, verdict}}
|
|
55
|
+
* verdict: 'match' | 'mismatch' | 'not-applicable'
|
|
56
|
+
*/
|
|
57
|
+
export function evaluateVendor(compat, deps) {
|
|
58
|
+
const gating = compat.gatingPeer;
|
|
59
|
+
const gatingInstalled = gating ? Boolean(deps[gating]) : true;
|
|
60
|
+
if (gating && !gatingInstalled) {
|
|
61
|
+
return {
|
|
62
|
+
vendorId: compat.vendorId,
|
|
63
|
+
gatingInstalled: false,
|
|
64
|
+
peers: [],
|
|
65
|
+
verdict: "not-applicable",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const peers = Object.entries(compat.peers).map(([name, expected]) => {
|
|
69
|
+
const actual = deps[name] || null;
|
|
70
|
+
const ok = !!actual && actual.includes(expected);
|
|
71
|
+
return { name, expected, actual, ok };
|
|
72
|
+
});
|
|
73
|
+
const allOk = peers.every((p) => p.ok);
|
|
74
|
+
return {
|
|
75
|
+
vendorId: compat.vendorId,
|
|
76
|
+
gatingInstalled: true,
|
|
77
|
+
peers,
|
|
78
|
+
verdict: allOk ? "match" : "mismatch",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 输出 pnpm overrides 修复片段(仅对 mismatch 的 peers) */
|
|
83
|
+
export function buildOverridesSnippet(evaluations) {
|
|
84
|
+
const overrides = {};
|
|
85
|
+
for (const ev of evaluations) {
|
|
86
|
+
if (ev.verdict !== "mismatch") continue;
|
|
87
|
+
for (const p of ev.peers) {
|
|
88
|
+
if (!p.ok) overrides[p.name] = p.expected;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (Object.keys(overrides).length === 0) return null;
|
|
92
|
+
return {
|
|
93
|
+
pnpm: { overrides },
|
|
94
|
+
npmYarn: { resolutions: overrides, overrides },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -39,7 +39,30 @@
|
|
|
39
39
|
"_jh-tree.scss",
|
|
40
40
|
"_jh-pagination.scss",
|
|
41
41
|
"_jh-drag-col.scss"
|
|
42
|
-
]
|
|
42
|
+
],
|
|
43
|
+
"compat": {
|
|
44
|
+
"peers": {
|
|
45
|
+
"element-plus": "2.2.6-prod.3",
|
|
46
|
+
"@jhlc/jh-ui": "3.1.0"
|
|
47
|
+
},
|
|
48
|
+
"gatingPeer": "@jhlc/jh-ui",
|
|
49
|
+
"conflictsWith": [
|
|
50
|
+
{
|
|
51
|
+
"name": "element-plus",
|
|
52
|
+
"rangeNote": ">=2.3.0",
|
|
53
|
+
"reason": "EP 2.3.0 起引入 .el-input__wrapper,与 jh-ui 3.x 假设的 .el-input>.el-input__inner DOM 不兼容"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"domAssumptions": [
|
|
57
|
+
".el-input>.el-input__inner",
|
|
58
|
+
".el-form-item__label>.com-text",
|
|
59
|
+
".has-colon .com-text::after"
|
|
60
|
+
],
|
|
61
|
+
"elementPlus": "2.2.6-prod.3",
|
|
62
|
+
"elementPlusRange": "<2.3.0",
|
|
63
|
+
"jhUi": "3.1.0",
|
|
64
|
+
"note": "jh-ui 3.x 假设 EP 2.2.x DOM (.el-input>.el-input__inner),EP 2.3 起引入 .el-input__wrapper 后不再兼容"
|
|
65
|
+
}
|
|
43
66
|
},
|
|
44
67
|
{
|
|
45
68
|
"id": "c",
|
|
@@ -25,13 +25,22 @@ applyTo: "**/*.vue"
|
|
|
25
25
|
|
|
26
26
|
### `@jhlc/jh-ui` SCSS 皮肤包识别
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
**推荐组合(钉死版本)**:`@jhlc/jh-ui@3.1.0` + `element-plus@2.2.6-prod.3`。详见 `docs/compat-matrix.md` 与 `skills/_meta/_compat/vendors.json` 的 `jh.compat` 字段。
|
|
29
|
+
|
|
30
|
+
项目同时安装 `@jhlc/jh-ui` 与 `element-plus@2.2.x` 时,输入控件 DOM 为 `.el-input > .el-input__inner`(**无 `.el-input__wrapper`**,EP 2.3.0 起才引入),表单 label 文本被包成 `<span class="com-text">`,并由 `.has-colon .com-text::after` 注入冒号。`_jh-ui.scss` 已精准覆盖:
|
|
29
31
|
|
|
30
32
|
- label `.com-text` 单行省略 + has-colon 冒号保留
|
|
31
33
|
- EP 2.2.x `.el-input__inner` 圆角 / focus / error 三态
|
|
32
34
|
- jh-select / jh-date-picker 的 `.el-select.is-focus` / `.el-input.is-focus` 兼容
|
|
33
35
|
- 必填星号在 inline-flex label 下显式声明颜色
|
|
34
36
|
|
|
37
|
+
**反例**(常见错误覆盖姿势):
|
|
38
|
+
|
|
39
|
+
- ❌ 在 jh-ui 项目里用 `.el-input__wrapper.is-focus` —— EP 2.2.x 没有 `__wrapper`,规则永不生效。
|
|
40
|
+
- ❌ 直接给 `.el-form-item__label` 写 `text-overflow: ellipsis` —— jh-ui 把它设为 `inline-flex`,文本在 `.com-text` 里,省略不会触发。
|
|
41
|
+
- ❌ 给 `.el-form-item__label` 加 `padding-right` 期望腾出冒号位 —— 冒号是 `.com-text::after`,应在 `.com-text` 上加 padding。
|
|
42
|
+
- ❌ 在不确定 EP 版本的情况下下笔修复 —— 必须先跑 `wl_ui_detect_skin` 或 `wl-ui check` 看 I005 结果。
|
|
43
|
+
|
|
35
44
|
## Diagnose
|
|
36
45
|
|
|
37
46
|
- ❌ 自行给 `.jh-tree` 写颜色覆盖(应用全局 `vendors/_jh-tree.scss`)
|