diff --git a/openhis-ui-vue3/scripts/patch-deps.js b/openhis-ui-vue3/scripts/patch-deps.js new file mode 100644 index 000000000..59c081da9 --- /dev/null +++ b/openhis-ui-vue3/scripts/patch-deps.js @@ -0,0 +1,138 @@ +/** + * patch-deps.js — 统一修补 node_modules 依赖 + * + * 解决 Vue 3 + Vite 项目中依赖兼容性问题。 + * 每次 npm install 后由 postinstall 自动执行,也可手动运行: + * node scripts/patch-deps.js + * + * 修补清单: + * 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容 + * 2. element-plus form-label-wrap.mjs — NaN 防护 + 生命周期守卫 + * + * 特性:幂等(safe to run multiple times) + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); + +function patchFile(filePath, marker, patcher) { + if (!fs.existsSync(filePath)) { + console.log(`[patch-deps] SKIP (not found): ${path.relative(ROOT, filePath)}`); + return false; + } + const code = fs.readFileSync(filePath, 'utf-8'); + if (code.includes(marker)) { + console.log(`[patch-deps] OK (already patched): ${path.relative(ROOT, filePath)}`); + return false; + } + const patched = patcher(code); + if (patched === code) { + console.log(`[patch-deps] SKIP (no change): ${path.relative(ROOT, filePath)}`); + return false; + } + fs.writeFileSync(filePath, patched, 'utf-8'); + console.log(`[patch-deps] PATCHED: ${path.relative(ROOT, filePath)}`); + return true; +} + +// ────────────────────────────────────────────── +// 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容 +// ────────────────────────────────────────────── +// 根因:Object.prototype.hasOwnProperty.call(proxyObj, key) 在 Vue 3 reactive +// Proxy 上会触发 reactivity 拦截,抛出 "obj.hasOwnProperty is not a function"。 +// 修复:try-catch + key-in fallback。 +// ────────────────────────────────────────────── +patchFile( + path.join(ROOT, 'node_modules/xe-utils/hasOwnProp.js'), + '[vue3-proxy-safe]', + () => `/** + * Check if object has own property - Vue 3 Proxy safe [vue3-proxy-safe] + * + * Patched by scripts/patch-deps.js — do not edit manually. + * Re-run: node scripts/patch-deps.js + */ +function hasOwnProp (obj, key) { + if (obj == null) return false + try { + return Object.prototype.hasOwnProperty.call(obj, key) + } catch (e) { + try { + return key in Object(obj) + } catch (e2) { + return false + } + } +} + +module.exports = hasOwnProp +` +); + +// ────────────────────────────────────────────── +// 2. element-plus form-label-wrap.mjs — NaN + 生命周期守卫 +// ────────────────────────────────────────────── +// 根因:vxe-table 展开行收起时 el-form 组件已卸载,但 nextTick/onUpdated +// 回调仍尝试访问已销毁的 formContext,导致 NaN width 和各种 teardown 错误。 +// 修复:NaN 防护 + _isMounted 生命周期守卫 + try-catch。 +// ────────────────────────────────────────────── +patchFile( + path.join(ROOT, 'node_modules/element-plus/es/components/form/src/form-label-wrap.mjs'), + '_isMounted', + (code) => { + let patched = code + // NaN 防护 + .replace( + 'return Math.ceil(Number.parseFloat(width))', + 'return Math.ceil(Number.parseFloat(width)) || 0' + ) + // 添加 _isMounted 守卫变量 + .replace( + 'const updateLabelWidth = (action = "update") => {', + 'let _isMounted = true;\n\tconst updateLabelWidth = (action = "update") => {' + ) + // nextTick 回调前置守卫 + .replace( + 'nextTick(() => {', + 'nextTick(() => {\n\t\t\t\tif (!_isMounted) return;' + ) + // try-catch 包裹核心逻辑 + .replace( + 'if (slots.default && props.isAutoWidth) {', + 'try {\n\t\t\t\tif (slots.default && props.isAutoWidth) {' + ) + .replace( + 'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);', + 'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);\n\t\t\t\t}\n\t\t\t} catch (e) { /* teardown race */ }' + ) + // onBeforeUnmount 设置守卫 + .replace( + 'onBeforeUnmount(() => {', + 'onBeforeUnmount(() => {\n\t\t\t_isMounted = false;' + ) + // onUpdated 守卫 + .replace( + 'onUpdated(() => updateLabelWidthFn())', + 'onUpdated(() => { if (_isMounted) updateLabelWidthFn(); })' + ) + // watch 守卫 + .replace( + 'if (props.updateAll) formContext?.registerLabelWidth(val, oldVal);', + 'if (_isMounted && props.updateAll) formContext?.registerLabelWidth(val, oldVal);' + ) + // render 函数守卫 + .replace( + 'return () => {', + 'return () => {\n\t\t\tif (!_isMounted) return null;' + ); + return patched; + } +); + +// ────────────────────────────────────────────── +// 完成 +// ────────────────────────────────────────────── +console.log('[patch-deps] Done.'); \ No newline at end of file