@epxing/cli 0.5.0-beta.6 → 0.5.0-beta.7

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.
@@ -446,6 +446,8 @@ recruit 实测(145 组件):表单 note 不显示、折叠操作点击无
446
446
 
447
447
  #### 继承组件经过插槽渲染后,局部样式可能失效
448
448
 
449
+ **卡片列表的分发入口也要检查**:产品重写 `renderTable` 并不代表卡片模式会调用它。先按当前基类的 `renderList` 分发契约检查 `renderTable`/`renderCardList`;旧的 `renderVerticalItem` 可能被平台卡片子组件作为回调调用,生成的卡片后代没有产品的 scopeId。薪酬发放页已出现根节点有 scopeId、卡片容器却无 scopeId,最终卡片 padding 为 0、display 为 block,标题继承复选框组的 font-size: 0。产品有自己的卡片主体时,应接入 `renderCardList` 保留原结构;对经过 UI 插槽的后代,在有效的产品容器内局部使用 `:deep(.card-item...)`。同时检查空数据、分页、卡片方向和操作按钮,不能只补一个标题字号。
450
+
449
451
  不要仅凭 JSX、`extends` 或无 template 判定 scoped 失效,也不要仅凭隔离测试正常就排除问题。必须检查实际平台页面的完整渲染链:继承组件的内容可能经过平台包装组件、Element Plus 等组件的插槽。实际 DOM 中,组件根节点带有 `data-v-*`,插槽内的图片、标题等后代却可能没有对应属性,导致 `.projectTop[data-v-xxx]` 等产物规则无法匹配。根节点有作用域属性不等于所有后代都有;问题应定位到具体元素和选择器,不能扩大成整个组件的 scoped 都无效。
450
452
 
451
453
  **排查步骤**:
@@ -455,6 +457,8 @@ recruit 实测(145 组件):表单 note 不显示、折叠操作点击无
455
457
  3. 使用 `getComputedStyle()` 和实际尺寸检查图片宽高、`object-fit`、字号、内边距等,同时追查继承值与规则来源。例如卡片标题规则失配后,可能继承复选框组的 `font-size: 0`,文本仍在 DOM 中却不可见。
456
458
  4. 在实际页面验证“继承组件 → 平台包装组件 → UI组件插槽 → 目标DOM”的完整链路。简化基类、直接挂载子组件或替换 UI 组件的隔离测试可辅助定位,但可能改变作用域传递路径,不能替代实际页面验收。
457
459
 
460
+ **局部渲染回调的识别**:重点追踪 `renderVerticalItem`、`renderField`、`renderInputContent` 等被继承组件或 UI 插槽调用的方法;方法写在产品 SFC 内,并不保证返回的所有 DOM 都获得该 SFC 的作用域属性。卡片背景、间距、装饰图和字段布局同时消失,文字却仍正常显示时,要检查原局部规则是否失配,导致字号等属性继承自复选框组或其它祖先;不能只检查字体文件或用全局 CSS 补外观。记录目标元素和祖先的作用域属性、匹配规则以及修复前后的字号、padding、display、背景与尺寸。
461
+
458
462
  **推荐修法**:确认作用域失配后,保留 scoped,在带有对应作用域属性的专属容器内,对必要的后代使用 `:deep()`。例如:
459
463
 
460
464
  ```vue
@@ -475,8 +479,12 @@ recruit 实测(145 组件):表单 note 不显示、折叠操作点击无
475
479
 
476
480
  示例尺寸按目标页面设计调整;标题等其他失效规则也逐条处理,不把整块样式机械改为 deep。检查最终 CSS 仍类似 `.epx-training-project-list[data-v-xxx] .projectTop`,并确认专属容器在实际渲染分支中存在且带有匹配的作用域属性,图片确实是其后代。若内容经 Teleport 等挂到容器之外,应单独定位其专属挂载范围,不能为命中目标而取消边界。
477
481
 
482
+ 已有继承根满足上述条件时,可直接作为锚点,无需为了修样式新增 template 或改写整套 render。例如 `.epx-entity-list :deep(.product-card__field)` 应编译为 `.epx-entity-list[data-v-xxx] .product-card__field`,且该根在当前分支确实是目标节点的祖先。通用根类的隔离依赖对应作用域属性,不能只保留裸 `.epx-entity-list .product-card__field`。只修已确认失配的规则,仍正常匹配的工具栏、根节点和独立浮层规则分别判断。
483
+
478
484
  **发布验收**:用最终完整包或 lazy/chunks 产物在实际页面确认插槽内图片、标题等样式恢复,同时组件外部同名元素不受影响;再检查离开页面后的其他页面。不要直接删除 scoped,让 `.projectTop`、`.avatar`、`.container` 等通用类变成全局规则。未能复现完整页面链路时记录为待验证,不以隔离测试通过宣布修复完成。
479
485
 
486
+ **同类组件检查**:确认一个失配案例后,检查继承它的子组件、复制了同套样式或卡片结构的组件,以及同样通过插槽调用局部渲染方法的组件,复用 E2 台账记录具体选择器和消费链路。子组件重复声明父样式不能保证修复,仍需核对它自己的作用域属性;父组件修好也不代表所有子组件已经通过。静态命中仅列为候选,逐项区分“运行确认失配”“已改待页面验证”“已运行通过”和“缺业务数据或入口、待验证”;说明缺少的条件,不把这些状态汇总为全部正常。后续验证按各组件实际业务入口进行,不靠批量删除 scoped 或替换所有选择器关闭清单。
487
+
480
488
  **修复顺序**:先修正加载、渲染根、选择器或局部布局,尽量保留 scoped。确认为 scoped 无法匹配继承/子组件的目标 DOM 时,才针对具体规则评估 `:deep()`,并确认其锚定在本组件有效的根范围内;`:deep()` 本身不保证隔离,仍须验收。历史 recruit `CardFormItem` 头像缺少对应 `data-v-*`、`MainEntityList` 深层规则失配等案例,只证明当时那些元素的具体问题,不能推广到其他组件。
481
489
 
482
490
  **去掉 scoped 是备选方案,隔离与修改必须同时完成**:
@@ -496,6 +504,10 @@ recruit 实测(145 组件):表单 note 不显示、折叠操作点击无
496
504
 
497
505
  **静态扫描的边界**:迁移器的“无 template + scoped”列表和 `detect-vue3-risks.mjs` R1 都只提示人工复核,不证明运行时失效。扫描器不覆盖所有全局泄漏,`R1` 未命中(包括改用 `:deep()` 后)也不表示安全;不得为了让扫描结果变绿而破坏隔离。
498
506
 
507
+ **空状态与栅格嵌套**:列表正常不代表 `v-else` 正常。若空卡片文字逐字换行,先量父容器与卡片宽度,并核对 `flex-basis`、`max-width` 和栅格类。工资 `ViewdataCard` 的空分支将手写 `el-col-3` 放在自适应宽度的中间容器内,12.5% 规则使卡片实际仅 20px。此类问题应按布局意图去除多余嵌套及错误栅格类,给空状态独立、可收缩的宽度,保留有数据分支的栅格配置;不要全局覆盖 `.el-col-*` 或修改数据来隐藏空状态。扫描同类手写栅格后,分别验证空/非空分支与窄容器,记录文字换行、宽度和溢出;源码隔离验证与目标站发布验收分开记录。
508
+
509
+ **统计卡片挤压后续区域**:同时测量配置外层、UI 卡片、统计子项和后续兄弟的位置。KqShowCard 曾出现外层配置高度 180px、内部实际高度 238px,造成后续卡片与统计区域重叠;其 BEM mixin 生成的类名还与模板不一致,scoped 样式也未命中 UI 内部 header/body。此类内容驱动的概览容器应按实际用途随内容撑高,使用与模板一致的局部选择器,并仅对 UI 内部节点局部 deep;子项间距可由列 padding 承担,避免百分比高度再叠垂直 margin 造成溢出。不能全局取消所有配置高度。以修复后子项位于容器内、后续区域不重叠为验收依据,保留线上发布边界。
510
+
499
511
  ### F16. 数组 `splice` 同引用 + `deep:1` watcher 不触发(弹窗标题恒空,postAppoint 平台侧实证)
500
512
  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 过、无报错)。
501
513
  - 修法: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。
@@ -524,6 +536,8 @@ Vue3 移除实例 `$on/$off`,平台改用实例级轻量事件 `$epxOn/$epxEmi
524
536
 
525
537
  **修法与边界**:在产品拥有的工具栏内设置选择框宽度、按钮间距及对齐方式,加入迁移人工检查项;不要给全局 `.el-select` 统一定宽,也不要为了布局让 Vue3 产品重新走 Vue2 垫片。全局改宽度会影响表单、查询面板和弹窗中需要 100% 宽度的选择框。无法仅凭标签自动判断工具栏还是表单,因此不对所有 `el-select` 做机械替换。平台自有工具栏出现同类问题时,在对应平台组件修复。
526
538
 
539
+ 工资 `BatchSearch.vue` 的“当前批次”也命中此模式:裸 `.container` 内的选择框没有局部宽度。改用组件专属根类、flex 对齐与间距,并给选择框独立宽度及最大宽度,桌面保持标题/选择框/链接同排,窄屏允许换行且不溢出。检查标题加选择框的紧凑查询组件,不能只扫按钮工具栏;无运行环境时明确区分编译/布局隔离验证与真实页面验收。
540
+
527
541
  例如 HR 组织图右侧容器增加 `class="org-chart-actions"`,层级选择框增加 `class="org-chart-level"` 并删除无效的 `marginright`;在现有 scoped 样式中加入(宽度以原页面设计为准,202px 是旧垫片参考值):
528
542
 
529
543
  ```scss
@@ -577,15 +591,120 @@ Vue3 移除实例 `$on/$off`,平台改用实例级轻量事件 `$epxOn/$epxEmi
577
591
 
578
592
  **根因**:旧方法返回 `{ scopedSlots: { default: ({ row }) => ... } }`,Vue3 平台继承的 renderTable 通过 `v-slots={this.renderField(...)}` 消费,需要 `{ default: ({ row }) => ... }`。旧作用域插槽被当成名为 scopedSlots 的插槽,默认渲染回退到原始字段;构建和控制台可能完全正常。renderColumnOperates 的旧返回结构也会使操作按钮不显示。
579
593
 
594
+ **另一端同样必查**:产品重写 `renderTable` 时,即使 `renderField` 已返回新版 `{ default }`,`<el-table-column {...this.renderField(field)}>` 仍会把函数当成普通属性,未传给 Vue3 插槽。可见列可能退回 ID/代码;操作列还可能因 `type="index"` 而显示序号。实例上 `attrs.default` 是函数而有效默认插槽缺失或仅为无内容的 JSX 子节点,是调用点未迁移的证据;仅确认 `$slots.default` 存在不足以判通过。
595
+
580
596
  **执行升级技能时必须关闭此项**:
581
597
 
582
598
  1. 扫描产品 src 中的 renderField、renderColumnOperates 及其引用的辅助函数、父类回退分支,检查返回值是否仍嵌套 scopedSlots;覆盖 Vue script、JS/TS、JSX/TSX。CLI 的 JSX `scopedSlots=` 属性转换不处理这种 `scopedSlots:` 对象返回,不能据 CLI 完成就跳过。
583
599
  2. 确认调用方消费的是 Vue3 插槽对象后,去掉返回值外层 scopedSlots,保留内部 default/header 等插槽名、参数、字段名称优先级、标签样式和事件。例如脚本中返回 `{ default: ({ row }) => h('span', row[field.attriName + '#'] || row[field.attriName]) }`;父类回退直接保留新版父类的返回对象。不要把所有 vnode data 中的 scopedSlots 一律扁平化,也不要全仓替换 `scopedSlots:`。
600
+ 同时检查调用点:JSX 叶子列使用 `v-slots={this.renderField(field, index)}`,操作列使用 `v-slots={this.renderColumnOperates()}`;`h` 渲染将插槽对象传入第三参。若同一 JSX 元素还通过 children 生成分组子列,应按用途选择默认插槽,例如 `v-slots={field.children?.length ? undefined : this.renderField(field, index)}`,保留父分组列的子列结构。只转换经确认返回插槽对象的方法调用;属性对象 spread 与直接返回 VNode 的方法保留。验证分组标题、叶子列数、表体列对齐与原有列顺序,不能以单元格修好为由丢失子列。
584
601
  3. 核对同一条记录的请求列、接口响应、组件行数据与可见单元格:列表请求应包含相应 `字段#`,名称存在时实际显示名称,原值仍用于提交与操作。未返回名称的情况另查取数或标签解析,不用改字段类型或硬编码名称掩盖。
585
602
  4. 用最终 Vue3 产品产物验证对象、代码项及关联字段列的显示、标签样式、名称更新、点击与操作列。关键列的 ElTableColumn 应有 default 插槽;只剩 scopedSlots 表示此项未完成。已有旧垫片能显示名称不能作为升级完成依据,须确认产品返回值已是 Vue3 结构且不依赖旧方法归一化;原生 Vue3 独立挂载回归也应通过。
586
603
 
604
+ **制品来源核验**:本地入口注册过组件,不代表页面正在使用该实现。后台远程库可在稍后覆盖注册或创建异步包装器;核对实际实例、远程脚本地址和本批制品。开发采证应明确区分源码热更新与远程库加载;需要替换时只在本地将本产品 JS、CSS、deps/manifest 和 chunks 统一指向同批本地制品,不改后台接入配置。未确认实际加载修复产物时,不能将刷新后仍显示 ID 归因于接口,也不能把临时改实例的显示结果记为交付通过。
605
+
606
+ **本地制品必须按原文件提供**:按需 UMD 入口虽然包含动态 import,仍由经典 script 加载;如果本地中间件把 lib 请求转发到 Vite `/@fs/` 等模块转换链,Vite 可能注入顶层 `import { injectQuery } ...`,触发 `Cannot use import statement outside a module`,导致组件库未加载完整。开发服务应直接返回制品原字节,JS/CSS/JSON 设置正确 MIME 和禁缓存,保留入口与分片各自的加载方式。验收入口、CSS、deps/manifest 和至少一个实际分片的 HTTP 响应与磁盘文件一致,经典入口可按其格式解析,再实页点击复核。源码预注册或旧缓存可能掩盖加载失败,不能只看首页正常就认定按需库可用。另将接口空列表与渲染失败分开:例如方案发放配置为空时概览无业务卡片,应查接口与配置来源,不为填满页面改写业务数据。
607
+
587
608
  **兼容边界**:暂时不能重建的旧库由平台 Vue2 垫片在实例上包装 renderField、renderColumnOperates,提供 Vue3 可枚举插槽,并保留不可枚举的旧 scopedSlots 读取入口;不修改平台原生列表或共享方法定义。升级产品应修改源码,保留 runtime 标识,不以回挂垫片替代迁移。存在未修复分支或缺少目标环境时明确记录待验证,不能声称升级后已消除问题。
588
609
 
610
+ ### F24. 控件初值不回显/弹窗打不开:模型属性和 JSX 事件仍是 Vue2 协议
611
+
612
+ **现象与根因**:`el-switch value={enabled}` 在 Element Plus 中不会设置模型初值;即使行数据为启用,开关也可能一直显示关闭。`el-dialog visible={flag}`、旧的 `{...{ on: { 'update:visible': fn } }}`、向默认模型已经改名的组件传 `v-model:value`,同样可能构建成功但不回显、不关闭或不回写。迁移工具处理过模板不能代表 JSX 和手写 VNode 已全部适配。
613
+
614
+ **检查与修复**:
615
+
616
+ 1. 扫描所有产品组件的 template、JSX、JS/TS render 和包装组件,按目标版本实际 props/emits 核对协议。Element Plus 默认模型是 `modelValue`/`update:modelValue`,Vant 4 Dialog 显隐是 `show`/`update:show`。平台控件需逐项确认:TableForm、FormScope、ScopeDrawer 的值使用默认模型;ScopeDrawer 的显隐仍是 `visible`/`update:visible`,部分设计器控件仍公开 `value`,不能全仓替换所有 value/visible。
617
+ 2. JSX 中双向状态可用 `v-model={this.dialogVisible}`;需要确认后才提交的开关保留受控方式,如 `<el-switch modelValue={enabled} onChange={this.confirmStatusChange} />`。不要为了改成 v-model 而提前改业务行状态,或改变取消后的值。
618
+ 3. Vue3 VNode 属性与事件是平铺的:`{...{ on: { 'update:visible': fn } }}` 改为 `{...{ 'onUpdate:visible': fn }}`;`{...{ props: this.tableProps }}` 改为 `{...this.tableProps}`。目标事件改名时同步调整监听;保留业务自定义事件名与回调参数,不把 change 一律改成模型更新事件。
619
+ 4. 核对包装组件的两端:子控件 `:model-value`/`@update:model-value` 与包装组件公开的 `formData`/`update:formData` 可以不同。原生 input 的 value/input、el-option 的 value、明确公开 value 的平台控件属于正常用法。涉及 UI 库代际升级时还要核对回调签名,例如 Vant 4 beforeClose 返回 boolean/Promise,不能继续调用旧 done 回调。
620
+
621
+ **验收**:使用最终产品制品检查启用、停用、空值初态;打开、取消、关闭后再次打开;父状态与子控件同步,回调不重复。真实写入需在授权测试场景进行;可用隔离的回调替身先验证渲染与事件,明确它不等于接口保存已验证。记录扫描范围、已修项、正常保留项及待验证项,不能以全量 lint/build 通过代替交互验证。
622
+
623
+ 参考:[Vue3 v-model 迁移](https://v3-migration.vuejs.org/breaking-changes/v-model.html)、[Element Plus Switch](https://element-plus.org/en-US/component/switch)、[Dialog](https://element-plus.org/en-US/component/dialog)、[Vant Dialog](https://vant-ui.github.io/vant/#/zh-CN/dialog)。
624
+
625
+ **对象表单回显与必填联查(薪酬升级实证)**:目标 `epx-object` 公开协议为 `modelValue` / `update:modelValue` 时,模板的 `v-model:value` 会静默丢失已保存对象的回显,应改成默认 `v-model`。UI 保留 `{ id, name }`,业务 ID 字段在请求中仍发送标量 ID;不要将控件对象直接塞进 ID 字段。必填不能只判断对象 truthy:`{ id: '' }`、空数组、空白字符串均应拦截,0、false 和有效 ID 应按字段语义保留。联查读取、显示、选择、提交序列化与再次读取,空对象不得发出提交请求;未实际保存时只能记为模拟请求回归通过,不可推断后端已写入。此项与 F24 模型迁移一并检查。
626
+
627
+ ### F25. 页面有字段却查不到数据:参数占位符中的 eval 依赖局部变量
628
+
629
+ **已复现**:起薪办理页已收到 people、id、changePersonId;人员卡片及变动通知却为空。实际查询的 scopeParams.values 仍是 `$[people]`、`$[changePersonId]`。源码用 `eval` 拼接 `that.$query...` 读取参数,但最终产品制品中的 that 声明缺失,异常被 catch 后返回原占位符。把查询值换成页面已有 ID 后,同一接口立即返回人员和通知数据;不是模型绑定或接口缺数。
630
+
631
+ **自动执行流程(每次升级都执行,含已有 Vue3 产品)**:
632
+
633
+ 目标工程安装的依赖须可解析 `@babel/parser`、`@babel/traverse`、`@vue/compiler-sfc`;缺少时报告工具链问题,不临时下载依赖或跳过后宣称已扫描。脚本使用平台公开的 `$_.get`,适用本技能范围内的 epx 产品。
634
+
635
+ ```sh
636
+ node "<技能目录>/scripts/fix-eval-paths.cjs" "<前端工程>" --report ".cache/eval-paths-preview.json"
637
+ node "<技能目录>/scripts/fix-eval-paths.cjs" "<前端工程>" --apply --report ".cache/eval-paths-applied.json"
638
+ ```
639
+
640
+ 1. 预览覆盖 `src` 下 Vue 双脚本、JS/JSX、TS/TSX/MJS/CJS;跳过依赖、构建目录和符号链接。先核对目标与报告,再在已有升级授权内应用确定项,不因工作树已有修改而覆盖或清空它。
641
+ 2. AST 会追踪动态 key 是否来自 `$[...]`、`#[...]`、`#{...}` 的替换回调或路径提取,确认组件 this/只读组件别名后才改为路径读取;保留优先级、回调和配置。有效别名显式引用,缺少的 that/_this 仅在已确认组件词法 this 时替换。普通回调的 this、被重新赋值的别名、模板内 eval、业务表达式和未知来源只报告。
642
+ 3. 默认预览不改源码;`--report` 仅写指定报告。应用前完成全部源码解析并备份到目标工程 `.cache/eval-path-backups/`,只替换命中表达式,保留格式和已有修改。任一解析失败时不应用其它文件;报告保留路径、行号、前后表达式及备份位置。重复应用应无新增改动。
643
+ 4. 退出码 `0` 表示本次无遗留工具项,`2` 表示预览有可改项或仍有人工复核项,`1` 表示依赖/解析等执行失败。`--apply` 返回 `2` 时确定项可能已应用,必须读报告;不要重复应用试图清掉合法业务表达式。AI 按下方检查步骤复核并记录保留依据、已修复或待验证状态,不能把所有非零结果当成语法失败。
644
+ 5. 应用后重扫,确认自动项清零;运行目标工程 ESLint,重建产品库,用最终制品验证实际查询值和显示。维护此脚本时运行 `node "<技能目录>/scripts/fix-eval-paths.test.cjs" "<前端工程>"`;回归使用目标构建链的 esbuild 和 lodash-es,覆盖压缩后取值、回调边界、预览、备份、幂等及解析失败不写入。
645
+
646
+ **检查与修复**:
647
+
648
+ 1. 追踪进入页面的参数 → 子组件 `$query` → 格式化后的 scopeParams/跳转参数 → 实际请求 → 响应 → 组件数据/DOM。页面有 ID 不代表子组件查询使用了 ID;核对请求中是否残留 `$[...]`、`#{...}`、`#[...]`,不要先改元数据或填默认值。
649
+ 2. 扫描 template/script、JS/TS/JSX 内拼接路径的 eval,尤其是 ``eval(`that.$query.${key}`)``、``eval(`_this.${key}`)``。字符串内的变量引用不会被静态工具可靠识别,既可能掩盖源码未声明变量,也可能在清理/构建后失去声明;源码 lint 不报错不等于制品可用。
650
+ 3. 确认只是属性路径读取后,改成直接下标访问或公开的路径读取工具,例如 `this.$_.get(this.$query, key)`、`this.$_.get(this, valueKey)`,保留原查找优先级和业务值语义。嵌套路径、可选上下文及含 `|` 的字段不能用简单拆字符串替代。无声明的 that/_this 应按真实词法上下文改用 this,不重新引入供 eval 使用的别名。
651
+ 4. 按用途区分业务条件、公式计算及配置对象解析中的 eval;它们可能接受完整表达式,不能当路径读取批量替换成 get,也不能仅为消除构建警告删除。保留的表达式仍要检查上下文:若只通过字符串使用 that,构建后可能同样丢失变量,导致工资项/操作区渲染异常。确认当前方法及嵌套箭头函数的 this 均指向组件后,可将表达式前缀改为 this,保留表达式本身及其真值规则;不能在改变 this 的普通回调中机械套用。记录未处理类别与验证边界。
652
+
653
+ **验收**:用最终产品制品打开真实页面,确认请求值等于当前人员/记录 ID,响应、组件数据与可见内容一致;验证参数替换、嵌套路径、缺省上下文及原配置未被改写。源码单测通过后仍需制品实页复验,防止优化阶段再次产生差异。只读排障不执行发起、审批、保存等写操作;无法定位截图对应记录时说明使用的是同页可复现记录。
654
+
655
+ ### F26. 旧共享入口有基类,独立包缺导出,继承组件初始化白屏
656
+
657
+ **已复现**:测试宿主中 `window.frame.Step`、`StepItem` 已有真身,`__EPX_PACKAGES__['@epxing/components']` 却没有这两个导出。产品按需分片从独立共享包同步取值,实际生成 `extends: undefined`,随后 created 读取 upPage.component、render 读取 $component.style 接连失败。本地安装完整独立包会掩盖差异;这类堆栈不能直接归因于业务数据或一律改成空对象兜底。
658
+
659
+ **检查**:在用户指定宿主的新会话中读取最终 deps 清单与共享 shim 的实际包映射,核对需要继承的导出是否存在、是否为同步组件真身;同时比较旧入口与独立共享包。不能只检查包对象存在、npm 版本或 app 的组件注册数。源码与分片使用同一哈希也不能证明宿主导出齐全。
660
+
661
+ **根因与修法**:平台按需预加载把真身写回旧入口,却漏了独立命名空间。统一在首次解析和缓存命中时原地发布真身,保留命名空间与组件对象身份;可选 designer 基类按包归属发布,解析失败不发布 undefined。同步检查其它产品依赖的懒导出,避免只补 Step 名字。该问题需平台修复与宿主发布,产品参数修复脚本不负责改宿主。
662
+
663
+ **验收与状态**:覆盖首次 ensure、缓存回填、未解析组件与可选包归属的回归;使用目标宿主验证继承上下文及详情渲染。ESM 分片可能已捕获 undefined,补齐注册表后须新会话重新加载。浏览器临时补映射只能证明根因,刷新撤销,单独记录;不能记为平台已发布或用户页面已永久恢复。发布后才对原入口关闭此项。
664
+
665
+ ### F27. 当前角色 ID/code 统一使用平台角色上下文
666
+
667
+ **迁移契约**:项目组件获取当前角色 ID 使用 `this.$currentRoleId`,也可使用 `this.$currentRole?.id`;编号使用 `this.$currentRole?.code`。平台统一兼容旧地址及不带角色的新菜单地址,并从当前应用角色列表匹配 ID。产品不自行解析 URL,不读 localStorage/sessionStorage 等角色缓存,不自行请求角色列表做当前角色匹配,也不取账号角色列表第一项代替当前角色。
668
+
669
+ 继承平台列表、表单等基类,或已经接入 `mixins.component`/`ComponentMixin` 的组件直接使用,避免重复混入导致初始化或请求重复。普通自定义 Options API 组件按公共接口接入:
670
+
671
+ ```js
672
+ import { ComponentMixin } from '@epxing/core'
673
+
674
+ export default {
675
+ mixins: [ComponentMixin],
676
+ computed: {
677
+ roleId () {
678
+ return this.$currentRoleId
679
+ },
680
+ roleCode () {
681
+ return this.$currentRole?.code || ''
682
+ }
683
+ }
684
+ }
685
+ ```
686
+
687
+ **执行升级时扫描并修复**:
688
+
689
+ 1. 覆盖 template、script、JS/TS/JSX/TSX、共享方法和继承链,定位当前角色的来源与消费点:`$route.params` 的角色参数、path/hash 分段、当前角色存储项、按编号查询/遍历角色列表,以及 `roleId`、`roleCode`、`curRole` 等传参。命中后追踪用途,不能仅按变量名全仓替换。
690
+ 2. 确认为当前角色后,ID 查询、角色 ID 范围筛选及页签参数使用平台 ID,编号接口及编号比较使用平台 code。保留 ID/code 原有业务区别,不把编号填进 ID 字段;同时检查跳转参数、工资方案筛选、字段权限及菜单后续请求,不能只改一个显示值。
691
+ 3. 删除只为解析当前角色而存在的地址拆分、角色列表请求及缓存读取;若列表还用于角色选择、管理或业务展示则保留相应用途。角色管理表单中的被编辑角色、用户选择的目标角色、业务记录角色字段不属于当前角色,不能强制替换。
692
+ 4. 在请求发生时读取上下文;需要随角色变化的派生值使用 computed,不在 data 中保存一次快照。按组件实际初始化和复用链路核对角色切换后的重新取数,避免重复加 watcher。上下文缺失时检查目标宿主 API/基类/混入及角色加载时序,不退回解析地址、读缓存或额外匹配角色,也不以静默跳过请求掩盖问题。
693
+
694
+ **验收**:最终制品覆盖旧含角色地址、新不含角色的菜单地址、直接进入/刷新、多应用及切换角色后的缓存组件复用;核对实际请求中的 roleId/roleCode、ID 范围筛选和编号权限判断。宿主未提供新 API 时明确记录依赖及待发布状态。源码检查或单个角色页面通过不能代替这些场景,也不能改写角色权限数据来让请求成功。
695
+
696
+ ### F28. 审核详情空白、加载遮罩残留与懒加载页签计数
697
+
698
+ 审核列表/卡片已返回数据但详情空白时,先捕获首条异常及实际接收组件。平台表单会下发 `form` 实例;未声明为 prop 的接收组件在 Vue3 自动继承 attrs 时可能将实例写到原生 DOM,报 `setAttribute: Cannot convert object to primitive value`,后续可能连带出现 null vnode/parentNode 异常。工资审核页实证接收端为平台 Card、EntityList(含产品派生列表)。修复接收端声明 `form: Object`,保留 class/style 继承及上下文;不要 stringify 实例、吞渲染异常或批量关闭 inheritAttrs。扫描同一传参链的其他接收端,以 null 原型对象或真实 Vue 实例验证 form 不落 DOM、外部样式仍落根;平台源码修复须另核对目标宿主发布版本,不能只发布产品包。
699
+
700
+ 若加载变量已 false 但遮罩仍在,检查实际可见节点、VNode 分支及指令生命周期。JSX/render 在有/无数据分支复用同类型根 div,仅空分支带 v-loading 时可能留下旧遮罩;给互斥分支不同 key 或统一指令绑定,验证加载→内容、空结果及请求失败,不以手动删除遮罩验收。同时检查异步链空结果是否结束加载。模板 v-if 的编译器处理与手写 render 不同,静态命中不等于缺陷;按实际分支检查。
701
+
702
+ 页签计数读取子项配置前,核对懒加载实例及 itemConfigs 是否就绪。配置尚未就绪时延后该项统计,不能用空配置发起无范围查询,也不能将计数异常认作后端无数据。已有模板/实例配置来源按当前平台契约保留。
703
+
704
+ **懒加载页签不可只验默认页**:同一 form 透传链必须逐个激活业务页签和嵌套页签,记录组件名、首条异常和实际内容。工资“办理→流程信息”实证还涉及 CheckHistory、ProcessChart,不能仅补 Card/EntityList 就认定共性扫描完成。流程信息应核对审核记录节点及流程图实际内容,工资明细应核对列表/嵌入页,空内容要区分接口空结果、权限和渲染失败。比较目标宿主运行时 props 与源码声明,旧制品仍缺声明和源码遗漏是两个独立结论;不以补默认页、临时透传实验或单个字段正常代替全部页签验收。
705
+
706
+ 验收分别记录平台/产品源码、自动化与临时浏览器验证、正式发布状态;审批按钮进入详情只作只读验证,不执行同意、退回或提交来证明修复。
707
+
589
708
  ## G. 产品升级的跨会话缺陷回归
590
709
 
591
710
  本节从近期产品故障的提交及交接记录提炼检查场景,补充 E2 的逐组件主题验收。历史记录证明曾出现过这些问题,不证明当前目标环境已发布修复;其中只有临时浏览器实验、单测或尚待现场验证的结论,不能提升为已部署通过。具体来源及证据层次保留在平台仓 `docs/handoff/tasks/` 的原任务中,技能只保留可复用检查方法。
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ // 将参数占位符中的动态属性读取迁移为公开路径读取;业务表达式只报告。
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+ const { createRequire } = require('node:module')
6
+
7
+ function dependencies (project) {
8
+ const requireProject = createRequire(path.join(project, 'package.json'))
9
+ try {
10
+ return {
11
+ parser: requireProject('@babel/parser'),
12
+ traverse: requireProject('@babel/traverse').default,
13
+ sfc: requireProject('@vue/compiler-sfc')
14
+ }
15
+ } catch (error) {
16
+ throw new Error('需要目标工程已安装的 @babel/parser、@babel/traverse 和 @vue/compiler-sfc;未自动安装依赖。' + error.message)
17
+ }
18
+ }
19
+
20
+ function nameOf (node) {
21
+ return node?.name || node?.value
22
+ }
23
+
24
+ function componentObject (nodePath) {
25
+ if (!nodePath?.isObjectExpression()) return false
26
+ const parent = nodePath.parentPath
27
+ return parent?.isExportDefaultDeclaration() ||
28
+ (parent?.isCallExpression() && nameOf(parent.node.callee) === 'defineComponent' &&
29
+ parent.node.arguments[0] === nodePath.node)
30
+ }
31
+
32
+ function thisOwner (nodePath) {
33
+ let fn = nodePath.getFunctionParent()
34
+ while (fn?.isArrowFunctionExpression()) fn = fn.getFunctionParent()
35
+ if (!fn || (!fn.isObjectMethod() && !fn.isFunctionExpression())) return null
36
+ let member = fn.isObjectMethod() ? fn : fn.parentPath
37
+ if (!member?.isObjectMethod() && !member?.isObjectProperty()) return null
38
+ // 普通嵌套函数会改变 this,不能仅因位于组件文件中就自动处理。
39
+ let object = member.parentPath
40
+ for (let level = 0; level < 4 && object?.isObjectExpression(); level++) {
41
+ if (componentObject(object)) {
42
+ return ['methods', 'computed', 'watch', 'created', 'mounted', 'render',
43
+ 'beforeCreate', 'beforeMount', 'updated', 'beforeUpdate',
44
+ 'unmounted', 'beforeUnmount', 'data', 'setup'].includes(nameOf(member.node.key))
45
+ ? (nameOf(member.node.key) === 'setup' ? null : fn)
46
+ : null
47
+ }
48
+ member = object.parentPath
49
+ if (!member?.isObjectProperty()) return null
50
+ object = member.parentPath
51
+ }
52
+ return null
53
+ }
54
+
55
+ function placeholderRegex (node) {
56
+ return node?.type === 'RegExpLiteral' &&
57
+ /(?:\\\$\\\[|#\\\[|#\\\{)/.test(node.pattern)
58
+ }
59
+
60
+ function replaceCall (node) {
61
+ return node?.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
62
+ !node.callee.computed && node.callee.property.name === 'replace' &&
63
+ placeholderRegex(node.arguments[0])
64
+ }
65
+
66
+ function placeholderKey (argPath) {
67
+ if (!argPath.isIdentifier()) return false
68
+ const binding = argPath.scope.getBinding(argPath.node.name)
69
+ if (!binding || !binding.constant) return false
70
+ const fn = binding.scope.path
71
+ if (binding.kind === 'param' && fn.isFunction() &&
72
+ fn.node.params[1]?.type === 'Identifier' && fn.node.params[1].name === argPath.node.name &&
73
+ replaceCall(fn.parentPath.node) && fn.parentPath.node.arguments[1] === fn.node) return true
74
+ if (!binding.path.isVariableDeclarator() || !replaceCall(binding.path.node.init)) return false
75
+ const callback = binding.path.node.init.arguments[1]
76
+ if (callback?.type === 'StringLiteral') return callback.value === '$1'
77
+ if (!callback || !['ArrowFunctionExpression', 'FunctionExpression'].includes(callback.type)) return false
78
+ const capture = callback.params[1]
79
+ if (capture?.type !== 'Identifier') return false
80
+ const body = callback.body.type === 'BlockStatement'
81
+ ? (callback.body.body.length === 1 && callback.body.body[0].type === 'ReturnStatement'
82
+ ? callback.body.body[0].argument : null)
83
+ : callback.body
84
+ return body?.type === 'Identifier' && body.name === capture.name
85
+ }
86
+
87
+ function contextFor (callPath, root) {
88
+ if (root === 'this') return thisOwner(callPath) ? { root } : null
89
+ const binding = callPath.scope.getBinding(root)
90
+ if (binding) {
91
+ if (!binding.constant || !binding.path.isVariableDeclarator() ||
92
+ binding.path.node.init?.type !== 'ThisExpression' || !thisOwner(binding.path)) return null
93
+ return { root }
94
+ }
95
+ // 只在已证实属于组件的词法 this 中修复旧代码缺少的常见别名。
96
+ return ['that', '_this'].includes(root) && thisOwner(callPath) ? { root: 'this' } : null
97
+ }
98
+
99
+ function analyzeCode (code, { filename, lang = '', offset = 0, source = code }, deps) {
100
+ const plugins = []
101
+ if (/tsx?$/.test(lang) || /\.[cm]?tsx?$/.test(filename)) plugins.push('typescript')
102
+ if (lang !== 'ts' && !/\.[cm]?ts$/.test(filename)) plugins.push('jsx')
103
+ const ast = deps.parser.parse(code, { sourceType: 'unambiguous', plugins })
104
+ const edits = []
105
+ const findings = []
106
+ deps.traverse(ast, {
107
+ CallExpression (callPath) {
108
+ const node = callPath.node
109
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'eval') return
110
+ // 同名业务函数不是全局 eval。
111
+ if (callPath.scope.getBinding('eval')) return
112
+ const finding = {
113
+ file: filename,
114
+ line: source.slice(0, offset + node.start).split('\n').length,
115
+ kind: 'review',
116
+ reason: '表达式、配置解析或未知路径来源,保留原代码并人工核对上下文。',
117
+ before: code.slice(node.start, node.end)
118
+ }
119
+ const value = node.arguments[0]
120
+ if (node.arguments.length === 1 && value?.type === 'TemplateLiteral' &&
121
+ value.expressions.length === 1 && value.quasis[1].value.cooked === '') {
122
+ const prefix = value.quasis[0].value.cooked
123
+ const match = /^(this|[A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*\.$/.exec(prefix)
124
+ const exprPath = callPath.get('arguments.0.expressions.0')
125
+ if (match && placeholderKey(exprPath)) {
126
+ const context = contextFor(callPath, match[1])
127
+ if (context) {
128
+ const object = context.root + prefix.slice(match[1].length, -1)
129
+ const key = code.slice(value.expressions[0].start, value.expressions[0].end)
130
+ const after = context.root + '.$_.get(' + object + ', ' + key + ')'
131
+ edits.push({ start: offset + node.start, end: offset + node.end, text: after })
132
+ Object.assign(finding, { kind: 'automatic', after, reason: '参数占位符路径读取,上下文已确认。' })
133
+ } else {
134
+ finding.reason = '参数占位符命中,但别名或 this 上下文无法确认。'
135
+ }
136
+ }
137
+ }
138
+ findings.push(finding)
139
+ }
140
+ })
141
+ return { edits, findings }
142
+ }
143
+
144
+ function analyzeFile (filename, source, deps) {
145
+ if (!filename.endsWith('.vue')) return analyzeCode(source, { filename }, deps)
146
+ const parsed = deps.sfc.parse(source, { filename })
147
+ if (parsed.errors.length) throw new Error(parsed.errors.map(e => e.message || e).join('; '))
148
+ const result = { edits: [], findings: [] }
149
+ for (const block of [parsed.descriptor.script, parsed.descriptor.scriptSetup]) {
150
+ if (!block) continue
151
+ if (block.src) {
152
+ result.findings.push({ file: filename, line: block.loc.start.line, kind: 'review', reason: '外部 script src 需核对是否在本次 src 扫描范围内。' })
153
+ continue
154
+ }
155
+ const part = analyzeCode(block.content, {
156
+ filename, lang: block.lang || '', offset: block.loc.start.offset, source
157
+ }, deps)
158
+ result.edits.push(...part.edits)
159
+ result.findings.push(...part.findings)
160
+ }
161
+ const template = parsed.descriptor.template
162
+ for (const match of (template?.content || '').matchAll(/\beval\s*\(/g)) {
163
+ result.findings.push({ file: filename, line: template.loc.start.line + template.content.slice(0, match.index).split('\n').length - 1, kind: 'review', reason: '模板内 eval 需人工核对模板上下文。' })
164
+ }
165
+ return result
166
+ }
167
+
168
+ function sourceFiles (directory) {
169
+ const result = []
170
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
171
+ if (entry.isSymbolicLink()) continue
172
+ const file = path.join(directory, entry.name)
173
+ if (entry.isDirectory() && !['node_modules', '.git', '.cache', 'dist', 'lib'].includes(entry.name)) result.push(...sourceFiles(file))
174
+ else if (entry.isFile() && /\.(vue|[cm]?js|jsx|[cm]?ts|tsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) result.push(file)
175
+ }
176
+ return result
177
+ }
178
+
179
+ function migrate ({ project, apply = false, reportFile }, deps = dependencies(project)) {
180
+ project = path.resolve(project)
181
+ const src = path.join(project, 'src')
182
+ if (!fs.existsSync(src)) throw new Error('目标必须是包含 src 的前端工程:' + project)
183
+ const files = sourceFiles(src)
184
+ const pending = []
185
+ const findings = []
186
+ const errors = []
187
+ for (const file of files) {
188
+ const source = fs.readFileSync(file, 'utf8')
189
+ try {
190
+ const result = analyzeFile(file, source, deps)
191
+ findings.push(...result.findings)
192
+ if (result.edits.length) {
193
+ let changed = source
194
+ for (const edit of result.edits.sort((a, b) => b.start - a.start)) changed = changed.slice(0, edit.start) + edit.text + changed.slice(edit.end)
195
+ pending.push({ file, source, changed })
196
+ }
197
+ } catch (error) {
198
+ errors.push({ file, message: error.message })
199
+ }
200
+ }
201
+ const report = {
202
+ version: 1, project, mode: apply ? 'apply' : 'preview', filesScanned: files.length,
203
+ automatic: findings.filter(f => f.kind === 'automatic').length,
204
+ review: findings.filter(f => f.kind === 'review').length,
205
+ filesChanged: [], findings, errors
206
+ }
207
+ // 先完成全部解析;任一文件失败时不修改源码。
208
+ if (apply && !errors.length && pending.length) {
209
+ const backupRoot = path.join(project, '.cache', 'eval-path-backups')
210
+ fs.mkdirSync(backupRoot, { recursive: true })
211
+ const backup = fs.mkdtempSync(path.join(backupRoot, 'run-'))
212
+ for (const entry of pending) {
213
+ const target = path.join(backup, path.relative(project, entry.file))
214
+ fs.mkdirSync(path.dirname(target), { recursive: true })
215
+ fs.writeFileSync(target, entry.source)
216
+ }
217
+ // 防止扫描到写入之间覆盖其它人的新改动。
218
+ for (const entry of pending) if (fs.readFileSync(entry.file, 'utf8') !== entry.source) throw new Error('扫描后文件已变化,未写入源码:' + entry.file)
219
+ for (const entry of pending) fs.writeFileSync(entry.file, entry.changed)
220
+ report.backup = backup
221
+ report.filesChanged = pending.map(entry => entry.file)
222
+ }
223
+ report.exitCode = errors.length ? 1 : ((report.review || (!apply && report.automatic)) ? 2 : 0)
224
+ if (reportFile) {
225
+ fs.mkdirSync(path.dirname(path.resolve(reportFile)), { recursive: true })
226
+ fs.writeFileSync(reportFile, JSON.stringify(report, null, 2) + '\n')
227
+ }
228
+ return report
229
+ }
230
+
231
+ if (require.main === module) {
232
+ try {
233
+ const args = process.argv.slice(2)
234
+ if (args.includes('--help')) {
235
+ console.log('用法:node fix-eval-paths.cjs <前端工程> [--apply] [--report <JSON文件>]\n默认只读预览;--apply 先备份后改写。退出码:0 已处理,2 有待修复/待复核项,1 解析或依赖失败。')
236
+ } else {
237
+ const options = { project: null, apply: false }
238
+ for (let i = 0; i < args.length; i++) {
239
+ if (args[i] === '--apply') options.apply = true
240
+ else if (args[i] === '--report' && args[i + 1] && !args[i + 1].startsWith('--')) options.reportFile = args[++i]
241
+ else if (!args[i].startsWith('--') && !options.project) options.project = path.resolve(args[i])
242
+ else throw new Error('未知或不完整参数:' + args[i])
243
+ }
244
+ if (!options.project) throw new Error('请指定前端工程;使用 --help 查看用法。')
245
+ const report = migrate(options)
246
+ console.log(JSON.stringify(report, null, 2))
247
+ process.exitCode = report.exitCode
248
+ }
249
+ } catch (error) {
250
+ console.error(error.message)
251
+ process.exitCode = 1
252
+ }
253
+ }
254
+
255
+ module.exports = { dependencies, analyzeCode, analyzeFile, migrate }
@@ -1,4 +1,18 @@
1
- import{a as r}from"./OVN5O3EZ.mjs";function c(i="5.3.0-beta.2"){let s=i.match(/^[~^]?(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?$/);if(!s)return!1;let[,p,t,e,n]=s,o=Number(p)*1e6+Number(t)*1e3+Number(e);return o!==5003e3?o>5003e3:!n||/^beta\.(\d+)$/.test(n)&&Number(n.slice(5))>=2}r(c,"usesAutomaticStyles");var m=["@epxing/core/style.css","@epxing/components/style.css","@epxing/business/style.css"],a={main:`import { createApp } from '@epxing/core'
1
+ import{a as o}from"./OVN5O3EZ.mjs";var a=`import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { spawnSync } from 'node:child_process'
4
+
5
+ const require = createRequire(import.meta.url)
6
+ const args = process.argv.slice(2)
7
+ const full = args.includes('--full') || process.env.npm_config_full === 'true'
8
+ const lazy = full ? 'false' : (process.env.LAZY_LIB || 'true')
9
+ const vite = path.join(path.dirname(require.resolve('vite/package.json')), 'bin/vite.js')
10
+ const result = spawnSync(process.execPath, [vite, 'build', ...args.filter(arg => arg !== '--full')], {
11
+ stdio: 'inherit', env: { ...process.env, LAZY_LIB: lazy }
12
+ })
13
+ if (result.error) throw result.error
14
+ process.exit(result.status ?? 1)
15
+ `;function c(n="5.3.0-beta.2"){let i=n.match(/^[~^]?(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?$/);if(!i)return!1;let[,r,t,e,s]=i,p=Number(r)*1e6+Number(t)*1e3+Number(e);return p!==5003e3?p>5003e3:!s||/^beta\.(\d+)$/.test(s)&&Number(s.slice(5))>=2}o(c,"usesAutomaticStyles");var l=["@epxing/core/style.css","@epxing/components/style.css","@epxing/business/style.css"],m={main:`import { createApp } from '@epxing/core'
2
16
  import Components from '@epxing/components'
3
17
  import Business from '@epxing/business'
4
18
  import '@epxing/theme'
@@ -26,10 +40,10 @@ const count = ref(0)
26
40
  <style scoped>
27
41
  .product-start { padding: var(--epx-space-4, 24px); color: var(--epx-text); }
28
42
  </style>
29
- `};function u(i,s,p){let t=r(n=>(n||"").replaceAll(`\r
43
+ `};function g(n,i,r){let t=o(s=>(s||"").replaceAll(`\r
30
44
  `,`
31
- `).trim(),"normalize"),e=t(i);for(let n of m)e=e.replace(`import '${n}'
32
- `,"");return e===t(a.main)&&t(s)===t(a.app.replaceAll("__NAME__",p))}r(u,"isLegacyWelcomeProduct");function d(i,s){let p={...i,"package.json":JSON.stringify({name:"__NAME__",version:"0.1.0",private:!0,runtime:"vue3",type:"module",scripts:{dev:"vite","build:lib":"vite build","build:lib:lazy":"cross-env LAZY_LIB=true vite build","build:app":"vite build --mode app",preview:"vite preview"},dependencies:{"@epxing/core":"__PACKAGES_VERSION__","@epxing/components":"__PACKAGES_VERSION__","@epxing/utils":"__PACKAGES_VERSION__","@epxing/business":"__PACKAGES_VERSION__","@epxing/theme":"__PACKAGES_VERSION__",vue:"3.5.39","vue-router":"4.6.4",vuex:"4.1.0","element-plus":"2.14.3"},devDependencies:{"@epxing/cli":"__CLI_VERSION__","@epxing/icons":"__PACKAGES_VERSION__",vite:"^6.0.0","@vitejs/plugin-vue":"^5.2.1","@vitejs/plugin-vue-jsx":"^4.1.1",sass:"^1.83.3","cross-env":"^7.0.3"}},null,2)+`
45
+ `).trim(),"normalize"),e=t(n);for(let s of l)e=e.replace(`import '${s}'
46
+ `,"");return e===t(m.main)&&t(i)===t(m.app.replaceAll("__NAME__",r))}o(g,"isLegacyWelcomeProduct");function x(n,i){let r={...n,"build-lib.mjs":a,"package.json":JSON.stringify({name:"__NAME__",version:"0.1.0",private:!0,runtime:"vue3",type:"module",scripts:{dev:"vite","build:lib":"node build-lib.mjs","build:lib:full":"node build-lib.mjs --full","build:lib:lazy":"cross-env LAZY_LIB=true vite build","build:app":"vite build --mode app",preview:"vite preview"},dependencies:{"@epxing/core":"__PACKAGES_VERSION__","@epxing/components":"__PACKAGES_VERSION__","@epxing/utils":"__PACKAGES_VERSION__","@epxing/business":"__PACKAGES_VERSION__","@epxing/theme":"__PACKAGES_VERSION__",vue:"3.5.39","vue-router":"4.6.4",vuex:"4.1.0","element-plus":"2.14.3"},devDependencies:{"@epxing/cli":"__CLI_VERSION__","@epxing/icons":"__PACKAGES_VERSION__",vite:"^6.0.0","@vitejs/plugin-vue":"^5.2.1","@vitejs/plugin-vue-jsx":"^4.1.1",sass:"^1.83.3","cross-env":"^7.0.3"}},null,2)+`
33
47
  `,"vite.config.mjs":`import path from 'node:path'
34
48
  import { fileURLToPath } from 'node:url'
35
49
  import { createRequire } from 'node:module'
@@ -121,9 +135,9 @@ npm run build:app
121
135
  \`\`\`
122
136
 
123
137
  \u672C\u5730\u9884\u89C8\u9ED8\u8BA4\u7AEF\u53E3 8080\uFF0C\u9ED8\u8BA4\u6253\u5F00\u5E73\u53F0\u7CFB\u7EDF\u3002\u542F\u52A8\u524D\u5728 .env.development.local \u914D\u7F6E\u540E\u7AEF\u53CA\u5BA2\u6237\u7AEF\u3001\u7CFB\u7EDF\u3001\u5E94\u7528\u53C2\u6570\uFF1B\u767B\u5F55\u3001\u83DC\u5355\u548C\u9996\u9875\u4F7F\u7528\u5E73\u53F0\u914D\u7F6E\u3002
124
- `,"PRODUCT-GUIDE.md":l};if(!c(s)){let t=m.map(e=>`import '${e}'`).join(`
125
- `);for(let e of["src/main.js","PRODUCT-GUIDE.md"])p[e]=p[e].replace("import '@epxing/theme'",`import '@epxing/theme'
126
- `+t)}return p}r(d,"createPackageTemplates");var l=`# \u4EA7\u54C1\u5F00\u53D1\u4E0E\u5347\u7EA7\u4F7F\u7528\u8BF4\u660E
138
+ `,"PRODUCT-GUIDE.md":u};if(!c(i)){let t=l.map(e=>`import '${e}'`).join(`
139
+ `);for(let e of["src/main.js","PRODUCT-GUIDE.md"])r[e]=r[e].replace("import '@epxing/theme'",`import '@epxing/theme'
140
+ `+t)}return r}o(x,"createPackageTemplates");var u=`# \u4EA7\u54C1\u5F00\u53D1\u4E0E\u5347\u7EA7\u4F7F\u7528\u8BF4\u660E
127
141
 
128
142
  <!-- epxing-generated-product-guide -->
129
143
 
@@ -199,7 +213,8 @@ export default {
199
213
  npm run build:app # dist/\uFF1A\u72EC\u7ACB\u5E94\u7528\uFF0C\u4F7F\u7528 npm \u5305
200
214
  npx epxing lib # lib/\uFF1A\u9ED8\u8BA4\u6309\u9700\u4EA7\u54C1\u5E93
201
215
  npx epxing lib --full # lib/\uFF1A\u5B8C\u6574\u4EA7\u54C1\u5E93
202
- npm run build:lib # \u76F4\u63A5 Vite \u5B8C\u6574\u5E93\u6784\u5EFA
216
+ npm run build:lib # \u9ED8\u8BA4\u61D2\u52A0\u8F7D\uFF0C\u4E0A\u4F20\u6574\u4E2A lib/ \u76EE\u5F55
217
+ npm run build:lib --full # \u663E\u5F0F\u5168\u91CF\u6784\u5EFA\uFF0C\u4E5F\u53EF\u7528 build:lib:full
203
218
  npm run build:lib:lazy # \u663E\u5F0F\u6309\u9700\u5E93\u6784\u5EFA
204
219
  \`\`\`
205
220
 
@@ -232,4 +247,4 @@ epxing --version
232
247
  \`\`\`
233
248
 
234
249
  \u6D4B\u8BD5\u7248\u4F7F\u7528 beta \u6807\u7B7E\uFF1B\u4EA7\u54C1\u5C06 @epxing \u5E73\u53F0\u5305\u56FA\u5B9A\u5728\u540C\u4E00\u5DF2\u9A8C\u8BC1\u7248\u672C\u3002CLI \u66F4\u65B0\u4E0D\u4F1A\u81EA\u52A8\u6539\u4EA7\u54C1\u4F9D\u8D56\uFF0C\u6309 migrate \u9884\u89C8\u3001\u5BA1\u9605\u3001\u5E94\u7528\u3001\u5B89\u88C5\u3001\u6784\u5EFA\u548C\u9A8C\u6536\u7684\u987A\u5E8F\u66F4\u65B0\u3002
235
- `;export{c as a,m as b,u as c,d};
250
+ `;export{a,c as b,l as c,g as d,x as e};