修复所有 Alpine 组件表达式报错

彻底移除 Proxy/has 陷阱方案,改用显式方法存根:
- userCardComponent 补充 35 个方法存根
- marriage-modals 8 个组件改用 createLazyAlpineComponent
- weddingSetup/weddingEnvelope 等 Modal 均正确包装
控制台现在应该没有任何 Alpine Expression Error
This commit is contained in:
pllx
2026-04-28 09:42:18 +08:00
parent e50502d8f6
commit 1c067e452b
2 changed files with 80 additions and 44 deletions
+77 -9
View File
@@ -769,16 +769,49 @@ if (typeof window !== "undefined") {
window.marriageAction = (...args) => _marriageStatus.wrap('marriageAction')(...args);
window.openMarriageStatusModal = (...args) => _marriageStatus.wrap('openMarriageStatusModal')(...args);
window.appendSystemMessage = (...args) => _marriageModals.wrap('appendSystemMessage')(...args);
window.divorceConfirmModal = (...args) => _marriageModals.wrap('divorceConfirmModal')(...args);
window.divorceRequestModal = (...args) => _marriageModals.wrap('divorceRequestModal')(...args);
window.marriageAcceptedModal = (...args) => _marriageModals.wrap('marriageAcceptedModal')(...args);
window.marriageDivorcedModal = (...args) => _marriageModals.wrap('marriageDivorcedModal')(...args);
window.marriageIncomingModal = (...args) => _marriageModals.wrap('marriageIncomingModal')(...args);
window.marriageProposeModal = (...args) => _marriageModals.wrap('marriageProposeModal')(...args);
window.openProposeModal = (...args) => _marriageModals.wrap('openProposeModal')(...args);
window.openWeddingSetupModal = (...args) => _marriageModals.wrap('openWeddingSetupModal')(...args);
window.weddingEnvelopeModal = (...args) => _marriageModals.wrap('weddingEnvelopeModal')(...args);
window.weddingSetupModal = (...args) => _marriageModals.wrap('weddingSetupModal')(...args);
// ── 婚姻弹窗 Alpine 组件(createLazyAlpineComponent 懒加载) ──
window.marriageProposeModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"marriageProposeModal",
{ show: false, targetUsername: "", error: "", loading: false, rings: [], selectedRing: null, selectedTier: null, tiers: [], selectedTierId: null, canAfford: false, sending: false, doPropose: () => {} }
);
window.marriageIncomingModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"marriageIncomingModal",
{ show: false }
);
window.marriageAcceptedModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"marriageAcceptedModal",
{ show: false }
);
window.marriageDivorcedModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"marriageDivorcedModal",
{ show: false }
);
window.divorceConfirmModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"divorceConfirmModal",
{ show: false }
);
window.divorceRequestModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"divorceRequestModal",
{ show: false }
);
window.weddingSetupModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"weddingSetupModal",
{ show: false }
);
window.weddingEnvelopeModal = createLazyAlpineComponent(
() => import("./chat-room/marriage-modals.js"),
"weddingEnvelopeModal",
{ show: false }
);
window.renderMarriedList = (...args) => _marriageStatus.wrap('renderMarriedList')(...args);
window.renderMarriageStatus = (...args) => _marriageStatus.wrap('renderMarriageStatus')(...args);
window.switchMarriageTab = (...args) => _marriageStatus.wrap('switchMarriageTab')(...args);
@@ -855,7 +888,42 @@ if (typeof window !== "undefined") {
targetMarriage: null,
marriageLoading: false,
mySex: "",
assetCache: [],
// 方法存根(防止 Alpine with(scope) 表达式找不到方法而报错)
hasPositionPermission: () => false,
canManageTargetByDuty: () => false,
assetValueLabel: () => "",
canRevealAssetValue: () => false,
displayAssetValue: () => "",
assetValueTitle: () => "",
assetValueStyle: () => "",
revealAssetValue: () => {},
displayBankBalance: () => "",
bankBalanceTitle: () => "",
bankBalanceStyle: () => "",
revealBankBalance: () => {},
toggleFriend: () => {},
handleConfirmDivorce: () => {},
doDivorce: () => {},
fetchUser: () => {},
_loadPositions: () => {},
doAppoint: () => {},
doRevoke: () => {},
kickUser: () => {},
muteUser: () => {},
warnUser: () => {},
banUser: () => {},
banIpUser: () => {},
loadWhispers: () => {},
sendAnnounce: () => {},
sendGift: () => {},
toggleGiftPanel: () => {},
toggleGiftGoldPanel: () => {},
sendGiftGold: () => {},
sendReward: () => {},
_headers: () => ({}),
$alert: (...args) => window.chatDialog?.alert(...args),
$confirm: (...args) => window.chatDialog?.confirm(...args),
$prompt: (...args) => window.chatDialog?.prompt(...args),
},
"showUserModal"
);
+3 -35
View File
@@ -101,13 +101,11 @@ export function createLazyAlpineComponent(importFn, exportName, defaults = {}, w
const proxy = this;
let loaded = false;
// 使用 Alpine 的 $watch 监听显示状态变化
// 仅在组件变为可见时才加载真实模块
if (
watchKey in proxy &&
typeof proxy.$watch === "function"
) {
proxy.$watch(watchKey, (value, oldValue) => {
proxy.$watch(watchKey, (value) => {
if (value && !loaded) {
loaded = true;
importFn()
@@ -118,21 +116,18 @@ export function createLazyAlpineComponent(importFn, exportName, defaults = {}, w
? componentFn(...args)
: componentFn;
// 通过 Alpine 响应式代理写入所有属性
Object.keys(realData).forEach((key) => {
if (key !== "init") {
proxy[key] = realData[key];
}
});
// 处理继承属性
for (const key in realData) {
if (!(key in proxy)) {
proxy[key] = realData[key];
}
}
// 调用真实组件的 init(如果存在)
if (
typeof proxy.init === "function" &&
proxy.init !== stub.init
@@ -145,7 +140,7 @@ export function createLazyAlpineComponent(importFn, exportName, defaults = {}, w
`[懒加载] Alpine 组件 "${exportName}" 加载失败:`,
err,
);
loaded = false; // 允许重试
loaded = false;
});
}
});
@@ -153,33 +148,6 @@ export function createLazyAlpineComponent(importFn, exportName, defaults = {}, w
},
};
// 使用 Proxy 包裹 stub,对任何未在 defaults 中定义的属性/方法提供安全兜底
// 这样组件模板中的所有表达式(方法调用、属性访问)都不会抛出 "not defined" 错误
return new Proxy(stub, {
get(target, prop, receiver) {
// 已存在的属性直接返回
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
// 对于未定义的属性/方法,返回一个安全的、支持调用的兜底值
// - 作为值访问(如 targetMarriage?.status: 返回 '',保证 .status 不报错
// - 作为方法调用(如 displayAssetValue('exp_num'): 返回空字符串
const fallback = () => "";
return fallback;
},
// Alpine 使用 with(scope) 求值表达式,with 用 has (in 操作符) 判断属性是否存在
// 如果没有 has 陷阱,with 认为属性不存在 → 跳到 window → "is not defined"
has(target, prop) {
// 不要拦截 Alpine/Vue 内部属性和魔术方法
if (typeof prop === "symbol") return Reflect.has(target, prop);
if (String(prop).startsWith("__v_")) return Reflect.has(target, prop);
if (String(prop).startsWith("$")) return Reflect.has(target, prop);
// 所有其他属性都报告存在,让 with 继续用 get 获取兜底值
return true;
},
set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
},
});
return stub;
};
}