553 lines
20 KiB
Vue
553 lines
20 KiB
Vue
<template>
|
||
<div class="original-site-page">
|
||
<div v-if="error" class="original-site-page__error">{{ error }}</div>
|
||
<div v-else ref="container" v-html="bodyHtml"></div>
|
||
<!-- 移动端菜单遮罩(由 Vue 管理,不受页面内容影响) -->
|
||
<div class="yuedong-menu-backdrop" :class="{ show: menuOpen }" @click="closeMobileMenu"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { originalPages } from '../data/originalPages.generated';
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const container = ref(null);
|
||
const bodyHtml = ref('');
|
||
const error = ref('');
|
||
const menuOpen = ref(false);
|
||
|
||
const managedAttr = 'data-original-site-managed';
|
||
const persistentAttr = 'data-original-site-persistent';
|
||
const defaultTitle = document.title;
|
||
const defaultDescription =
|
||
document.querySelector('meta[name="description"]')?.getAttribute('content') ?? '';
|
||
let loadToken = 0;
|
||
let prevPath = '';
|
||
const loadedExternalScripts = new Set();
|
||
const loadedStyles = new Set();
|
||
|
||
const assetExtensions =
|
||
/\.(?:css|js|mjs|png|jpe?g|gif|svg|webp|bmp|ico|ttf|otf|woff2?|eot|mp4|webm|ogg|mp3|wav|pdf|zip|txt|json|xml|map)$/i;
|
||
|
||
const isProductsPage = (path) => /\/products(?:_\d+)?$/.test(path);
|
||
|
||
const scrollToProductsList = () => {
|
||
const target = document.querySelector('.yuedong_goods_list');
|
||
if (target) {
|
||
target.scrollIntoView({ behavior: 'instant', block: 'start' });
|
||
}
|
||
};
|
||
|
||
// 替换缺失的 iconfont-caidan 图标为 SVG 汉堡菜单
|
||
const menuIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>`;
|
||
|
||
const replaceMenuIcon = () => {
|
||
document.querySelectorAll('.iconfont.iconfont-caidan').forEach((el) => {
|
||
el.innerHTML = menuIconSvg;
|
||
el.classList.remove('iconfont-caidan');
|
||
});
|
||
};
|
||
|
||
// 分页按钮:将"上一页"/"下一页"文字替换为左右箭头图标
|
||
const prevArrowSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="7" height="13" viewBox="0 0 7 13" fill="none"><path d="M6.5 12.5L0.5 6.5L6.5 0.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
||
const nextArrowSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="7" height="13" viewBox="0 0 7 13" fill="none"><path d="M0.5 12.5L6.5 6.5L0.5 0.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
||
|
||
const replacePaginationArrows = () => {
|
||
const pageSize = document.querySelector('#pagesize ul');
|
||
if (!pageSize) return;
|
||
const items = pageSize.querySelectorAll(':scope > li');
|
||
if (items.length >= 2) {
|
||
const firstLink = items[0].querySelector('a');
|
||
const lastLink = items[items.length - 1].querySelector('a');
|
||
if (firstLink && !firstLink.querySelector('svg')) {
|
||
firstLink.innerHTML = prevArrowSvg;
|
||
}
|
||
if (lastLink && !lastLink.querySelector('svg')) {
|
||
lastLink.innerHTML = nextArrowSvg;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 修复解决方案页面:翻译英文文本、修复缺失图片
|
||
const fixSolutionPage = () => {
|
||
// --- /solution 列表页 ---
|
||
// 翻译解决方案标题
|
||
document.querySelectorAll('.solution-title').forEach((el) => {
|
||
if (el.textContent.trim() === 'JumpArena X') {
|
||
el.innerHTML = 'JumpArena X <small style="font-size:0.7em;color:#888;font-weight:400">智能赛事系统</small>';
|
||
}
|
||
});
|
||
|
||
// 翻译解决方案描述
|
||
document.querySelectorAll('.solution-list p').forEach((el) => {
|
||
const html = el.innerHTML;
|
||
if (html.includes('successfully established a fully digital')) {
|
||
el.innerHTML = html.replace(
|
||
/The "Yuedong" Smart Rope Skipping Competition System successfully established a fully digital and intelligent event management platform that streamlines competition operations, improves judging accuracy, and enhances the overall efficiency and transparency of professional rope skipping events\./,
|
||
'跃动智能跳绳竞赛系统成功构建了全数字化、智能化的赛事管理平台,优化了赛事运营流程,提升了裁判精准度,全面增强了专业跳绳赛事的效率与透明度。'
|
||
);
|
||
}
|
||
if (html.includes('By integrating intelligent scoring')) {
|
||
el.innerHTML = html.replace(
|
||
/By integrating intelligent scoring, paperless operation, real-time visualization, and full-process traceability, the system promotes the standardization, fairness, and digital transformation of rope skipping competitions\./,
|
||
'通过集成智能评分、无纸化操作、实时可视化与全流程追溯,该系统推动了跳绳竞赛的标准化、公平性与数字化转型。'
|
||
);
|
||
}
|
||
});
|
||
|
||
// --- /solution 和 /jumparena-x ---
|
||
// 修复缺失的订阅区背景图
|
||
document.querySelectorAll('img[src$="image_327.png"]').forEach((img) => {
|
||
img.style.display = 'none';
|
||
});
|
||
|
||
// 修复 /jumparena-x 详情页缺失的 SVG 图标(中文文件名URL编码)
|
||
const infoIconSvg = (name) =>
|
||
`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;color:#888">${name === 'location' ? '<circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 0-8 8c0 6 8 12 8 12s8-6 8-12a8 8 0 0 0-8-8z"/>' : name === 'time' ? '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>' : '<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>'}</svg>`;
|
||
|
||
document.querySelectorAll('img[src*="%E5%87%BA%E5%8F%91%E5%9C%B0"]').forEach((img) => {
|
||
img.outerHTML = infoIconSvg('location');
|
||
});
|
||
document.querySelectorAll('img[src*="%E6%97%B6%E9%97%B4_"]').forEach((img) => {
|
||
img.outerHTML = infoIconSvg('time');
|
||
});
|
||
document.querySelectorAll('img[src*="%E4%BA%A7%E5%93%812_1"]').forEach((img) => {
|
||
img.outerHTML = infoIconSvg('monitor');
|
||
});
|
||
};
|
||
|
||
// 判断是否为解决方案相关页面
|
||
const isSolutionPage = (path) => /\/solution/.test(path);
|
||
|
||
// 移动端菜单:由 Vue 状态驱动,事件委托只做转发
|
||
let mobileMenuInited = false;
|
||
const initMobileMenu = () => {
|
||
if (mobileMenuInited) return;
|
||
mobileMenuInited = true;
|
||
|
||
// 全局委托:菜单按钮和关闭按钮点击 → 转发到 Vue 状态
|
||
document.addEventListener('click', (e) => {
|
||
const toggle = e.target.closest('[data-bs-toggle="collapse"][data-bs-target="#main_nav"]');
|
||
if (toggle) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
e.stopImmediatePropagation();
|
||
menuOpen.value = !menuOpen.value;
|
||
return;
|
||
}
|
||
const closeBtn = e.target.closest('.collapse .qico-guanbi');
|
||
if (closeBtn) {
|
||
e.preventDefault();
|
||
menuOpen.value = false;
|
||
}
|
||
}, true);
|
||
};
|
||
|
||
const closeMobileMenu = () => {
|
||
menuOpen.value = false;
|
||
};
|
||
|
||
// 同步 Vue 状态到实际 DOM 元素
|
||
const syncMobileMenu = () => {
|
||
const menu = document.querySelector('#main_nav');
|
||
if (!menu) return;
|
||
if (menuOpen.value) {
|
||
menu.classList.remove('collapse');
|
||
menu.classList.add('show');
|
||
menu.style.cssText = 'display:block!important;visibility:visible!important;opacity:1!important;height:100vh!important';
|
||
document.body.style.overflow = 'hidden';
|
||
} else {
|
||
menu.classList.remove('show', 'collapsing');
|
||
menu.classList.add('collapse');
|
||
menu.style.cssText = '';
|
||
document.body.style.overflow = '';
|
||
}
|
||
};
|
||
|
||
const normalizeRoutePath = (path) => {
|
||
if (!path || path === '/') return '/';
|
||
return path.endsWith('/') ? path.slice(0, -1) : path;
|
||
};
|
||
|
||
const removeManagedNodes = () => {
|
||
document.querySelectorAll(`[${managedAttr}="true"]`).forEach((node) => node.remove());
|
||
};
|
||
|
||
const closePopups = () => {
|
||
document
|
||
.querySelectorAll(
|
||
'.cd-popup, .click-popup, .custom-pop-inquiry, .custom-pop-inquiry-form, .modal, .modal-backdrop'
|
||
)
|
||
.forEach((node) => {
|
||
node.classList.remove('is-visible', 'show', 'active', 'open');
|
||
if (node.classList.contains('modal-backdrop')) node.remove();
|
||
});
|
||
document.body.classList.remove('modal-open');
|
||
document.body.style.removeProperty('overflow');
|
||
document.body.style.removeProperty('padding-right');
|
||
};
|
||
|
||
const restoreDefaults = () => {
|
||
document.title = defaultTitle;
|
||
const descriptionMeta = document.querySelector('meta[name="description"]');
|
||
if (descriptionMeta) descriptionMeta.setAttribute('content', defaultDescription);
|
||
document.documentElement.lang = 'zh-CN';
|
||
document.body.removeAttribute('style');
|
||
document.body.className = '';
|
||
};
|
||
|
||
const stripRelativePrefix = (value) => value.replace(/^(\.\.\/|\.\/)+/, '');
|
||
|
||
const rewriteAssetUrl = (value) => {
|
||
if (!value) return value;
|
||
if (
|
||
value.startsWith('javascript:') ||
|
||
value.startsWith('mailto:') ||
|
||
value.startsWith('tel:') ||
|
||
value.startsWith('data:') ||
|
||
value.startsWith('#') ||
|
||
value.startsWith('http://www.w3.org/')
|
||
) {
|
||
return value;
|
||
}
|
||
|
||
// 已经是绝对站点路径,直接返回,避免双重 /site/ 前缀
|
||
if (value.startsWith('/site/')) return value;
|
||
|
||
const knownAsset = /(?:^|\/)(assets|oss(?:-dev)?|cf)\//;
|
||
if (knownAsset.test(value)) {
|
||
let normalized = stripRelativePrefix(value);
|
||
normalized = normalized.replace(/^https?:\/\/(?:www\.)?ydrope\.com\//, '');
|
||
normalized = normalized.replace(/^https?:\/\//, '');
|
||
normalized = normalized.replace(/^\/+/, '');
|
||
return `/site/${normalized}`;
|
||
}
|
||
|
||
if (/^https?:\/\//i.test(value)) return value;
|
||
if (value.startsWith('/')) return value;
|
||
|
||
// 处理 images/ 开头的路径,映射到 assets/images/
|
||
if (/^images\//.test(value)) {
|
||
return `/site/assets/${stripRelativePrefix(value)}`;
|
||
}
|
||
|
||
return `/site/${stripRelativePrefix(value)}`;
|
||
};
|
||
|
||
const toAppRoute = (value) => {
|
||
if (!value) return value;
|
||
if (
|
||
value.startsWith('javascript:') ||
|
||
value.startsWith('mailto:') ||
|
||
value.startsWith('tel:') ||
|
||
value.startsWith('data:') ||
|
||
value.startsWith('#')
|
||
) {
|
||
return value;
|
||
}
|
||
|
||
if (/(?:^|\/)(assets|oss(?:-dev)?|cf)\//.test(value)) {
|
||
return rewriteAssetUrl(value);
|
||
}
|
||
|
||
try {
|
||
const parsed = new URL(value, window.location.origin);
|
||
if (parsed.origin !== window.location.origin && !/ydrope\.com$/i.test(parsed.hostname)) {
|
||
return parsed.toString();
|
||
}
|
||
|
||
let pathname = parsed.pathname;
|
||
pathname = pathname.replace(/^\/site/, '');
|
||
pathname = pathname.replace(/\/index\.html$/i, '/');
|
||
pathname = pathname.replace(/\.html$/i, '');
|
||
if (assetExtensions.test(pathname)) {
|
||
return rewriteAssetUrl(value);
|
||
}
|
||
if (!pathname || pathname === '/') pathname = '/';
|
||
if (pathname.endsWith('/') && pathname !== '/') pathname = pathname.slice(0, -1);
|
||
return `${pathname}${parsed.search}${parsed.hash}`;
|
||
} catch {
|
||
return value;
|
||
}
|
||
};
|
||
|
||
const rewriteSrcset = (srcset) => {
|
||
const entries = srcset
|
||
.split(',')
|
||
.map((item) => {
|
||
const parts = item.trim().split(/\s+/);
|
||
if (!parts[0]) return '';
|
||
return [rewriteAssetUrl(parts[0]), ...parts.slice(1)].join(' ');
|
||
})
|
||
.filter(Boolean)
|
||
// 过滤掉 OSS 图片处理链接(x-oss-process),这些是云端动态生成的缩略图,本地不存在
|
||
.filter((entry) => !/x-oss-process/i.test(entry));
|
||
|
||
return entries.join(', ');
|
||
};
|
||
|
||
const rewriteCssText = (text) =>
|
||
text.replace(/url\((['"]?)(?!data:|https?:|\/)([^'")]+)\1\)/gi, (_match, quote, url) => {
|
||
const rewritten = rewriteAssetUrl(url);
|
||
return `url(${quote}${rewritten}${quote})`;
|
||
});
|
||
|
||
const rewriteNodeUrls = (rootNode) => {
|
||
rootNode.querySelectorAll('[src]').forEach((node) => {
|
||
node.setAttribute('src', rewriteAssetUrl(node.getAttribute('src')));
|
||
});
|
||
rootNode.querySelectorAll('[data-src]').forEach((node) => {
|
||
const src = rewriteAssetUrl(node.getAttribute('data-src'));
|
||
node.setAttribute('data-src', src);
|
||
if (!node.getAttribute('src') || /placeholder|small-load/.test(node.getAttribute('src'))) {
|
||
node.setAttribute('src', src);
|
||
}
|
||
});
|
||
rootNode.querySelectorAll('[poster]').forEach((node) => {
|
||
node.setAttribute('poster', rewriteAssetUrl(node.getAttribute('poster')));
|
||
});
|
||
rootNode.querySelectorAll('[srcset]').forEach((node) => {
|
||
const rewritten = rewriteSrcset(node.getAttribute('srcset') || '');
|
||
if (rewritten) {
|
||
node.setAttribute('srcset', rewritten);
|
||
} else {
|
||
node.removeAttribute('srcset');
|
||
}
|
||
});
|
||
rootNode.querySelectorAll('a[href]').forEach((node) => {
|
||
node.setAttribute('href', toAppRoute(node.getAttribute('href')));
|
||
});
|
||
rootNode.querySelectorAll('form[action]').forEach((node) => {
|
||
node.setAttribute('action', toAppRoute(node.getAttribute('action')));
|
||
});
|
||
};
|
||
|
||
const updateHead = (doc) => {
|
||
removeManagedNodes();
|
||
document.title = doc.title || defaultTitle;
|
||
document.documentElement.lang = doc.documentElement.lang || 'zh-CN';
|
||
|
||
const descriptionMeta = document.querySelector('meta[name="description"]');
|
||
const sourceDescription = doc.querySelector('meta[name="description"]');
|
||
if (descriptionMeta && sourceDescription) {
|
||
descriptionMeta.setAttribute('content', sourceDescription.getAttribute('content') ?? '');
|
||
}
|
||
|
||
doc
|
||
.querySelectorAll(
|
||
'meta[name="keywords"], meta[property^="og:"], meta[name^="twitter:"], link[rel]:not([rel~="icon"]), style'
|
||
)
|
||
.forEach((node) => {
|
||
const cloned = document.createElement(node.tagName.toLowerCase());
|
||
Array.from(node.attributes).forEach((attr) => {
|
||
const attrValue = attr.name === 'href' ? rewriteAssetUrl(attr.value) : attr.value;
|
||
cloned.setAttribute(attr.name, attrValue);
|
||
});
|
||
if (node.tagName.toLowerCase() === 'style') cloned.textContent = rewriteCssText(node.textContent || '');
|
||
const href = cloned.getAttribute('href');
|
||
if (href && cloned.tagName.toLowerCase() === 'link') {
|
||
if (loadedStyles.has(href)) return;
|
||
loadedStyles.add(href);
|
||
cloned.setAttribute(persistentAttr, 'true');
|
||
} else {
|
||
cloned.setAttribute(managedAttr, 'true');
|
||
}
|
||
document.head.appendChild(cloned);
|
||
});
|
||
};
|
||
|
||
const collectScripts = (doc) =>
|
||
Array.from(doc.querySelectorAll('script'))
|
||
.map((script) => ({
|
||
src: script.getAttribute('src') ? rewriteAssetUrl(script.getAttribute('src')) : '',
|
||
type: script.getAttribute('type') || '',
|
||
text: script.textContent || '',
|
||
async: script.hasAttribute('async'),
|
||
defer: script.hasAttribute('defer')
|
||
}))
|
||
.filter((script) => {
|
||
const source = script.src || script.text;
|
||
return !/(shops-popup|okki\/analyze|googletagmanager|matomo|Sentry|trackPageView|custom_pop_inquiry_form)/i.test(
|
||
source
|
||
);
|
||
});
|
||
|
||
const runScripts = async (scripts, token) => {
|
||
for (const item of scripts) {
|
||
if (token !== loadToken) return;
|
||
await new Promise((resolve) => {
|
||
const script = document.createElement('script');
|
||
script.setAttribute(managedAttr, 'true');
|
||
if (item.type) script.type = item.type;
|
||
if (item.async) script.async = true;
|
||
if (item.defer) script.defer = true;
|
||
if (item.src) {
|
||
const loadOnce = /\/tpl-common\/common\/js\/|\/cdn-cgi\/scripts\/|\/sections\/shared\/head\/index\.js/i.test(item.src);
|
||
if (loadOnce && loadedExternalScripts.has(item.src)) {
|
||
resolve();
|
||
return;
|
||
}
|
||
if (loadOnce) loadedExternalScripts.add(item.src);
|
||
script.src = item.src;
|
||
script.onload = () => resolve();
|
||
script.onerror = () => resolve();
|
||
} else {
|
||
script.textContent = item.text;
|
||
resolve();
|
||
}
|
||
document.body.appendChild(script);
|
||
});
|
||
}
|
||
window.dispatchEvent(new Event('load'));
|
||
};
|
||
|
||
const loadPage = async () => {
|
||
const token = ++loadToken;
|
||
closePopups();
|
||
menuOpen.value = false;
|
||
const key = normalizeRoutePath(route.path);
|
||
const html = originalPages[key] || originalPages[`${key}/`] || originalPages['/'];
|
||
|
||
if (!html) {
|
||
error.value = '页面不存在';
|
||
return;
|
||
}
|
||
|
||
error.value = '';
|
||
removeManagedNodes();
|
||
|
||
const parser = new DOMParser();
|
||
const doc = parser.parseFromString(html, 'text/html');
|
||
|
||
// 产品分页页面之间切换:局部替换产品列表区域,避免整页闪烁
|
||
const currentIsProducts = isProductsPage(route.path);
|
||
const prevIsProducts = isProductsPage(prevPath);
|
||
|
||
if (currentIsProducts && prevIsProducts && bodyHtml.value) {
|
||
const newBody = doc.body.cloneNode(true);
|
||
newBody.querySelectorAll('script, style, link[rel="stylesheet"]').forEach((node) => node.remove());
|
||
rewriteNodeUrls(newBody);
|
||
|
||
const newGoodsList = newBody.querySelector('.yuedong_goods_list');
|
||
const existingGoodsList = document.querySelector('.yuedong_goods_list');
|
||
|
||
if (newGoodsList && existingGoodsList) {
|
||
// newGoodsList 的 URL 已被 rewriteNodeUrls(newBody) 处理过,直接使用
|
||
existingGoodsList.innerHTML = newGoodsList.innerHTML;
|
||
updateHead(doc);
|
||
|
||
// 分页箭头和菜单图标在脚本运行前替换,避免被覆盖
|
||
replacePaginationArrows();
|
||
replaceMenuIcon();
|
||
|
||
const scripts = collectScripts(doc);
|
||
if (token === loadToken) await runScripts(scripts, token);
|
||
closePopups();
|
||
|
||
if (token === loadToken) {
|
||
await nextTick();
|
||
scrollToProductsList();
|
||
}
|
||
prevPath = route.path;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 正常完整页面加载
|
||
const body = doc.body.cloneNode(true);
|
||
const scripts = collectScripts(doc);
|
||
|
||
document.body.className = doc.body.className;
|
||
const bodyStyle = doc.body.getAttribute('style');
|
||
if (bodyStyle) document.body.setAttribute('style', bodyStyle);
|
||
else document.body.removeAttribute('style');
|
||
|
||
body.querySelectorAll('script, style, link[rel="stylesheet"]').forEach((node) => node.remove());
|
||
rewriteNodeUrls(body);
|
||
updateHead(doc);
|
||
|
||
bodyHtml.value = body.innerHTML;
|
||
await nextTick();
|
||
if (token === loadToken) await runScripts(scripts, token);
|
||
closePopups();
|
||
|
||
// 替换缺失的菜单图标和分页箭头,初始化移动端菜单
|
||
if (token === loadToken) {
|
||
await nextTick();
|
||
replaceMenuIcon();
|
||
replacePaginationArrows();
|
||
initMobileMenu();
|
||
syncMobileMenu();
|
||
if (isSolutionPage(route.path)) {
|
||
fixSolutionPage();
|
||
}
|
||
}
|
||
|
||
prevPath = route.path;
|
||
};
|
||
|
||
const handleLinkClick = (event) => {
|
||
const anchor = event.target.closest('a');
|
||
if (!anchor) return;
|
||
if (anchor.classList.contains('cd-popup-trigger')) {
|
||
event.preventDefault();
|
||
document.querySelector('.custom-pop-inquiry, .click-popup')?.classList.add('is-visible');
|
||
return;
|
||
}
|
||
if (anchor.classList.contains('cd-popup-close')) {
|
||
event.preventDefault();
|
||
closePopups();
|
||
return;
|
||
}
|
||
if (anchor.target && anchor.target !== '_self') return;
|
||
if (event.defaultPrevented || event.button !== 0) return;
|
||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
||
|
||
const href = anchor.getAttribute('href');
|
||
if (!href) return;
|
||
if (
|
||
href.startsWith('javascript:') ||
|
||
href.startsWith('mailto:') ||
|
||
href.startsWith('tel:') ||
|
||
href.startsWith('#') ||
|
||
href.startsWith('/site/')
|
||
) {
|
||
return;
|
||
}
|
||
|
||
if (/^https?:\/\//i.test(href)) {
|
||
const url = new URL(href);
|
||
if (url.origin !== window.location.origin) return;
|
||
}
|
||
|
||
event.preventDefault();
|
||
router.push(href);
|
||
};
|
||
|
||
onMounted(() => {
|
||
container.value?.addEventListener('click', handleLinkClick);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
container.value?.removeEventListener('click', handleLinkClick);
|
||
removeManagedNodes();
|
||
restoreDefaults();
|
||
});
|
||
|
||
watch(
|
||
() => route.fullPath,
|
||
() => {
|
||
loadPage();
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
// 菜单状态变化时同步 DOM
|
||
watch(menuOpen, () => {
|
||
syncMobileMenu();
|
||
});
|
||
</script>
|