diff --git a/openhis-ui-vue3/src/patches/patch-deps-plugin.js b/openhis-ui-vue3/src/patches/patch-deps-plugin.js new file mode 100644 index 000000000..ab4169ff3 --- /dev/null +++ b/openhis-ui-vue3/src/patches/patch-deps-plugin.js @@ -0,0 +1,143 @@ +/** + * Vite 插件:在构建时拦截依赖模块加载,返回兼容 Vue 3 的补丁版本。 + * + * ⚠️ 不修改 node_modules 中的任何文件。 + * + * 拦截清单: + * 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容 + * 2. element-plus form-label-wrap.mjs — NaN 防护 + 生命周期守卫 + */ + +const VIRTUAL_HASOWNPROP = '\0patched:xe-utils/hasOwnProp' +const VIRTUAL_FORM_LABEL = '\0patched:element-plus/form-label-wrap' + +export default function patchDepsPlugin() { + return { + name: 'patch-deps-vue3-compat', + enforce: 'pre', + + // ── resolveId: 拦截模块 ID,返回虚拟模块路径 ── + resolveId(source, importer) { + // 拦截 xe-utils 内部的 require('./hasOwnProp') + if ( + source === './hasOwnProp' && + importer && + importer.includes('xe-utils') + ) { + return VIRTUAL_HASOWNPROP + } + // 拦截 element-plus form-label-wrap(完整路径匹配) + if ( + source.includes('element-plus') && + source.includes('form-label-wrap') + ) { + return VIRTUAL_FORM_LABEL + } + }, + + // ── load: 对被拦截的模块返回补丁代码 ── + load(id) { + if (id === VIRTUAL_HASOWNPROP) { + return PATCHED_HASOWNPROP + } + if (id === VIRTUAL_FORM_LABEL) { + return PATCHED_FORM_LABEL_WRAP + } + }, + } +} + +// ═══════════════════════════════════════════════ +// 补丁 1:xe-utils hasOwnProp — Proxy 兼容 +// ═══════════════════════════════════════════════ +// 根因:Object.prototype.hasOwnProperty.call(proxyObj, key) +// 在 Vue 3 reactive Proxy 上触发 reactivity 拦截, +// 抛出 "obj.hasOwnProperty is not a function"。 +// ═══════════════════════════════════════════════ +const PATCHED_HASOWNPROP = ` +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 } + } +} +export default hasOwnProp +export { hasOwnProp } +` + +// ═══════════════════════════════════════════════ +// 补丁 2:element-plus form-label-wrap +// ═══════════════════════════════════════════════ +// 根因:vxe-table 展开行收起时 el-form 组件已卸载, +// 但 nextTick/onUpdated 回调仍访问已销毁的 formContext, +// 导致 NaN width 和 teardown 错误。 +// +// 策略:从原始文件读取内容,在运行时用正则替换。 +// 这样不依赖 node_modules 中的修改。 +// ═══════════════════════════════════════════════ +import fs from 'fs' +import path from 'path' + +let cachedFormLabelWrap = null + +function getFormLabelWrapCode() { + if (cachedFormLabelWrap) return cachedFormLabelWrap + + const filePath = path.resolve( + process.cwd(), + 'node_modules/element-plus/es/components/form/src/form-label-wrap.mjs' + ) + + if (!fs.existsSync(filePath)) { + console.warn('[patch-deps] form-label-wrap.mjs not found, skipping') + return null + } + + const code = fs.readFileSync(filePath, 'utf-8') + + cachedFormLabelWrap = 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 const updateLabelWidth = (action = "update") => {' + ) + .replace( + 'nextTick(() => {', + 'nextTick(() => {\n if (!_isMounted) return;' + ) + .replace( + 'if (slots.default && props.isAutoWidth) {', + 'try {\n if (slots.default && props.isAutoWidth) {' + ) + .replace( + 'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);', + 'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);\n }\n } catch (e) { /* teardown race */ }' + ) + .replace( + 'onBeforeUnmount(() => {', + 'onBeforeUnmount(() => {\n _isMounted = false;' + ) + .replace( + 'onUpdated(() => updateLabelWidthFn())', + 'onUpdated(() => { if (_isMounted) updateLabelWidthFn(); })' + ) + .replace( + 'if (props.updateAll) formContext?.registerLabelWidth(val, oldVal);', + 'if (_isMounted && props.updateAll) formContext?.registerLabelWidth(val, oldVal);' + ) + .replace( + 'return () => {', + 'return () => {\n if (!_isMounted) return null;' + ) + + return cachedFormLabelWrap +} + +const PATCHED_FORM_LABEL_WRAP = getFormLabelWrapCode() \ No newline at end of file