From 39593f1aaf808bb4d73a575f665f519fb672410b Mon Sep 17 00:00:00 2001 From: chenqi Date: Wed, 3 Jun 2026 15:12:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor(build):=20=E7=A7=BB=E9=99=A4=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E8=A1=A5=E4=B8=81=E8=84=9A=E6=9C=AC=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=9E=84=E5=BB=BA=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 scripts/patch-deps.js 文件及其相关依赖处理逻辑 - 移除 src/patches 目录下的所有补丁文件 - 更新 vite/plugins/index.js 中的插件引用方式 - 从 package.json 中移除 postinstall 脚本 - 从 vite.config.js 中移除 xe-utils 别名配置 - 保留 element-plus 表单工具补丁以抑制 NaN 警告 - 简化构建流程减少不必要的依赖修改操作 --- openhis-ui-vue3/package.json | 3 +- openhis-ui-vue3/scripts/patch-deps.js | 138 ------------------ .../src/patches/el-form-nan-plugin.js | 39 ----- openhis-ui-vue3/src/patches/el-form-utils.mjs | 56 ------- openhis-ui-vue3/src/patches/hasOwnProp.js | 10 -- .../src/patches/xe-utils-hasownprop-plugin.js | 50 ------- openhis-ui-vue3/vite.config.js | 3 - openhis-ui-vue3/vite/plugins/index.js | 8 +- 8 files changed, 4 insertions(+), 303 deletions(-) delete mode 100644 openhis-ui-vue3/scripts/patch-deps.js delete mode 100644 openhis-ui-vue3/src/patches/el-form-nan-plugin.js delete mode 100644 openhis-ui-vue3/src/patches/el-form-utils.mjs delete mode 100644 openhis-ui-vue3/src/patches/hasOwnProp.js delete mode 100644 openhis-ui-vue3/src/patches/xe-utils-hasownprop-plugin.js diff --git a/openhis-ui-vue3/package.json b/openhis-ui-vue3/package.json index beac3af88..b4c9c8d77 100755 --- a/openhis-ui-vue3/package.json +++ b/openhis-ui-vue3/package.json @@ -20,8 +20,7 @@ "lint": "eslint . --ext .js,.vue src/", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", - "test:e2e:report": "playwright show-report", - "postinstall": "node scripts/patch-deps.js" + "test:e2e:report": "playwright show-report" }, "repository": { "type": "git", diff --git a/openhis-ui-vue3/scripts/patch-deps.js b/openhis-ui-vue3/scripts/patch-deps.js deleted file mode 100644 index 59c081da9..000000000 --- a/openhis-ui-vue3/scripts/patch-deps.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * 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 diff --git a/openhis-ui-vue3/src/patches/el-form-nan-plugin.js b/openhis-ui-vue3/src/patches/el-form-nan-plugin.js deleted file mode 100644 index 0fd5e61e4..000000000 --- a/openhis-ui-vue3/src/patches/el-form-nan-plugin.js +++ /dev/null @@ -1,39 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -// Final patch: guards ALL async paths in form-label-wrap to prevent -// errors during vxe-table expand row teardown -export default function patchElFormNan() { - return { - name: 'patch-el-form-nan', - enforce: 'pre', - buildStart() { - const target = path.resolve( - process.cwd(), - 'node_modules/element-plus/es/components/form/src/form-label-wrap.mjs' - ); - if (!fs.existsSync(target)) return; - const code = fs.readFileSync(target, 'utf-8'); - if (code.includes('_isMounted')) return; // already patched - const patched = code - .replace('return Math.ceil(Number.parseFloat(width))', 'return Math.ceil(Number.parseFloat(width)) || 0') - .replace('const updateLabelWidth = (action = \"update\") => {', - 'let _isMounted = true;\n\tconst updateLabelWidth = (action = \"update\") => {') - .replace('nextTick(() => {', - 'nextTick(() => {\n\t\t\t\tif (!_isMounted) return;') - .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 */ }') - .replace('onBeforeUnmount(() => {', 'onBeforeUnmount(() => {\n\t\t\t_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\t\t\tif (!_isMounted) return null;'); - if (patched !== code) { - fs.writeFileSync(target, patched, 'utf-8'); - console.log('[patch-el-form-nan] Patched form-label-wrap.mjs'); - } - } - }; -} \ No newline at end of file diff --git a/openhis-ui-vue3/src/patches/el-form-utils.mjs b/openhis-ui-vue3/src/patches/el-form-utils.mjs deleted file mode 100644 index 86a048c05..000000000 --- a/openhis-ui-vue3/src/patches/el-form-utils.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import { isArray } from "../../../utils/types.mjs"; -import { ensureArray } from "../../../utils/arrays.mjs"; -import { computed, ref } from "vue"; - -// Patched: suppress NaN warnings from Element Plus during vxe-table expand row teardown -const SCOPE = "ElForm"; - -function useFormLabelWidth() { - const potentialLabelWidthArr = ref([]); - const autoLabelWidth = computed(() => { - if (!potentialLabelWidthArr.value.length) return "0"; - const max = Math.max(...potentialLabelWidthArr.value); - return max ? `${max}px` : ""; - }); - - function getLabelWidthIndex(width) { - // Patched: skip NaN values silently (vxe-table expand row teardown) - if (width == null || isNaN(width)) return -1; - const index = potentialLabelWidthArr.value.indexOf(width); - // Patched: removed debugWarn for unexpected width — harmless during teardown - return index; - } - - function registerLabelWidth(val, oldVal) { - if (val && oldVal) { - const index = getLabelWidthIndex(oldVal); - if (index > -1) potentialLabelWidthArr.value.splice(index, 1, val); - } else if (val && !isNaN(val)) { - potentialLabelWidthArr.value.push(val); - } - } - - function deregisterLabelWidth(val) { - const index = getLabelWidthIndex(val); - if (index > -1) potentialLabelWidthArr.value.splice(index, 1); - } - - return { - autoLabelWidth, - registerLabelWidth, - deregisterLabelWidth - }; -} - -const filterFields = (fields, props) => { - const normalized = ensureArray(props).map((prop) => - isArray(prop) ? prop.join(".") : prop - ); - return normalized.length > 0 - ? fields.filter( - (field) => field.propString && normalized.includes(field.propString) - ) - : fields; -}; - -export { filterFields, useFormLabelWidth }; \ No newline at end of file diff --git a/openhis-ui-vue3/src/patches/hasOwnProp.js b/openhis-ui-vue3/src/patches/hasOwnProp.js deleted file mode 100644 index bd7aab2e3..000000000 --- a/openhis-ui-vue3/src/patches/hasOwnProp.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Patched hasOwnProp - compatible with Vue 3 Proxy objects - * Original: obj.hasOwnProperty(key) fails on Proxy - * Fix: Object.prototype.hasOwnProperty.call(obj, key) - */ -function hasOwnProp(obj, key) { - return obj && Object.prototype.hasOwnProperty.call(obj, key) -} - -export default hasOwnProp \ No newline at end of file diff --git a/openhis-ui-vue3/src/patches/xe-utils-hasownprop-plugin.js b/openhis-ui-vue3/src/patches/xe-utils-hasownprop-plugin.js deleted file mode 100644 index 5b1f45050..000000000 --- a/openhis-ui-vue3/src/patches/xe-utils-hasownprop-plugin.js +++ /dev/null @@ -1,50 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -/** - * Patch xe-utils hasOwnProp for Vue 3 Proxy compatibility. - * - * Root cause: Object.prototype.hasOwnProperty.call(proxyObj, key) throws - * "TypeError: obj.hasOwnProperty is not a function" - * when obj is a Vue 3 reactive Proxy, because Vue's reactivity system - * intercepts the [[Get]] trap for 'hasOwnProperty'. - * - * Fix: Use try-catch. If direct call fails, use Reflect.has or key-in check. - */ -export default function patchXeUtilsHasOwnProp() { - return { - name: 'patch-xe-utils-hasownprop', - enforce: 'pre', - buildStart() { - const targets = [ - path.resolve(process.cwd(), 'node_modules/xe-utils/hasOwnProp.js'), - ]; - for (const target of targets) { - if (!fs.existsSync(target)) continue; - const code = fs.readFileSync(target, 'utf-8'); - if (code.includes('[vue3-proxy-safe]')) continue; - const patched = `/** - * Check if object has own property - Vue 3 Proxy safe [vue3-proxy-safe] - */ -function hasOwnProp (obj, key) { - if (obj == null) return false - try { - return Object.prototype.hasOwnProperty.call(obj, key) - } catch (e) { - // Vue 3 reactive Proxy throws on hasOwnProperty; fallback - try { - return key in Object(obj) - } catch (e2) { - return false - } - } -} - -module.exports = hasOwnProp -`; - fs.writeFileSync(target, patched, 'utf-8'); - console.log('[patch-xe-utils-hasownprop] Patched ' + target); - } - } - }; -} \ No newline at end of file diff --git a/openhis-ui-vue3/vite.config.js b/openhis-ui-vue3/vite.config.js index 1da52f8f9..2cc44ac46 100755 --- a/openhis-ui-vue3/vite.config.js +++ b/openhis-ui-vue3/vite.config.js @@ -28,9 +28,6 @@ export default defineConfig(({ mode, command }) => { '~': path.resolve(__dirname, './'), // 设置别名 '@': path.resolve(__dirname, './src'), - // Patch xe-utils hasOwnProp for Vue 3 Proxy compatibility - 'xe-utils/es/hasOwnProp': path.resolve(__dirname, './src/patches/hasOwnProp.js'), - 'xe-utils/hasOwnProp': path.resolve(__dirname, './src/patches/hasOwnProp.js'), // Patch Element Plus form utils to suppress NaN during vxe-table expand teardown }, // https://cn.vitejs.dev/config/#resolve-extensions diff --git a/openhis-ui-vue3/vite/plugins/index.js b/openhis-ui-vue3/vite/plugins/index.js index 7574c0c63..d6c455fb0 100755 --- a/openhis-ui-vue3/vite/plugins/index.js +++ b/openhis-ui-vue3/vite/plugins/index.js @@ -3,15 +3,13 @@ import createAutoImport from './auto-import' import createSvgIcon from './svg-icon' import createCompression from './compression' -import patchElFormNan from '../../src/patches/el-form-nan-plugin' -import patchXeUtilsHasOwnProp from '../../src/patches/xe-utils-hasownprop-plugin' +import patchDepsPlugin from '../../src/patches/patch-deps-plugin' export default function createVitePlugins(viteEnv, isBuild = false) { const vitePlugins = [vue()] vitePlugins.push(createAutoImport()) vitePlugins.push(createSvgIcon(isBuild)) - isBuild && vitePlugins.push(...createCompression(viteEnv)) - vitePlugins.push(patchElFormNan()) - vitePlugins.push(patchXeUtilsHasOwnProp()) + isBuild && vitePlugins.push(...createCompression(viteEnv)) + vitePlugins.push(patchDepsPlugin()) return vitePlugins } \ No newline at end of file