@epxing/cli 0.3.0 → 0.4.0
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/README.md +33 -28
- package/dist/THIRD_PARTY_NOTICES.txt +25 -0
- package/dist/cli.mjs +29 -22
- package/dist/lazy-lib.mjs +5 -5
- package/dist/lib-build.mjs +1 -1
- package/dist/lib-vite-config.mjs +4 -2
- package/dist/project.mjs +6 -6
- package/dist/skills/epxing-frontend-vue3-migrate/SKILL.md +17 -2
- package/dist/skills/epxing-frontend-vue3-migrate/pitfalls.md +498 -381
- package/dist/tools/chunks/AMQWUBTZ.mjs +2 -0
- package/dist/tools/chunks/H4POBCHI.mjs +1 -0
- package/dist/tools/chunks/PP5RD4RH.mjs +4 -0
- package/dist/tools/chunks/SH6LMMHZ.mjs +32 -0
- package/dist/tools/epx-create/scaffold.mjs +2 -0
- package/dist/tools/epx-migrate/detect-vue3-risks.mjs +1 -1
- package/dist/tools/epx-migrate/fix-date-formats.mjs +2 -0
- package/dist/tools/epx-migrate/fix-theme-styles.mjs +2 -0
- package/dist/tools/epx-migrate/migrate.mjs +58 -65
- package/dist/tools/epx-migrate/verify-components.mjs +5 -5
- package/package.json +30 -29
|
@@ -1,381 +1,498 @@
|
|
|
1
|
-
# 踩坑清单(每条都在 product/hr 上实证过)
|
|
2
|
-
|
|
3
|
-
按"什么时候咬人"分组。每条给**判据**(怎么确认是它)和**修法**。
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## A. 迁移工具阶段(`epx migrate`)
|
|
8
|
-
|
|
9
|
-
### A1. gogocode 会改坏二进制文件
|
|
10
|
-
|
|
11
|
-
**现象**:字体/图片体积莫名变化,构建仍然绿。实测 `FontAwesome.otf` 134808→258854 B、
|
|
12
|
-
`fontawesome-webfont.eot` 165742→212524 B。
|
|
13
|
-
|
|
14
|
-
**根因**:整目录 `cpSync` 把 gogocode 处理过的文件原样拷回,二进制被按文本处理过。
|
|
15
|
-
|
|
16
|
-
**判据**:`git status` 看 `src/assets/` 有没有莫名其妙的改动;`git diff --stat` 看体积。
|
|
17
|
-
|
|
18
|
-
**修法**:拷回只走扩展名白名单(`.vue/.js/.jsx/.ts/.tsx`)。已修在
|
|
19
|
-
`tools/epx-migrate/copy-back-sources.mjs`。踩到就 `git checkout -- src/assets/`。
|
|
20
|
-
|
|
21
|
-
### A2. `install(Vue, options)` 的正则漏匹配
|
|
22
|
-
|
|
23
|
-
**现象**:运行时 install 报 TypeError,构建期完全正常。
|
|
24
|
-
|
|
25
|
-
**根因**:老正则只认 `install = function (Vue)`,产品写成 `(Vue, options)` 就不匹配 →
|
|
26
|
-
gogocode 按它自己的约定把函数体改成 `window.$vueApp.*`,而**平台从不设置这个全局**。
|
|
27
|
-
|
|
28
|
-
**判据**:`grep -n 'window.\$vueApp' src/` 有命中就是它。
|
|
29
|
-
|
|
30
|
-
**修法**:`tools/epx-migrate/codemod-lib-entry.mjs` 覆盖 fn/method 两种形态与额外形参,
|
|
31
|
-
且必须排在 gogocode(stage ③)**之前**。
|
|
32
|
-
|
|
33
|
-
### A3. 具名导入收敛丢掉正文还在用的名字
|
|
34
|
-
|
|
35
|
-
**现象**:运行时 `ReferenceError: store is not defined`,webpack 构建是绿的。
|
|
36
|
-
|
|
37
|
-
**修法**:import 收敛必须放在**所有正文改写之后**,且保留仍被引用的名字。
|
|
38
|
-
|
|
39
|
-
### A4. eslint 全家被删,工具链先坏
|
|
40
|
-
|
|
41
|
-
**现象**:`npx eslint --fix <file>` 报 `couldn't find the config "@vue/standard"`。
|
|
42
|
-
|
|
43
|
-
**根因**:依赖迁移删 vue-cli 生态时连带删了它。
|
|
44
|
-
|
|
45
|
-
**修法**(eslint 8 的正确组合):`@vue/eslint-config-standard@8` +
|
|
46
|
-
`eslint-config-standard@17`(16 要 eslint@7)+ `@babel/eslint-parser`;
|
|
47
|
-
`.eslintrc.js` 切 `plugin:vue/vue3-essential`、`parser: '@babel/eslint-parser'`。
|
|
48
|
-
注意 standard 17 起 `eslint-plugin-node` 改名 `eslint-plugin-n`。
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## B. Vite 化阶段
|
|
53
|
-
|
|
54
|
-
### B1. `recorder-core` 引擎与核心之间**没有模块图边**
|
|
55
|
-
|
|
56
|
-
**现象**:dev 壳白屏,`TypeError: Cannot read properties of undefined (reading 'i18n')`
|
|
57
|
-
指向 `.vite/deps/chunk-XXXX.js`。
|
|
58
|
-
|
|
59
|
-
**根因**:引擎(`src/engine/wav.js`)对核心是**零 import**,执行时从 `window.Recorder` 取,
|
|
60
|
-
第一行就是 `var rec = win.Recorder, ni = rec.i18n`。模块图里没有 wav→core 这条边,
|
|
61
|
-
**没有任何打包器有义务先执行核心**。源码里两条相邻 import 只是"声明序",
|
|
62
|
-
esbuild 依赖预打包一拆 chunk 就没了。
|
|
63
|
-
|
|
64
|
-
**注意**:炸的是 **epx-frame 自己**(`lib/epx-frame.esm.mjs` 第 1、2 行),产品侧收口模块管不着。
|
|
65
|
-
|
|
66
|
-
**修法**:补边 shim + **dev 专用** alias(正则 `$` 锚定,shim 内部引带 `.js` 后缀的说明符
|
|
67
|
-
避免 alias 回自己)。
|
|
68
|
-
|
|
69
|
-
**别用 `optimizeDeps.exclude`**:recorder-core 只有 CJS 入口,排除预打包就没有 interop,
|
|
70
|
-
换来 `SyntaxError: ... does not provide an export named 'default'`。
|
|
71
|
-
|
|
72
|
-
### B2. `import * as ns` 挡住 shim 生成
|
|
73
|
-
|
|
74
|
-
**现象**:按需构建报死,说某个 external 无法生成 shim。
|
|
75
|
-
|
|
76
|
-
**根因**:shim 无法静态枚举命名空间导入会访问哪些名字。
|
|
77
|
-
|
|
78
|
-
**修法**:改具名导入。这是**构建期报死**的设计(不静默),改掉即可。
|
|
79
|
-
|
|
80
|
-
### B3. 未映射的 external 全局名
|
|
81
|
-
|
|
82
|
-
**现象**:构建期报 `external 'xxx' 没有对应的运行时全局映射`。
|
|
83
|
-
|
|
84
|
-
**修法**:产品自己知道全局名的(如 `recorder-core` → `Recorder`),
|
|
85
|
-
在 `vite.config.mjs` 的 `output.globals` 里配;平台提供的去 `build/lazy-lib/gen-shims.mjs` 补。
|
|
86
|
-
|
|
87
|
-
### B4. `url(~@)` 在 Vite 下静默 404
|
|
88
|
-
|
|
89
|
-
webpack 遗留写法,Vite 不认 `~`。改成 `@/` 或相对路径。
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## C. 按需打包特有(**dev 壳一个都验不出来**)
|
|
94
|
-
|
|
95
|
-
### C1. `extractName` 抽不到 `name` → 注册名退成路径兜底
|
|
96
|
-
|
|
97
|
-
**现象**:页面按 `epx-hr-search` 找组件找不到,控制台一声不吭。
|
|
98
|
-
|
|
99
|
-
**根因**(已修):老实现用 `export default` 之后 **400 字符的窗口**找 `name`。
|
|
100
|
-
`Search.vue` 里 `export default {` 在 264 行、`name` 在 303 行,隔了 39 行远超窗口 →
|
|
101
|
-
退到路径兜底名 `hrcomponentsSearch`。而 dev 壳读**运行时** `comp.name`,两边不一致。
|
|
102
|
-
|
|
103
|
-
**判据**:`index.manifest.json` 里有不含 `-` 的键,就是路径兜底名。
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
node -e "const m=require('./lib/index.manifest.json');console.log(Object.keys(m.components).filter(k=>!k.includes('-')))"
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**修法**:babelParse 真解析、只认顶层属性。**别把窗口调大**——`name` 写在 methods
|
|
110
|
-
后面又会漏。
|
|
111
|
-
|
|
112
|
-
### C2. 平台占位抢注
|
|
113
|
-
|
|
114
|
-
**现象**:`[loadComponent] system.component 里没有 epx-xxx`。
|
|
115
|
-
|
|
116
|
-
**根因**:平台 `loadComponent` 与产品 `install` 都是「没人占我才占」
|
|
117
|
-
(都是 `if (!app.component(name))`),谁先跑谁赢。页面配置引用组件时
|
|
118
|
-
`Page.vue` 先按名字调平台工厂抢注了占位,产品 install 随后跳过。
|
|
119
|
-
而占位的解析链里**只有平台组件 + dashboard + designer,没有产品库**。
|
|
120
|
-
|
|
121
|
-
**判据**:控制台查槽位里是谁——产品的懒加载 wrapper 有 `.name`,平台占位没有。
|
|
122
|
-
|
|
123
|
-
```js
|
|
124
|
-
const c = document.querySelector('#app').__vue_app__._context.components['epx-hr-search']
|
|
125
|
-
c?.name ? 'product wrapper' : 'platform stub'
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**修法**(已修):解析链在平台组件之后、可选能力组之前查 `window.componentLibrary`。
|
|
129
|
-
排在可选能力组之前很关键,否则一个产品组件会白拉 dashboard + 体积很大的 designer。
|
|
130
|
-
|
|
131
|
-
### C3. 记账写在 install 之前 → 产品幂等守卫空转
|
|
132
|
-
|
|
133
|
-
**现象**:`window.__epxInstalledLibs__` 里有 `hr@3.0.1`,但 app 里 **0 个**产品组件。完全静默。
|
|
134
|
-
|
|
135
|
-
**根因**:平台 `util.loadJs` 先记账再调 install,而产品 install 开头是
|
|
136
|
-
`if (marks[sig]) return`(PROJECT-GUIDE 约定的幂等写法)——**一进门就返回**。
|
|
137
|
-
两侧去重分两次提交时各自都对,撞一起才炸。
|
|
138
|
-
|
|
139
|
-
**判据**:
|
|
140
|
-
|
|
141
|
-
```js
|
|
142
|
-
JSON.stringify({
|
|
143
|
-
libs: Object.keys(window.__epxInstalledLibs__ || {}),
|
|
144
|
-
count: Object.keys(document.querySelector('#app').__vue_app__._context.components)
|
|
145
|
-
.filter(n => /^epx-/.test(n)).length
|
|
146
|
-
})
|
|
147
|
-
```
|
|
148
|
-
表里有记录、count 却是 0,就是它。
|
|
149
|
-
|
|
150
|
-
**修法**(已修):记账挪到 `useVuePlugin` **之后**。
|
|
151
|
-
|
|
152
|
-
### C4. 嵌套构建没继承 `assetsInlineLimit`
|
|
153
|
-
|
|
154
|
-
**现象**:图片变成 `assets/*.png` 文件,产物里引用为**绝对路径** `/assets/xxx.png`。
|
|
155
|
-
远程库从文件服务子路径加载,这个 URL 解析到平台页面的 origin 根目录 → **运行时 404,构建期无声**。
|
|
156
|
-
|
|
157
|
-
**判据**:整体单体形态**不产生** `assets/` 目录(全内联),按需形态却有 → 就是没继承。
|
|
158
|
-
|
|
159
|
-
**修法**(已修):`inheritedBuildOptions()` 把 `assetsInlineLimit`/`assetsDir`/`target`/
|
|
160
|
-
`cssTarget`/`sourcemap`/`minify` 一并转发。`target` 同样要紧:壳由外层构建、chunk 由嵌套
|
|
161
|
-
构建,两边不一致会产出语法级别不同的代码。
|
|
162
|
-
|
|
163
|
-
### C5. 没有读者的 `.deps.json`
|
|
164
|
-
|
|
165
|
-
第①趟给每个 chunk、第③趟给 `index.full.js` 都会产出一份,**都没人读**
|
|
166
|
-
(chunk 走 `import()` 不过 ensure;`index.full.js` 只经降级裸 script 加载)。
|
|
167
|
-
实测 hr:49 个文件 49 KB 死重量。
|
|
168
|
-
|
|
169
|
-
**修法**(已修):`rmStrayDepsJson` 事后删,**不能**把 `epx-frame-deps` 插件从嵌套构建滤掉——
|
|
170
|
-
组件源码只在第①趟被 transform,插件靠同一实例跨趟累积,滤掉会让壳的清单变成空数组。
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## D. 本地联调环境
|
|
175
|
-
|
|
176
|
-
### D1. junction 链接平台必须配 `resolve.dedupe`
|
|
177
|
-
|
|
178
|
-
Vite 默认 `preserveSymlinks:false`,按真实路径解析 → vue 会有两份
|
|
179
|
-
(实测 frame 3.5.39 / 产品 3.5.42),provide/inject 断链、组件注册表分裂。
|
|
180
|
-
|
|
181
|
-
```js
|
|
182
|
-
resolve: { dedupe: ['vue', 'vuex', 'vue-router'] }
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### D2. Git Bash 不跟随 junction
|
|
186
|
-
|
|
187
|
-
`ls/stat/grep` 会报文件不存在。用 PowerShell 或 node 验。Vite/Node 走 Windows 原生 API 正常。
|
|
188
|
-
|
|
189
|
-
### D3. 本地顶替远程库时,CSS 会走线上旧构建
|
|
190
|
-
|
|
191
|
-
后端 `listWebpackUrl` 返回的**一相对一绝对**:
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
{"code":"0","data":{"entitys":[{"path":[
|
|
195
|
-
"/file-server/npm/test/web_hr/index.js", // 相对 → 拼 window.IP → 本地
|
|
196
|
-
"https://dev.epxing.com:88/file-server/npm/test/web_hr/index.css" // 绝对 → 原样用 → 线上
|
|
197
|
-
]}]}}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
`loadLibrary` 只给相对路径拼 `window.IP`。于是组件是本地新 chunk、样式是线上旧 CSS,
|
|
201
|
-
scoped 的 `data-v-<hash>` 两边对不上 → **组件渲染出来但完全没样式**。
|
|
202
|
-
|
|
203
|
-
**判据**:`[...document.styleSheets].map(s=>s.href).filter(h=>h&&h.includes('<pkg>'))`
|
|
204
|
-
看 host 是不是本地。
|
|
205
|
-
|
|
206
|
-
**修法**:本地反代里拦 `listWebpackUrl` 响应,把绝对地址改写成相对。
|
|
207
|
-
注意响应外面**包了一层 `data`**(`{code,msg,data:{entitys},...}`),
|
|
208
|
-
直接取 `json.entitys` 会拿到 undefined 并静默改写 0 条。
|
|
209
|
-
|
|
210
|
-
### D4. `epx-frame` 未发布时 npm 装不了任何东西
|
|
211
|
-
|
|
212
|
-
产品 package.json 声明 `epx-frame@^5.3.0`,未发布前 npm **解析根清单就失败**(ETARGET),
|
|
213
|
-
连加一个 devDep 都装不了。绕法:别处建空目录装好,再逐包拷进产品的 `node_modules`
|
|
214
|
-
(所有 `@scope` 要下沉到子包粒度判存在,按整个 scope 目录判会漏);
|
|
215
|
-
版本冲突的包放**嵌套** `node_modules`。
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## E. 主题/样式
|
|
220
|
-
|
|
221
|
-
### E1. 换肤 mixin 按主题展开,既臃肿又漏主题
|
|
222
|
-
|
|
223
|
-
**现象**:`index.css` 里同一条规则有 N 份 `[data-theme=x]` 拷贝;
|
|
224
|
-
用户选了某些主题色,产品组件不跟着变。
|
|
225
|
-
|
|
226
|
-
**根因**:产品自带的 `mixins.scss` 用 `@each` 遍历**写死的主题列表**。
|
|
227
|
-
实测 hr 写死 8 套,而平台有 **11 套预设 + custom**(运行时注入)——**4 套完全不生效**。
|
|
228
|
-
|
|
229
|
-
**判据**:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
node -e "const c=require('fs').readFileSync('lib/index.css','utf8');console.log(c.split('}').filter(r=>/\[data-theme=/.test(r.split('{')[0])).length)"
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
**修法**:统一走 `rgba(var(--color-primary), $opacity)`。平台在 `[data-theme=x]` 上设
|
|
236
|
-
`--color-primary: r,g,b`,custom 由运行时 JS 注入,所有主题自动生效。
|
|
237
|
-
`$color` 参数改作 `var()` 的兜底值。实测 hr:130.4 → 85.0 KB(−35%),
|
|
238
|
-
`[data-theme=*]` 规则 600 → 0。
|
|
239
|
-
|
|
240
|
-
**注意 gzip 收益很小**(21.3 → 17.4 KB)——重复内容压得好,**省的是解析与选择器匹配成本**。
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
recruit
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
`
|
|
299
|
-
|
|
300
|
-
###
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
###
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`
|
|
314
|
-
|
|
315
|
-
`
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
###
|
|
324
|
-
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
1
|
+
# 踩坑清单(每条都在 product/hr 上实证过)
|
|
2
|
+
|
|
3
|
+
按"什么时候咬人"分组。每条给**判据**(怎么确认是它)和**修法**。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## A. 迁移工具阶段(`epx migrate`)
|
|
8
|
+
|
|
9
|
+
### A1. gogocode 会改坏二进制文件
|
|
10
|
+
|
|
11
|
+
**现象**:字体/图片体积莫名变化,构建仍然绿。实测 `FontAwesome.otf` 134808→258854 B、
|
|
12
|
+
`fontawesome-webfont.eot` 165742→212524 B。
|
|
13
|
+
|
|
14
|
+
**根因**:整目录 `cpSync` 把 gogocode 处理过的文件原样拷回,二进制被按文本处理过。
|
|
15
|
+
|
|
16
|
+
**判据**:`git status` 看 `src/assets/` 有没有莫名其妙的改动;`git diff --stat` 看体积。
|
|
17
|
+
|
|
18
|
+
**修法**:拷回只走扩展名白名单(`.vue/.js/.jsx/.ts/.tsx`)。已修在
|
|
19
|
+
`tools/epx-migrate/copy-back-sources.mjs`。踩到就 `git checkout -- src/assets/`。
|
|
20
|
+
|
|
21
|
+
### A2. `install(Vue, options)` 的正则漏匹配
|
|
22
|
+
|
|
23
|
+
**现象**:运行时 install 报 TypeError,构建期完全正常。
|
|
24
|
+
|
|
25
|
+
**根因**:老正则只认 `install = function (Vue)`,产品写成 `(Vue, options)` 就不匹配 →
|
|
26
|
+
gogocode 按它自己的约定把函数体改成 `window.$vueApp.*`,而**平台从不设置这个全局**。
|
|
27
|
+
|
|
28
|
+
**判据**:`grep -n 'window.\$vueApp' src/` 有命中就是它。
|
|
29
|
+
|
|
30
|
+
**修法**:`tools/epx-migrate/codemod-lib-entry.mjs` 覆盖 fn/method 两种形态与额外形参,
|
|
31
|
+
且必须排在 gogocode(stage ③)**之前**。
|
|
32
|
+
|
|
33
|
+
### A3. 具名导入收敛丢掉正文还在用的名字
|
|
34
|
+
|
|
35
|
+
**现象**:运行时 `ReferenceError: store is not defined`,webpack 构建是绿的。
|
|
36
|
+
|
|
37
|
+
**修法**:import 收敛必须放在**所有正文改写之后**,且保留仍被引用的名字。
|
|
38
|
+
|
|
39
|
+
### A4. eslint 全家被删,工具链先坏
|
|
40
|
+
|
|
41
|
+
**现象**:`npx eslint --fix <file>` 报 `couldn't find the config "@vue/standard"`。
|
|
42
|
+
|
|
43
|
+
**根因**:依赖迁移删 vue-cli 生态时连带删了它。
|
|
44
|
+
|
|
45
|
+
**修法**(eslint 8 的正确组合):`@vue/eslint-config-standard@8` +
|
|
46
|
+
`eslint-config-standard@17`(16 要 eslint@7)+ `@babel/eslint-parser`;
|
|
47
|
+
`.eslintrc.js` 切 `plugin:vue/vue3-essential`、`parser: '@babel/eslint-parser'`。
|
|
48
|
+
注意 standard 17 起 `eslint-plugin-node` 改名 `eslint-plugin-n`。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## B. Vite 化阶段
|
|
53
|
+
|
|
54
|
+
### B1. `recorder-core` 引擎与核心之间**没有模块图边**
|
|
55
|
+
|
|
56
|
+
**现象**:dev 壳白屏,`TypeError: Cannot read properties of undefined (reading 'i18n')`
|
|
57
|
+
指向 `.vite/deps/chunk-XXXX.js`。
|
|
58
|
+
|
|
59
|
+
**根因**:引擎(`src/engine/wav.js`)对核心是**零 import**,执行时从 `window.Recorder` 取,
|
|
60
|
+
第一行就是 `var rec = win.Recorder, ni = rec.i18n`。模块图里没有 wav→core 这条边,
|
|
61
|
+
**没有任何打包器有义务先执行核心**。源码里两条相邻 import 只是"声明序",
|
|
62
|
+
esbuild 依赖预打包一拆 chunk 就没了。
|
|
63
|
+
|
|
64
|
+
**注意**:炸的是 **epx-frame 自己**(`lib/epx-frame.esm.mjs` 第 1、2 行),产品侧收口模块管不着。
|
|
65
|
+
|
|
66
|
+
**修法**:补边 shim + **dev 专用** alias(正则 `$` 锚定,shim 内部引带 `.js` 后缀的说明符
|
|
67
|
+
避免 alias 回自己)。
|
|
68
|
+
|
|
69
|
+
**别用 `optimizeDeps.exclude`**:recorder-core 只有 CJS 入口,排除预打包就没有 interop,
|
|
70
|
+
换来 `SyntaxError: ... does not provide an export named 'default'`。
|
|
71
|
+
|
|
72
|
+
### B2. `import * as ns` 挡住 shim 生成
|
|
73
|
+
|
|
74
|
+
**现象**:按需构建报死,说某个 external 无法生成 shim。
|
|
75
|
+
|
|
76
|
+
**根因**:shim 无法静态枚举命名空间导入会访问哪些名字。
|
|
77
|
+
|
|
78
|
+
**修法**:改具名导入。这是**构建期报死**的设计(不静默),改掉即可。
|
|
79
|
+
|
|
80
|
+
### B3. 未映射的 external 全局名
|
|
81
|
+
|
|
82
|
+
**现象**:构建期报 `external 'xxx' 没有对应的运行时全局映射`。
|
|
83
|
+
|
|
84
|
+
**修法**:产品自己知道全局名的(如 `recorder-core` → `Recorder`),
|
|
85
|
+
在 `vite.config.mjs` 的 `output.globals` 里配;平台提供的去 `build/lazy-lib/gen-shims.mjs` 补。
|
|
86
|
+
|
|
87
|
+
### B4. `url(~@)` 在 Vite 下静默 404
|
|
88
|
+
|
|
89
|
+
webpack 遗留写法,Vite 不认 `~`。改成 `@/` 或相对路径。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## C. 按需打包特有(**dev 壳一个都验不出来**)
|
|
94
|
+
|
|
95
|
+
### C1. `extractName` 抽不到 `name` → 注册名退成路径兜底
|
|
96
|
+
|
|
97
|
+
**现象**:页面按 `epx-hr-search` 找组件找不到,控制台一声不吭。
|
|
98
|
+
|
|
99
|
+
**根因**(已修):老实现用 `export default` 之后 **400 字符的窗口**找 `name`。
|
|
100
|
+
`Search.vue` 里 `export default {` 在 264 行、`name` 在 303 行,隔了 39 行远超窗口 →
|
|
101
|
+
退到路径兜底名 `hrcomponentsSearch`。而 dev 壳读**运行时** `comp.name`,两边不一致。
|
|
102
|
+
|
|
103
|
+
**判据**:`index.manifest.json` 里有不含 `-` 的键,就是路径兜底名。
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
node -e "const m=require('./lib/index.manifest.json');console.log(Object.keys(m.components).filter(k=>!k.includes('-')))"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**修法**:babelParse 真解析、只认顶层属性。**别把窗口调大**——`name` 写在 methods
|
|
110
|
+
后面又会漏。
|
|
111
|
+
|
|
112
|
+
### C2. 平台占位抢注
|
|
113
|
+
|
|
114
|
+
**现象**:`[loadComponent] system.component 里没有 epx-xxx`。
|
|
115
|
+
|
|
116
|
+
**根因**:平台 `loadComponent` 与产品 `install` 都是「没人占我才占」
|
|
117
|
+
(都是 `if (!app.component(name))`),谁先跑谁赢。页面配置引用组件时
|
|
118
|
+
`Page.vue` 先按名字调平台工厂抢注了占位,产品 install 随后跳过。
|
|
119
|
+
而占位的解析链里**只有平台组件 + dashboard + designer,没有产品库**。
|
|
120
|
+
|
|
121
|
+
**判据**:控制台查槽位里是谁——产品的懒加载 wrapper 有 `.name`,平台占位没有。
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
const c = document.querySelector('#app').__vue_app__._context.components['epx-hr-search']
|
|
125
|
+
c?.name ? 'product wrapper' : 'platform stub'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**修法**(已修):解析链在平台组件之后、可选能力组之前查 `window.componentLibrary`。
|
|
129
|
+
排在可选能力组之前很关键,否则一个产品组件会白拉 dashboard + 体积很大的 designer。
|
|
130
|
+
|
|
131
|
+
### C3. 记账写在 install 之前 → 产品幂等守卫空转
|
|
132
|
+
|
|
133
|
+
**现象**:`window.__epxInstalledLibs__` 里有 `hr@3.0.1`,但 app 里 **0 个**产品组件。完全静默。
|
|
134
|
+
|
|
135
|
+
**根因**:平台 `util.loadJs` 先记账再调 install,而产品 install 开头是
|
|
136
|
+
`if (marks[sig]) return`(PROJECT-GUIDE 约定的幂等写法)——**一进门就返回**。
|
|
137
|
+
两侧去重分两次提交时各自都对,撞一起才炸。
|
|
138
|
+
|
|
139
|
+
**判据**:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
JSON.stringify({
|
|
143
|
+
libs: Object.keys(window.__epxInstalledLibs__ || {}),
|
|
144
|
+
count: Object.keys(document.querySelector('#app').__vue_app__._context.components)
|
|
145
|
+
.filter(n => /^epx-/.test(n)).length
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
表里有记录、count 却是 0,就是它。
|
|
149
|
+
|
|
150
|
+
**修法**(已修):记账挪到 `useVuePlugin` **之后**。
|
|
151
|
+
|
|
152
|
+
### C4. 嵌套构建没继承 `assetsInlineLimit`
|
|
153
|
+
|
|
154
|
+
**现象**:图片变成 `assets/*.png` 文件,产物里引用为**绝对路径** `/assets/xxx.png`。
|
|
155
|
+
远程库从文件服务子路径加载,这个 URL 解析到平台页面的 origin 根目录 → **运行时 404,构建期无声**。
|
|
156
|
+
|
|
157
|
+
**判据**:整体单体形态**不产生** `assets/` 目录(全内联),按需形态却有 → 就是没继承。
|
|
158
|
+
|
|
159
|
+
**修法**(已修):`inheritedBuildOptions()` 把 `assetsInlineLimit`/`assetsDir`/`target`/
|
|
160
|
+
`cssTarget`/`sourcemap`/`minify` 一并转发。`target` 同样要紧:壳由外层构建、chunk 由嵌套
|
|
161
|
+
构建,两边不一致会产出语法级别不同的代码。
|
|
162
|
+
|
|
163
|
+
### C5. 没有读者的 `.deps.json`
|
|
164
|
+
|
|
165
|
+
第①趟给每个 chunk、第③趟给 `index.full.js` 都会产出一份,**都没人读**
|
|
166
|
+
(chunk 走 `import()` 不过 ensure;`index.full.js` 只经降级裸 script 加载)。
|
|
167
|
+
实测 hr:49 个文件 49 KB 死重量。
|
|
168
|
+
|
|
169
|
+
**修法**(已修):`rmStrayDepsJson` 事后删,**不能**把 `epx-frame-deps` 插件从嵌套构建滤掉——
|
|
170
|
+
组件源码只在第①趟被 transform,插件靠同一实例跨趟累积,滤掉会让壳的清单变成空数组。
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## D. 本地联调环境
|
|
175
|
+
|
|
176
|
+
### D1. junction 链接平台必须配 `resolve.dedupe`
|
|
177
|
+
|
|
178
|
+
Vite 默认 `preserveSymlinks:false`,按真实路径解析 → vue 会有两份
|
|
179
|
+
(实测 frame 3.5.39 / 产品 3.5.42),provide/inject 断链、组件注册表分裂。
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
resolve: { dedupe: ['vue', 'vuex', 'vue-router'] }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### D2. Git Bash 不跟随 junction
|
|
186
|
+
|
|
187
|
+
`ls/stat/grep` 会报文件不存在。用 PowerShell 或 node 验。Vite/Node 走 Windows 原生 API 正常。
|
|
188
|
+
|
|
189
|
+
### D3. 本地顶替远程库时,CSS 会走线上旧构建
|
|
190
|
+
|
|
191
|
+
后端 `listWebpackUrl` 返回的**一相对一绝对**:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{"code":"0","data":{"entitys":[{"path":[
|
|
195
|
+
"/file-server/npm/test/web_hr/index.js", // 相对 → 拼 window.IP → 本地
|
|
196
|
+
"https://dev.epxing.com:88/file-server/npm/test/web_hr/index.css" // 绝对 → 原样用 → 线上
|
|
197
|
+
]}]}}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`loadLibrary` 只给相对路径拼 `window.IP`。于是组件是本地新 chunk、样式是线上旧 CSS,
|
|
201
|
+
scoped 的 `data-v-<hash>` 两边对不上 → **组件渲染出来但完全没样式**。
|
|
202
|
+
|
|
203
|
+
**判据**:`[...document.styleSheets].map(s=>s.href).filter(h=>h&&h.includes('<pkg>'))`
|
|
204
|
+
看 host 是不是本地。
|
|
205
|
+
|
|
206
|
+
**修法**:本地反代里拦 `listWebpackUrl` 响应,把绝对地址改写成相对。
|
|
207
|
+
注意响应外面**包了一层 `data`**(`{code,msg,data:{entitys},...}`),
|
|
208
|
+
直接取 `json.entitys` 会拿到 undefined 并静默改写 0 条。
|
|
209
|
+
|
|
210
|
+
### D4. `epx-frame` 未发布时 npm 装不了任何东西
|
|
211
|
+
|
|
212
|
+
产品 package.json 声明 `epx-frame@^5.3.0`,未发布前 npm **解析根清单就失败**(ETARGET),
|
|
213
|
+
连加一个 devDep 都装不了。绕法:别处建空目录装好,再逐包拷进产品的 `node_modules`
|
|
214
|
+
(所有 `@scope` 要下沉到子包粒度判存在,按整个 scope 目录判会漏);
|
|
215
|
+
版本冲突的包放**嵌套** `node_modules`。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## E. 主题/样式
|
|
220
|
+
|
|
221
|
+
### E1. 换肤 mixin 按主题展开,既臃肿又漏主题
|
|
222
|
+
|
|
223
|
+
**现象**:`index.css` 里同一条规则有 N 份 `[data-theme=x]` 拷贝;
|
|
224
|
+
用户选了某些主题色,产品组件不跟着变。
|
|
225
|
+
|
|
226
|
+
**根因**:产品自带的 `mixins.scss` 用 `@each` 遍历**写死的主题列表**。
|
|
227
|
+
实测 hr 写死 8 套,而平台有 **11 套预设 + custom**(运行时注入)——**4 套完全不生效**。
|
|
228
|
+
|
|
229
|
+
**判据**:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
node -e "const c=require('fs').readFileSync('lib/index.css','utf8');console.log(c.split('}').filter(r=>/\[data-theme=/.test(r.split('{')[0])).length)"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**修法**:统一走 `rgba(var(--color-primary), $opacity)`。平台在 `[data-theme=x]` 上设
|
|
236
|
+
`--color-primary: r,g,b`,custom 由运行时 JS 注入,所有主题自动生效。
|
|
237
|
+
`$color` 参数改作 `var()` 的兜底值。实测 hr:130.4 → 85.0 KB(−35%),
|
|
238
|
+
`[data-theme=*]` 规则 600 → 0。
|
|
239
|
+
|
|
240
|
+
**注意 gzip 收益很小**(21.3 → 17.4 KB)——重复内容压得好,**省的是解析与选择器匹配成本**。
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### E2. 产品/项目样式必须跟随平台主题轴
|
|
245
|
+
|
|
246
|
+
平台主题包已经由宿主加载。产品只消费变量,不再次导入主题入口,也不在 `:root`、`html` 或 `body` 写默认主题变量或固定主题属性;否则懒加载 CSS 到达后可能重置用户当前主题。不要因此改回 Vue2 垫片或删除 scoped。
|
|
247
|
+
|
|
248
|
+
**升级时执行修复**:`epxing migrate ... --apply` 自动迁移常见静态值;已升级工程用 `epxing theme <工程目录>` 预览,再加 `--apply`。覆盖 src 中 Vue style、template/JSX/render 内联样式及 CSS/SCSS/Less。原值作为变量 fallback,默认外观保持一致;重复执行幂等。返回码 2 表示仍有动态或特殊样式需复核,自动部分可能已经写入。`epxing scan` 的 R3 检查静态残留。
|
|
249
|
+
|
|
250
|
+
| 样式语义 | 平台变量 | 默认值 |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| 小/正文/大/标题字号 | `--epx-font-size-s/base/l/xl`(分别使用完整变量名) | 12/14/16/20px |
|
|
253
|
+
| 小/基础/大圆角 | `--epx-radius-s/base/l`(分别使用完整变量名) | 2/4/8px |
|
|
254
|
+
| 页面底色/卡片底色 | `--epx-bg` / `--epx-bg-card` | #f5f7fa / #fff |
|
|
255
|
+
| 正文/次要文字/边框 | `--epx-text` / `--epx-text-secondary` / `--epx-border` | #303133 / #909399 / #dcdfe6 |
|
|
256
|
+
| 与密度一起变化的页面留白 | `--epx-margin` | 10px |
|
|
257
|
+
| 品牌主色 | `rgba(var(--color-primary), 透明度)` | RGB 元组,参照 E1 |
|
|
258
|
+
|
|
259
|
+
例如 `font-size: var(--epx-font-size-base, 14px); border-radius: var(--epx-radius-base, 4px); background-color: var(--epx-bg-card, #fff);`。Element Plus 组件的字号、圆角由平台 `--el-*` 映射管理,不在产品全局重写 `.el-*`。`--epx-space-*` 是固定间距级别,不等于密度轴;只有明确属于密度留白的布局才使用 `--epx-margin`。
|
|
260
|
+
|
|
261
|
+
**自动修复的边界与后续处理**:
|
|
262
|
+
|
|
263
|
+
1. 追踪工具报告的 style 变量、函数、对象展开、Sass/Less 变量及 mixin。区分实际 DOM 样式与 ECharts/canvas/导出数据等业务配置,确认消费位置后完成主题适配。CSS 变量或 calc 已存在也需确认它最终指向主题轴,不能仅凭使用 var 就验收。
|
|
264
|
+
2. 13/15/18px 字号、12px 等非标准圆角不盲目改成最近令牌。先按设计角色选令牌;若必须保持默认尺寸且随平台字号变化,可使用 `calc(var(--epx-font-size-base, 14px) + 4px)`。记录理由,不能把所有尺寸机械加减。固定宽高、图标/图表、圆形头像/胶囊、字体零值布局、打印/导出、动画和品牌/状态颜色按实际设计保留;有疑问先验证用途。
|
|
265
|
+
3. 缩进式 Sass/Stylus、外部 src 样式、脚本动态值、font 简写以及旧主题展开 mixin 需继续修复源文件。工具只处理 src 范围,不回写依赖和构建产物。检查最终编译 CSS 是否仍有阻断主题的覆盖;新建组件同样使用主题变量。
|
|
266
|
+
4. 主题迁移保持原有作用域隔离。继承与插槽链依 F15 验证,用专属容器局部 `:deep()` 处理实际失配,禁止为了主题同步制造全局泄漏。
|
|
267
|
+
|
|
268
|
+
**发布验收**:以最终完整包或懒加载包,在实际平台页面切换默认/大/小字号、默认/直角/圆润圆角、明暗主题和密度;核对卡片、表单、按钮、标题及弹窗同步且不裁切/溢出。主题切换后再首次加载产品组件,确认不回到默认主题;离开页面后检查平台与外部同名元素不受影响。保留打印等设计例外,并记录测试主题、源文件、动态项处理、截图及待验证项。lint/build 或隔离 fixture 通过不能代替实际产品验收。
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## F. gogocode 转换残留 & 第三方库(product/recruit 145 组件实证)
|
|
273
|
+
|
|
274
|
+
> 这一组是「gogocode 没转干净 + 项目自带 Vue2 依赖」,多数在 `build:lib:lazy` 才暴露。
|
|
275
|
+
> **能机械化的已进 CLI 自动做**(下面标「已 CLI」),**结构性/换库的靠本节手动**。
|
|
276
|
+
|
|
277
|
+
### F1. gogocode 漏转「元素上的」旧具名插槽 `slot="x"`
|
|
278
|
+
|
|
279
|
+
**现象**:build 报 `[vite:vue] Cannot read properties of undefined (reading 'type')`(**无行号**),
|
|
280
|
+
或 `slot="x" is deprecated`。gogocode-plugin-vue 转了 `<template slot>`,却漏了**直接写在元素上**的。
|
|
281
|
+
|
|
282
|
+
**判据**:`grep -rn 'slot="' src --include=*.vue | grep -v v-slot`(排掉注释行)。
|
|
283
|
+
|
|
284
|
+
**修法**(结构变化,逐个手改;CLI 不做——配对闭合标签的正则不可靠):
|
|
285
|
+
```
|
|
286
|
+
<el-dropdown-menu slot="dropdown">…</el-dropdown-menu>
|
|
287
|
+
→ <template #dropdown><el-dropdown-menu>…</el-dropdown-menu></template>
|
|
288
|
+
|
|
289
|
+
<span slot="footer" class="dialog-footer">…</span> (el-dialog 的 footer)
|
|
290
|
+
→ <template #footer><span class="dialog-footer">…</span></template>
|
|
291
|
+
```
|
|
292
|
+
recruit 实测 7 处需改(另 3 处在注释里,忽略)。定位无行号的 `reading 'type'`:从报错文件里逐个 `slot="` 查起。
|
|
293
|
+
|
|
294
|
+
### F2. gogocode 漏转 `slot-scope`
|
|
295
|
+
`slot-scope="scope"` → `v-slot="scope"`(或 `#default="scope"`)。recruit 1 处,手改。
|
|
296
|
+
|
|
297
|
+
### F3.(已 CLI)`:prop.sync` → `v-model:prop`
|
|
298
|
+
Vue3 移除 `.sync`。gogocode 漏,现由 `fix-inline-handlers.mjs` 的 `fixSyncModifier` 在 migrate ③ 自动转,不用手改。
|
|
299
|
+
|
|
300
|
+
### F4. epxing 字段名里的 `|` 把 gogocode filters 规则搞崩(良性,忽略)
|
|
301
|
+
**现象**:③ 转换刷 `文件转换异常,规则:filters … expressionStatementAst.attr is not a function`。
|
|
302
|
+
**根因**:epxing 字段名带竖线(`info['SCHEME|NOTICE_FILE#']`),gogocode 过滤器规则把它当 Vue2 过滤器管道解析 AST,崩。
|
|
303
|
+
**良性**:不是真过滤器,文件其它转换正常、内容没坏。dry-run 报的「过滤器 N 文件」也因此**虚高**(把字段名 `|` 误算成过滤器)。
|
|
304
|
+
**判据**:`grep -rnE "\{\{[^}]*\|\s*[a-z_]+\s*\}\}" src` 把 `|大写字段名` 排掉后基本为 0 → 确认无真过滤器,忽略刷屏即可。
|
|
305
|
+
|
|
306
|
+
### F5.(已 CLI)内联事件多语句缺分号
|
|
307
|
+
Vue2 容忍换行分隔、Vue3 编译器报 `Unexpected token, expected ","`。现由 `fixInlineHandlers` 在 migrate ③ 自动补分号。
|
|
308
|
+
|
|
309
|
+
### F6. 换掉没有 Vue3 版的第三方库(⑥ 会告警,换库是人工决策)
|
|
310
|
+
|
|
311
|
+
| 库 | Vue3 替代 | 改动 |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `vue-quill-editor@3` | `@vueup/vue-quill` | 换 import + 组件用法(v-model/options 略变) |
|
|
314
|
+
| `vuedraggable@2` | `vuedraggable@4`(同名升 major) | 用法基本兼容,重装升版本 |
|
|
315
|
+
| `vue-orgchart-direction` | 无——改 `bpmn-js` 直用 / SVG 自实现 | 组织架构图重写(hr T2 同款) |
|
|
316
|
+
| `element-ui` | 平台运行时已提供 element-plus | **直接删 import**(el-* 全局可用),不装 element-plus |
|
|
317
|
+
| `vant@2` | `vant@4`(移动端) | 用法有差异,逐个核 |
|
|
318
|
+
|
|
319
|
+
**先查有几个文件真 import 再决定工作量**:`grep -rl <lib> src --include=*.vue --include=*.js`。
|
|
320
|
+
recruit 实测:element-ui 1 / vue-quill-editor 1 / vuedraggable 2 / vant 2 —— 量小,逐个换即可。
|
|
321
|
+
(依赖在 package.json 但源码 0 引用的是**遗留依赖**,如 recruit 的 bpmn-js/vue-bpmn,直接从 deps 删。)
|
|
322
|
+
|
|
323
|
+
### F7.(已 CLI)`node-sass` / `fibers` 残留
|
|
324
|
+
老 scss 工具链(node-gyp 原生编译),新 node 上 `npm i` 必挂、还骗过 exit code(管道吞 code)。
|
|
325
|
+
已加进 to-vite 的 `WEBPACK_ONLY_DEPS` 自动删,Vite 用 dart-sass(`sass`)。**判 npm i 真成败别信 exit code**——
|
|
326
|
+
`grep -iE "gyp ERR|Build failed" 安装日志`。
|
|
327
|
+
|
|
328
|
+
### F8. 垃圾副本文件(`Xxx copy.vue`)在按需形态撞名
|
|
329
|
+
**现象**:build 报 `[lazy-lib] 组件注册名冲突:epx-xxx 同时来自 A copy.vue / A.vue`。
|
|
330
|
+
单体形态静默覆盖(线上不暴露)、按需形态 `assertUniqueKeys` 拦下(替项目揪出垃圾副本)。
|
|
331
|
+
**判据**:`find src -iname "*copy*"`。**修法**:删副本(或改其 name)。
|
|
332
|
+
|
|
333
|
+
### F9.(已 CLI)无扩展名 import .vue
|
|
334
|
+
老项目 webpack `resolve.extensions` 含 `.vue`,`import X from './X'` 遍地;Vite 默认不含。
|
|
335
|
+
create 模板的 vite.config 已带 `resolve.extensions: ['.mjs','.js','.json','.vue']`,`--to-vite` 生成的配置自带,不用手加。
|
|
336
|
+
|
|
337
|
+
### F10.(已 CLI)`import * as Vue/Vuex/VueRouter` 命名空间导入 → 具名
|
|
338
|
+
gogocode 把 Vue2 默认导入转 Vue3 时,对 vue/vuex/vue-router 常产**命名空间导入**:
|
|
339
|
+
`import * as Vue from 'vue'` + `Vue.h(...)`、`import * as Vuex from 'vuex'` + `Vuex.createStore(...)`、
|
|
340
|
+
`import * as VueRouter from 'vue-router'` + `VueRouter.createRouter/createWebHashHistory(...)`(router/store 是 .js,gogocode `-s ./src` 也转了它们)。
|
|
341
|
+
`lazyLib` 为 external `'vue'` 生成 shim 时**不支持 `import * as ns`**(见 B2,recruit 实测 build 挂:`无法为 external 'vue' 生成 shim`)。
|
|
342
|
+
CLI ③ 修补⑥(`fix-namespace-import.mjs`)收敛为具名:收集 `NS.member` 用到的成员改 `import { m1, m2 } from 'lib'`,
|
|
343
|
+
`Vue.h`→`h` 等;整文件没用到就删这行。只认这三个框架库(白名单),业务库的命名空间导入不碰。
|
|
344
|
+
|
|
345
|
+
### F11.(已 CLI)`require('@/assets/*.png')` → 静态 import + `assetsInlineLimit`
|
|
346
|
+
webpack 支持 `require('图片')` 当 URL 用(三元/字符串拼接里),Vite 不支持 → 运行时 `require is not defined`。
|
|
347
|
+
CLI V 阶段(`fix-vite-source.mjs`)提为顶部静态 import(别名 `@` 支持)、`require(...)` 换成变量。
|
|
348
|
+
**只在 `<script>`/JS 里改**:模板表达式里的 `require` 换成模块顶层变量后 Options API 的 this 取不到,那种报人工(手工挂 data/computed,见 F11 配套的 recruit Center.vue/Guide.vue 改法)。
|
|
349
|
+
配套:create 模板的 `build.assetsInlineLimit` 设 `50*1024`,让这些小图标内联成 data URI——否则超 4096 的(recruit 的 `fileHover.png` 4115B,只超一点点)会出独立文件、远程库形态引用 `/assets/xxx` 绝对路径运行时 404(构建期看不出;C4 管 lazy 嵌套构建继承这个值,前提是主 build 设了)。
|
|
350
|
+
|
|
351
|
+
### F12.(已 CLI)`require.context` 非入口(store 收集 vuex modules)→ `import.meta.glob`
|
|
352
|
+
入口的 `require.context('@/X', true, ...)` `--to-vite` 一直会转;**漏网的是 store/index.js** 的
|
|
353
|
+
`require.context('./modules', false, ...)`(收集 vuex modules)。CLI V 阶段现遍历全 src 通用改写:
|
|
354
|
+
recursive 决定 `**/` 还是单层、正则决定扩展名(`.vue`/`.js`)、dir 前缀(`@/` 或 `./`)原样保留;
|
|
355
|
+
配套 `VAR.keys()`→`Object.keys(VAR)`、`VAR(k)`→`VAR[k]`(变量名不一定叫 files,store 里叫 context,故按声明抓变量名)。
|
|
356
|
+
|
|
357
|
+
### F13. `::v-deep` 弃用警告(**不进 CLI;容器块形态手工 / postcss**)
|
|
358
|
+
Vue3 弃用 `::v-deep` 作 combinator,建议 `:deep(<inner>)`。recruit 实测 **141 处是容器块 `::v-deep{ ... }`**(combinator 仅 10、`>>>` 仅 5)。
|
|
359
|
+
容器块转 `:deep()` 要拆 SCSS 嵌套块(花括号配对、多层嵌套),141 处正则拆分**极易改坏 style,风险远大于收益**——
|
|
360
|
+
而它只是**弃用警告:build 通过、运行正常**。故**不进 CLI**(同 gogocode 的 style 快照保护:不用正则动 style 块)。
|
|
361
|
+
真要消警告:写 postcss 插件按 AST 转、或按文件手工,别正则批量。
|
|
362
|
+
|
|
363
|
+
### F14.(部分已 CLI)`<script>` 里的 Vue2 JSX 残留 → Vue3(gogocode 只转 template,不碰 JSX)
|
|
364
|
+
gogocode 只转 `<template>`,**不碰 `<script>` 里的 JSX / 手写 render**——Vue2 JSX 前缀原样留到 Vue3,
|
|
365
|
+
`@vue/babel-plugin-jsx` 不识别、当普通属性丢弃 → **build 过、console 不报错、运行时静默失效**(最隐蔽的一类)。
|
|
366
|
+
recruit 实测(145 组件):表单 note 不显示、折叠操作点击无效、子表格自定义列/操作列不渲染。
|
|
367
|
+
③ 本地简历审核详情「流程图」tab 空白也是同款——平台 `ProcessChart.vue` 用 `domPropsInnerHTML` 贴 saveSVG 的图,
|
|
368
|
+
实证显示容器 `innerHTML.length=0`(图在隐藏容器画好了、没贴出来);平台侧 Vue2 JSX 残留共 **11 处 / 8 文件**(`domPropsInnerHTML` 9 + `nativeOnClick` 2:`ProcessChart` / `EntityList`×2 / `Mobile`×2 / `Field` / `AddFormItem` / `NoInstallSystemList` / `AppNav` / `Login`),本会话已用**同一个 CLI `fix-jsx-vue2`** 批量修完(平台组件同样适用)、`build:pre` 通过,`ProcessChart` 流程图肉眼验证 `innerHTML 0→30753`。
|
|
369
|
+
- **已 CLI**(③ 修补⑦ `fix-jsx-vue2.mjs`,只换 JSX 属性名、不动值/结构,安全):
|
|
370
|
+
`domPropsInnerHTML=`→`innerHTML=`、`domPropsValue=`→`value=`、`nativeOn<E>=`→`on<E>=`(nativeOnClick→onClick,Vue3 组件事件默认透传根元素)、`scopedSlots={{`→`v-slots={{`。
|
|
371
|
+
- **人工**(结构性重排,CLI 不碰):手写 render 的 data 对象 `return { scopedSlots:{...} }` / `h(tag,{on,props,attrs})`——
|
|
372
|
+
Vue2 createElement data 到 Vue3 差异大,正则不保险。recruit 实测 InnerEntitySubList 3 处(方法返回 `{scopedSlots:{default}}` +
|
|
373
|
+
调用点 `<el-table-column {...this.renderX()}>` spread),最小改法:调用点 `{...this.renderX()}` → `v-slots={this.renderX().scopedSlots}`(方法内数据属性名不动)。改后实测子表格字段/操作列/彩色标签正常渲染。
|
|
374
|
+
- 排查:`grep -rE "domProps|nativeOn[A-Z]|scopedSlots" src`;`scopedSlots=`(等号)=JSX 属性可自动,`scopedSlots:`(冒号)=render 对象需人工。
|
|
375
|
+
- **第 5 类 `slot="xxx"`(postAppoint 补)**:`<script>`/JSX 区的具名插槽(`slot="reference"`/`slot="footer"`/`slot="title"`)同样被 Vue3 JSX 丢弃 → el-popover 触发钮不渲染(「更多」按钮消失)/ el-dialog 底部按钮·标题不显示。`fix-jsx-vue2` 现**报告**(列人工清单,不自动改——改 `v-slots` 要重组 JSX 结构,正则做不了)。⚠ **不能一律判失效**:实测 el-dropdown 的 `slot="dropdown"` 靠组件运行时兼容仍正常(`$slots` 含 dropdown),el-popover `reference` / el-dialog `footer`·`title` 才真失效——逐个 `$slots` 或 DOM 实证。postAppoint `MainEntityList..vue` 的 `slot="reference"`(卡片「更多」)实证失效,改 `v-slots={{ reference: () => (...) }}`(commit `d6386c3`)。
|
|
376
|
+
|
|
377
|
+
### F15. JSX/render 与 scoped 样式:先验证匹配,再做最小修复并验收隔离
|
|
378
|
+
|
|
379
|
+
**默认保留 scoped**。JSX/render、无 `<template>` 或 `extends` 只能作为排查线索,不能推断“深层元素一定没有 scopeId”或“scoped 整体失效”。实际结果与项目的编译插件、组件继承、插槽和渲染路径有关;当前项目中能正确携带作用域并匹配规则的组件保持原样。不得依据历史案例或静态告警批量删除 scoped,也不得把所有选择器机械改为 `:deep()`。
|
|
380
|
+
|
|
381
|
+
**先证明具体问题**:使用最终构建产物(按需发布时使用最终 lazy/chunks 产物),确认实际加载的是本次 JS 与 CSS。对一个确实显示异常的元素,记录:
|
|
382
|
+
|
|
383
|
+
- 所属组件、页面、预期样式与实际 `getComputedStyle`/尺寸;
|
|
384
|
+
- 产物中的具体 CSS 选择器、样式是否已加载,以及目标/祖先 DOM 上实际的 class、`data-v-*`;用开发者工具匹配规则或 `element.matches(实际选择器)` 确认是否命中;
|
|
385
|
+
- 失效究竟来自 CSS 未加载、优先级覆盖、根命名空间缺失、作用域不匹配,还是其他布局原因。作用域存在且规则命中时,不按“去 scoped”处理。
|
|
386
|
+
|
|
387
|
+
#### 继承组件经过插槽渲染后,局部样式可能失效
|
|
388
|
+
|
|
389
|
+
不要仅凭 JSX、`extends` 或无 template 判定 scoped 失效,也不要仅凭隔离测试正常就排除问题。必须检查实际平台页面的完整渲染链:继承组件的内容可能经过平台包装组件、Element Plus 等组件的插槽。实际 DOM 中,组件根节点带有 `data-v-*`,插槽内的图片、标题等后代却可能没有对应属性,导致 `.projectTop[data-v-xxx]` 等产物规则无法匹配。根节点有作用域属性不等于所有后代都有;问题应定位到具体元素和选择器,不能扩大成整个组件的 scoped 都无效。
|
|
390
|
+
|
|
391
|
+
**排查步骤**:
|
|
392
|
+
|
|
393
|
+
1. 确认 CSS 已加载,且与 JS/chunks 来自同一次构建;先排除旧缓存或不同批产物造成的 scopeId 失配。
|
|
394
|
+
2. 检查失效元素及祖先的 class、`data-v-*`,核对最终产物选择器是否实际匹配;不要只检查组件根节点。
|
|
395
|
+
3. 使用 `getComputedStyle()` 和实际尺寸检查图片宽高、`object-fit`、字号、内边距等,同时追查继承值与规则来源。例如卡片标题规则失配后,可能继承复选框组的 `font-size: 0`,文本仍在 DOM 中却不可见。
|
|
396
|
+
4. 在实际页面验证“继承组件 → 平台包装组件 → UI组件插槽 → 目标DOM”的完整链路。简化基类、直接挂载子组件或替换 UI 组件的隔离测试可辅助定位,但可能改变作用域传递路径,不能替代实际页面验收。
|
|
397
|
+
|
|
398
|
+
**推荐修法**:确认作用域失配后,保留 scoped,在带有对应作用域属性的专属容器内,对必要的后代使用 `:deep()`。例如:
|
|
399
|
+
|
|
400
|
+
```vue
|
|
401
|
+
<template>
|
|
402
|
+
<div class="epx-training-project-list">
|
|
403
|
+
<!-- 包装组件及插槽内容 -->
|
|
404
|
+
</div>
|
|
405
|
+
</template>
|
|
406
|
+
|
|
407
|
+
<style scoped>
|
|
408
|
+
.epx-training-project-list :deep(.projectTop) {
|
|
409
|
+
width: 100%;
|
|
410
|
+
height: 160px;
|
|
411
|
+
object-fit: cover;
|
|
412
|
+
}
|
|
413
|
+
</style>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
示例尺寸按目标页面设计调整;标题等其他失效规则也逐条处理,不把整块样式机械改为 deep。检查最终 CSS 仍类似 `.epx-training-project-list[data-v-xxx] .projectTop`,并确认专属容器在实际渲染分支中存在且带有匹配的作用域属性,图片确实是其后代。若内容经 Teleport 等挂到容器之外,应单独定位其专属挂载范围,不能为命中目标而取消边界。
|
|
417
|
+
|
|
418
|
+
**发布验收**:用最终完整包或 lazy/chunks 产物在实际页面确认插槽内图片、标题等样式恢复,同时组件外部同名元素不受影响;再检查离开页面后的其他页面。不要直接删除 scoped,让 `.projectTop`、`.avatar`、`.container` 等通用类变成全局规则。未能复现完整页面链路时记录为待验证,不以隔离测试通过宣布修复完成。
|
|
419
|
+
|
|
420
|
+
**修复顺序**:先修正加载、渲染根、选择器或局部布局,尽量保留 scoped。确认为 scoped 无法匹配继承/子组件的目标 DOM 时,才针对具体规则评估 `:deep()`,并确认其锚定在本组件有效的根范围内;`:deep()` 本身不保证隔离,仍须验收。历史 recruit `CardFormItem` 头像缺少对应 `data-v-*`、`MainEntityList` 深层规则失配等案例,只证明当时那些元素的具体问题,不能推广到其他组件。
|
|
421
|
+
|
|
422
|
+
**去掉 scoped 是备选方案,隔离与修改必须同时完成**:
|
|
423
|
+
|
|
424
|
+
1. 为组件定义唯一命名空间(如 `.epx-product-resume-card`),确认其覆盖全部实际渲染入口:自有 render、继承根、多分支、动态页,以及弹窗/Teleport 等独立挂载的内容。不能假定给某一个根加 class 就覆盖全部 DOM,另见 F19。
|
|
425
|
+
2. 将该 style 块内所有规则限制在命名空间内,包括逗号分隔的每个选择器、SCSS 嵌套展开、媒体查询、深层选择器和 mixin 生成的规则。明确本就全局的主题规则另行审查。核对最终编译 CSS,不能只看源码外层有一层嵌套;不得留下裸 `.avatar`、`.container`、`img` 或平台通用类选择器。
|
|
426
|
+
3. 所需规则若作用于根本身,使用 `.epx-product-resume-card.container` 等同元素选择器;`.epx-product-resume-card .container` 只匹配后代。移除 scoped 后,对原 `:deep()` 等语法按最终 CSS 和目标 DOM 调整,不能留下无效或无边界的穿透规则。
|
|
427
|
+
4. BEM 名称也不能免除检查:只有最终 CSS 的每个选择器都实际限定在组件唯一前缀内、且组件外回归通过时,才可沿用现有 BEM 隔离;不要仅凭“使用了 BEM/mixin”就判安全。
|
|
428
|
+
5. 根范围和完整样式隔离尚未完成时,保留或恢复 scoped,将该项记为未解决;不要留下“先去 scoped,稍后补隔离”的提交或发布产物。逐个组件处理,避免整批转换扩大影响。
|
|
429
|
+
|
|
430
|
+
**验收必须同时证明“组件内生效”和“组件外不受影响”**:
|
|
431
|
+
|
|
432
|
+
- 记录组件内的关键宽高、布局、图片与弹窗样式,并核对所有渲染分支及产品支持的窄视口;仅改 `innerWidth` 不能代替真实移动端视口验收。
|
|
433
|
+
- 在组件范围外放置或选取真实的 `.avatar`、`.container` 等同名对照元素,记录组件加载前、懒加载 JS/CSS 后的计算样式和尺寸,确认未被本组件规则改变。若 CSS 在入口即加载,应与隔离变更前的产物基线对照;不能只比较样式早已污染后的两次快照。
|
|
434
|
+
- 从目标页面离开再进入其他页面,复核外部同名元素、平台布局和弹窗。懒加载 CSS 可能在离开页面后继续存在,关闭组件并不等于清除影响。
|
|
435
|
+
- 在迁移报告中逐个记录改动组件、失配证据、隔离方式、产物版本及上述检查结果。未具备环境或未执行的检查写为待验证;构建、lint 或静态扫描通过不代表隔离验收通过。
|
|
436
|
+
|
|
437
|
+
**静态扫描的边界**:迁移器的“无 template + scoped”列表和 `detect-vue3-risks.mjs` R1 都只提示人工复核,不证明运行时失效。扫描器不覆盖所有全局泄漏,`R1` 未命中(包括改用 `:deep()` 后)也不表示安全;不得为了让扫描结果变绿而破坏隔离。
|
|
438
|
+
|
|
439
|
+
### F16. 数组 `splice` 同引用 + `deep:1` watcher 不触发(弹窗标题恒空,postAppoint 平台侧实证)
|
|
440
|
+
Vue3 起浅层 watcher 不响应 push/splice;`deep:1` 只监听「数组元素引用」这一层。若 `route.name=title; routes.splice(i,1,route)`——splice 放回**同一对象引用**、只改元素内部属性 `name`,`deep:1` 监听不到 → watcher 不触发 → `$emit('change')` 不重发 → 消费方(`Popup.routerChange`)拿到的 route.name 恒空 → **弹窗标题不显示**(build 过、无报错)。
|
|
441
|
+
- 修法:splice 放**浅拷贝新对象** `routes.splice(i,1,{...route,name:title})`(改元素引用才触发 `deep:1`);消费方再加空值兜底(异步 `$refs.component` 未 resolve 时用 `route.name`)。平台 `RouterView.pageChange` + `Popup.routerChange` 双改、build:pre 实测标题空→「新增公示」(commit `0061cb7c63`)。修前先运行时验证:手动 `rv.routes.splice(0,1,{...rv.routes[0]})` 看消费方值是否变,成立再改、免白 build。
|
|
442
|
+
- 通用:Vue3 靠 push/splice 增删的数组,watcher 要么 `deep`、要么 splice 换新对象引用;**改元素内部属性不触发浅层 watcher**(同类历史 commit `df29393`/`2161c04` 数组 watcher 补 deep)。
|
|
443
|
+
|
|
444
|
+
### F17. CSS code-split 下组件借用别组件的全局样式(图表容器塌陷,postAppoint 平台侧实证)
|
|
445
|
+
Vue2 webpack 把全部 CSS 合进主 bundle,任何组件的非 scoped 全局样式总能加载;Vue3 vite **CSS code-split** 按组件拆 CSS chunk。若「实际渲染的组件」自身无 style、借用**别组件**里定义的非 scoped 全局样式(如 `.epx-chart` 布局散在 `chart/Table.vue`、`report-chart/Chart.vue` 的 `@include b(chart)`,而实际渲染的 `Common.vue`(epx-chart-common) 无 style),只加载渲染组件 chunk 时**不带那些 CSS chunk** → 样式缺失。实证:`.epx-chart__main` 高度塌陷 0、echarts 画不出(JS chunk 已加载、`getReportData` 数据正常)。
|
|
446
|
+
- 修法:让渲染组件**自带**渲染必需的样式(Common.vue 补 scoped `<style>` 定义 `.epx-chart` 根 flex+min-height / `__main` flex / `__echarts` 铺满,随本组件 chunk 加载、有 template 故 scoped 生效、零污染,commit `0061cb7c63`);别关 `cssCodeSplit`(牺牲首屏)。
|
|
447
|
+
- 排查判据:`getComputedStyle(容器).height==='0px'` + `performance` 里该组件 CSS chunk **不在**已加载列表(`document.styleSheets` 里也搜不到那几条规则)。
|
|
448
|
+
|
|
449
|
+
### F18. `$emit` 不读实例 `__epxEvents`(程序化事件断链、内容区空白,postAppoint 平台侧实证)
|
|
450
|
+
Vue3 移除实例 `$on/$off`,平台改用实例级轻量事件 `$epxOn/$epxEmit`(handler 存实例 `__epxEvents`,见 core `mixins/methods-to-prototype.js`)。子组件(如 RouterView)用 `$parent.$epxOn('createPage',...)` 注册;而发射方仍是裸 `this.$emit('createPage')`——Vue3 `$emit` 只触发**父模板上**的监听器、**不读 `__epxEvents`** → 注册方永远收不到(emit 没人听的事件是合法 no-op、无报错)→ 内容区整块空白、点击无效。实证 `EntityTreeManage.vue`(机构管理右侧空白)。
|
|
451
|
+
- 修法:发射走「`$epxEmit` + `$emit`」**双发射**闸口(保留 `$emit` 以防模板上有 `@xxx`)。commit `543f85daf3`。
|
|
452
|
+
- 排查:全仓 grep `\.\$emit\('程序化事件名'`(createPage / backModule / push 等),逐个确认消费方是 `$epxOn` 注册还是模板绑定;全仓「没有任何模板 `@createPage`」即强信号。
|
|
453
|
+
|
|
454
|
+
### F19. 非 scoped 样式改为根 class 命名空间后,其他渲染入口漏加 class(服务大厅登录页实证)
|
|
455
|
+
|
|
456
|
+
仅在 F15 已证明需要调整隔离且选择根命名空间方案时,把 `<style scoped>` 改成 `<style>`,并将规则包进 `.epx-xxx { ... }`;所有实际渲染入口都必须在该命名空间内。检查 `extends` 子组件覆盖的 `render()`、父组件的自定义页面分支、动态 `<component>` 和多分支根节点;只给默认分支加 class 不够。此问题与 `data-v` hash 失配不同:CSS 已加载、DOM 也有原来的内部类,但根节点缺少 `.epx-xxx`,整组选择器匹配不到,构建和控制台通常正常。
|
|
457
|
+
|
|
458
|
+
- **实证**:`Login.vue` 样式改为 `.epx-login` 包裹后,默认登录分支有该类;`ClientLogin.vue` 覆盖的根节点及配置 `LOGIN_PAGE` 走的 `renderPage()` 没有。`#/hall/login/frame` 渲染出 `.PCcontainer` 和 `.loginCon`,却没有 `.epx-login` 祖先,登录页失去布局。给 `ClientLogin` 根和 `renderPage()` 的页面容器补 class 后,浏览器实测 `.loginCon` 恢复绝对定位、宽 357px,`.PCcontainer` 铺满 1440×900 视口。
|
|
459
|
+
- **升级检查**:凡将 scoped 样式改为根 class 命名空间,逐个打开使用该组件的实际页面/分支,检查 `el.closest('.epx-xxx')` 是否存在,并用 `getComputedStyle(el)` 验证宽高、定位等关键规则。若 CSS 已加载但命名空间不存在,在相应渲染根或共同祖先补 class;不要靠失效的 `__scopeId` 转写补丁。静态扫描只能提示风险,页面验收仍需运行时确认。
|
|
460
|
+
|
|
461
|
+
### F20. Element Plus 选择框占满工具栏,升级后按钮换行(HR 组织架构图)
|
|
462
|
+
|
|
463
|
+
**现象与判断**:旧产品在 Vue3 平台显示正常,升级为原生 Vue3 后右侧层级选择框与导出按钮分成两行。Element Plus 的 `.el-select` 使用 `width: var(--el-select-width, 100%)`;旧产品的 Vue2 render 兼容层曾给选择框补 `--el-select-width: 202px`,原生 Vue3 产品不再走该处理。若产品未明确工具栏选择框宽度,默认 100% 就会挤走相邻按钮。HR `OrgChart.vue` 中还存在无效的 `style="marginright: 20px"`,须改为有效 CSS 属性。
|
|
464
|
+
|
|
465
|
+
**修法与边界**:在产品拥有的工具栏内设置选择框宽度、按钮间距及对齐方式,加入迁移人工检查项;不要给全局 `.el-select` 统一定宽,也不要为了布局让 Vue3 产品重新走 Vue2 垫片。全局改宽度会影响表单、查询面板和弹窗中需要 100% 宽度的选择框。无法仅凭标签自动判断工具栏还是表单,因此不对所有 `el-select` 做机械替换。平台自有工具栏出现同类问题时,在对应平台组件修复。
|
|
466
|
+
|
|
467
|
+
例如 HR 组织图右侧容器增加 `class="org-chart-actions"`,层级选择框增加 `class="org-chart-level"` 并删除无效的 `marginright`;在现有 scoped 样式中加入(宽度以原页面设计为准,202px 是旧垫片参考值):
|
|
468
|
+
|
|
469
|
+
```scss
|
|
470
|
+
.org-header > .org-chart-actions {
|
|
471
|
+
display: flex;
|
|
472
|
+
align-items: center;
|
|
473
|
+
gap: 20px;
|
|
474
|
+
}
|
|
475
|
+
.org-chart-actions > .org-chart-level {
|
|
476
|
+
width: 202px;
|
|
477
|
+
flex: 0 0 202px;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
不要仅把父容器改成 `display:flex` 而让选择框继续以 100% 宽度参与收缩;也不要只修间距拼写。先确认实际根 class、CSS 已加载且选择器命中;无模板的 JSX/render scoped 问题另按 F15 处理。
|
|
482
|
+
|
|
483
|
+
**验收**:用实际 Vue3 产品产物加载页面,确认宽度约等于设定值,选择框和按钮纵向中心对齐;切换层级、打开/关闭导出弹窗,检查对话框中格式选择框仍按其自身布局显示。分别检查常用桌面宽度和产品支持的最窄视口,窄屏按产品设计允许整组换行或收缩,避免按钮挤出容器。记录实际页面截图与尺寸,构建成功不能代替布局验收。
|
|
484
|
+
|
|
485
|
+
### F21. Element Plus 日期格式 token 迁移遗漏(HR 历史组织机构图)
|
|
486
|
+
|
|
487
|
+
**已定位实例**:HR `OrgHistoryChart.vue` 日期控件仍写 `value-format="yyyy-MM-dd"`。本地已复现:绑定日期为 `2026-10-20`,输入框却显示 `2026-02-01`,下方数据时点仍正确;不是仅凭截图就能判定历史查询接口出错。产品已把该处改为 `YYYY-MM-DD` 并通过 lint,须重新构建、部署后验证浏览器。此前报告中的日期绑定/历史查询“待核”应按此根因更新,不把源码修复当成部署已生效。
|
|
488
|
+
|
|
489
|
+
**根因与边界**:Element UI 的 fecha 与 Element Plus 的 Day.js 格式 token 不同;`format` 控制显示,`value-format`(JS为`valueFormat`)控制绑定值,应一起检查。`yyyy→YYYY`、`yy→YY`、日 `dd→DD`/`d→D`;保留月 `MM`、分钟 `mm`、秒 `ss`,星期 `ddd/dddd` 和方括号转义文字不作日 token 替换。保留原来的年/月/日粒度、分隔符和时区约定。后端 Java、date-fns 等其它格式不能机械套用 Day.js 规则。参考 [Element Plus 日期格式](https://element-plus.org/en-US/component/date-picker.html#date-formats) 和 [Day.js 格式 token](https://day.js.org/docs/en/display/format)。
|
|
490
|
+
|
|
491
|
+
**技能执行要求:发现同类问题就修,不只加说明**。
|
|
492
|
+
|
|
493
|
+
1. 产品、项目升级及已迁移工程补修都扫描 `src`,覆盖 `.vue` template/script、JSX/TSX 和 JS/TS render。检查 `el-date-picker`、`ElDatePicker`、`el-time-picker`、`ElTimePicker` 的 `format`、`value-format/valueFormat`,包括导入别名、静态绑定、条件分支,以及 `h/createVNode` 的 props/attrs。
|
|
494
|
+
2. `migrate.mjs` 在常规 `--apply` 中自动改可静态确定的格式;不需要额外开启 gogocode。单独补修:`node tools/epx-migrate/fix-date-formats.mjs <目标工程> --apply`。只读预览去掉 `--apply`;修复幂等,保留 scoped、注释和其它代码。`detect-vue3-risks.mjs` 的 R2 在发布前检查残留。
|
|
495
|
+
3. 动态变量、`v-bind`、JSX展开、函数返回或共享配置需追踪来源:确认该值给 Element Plus 控件消费后,自动修复相应配置或在控件侧生成正确格式。若同一常量还服务后端/其它日期库,分别处理消费场景,不能直接改共享常量。工具不能安全确定的位置须由执行技能的代理继续分析并修复;只有信息不足才列未解决项。不要扫描或回写 `node_modules`、`lib`、`dist` 构建产物来代替源代码修复。
|
|
496
|
+
4. 原生 `runtime: vue3` 包不再通过旧 Vue2 render 垫片转换格式。不要去掉 runtime 标识、回挂旧垫片或给所有日期控件打全局运行时补丁;修复源组件并重新构建发布。
|
|
497
|
+
|
|
498
|
+
**验收**:至少检查 `2026-10-20` 初始回显不漂移;选择另一日期后输入框、绑定值、历史提示/数据时点及接口日期参数一致;月份/年份控件保留粒度,时间保持分钟与秒。检查实际完整包或懒加载产物与CSS同批部署。记录修复文件/行号、转换前后格式、动态来源处理结果和部署后截图。lint/build通过不等于日期逻辑已验收;没有部署权限时明确写“源码已修,待重新构建部署复测”。
|