英亚体育携手维拉开启官方合作 - 英亚体育
近期发布
英亚体育携手维拉开启官方合作
在孔子故里续写当代书院
英亚体育携手维拉开启官方合作
深圳自然博物馆正式向公众开放
英亚体育携手维拉开启官方合作
充满巧克力味道、香气以及故事的丰富世界
英亚体育携手维拉开启官方合作
水与地的交汇——一座由自然力量塑造的景观英亚体育携手维拉开启官方合作
资讯
以开放式藏品库和可灵活调整的展示系统重塑博物馆
英亚体育携手维拉开启官方合作
文化综合体的维度拓展与地域性表达
英亚体育携手维拉开启官方合作
新增11.4万平方英尺的展厅、教育空间等
英亚体育携手维拉开启官方合作
一处可被行走与体验的文化叙事空间
英亚体育携手维拉开启官方合作
一条动线的两种连贯空间体验
英亚体育携手维拉开启官方合作
雪山下的亚丁博物馆、山谷餐厅与村落更新
相关文章
本页围绕「英亚体育携手维拉开启官方合作」整理公开信息,便于结合栏目动态与后续阅读。 相关文章
移动端与电脑端入口保持一致。 同题参见如某条暂不可用,可改走栏目列表继续查找。
列表适合快速定位,正文适合核对表述。 相关文章两者都保留在站内即可形成完整阅读路径。
栏目入口
工作/招聘 · 复合检索 · 每个人 · 先锋专辑 · 长江 · 合集
`).join('');
const submit = document.querySelector('.job-head .job-submit');
const all = document.querySelector('.job-head .job-all');
if (submit && jumpUrls.options_new_recommend_linkurl) submit.href = jumpUrls.options_new_recommend_linkurl;
if (all) all.href='/category/jobs';
};
const latestShowNumbers = (snapshot = {}) => {
return snapshot?.home?.showNumbers || snapshot?.showNumbers || {};
};
const renderBackendSideJobs = (jobs = [], showNumbers = {}) => {
const list = document.querySelector('.side-jobs.home-pinned');
if (!list || !jobs.length) return;
const backendJobCount = Number(
showNumbers.options_jobs_index_number ||
latestShowNumbers(backendSnapshot).options_jobs_index_number
) || 0;
const targetCount = backendJobCount || Number(list.dataset.defaultCount) || 74;
const normalizedJobs = jobs.slice(0, targetCount);
list.dataset.source = snapshot?.home?.showNumbersSource || (showNumbers.options_jobs_index_number ? 'live-home-runtime.showNumbers' : 'window.__GOOOOD_V10_BACKEND_SNAPSHOT__.home.homeSideJobs');
list.dataset.renderedCount = String(normalizedJobs.length);
list.dataset.backendCount = String(jobs.length);
list.dataset.configuredCount = String(targetCount);
list.innerHTML = normalizedJobs.map((job) => {
const titleSource = jobTitle(job);
const title = zhText(titleSource);
const titleEn = enText(titleSource, title);
const image = jobImage(job);
return `${escapeHtml(language === 'en' ? titleEn : title)}
`;
}).join('');
backendRenderedLatestJobs = true;
};
const collectPinnedJobs = (snapshot) => {
const home = snapshot?.home || {};
const candidates = [
home.pinnedJobs,
home.topJobs,
home.featuredJobs,
home.homeTopJobs,
home.homePinnedJobs,
home.topRecruitJobs,
snapshot?.pinnedJobs,
snapshot?.topJobs
];
return candidates.find((items) => Array.isArray(items) && items.length) || [];
};
const renderBackendPinnedJobs = (jobs = [], jumpUrls = {}) => {
const list = document.querySelector('.side-jobs.pinned');
const section = list?.closest('.pinned-jobs-section');
if (!list || !section) return;
const normalizedJobs = Array.isArray(jobs) ? jobs.filter(Boolean) : [];
const topLink = section.querySelector('.side-title-action');
if (topLink) {
topLink.href = jumpUrls.options_top_recruit_linkurl || jumpUrls.options_buy_top_linkurl || topLink.href;
}
if (!normalizedJobs.length) {
list.innerHTML = '';
section.hidden = true;
section.dataset.renderedCount = '0';
section.dataset.emptyState = 'no-pinned-jobs';
return;
}
section.hidden = false;
section.dataset.renderedCount = String(normalizedJobs.length);
section.dataset.emptyState = '';
list.innerHTML = normalizedJobs.map((job) => {
const titleSource = jobTitle(job);
const title = zhText(titleSource);
const titleEn = enText(titleSource, title);
const image = jobImage(job);
return `${escapeHtml(language === 'en' ? titleEn : title)}
`;
}).join('');
};
const renderPagination = (pageInfo = {}) => {
const nav = document.querySelector('.pagination');
if (!nav) return;
const current = Math.max(1, Number(pageInfo.page || currentPageFromLocation() || 1));
const totalPages = Math.max(1, Number(pageInfo.totalPages || pageInfo.total_pages || nav.dataset.totalPages || 1));
const totalItems = Number(pageInfo.total || nav.dataset.totalItems || 0);
const items = [];
items.push(current > 1
? `←`
: '←');
items.push(`${current}`);
[current + 1, current + 2].forEach((page) => {
if (page <= totalPages) items.push(`${page}`);
});
if (current + 3 < totalPages) items.push('...');
if (current + 2 < totalPages) items.push(`${totalPages}`);
items.push(current < totalPages
? `→`
: '→');
items.push(`${current}/${totalPages}`);
items.push(``);
nav.dataset.currentPage = String(current);
nav.dataset.totalPages = String(totalPages);
if (totalItems) nav.dataset.totalItems = String(totalItems);
nav.innerHTML = items.join('');
const input = nav.querySelector('.page-jump input');
const button = nav.querySelector('.page-jump button');
const go = () => {
const target = Math.min(totalPages, Math.max(1, Number(input?.value || 1)));
window.locations.href = pageUrl(target, pageInfo);
};
button?.addEventListener('click', go);
input?.addEventListener('keydown', (event) => {
if (event.key === 'Enter') go();
});
};
const renderBackendSeries = (posts = []) => {
const panel = document.querySelector('.sidebar .panel:nth-of-type(1)');
if (!panel || !posts.length) return;
const head = panel.querySelector('.panel-head');
const allLink = head?.querySelector('a');
if (allLink) {
allLink.href='/category/series';
allLink.target = '_blank';
allLink.rel = 'noopener';
}
panel.innerHTML = '';
if (head) panel.appendChild(head);
posts.slice(0, 3).forEach((post) => {
const title = post.titleZh || zhText(post.title);
const titleEn = post.titleEn || enText(post.title, title);
const subtitle = post.excerptZh || zhText(post.excerpt);
const subtitleEn = post.excerptEn || enText(post.excerpt, subtitle);
const link = document.createElement('a');
link.className = 'album';
link.href = normalizeUrl(post.url || post.link || (post.slug ? postUrl(post.slug) : '#'));
link.target = '_blank';
link.rel = 'noopener';
const image = post.thumbnail || post.cover || post.summary_image_url || post.image || post.featured_image?.source_url || post.featured_image?.sizes?.medium?.source_url || '';
link.innerHTML = `${escapeHtml(language === 'en' ? titleEn : title)}${escapeHtml(language === 'en' ? subtitleEn : subtitle)}
`;
panel.appendChild(link);
});
};
const renderBackendHot = (posts = []) => {
const panel = document.querySelector('.sidebar .panel:nth-of-type(2)');
if (!panel || !posts.length) return;
const head = panel.querySelector('.panel-head');
head?.querySelector('a')?.remove();
panel.innerHTML = '';
if (head) panel.appendChild(head);
posts.slice(0, 5).forEach((post, index) => {
const title = post.titleZh || compactTitle(post.title);
const titleEn = post.titleEn || enText(post.title, title);
const visibleTitle = language === 'en' ? titleEn : title;
const link = document.createElement('a');
link.className = 'hot';
link.href = normalizeUrl(post.url || post.link || (post.slug ? postUrl(post.slug) : '#'));
link.target = '_blank';
link.rel = 'noopener';
link.innerHTML = `${String(index + 1).padStart(2, '0')}
${escapeHtml(visibleTitle)}
`;
panel.appendChild(link);
});
};
const renderBackendData = (snapshot) => {
if (!snapshot?.home) return;
renderBackendCarousel(snapshot);
renderBackendHomeTopAd(snapshot);
renderBackendInlineAd({ snapshot, selector: '.ad-embed.materials-ad', field: 'homeMaterialAds', slug: 'home_list_8', positionId: '34630', label: '首页第 8 篇文章后' });
renderBackendInlineAd({ snapshot, selector: '.ad-embed.project-ad', field: 'homeProjectAds', slug: 'home_list_12', positionId: '34632', label: '首页第 12 篇文章后' });
renderBackendInlineAd({ snapshot, selector: '.ad-embed.page-bottom', field: 'homePaginationBottomAds', slug: 'home_pagination_bottom', positionId: '34642', label: '页码下方广告' });
const latestJobs = snapshot.home.homeSideJobs?.length ? snapshot.home.homeSideJobs : (snapshot.home.jobs || []);
const showNumbers = latestShowNumbers(snapshot);
renderBackendPosts(snapshot.home.posts || []);
renderBackendTextJobs(latestJobs, showNumbers, snapshot.jumpUrls || snapshot.home.jumpUrls || {});
renderBackendPinnedJobs(collectPinnedJobs(snapshot), snapshot.jumpUrls || snapshot.home.jumpUrls || {});
renderBackendSideJobs(latestJobs, showNumbers);
renderBackendSeries(snapshot.home.top_posts || []);
renderBackendHot(snapshot.home.week_hot || []);
renderPagination(snapshot.home.pageInfo || snapshot.pageInfo || {});
normalizeV10ImagePerformanceAttrs();
document.documentElement.dataset.backendSnapshot = '20260709';
};
const shouldUseLiveHomeData = () => {
const params = new URLSearchParams(window.locations.search);
const hostname = window.locations.hostname;
return hostname === 'www.gooood.cn' || hostname === '127.0.0.1' || hostname === 'localhost' || params.get('live') === '1' || params.has('fixture');
};
const loadFixtureSnapshot = async () => {
const params = new URLSearchParams(window.locations.search);
const fixture = params.get('fixture');
if (!fixture) return null;
if (!/^[a-zA-Z0-9._-]+$/.test(fixture)) throw new Error('Invalid V10 fixture name');
const fixtureResponse = await fetch(`/api/home/v10-fixture?name=${encodeURIComponent(fixture)}`, {
credentials: 'omit',
cache: 'no-store'
});
if (!fixtureResponse.ok) throw new Error(`V10 fixture not found: ${fixture}`);
const fixtureSnapshot = await fixtureResponse.json();
return { fixture, snapshot: fixtureSnapshot };
};
const withFreshCarouselAds = async (snapshot) => {
try {
const carouselUrl = `https://dashboard.gooood.cn/wp-json/wp/v2/advertisement?adposition=34627&per_page=20&order=asc&orderby=menu_order&_fresh=${Date.now()}`;
const response = await fetch(carouselUrl, { credentials: 'omit', cache: 'no-store' });
if (!response.ok) return snapshot;
const carouselAds = await response.json();
if (!Array.isArray(carouselAds)) return snapshot;
const freshSnapshot = JSON.parse(JSON.stringify(snapshot));
freshSnapshot.home = freshSnapshot.home || {};
freshSnapshot.home.carouselAds = carouselAds;
freshSnapshot.adsByPosition = freshSnapshot.adsByPosition || {};
freshSnapshot.adsByPosition['34627'] = carouselAds;
freshSnapshot.adFreshnessByPosition = freshSnapshot.adFreshnessByPosition || {};
freshSnapshot.adFreshnessByPosition['34627'] = {
source: 'wp/v2/advertisement',
sourceFetchedAt: new Date().toISOString(),
cacheStatus: 'direct-request',
itemCount: carouselAds.length,
setFingerprint: adSetFingerprint(carouselAds)
};
return freshSnapshot;
} catch (error) {
return snapshot;
}
};
const refreshLiveHomeData = async () => {
if (!backendSnapshot?.home || !shouldUseLiveHomeData()) return null;
if (backendSnapshot.home.pageInfo?.basePath) {
const freshArchiveSnapshot = await withFreshCarouselAds(backendSnapshot);
renderBackendData(freshArchiveSnapshot);
document.documentElement.dataset.liveHomeData = 'ssr-archive-snapshot';
document.documentElement.dataset.liveHomeSource = freshArchiveSnapshot.source || 'nuxt-v10-runtime:archive-full-page';
return freshArchiveSnapshot;
}
const page = currentPageFromLocation();
const fixtureResult = await loadFixtureSnapshot();
if (fixtureResult?.snapshot) {
renderBackendData(fixtureResult.snapshot);
document.documentElement.dataset.liveHomeData = 'connected';
document.documentElement.dataset.liveHomeSource = `v10-fixture:${fixtureResult.fixture}`;
return fixtureResult.snapshot;
}
try {
const localPreview = window.locations.hostname === '127.0.0.1' || window.locations.hostname === 'localhost';
const runtimeUrl = `/api/home/v10-runtime?page=${page}&hotPerPage=5${localPreview ? '&refresh=1' : ''}`;
const runtimeResponse = await fetch(runtimeUrl, { credentials: 'omit', cache: localPreview ? 'no-store' : 'default' });
if (runtimeResponse.ok) {
const runtimeSnapshot = await withFreshCarouselAds(await runtimeResponse.json());
renderBackendData(runtimeSnapshot);
document.documentElement.dataset.liveHomeData = 'connected';
document.documentElement.dataset.liveHomeSource = runtimeSnapshot.source || 'nuxt-v10-runtime';
return runtimeSnapshot;
}
} catch (error) {
document.documentElement.dataset.liveHomeData = 'nuxt-v10-runtime-fallback';
}
const initialPerPage = Number(backendSnapshot.home.pageInfo?.perPage) || 18;
let perPage = initialPerPage;
try {
const countResponse = await fetch(`https://dashboard.gooood.cn/api/api-blogjobs-number.php?_fresh=${Date.now()}`, { credentials: 'omit', cache: 'no-store' });
const countText = await countResponse.text();
perPage = Number(countText.trim()) || initialPerPage;
} catch (error) {
perPage = initialPerPage;
}
const postsEndpoint = `https://dashboard.gooood.cn/wp-json/wp/v2/fetch-posts?post_type%5B0%5D=post&post_type%5B1%5D=jobs&page=${page}&per_page=${perPage}&orderby=date&order=desc&_fresh=${Date.now()}`;
const [postsResult, jobsResult, carouselResult, runtimeResult, showNumbersResult] = await Promise.allSettled([
fetch(postsEndpoint, { credentials: 'omit', cache: 'no-store' }),
fetch('https://dashboard.gooood.cn/api/api-jobs-index.php', { credentials: 'omit' }),
fetch(`https://dashboard.gooood.cn/wp-json/wp/v2/advertisement?adposition=34627&per_page=20&order=asc&orderby=menu_order&_fresh=${Date.now()}`, { credentials: 'omit', cache: 'no-store' }),
fetch('/api/home/runtime?hotPerPage=5', { credentials: 'omit' }),
fetch(`https://dashboard.gooood.cn/api/api-show-number.php?_fresh=${Date.now()}`, { credentials: 'omit', cache: 'no-store' })
]);
const postsResponse = postsResult.status === 'fulfilled' && postsResult.value.ok ? postsResult.value : null;
const posts = postsResponse ? await postsResponse.json() : (backendSnapshot.home.posts || []);
const jobs = jobsResult.status === 'fulfilled' && jobsResult.value.ok ? await jobsResult.value.json() : backendSnapshot.home.homeSideJobs || [];
const carouselAds = carouselResult.status === 'fulfilled' && carouselResult.value.ok ? await carouselResult.value.json() : null;
const runtime = runtimeResult.status === 'fulfilled' && runtimeResult.value.ok ? await runtimeResult.value.json() : null;
const freshShowNumbers = showNumbersResult.status === 'fulfilled' && showNumbersResult.value.ok ? await showNumbersResult.value.json() : null;
const total = Number(postsResponse?.headers.get('x-wp-total')) || Number(backendSnapshot.home.pageInfo?.total) || posts.length;
const totalPages = Number(postsResponse?.headers.get('x-wp-totalpages')) || Number(backendSnapshot.home.pageInfo?.totalPages) || Math.max(1, Math.ceil(total / perPage));
const liveSnapshot = JSON.parse(JSON.stringify(backendSnapshot));
liveSnapshot.home.posts = posts;
liveSnapshot.home.homeSideJobs = jobs;
liveSnapshot.home.sideJobsCount = jobs.length;
const liveShowNumbers = freshShowNumbers && Object.keys(freshShowNumbers).length ? freshShowNumbers : runtime?.showNumbers;
if (liveShowNumbers) {
liveSnapshot.showNumbers = liveShowNumbers;
liveSnapshot.home.showNumbers = liveShowNumbers;
liveSnapshot.home.showNumbersSource = freshShowNumbers ? 'https://dashboard.gooood.cn/api/api-show-number.php' : (runtime.home?.showNumbersSource || runtime.home?.v10RuntimeSources?.showNumbers || runtime.source || 'local-home-runtime');
liveSnapshot.home.showNumbersFetchedAt = freshShowNumbers ? new Date().toISOString() : (runtime.home?.showNumbersFetchedAt || runtime.fetchedAt || '');
}
if (Array.isArray(runtime?.weekHot) && runtime.weekHot.length) {
liveSnapshot.home.week_hot = runtime.weekHot;
}
liveSnapshot.home.pageInfo = {
page,
perPage,
total,
totalPages,
source: postsEndpoint,
countSource: 'https://dashboard.gooood.cn/api/api-blogjobs-number.php',
frontendBaseUrl: 'https://www.rvu-inc.com',
corsAllowedOrigin: 'https://www.rvu-inc.com'
};
if (Array.isArray(carouselAds)) {
liveSnapshot.adsByPosition = liveSnapshot.adsByPosition || {};
liveSnapshot.ads = liveSnapshot.ads || {};
liveSnapshot.indexAds = liveSnapshot.indexAds || {};
liveSnapshot.adsByPosition['34627'] = carouselAds;
liveSnapshot.ads.home_banners = carouselAds;
liveSnapshot.indexAds.home_banners = carouselAds;
liveSnapshot.home.carouselAds = carouselAds;
liveSnapshot.adFreshnessByPosition = liveSnapshot.adFreshnessByPosition || {};
liveSnapshot.adFreshnessByPosition['34627'] = {
source: 'wp/v2/advertisement',
sourceFetchedAt: new Date().toISOString(),
cacheStatus: 'direct-request',
itemCount: carouselAds.length,
setFingerprint: adSetFingerprint(carouselAds)
};
}
renderBackendData(liveSnapshot);
document.documentElement.dataset.liveHomeData = 'connected';
return liveSnapshot;
};
renderBackendData(backendSnapshot);
document.querySelectorAll('.article').forEach((article) => {
const titleLink = article.querySelector('.article-text h3 a');
const cover = article.querySelector(':scope > .cover');
if (!titleLink || !cover || cover.querySelector('a')) return;
const coverLink = document.createElement('a');
coverLink.className = 'article-cover-link';
coverLink.href = titleLink.getAttribute('href') || '#';
coverLink.setAttribute('aria-label', titleLink.textContent.trim());
if (titleLink.target) coverLink.target = titleLink.target;
if (titleLink.rel) coverLink.rel = titleLink.rel;
while (cover.firstChild) coverLink.appendChild(cover.firstChild);
cover.appendChild(coverLink);
});
document.querySelectorAll('.article-actions').forEach((actions) => {
actions.innerHTML = `
☆收藏
`;
});
document.querySelectorAll('.article-actions').forEach((actions) => {
const links = actions.querySelectorAll('a, button');
setBilingualText(links[0], '收藏', 'Favorites');
setBilingualText(links[1], '分享', 'Share');
});
document.querySelectorAll('.page-jump button').forEach((button) => setBilingualText(button, '跳页', 'Go'));
const translatable = () => Array.from(document.querySelectorAll('[data-zh][data-en]'));
const languageToggles = Array.from(document.querySelectorAll('[data-lang-toggle]'));
const applyLanguage = (next) => {
language = next === 'en' ? 'en' : 'zh';
document.documentElement.lang = language === 'en' ? 'en' : 'zh-CN';
translatable().forEach((node) => {
node.textContent = node.dataset[language] || '';
});
document.querySelectorAll('[data-placeholder-zh][data-placeholder-en]').forEach((node) => {
node.setAttribute('placeholder', node.dataset[`placeholder${language === 'en' ? 'En' : 'Zh'}`] || '');
});
};
languageToggles.forEach((toggle) => {
toggle.addEventListener('click', () => applyLanguage(language === 'zh' ? 'en' : 'zh'));
});
applyLanguage('zh');
const headerSearchToggle = document.querySelector('[data-header-search-toggle]');
const headerSearchPanel = document.querySelector('#header-search-panel');
const headerSearchInput = headerSearchPanel?.querySelector('input');
const setHeaderSearchOpen = (nextOpen) => {
if (!headerSearchToggle || !headerSearchPanel) return;
headerSearchToggle.setAttribute('aria-expanded', String(nextOpen));
headerSearchPanel.hidden = !nextOpen;
if (nextOpen) {
window.setTimeout(() => headerSearchInput?.focus(), 0);
}
};
const buildSearchPath = (value) => {
const query = String(value || '').trim();
return query ? `https://www.rvu-inc.com/search/${encodeURIComponent(query)}` : '';
};
const bindSearchPathSubmit = (form) => {
form?.addEventListener('submit', (event) => {
const input = form.querySelector('input[name="q"], input[name="s"]');
const path = buildSearchPath(input?.value);
if (!path) return;
event.preventDefault();
const navigationTarget = document.documentElement.dataset.v10Embed === 'header' && window.top !== window
? window.top
: window;
navigationTarget.locations.href = path;
});
};
headerSearchToggle?.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
setHeaderSearchOpen(headerSearchToggle.getAttribute('aria-expanded') !== 'true');
});
headerSearchPanel?.addEventListener('click', (event) => {
event.stopPropagation();
});
document.querySelectorAll('[data-header-search-close]').forEach((button) => {
button.addEventListener('click', () => setHeaderSearchOpen(false));
});
document.addEventListener('click', () => setHeaderSearchOpen(false));
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') setHeaderSearchOpen(false);
});
bindSearchPathSubmit(document.querySelector('.header-search-form'));
bindSearchPathSubmit(document.querySelector('.mobiles-search-form'));
const mobileMenuToggle = document.querySelector('.mobiles-menu-toggle');
const mobileMenu = document.querySelector('#mobiles-menu');
const mobileMenuBackdrop = document.querySelector('.mobiles-menu-backdrop');
const headerActions = document.querySelector('.header-actions');
const setMobileMenuOpen = (nextOpen) => {
mobileMenuToggle?.setAttribute('aria-expanded', String(nextOpen));
if (mobileMenu) {
mobileMenu.hidden = !nextOpen;
mobileMenu.classList.toggle('is-open', nextOpen);
}
if (mobileMenuBackdrop) {
mobileMenuBackdrop.hidden = !nextOpen;
mobileMenuBackdrop.classList.toggle('is-open', nextOpen);
}
headerActions?.classList.toggle('menu-open', nextOpen);
};
mobileMenuToggle?.addEventListener('click', () => {
const isOpen = mobileMenuToggle.getAttribute('aria-expanded') === 'true';
setMobileMenuOpen(!isOpen);
});
mobileMenuBackdrop?.addEventListener('click', () => setMobileMenuOpen(false));
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') setMobileMenuOpen(false);
});
document.querySelectorAll('.mobiles-menu-parent').forEach((button) => {
button.addEventListener('click', () => {
button.closest('.mobiles-menu-item')?.classList.toggle('is-open');
});
});
const setupSlider = () => {
const slider = document.querySelector('.top-slider');
if (!slider) return;
const slides = Array.from(slider.querySelectorAll('.slide'));
const dots = Array.from(slider.querySelectorAll('[data-slide-dot]'));
const prev = slider.querySelector('[data-slide-prev]');
const next = slider.querySelector('[data-slide-next]');
if (!slides.length) return;
let active = Math.max(0, slides.findIndex((slide) => slide.classList.contains('is-active')));
const loadSlideImage = (slide) => {
const img = slide?.querySelector('img[data-src]');
if (!img) return;
img.src = img.dataset.src || '';
img.removeAttribute('data-src');
};
const show = (index) => {
active = (index + slides.length) % slides.length;
loadSlideImage(slides[active]);
slides.forEach((slide, i) => slide.classList.toggle('is-active', i === active));
dots.forEach((dot, i) => dot.classList.toggle('is-active', i === active));
};
if (prev) prev.onclick = () => show(active - 1);
if (next) next.onclick = () => show(active + 1);
dots.forEach((dot, i) => {
dot.onclick = () => show(i);
});
show(active);
};
if (document.documentElement.dataset.v10Embed === 'header') return;
setupSlider();
refreshLiveHomeData()
.then((snapshot) => {
if (!snapshot) return;
applyLanguage(language);
setupSlider();
})
.catch((error) => {
document.documentElement.dataset.liveHomeData = 'snapshot-fallback';
console.warn('[gooood V10] live home data fallback', error);
});
})();
`;
document.body.appendChild(modal);
const toast = document.createElement('div');
toast.className = 'gooood-auth-toast';
toast.setAttribute('role', 'status');
toast.hidden = true;
document.body.appendChild(toast);
const form = modal.querySelector('.gooood-auth-form');
const titleNode = modal.querySelector('#gooood-auth-title');
const errorNode = modal.querySelector('.gooood-auth-error');
const feedbackNode = modal.querySelector('.gooood-auth-feedback');
const submitButton = modal.querySelector('.gooood-auth-submit');
const codeButtons = Array.from(modal.querySelectorAll('.gooood-auth-code'));
const wechatButton = modal.querySelector('.gooood-auth-wechat');
const socialBlock = modal.querySelector('.gooood-auth-social');
const wechatMobileGuidance = document.createElement('p');
wechatMobileGuidance.className = 'gooood-auth-wechat-mobiles-guidance';
wechatMobileGuidance.setAttribute('role', 'status');
wechatMobileGuidance.dataset.zh = '微信登录请在微信内打开。当前浏览器会显示电脑扫码,无法在同一部手机完成登录。';
wechatMobileGuidance.dataset.en = 'Open this page in WeChat to continue. This browser can only show a desktop QR code.';
wechatMobileGuidance.hidden = true;
socialBlock.appendChild(wechatMobileGuidance);
const currentLanguage = () => document.documentElement.lang === 'en' ? 'en' : 'zh';
const currentPathLanguage = () => /^\/en(?:\/|$)/.test(window.locations.pathname) ? 'en' : 'zh';
const isExternalMobileWechatLogin = () => window.matchMedia('(max-width: 767px)').matches && !/MicroMessenger/i.test(navigator.userAgent);
const syncWechatLoginEntry = () => {
const showGuidance = isExternalMobileWechatLogin();
wechatButton.hidden = showGuidance;
wechatMobileGuidance.hidden = !showGuidance;
if (showGuidance) wechatButton.disabled = false;
};
const mobileWechatLoginMedia = window.matchMedia('(max-width: 767px)');
if (typeof mobileWechatLoginMedia.addEventListener === 'function') mobileWechatLoginMedia.addEventListener('change', syncWechatLoginEntry);
const stripEnglishPrefix = (path) => {
const normalized = path && path.startsWith('/') ? path : `/${path || ''}`;
if (normalized === '/en' || normalized === '/en/') return '/';
return normalized.startsWith('/en/') ? normalized.slice(3) || '/' : normalized;
};
const localizedPath = (path, language) => {
const clean = stripEnglishPrefix(path);
if (language !== 'en') return clean;
return clean === '/' ? '/en/' : `/en${clean}`;
};
const saveLanguage = (language) => {
const parts = [
`language=${language === 'en' ? 'en_US' : 'zh_CN'}`,
'Path=/',
'SameSite=Lax',
'Max-Age=31536000'
];
if (window.locations.protocol === 'https:') parts.push('Secure');
if (window.locations.hostname === 'gooood.cn' || window.locations.hostname.endsWith('.gooood.cn')) parts.push('Domain=.gooood.cn');
document.cookie = parts.join('; ');
};
const switchLanguage = (language) => {
saveLanguage(language);
const url = new URL(window.location.href);
url.pathname = localizedPath(url.pathname, language);
url.searchParams.delete('lang');
window.locations.assign(`${url.pathname}${url.search}${url.hash}`);
};
const responseMessage = (response, fallback) => String((response && (response.msg || response.message)) || fallback).split('|')[0] || fallback;
const responseSucceeded = (response) => {
if (!response) return true;
const status = String(response.status || '').toLowerCase();
const code = String(response.code || '');
return status === '1' || status === 'success' || code === '200';
};
const showToast = (zh, en = zh) => {
window.clearTimeout(toastTimer);
toast.textContent = currentLanguage() === 'en' ? en : zh;
toast.hidden = false;
toastTimer = window.setTimeout(() => { toast.hidden = true; }, 2600);
};
const setError = (message = '') => { errorNode.textContent = message; errorNode.hidden = !message; };
const setFeedback = (message = '') => { feedbackNode.textContent = message; feedbackNode.hidden = !message; };
const applyModalLanguage = () => {
const language = currentLanguage();
modal.querySelectorAll('[data-zh][data-en]').forEach((node) => { node.textContent = node.dataset[language] || node.dataset.zh || ''; });
modal.querySelectorAll('[data-placeholder-zh][data-placeholder-en]').forEach((node) => { node.placeholder = language === 'en' ? node.dataset.placeholderEn : node.dataset.placeholderZh; });
};
const updateCodeButton = (kind, busy = false) => {
const button = modal.querySelector(`[data-code-kind="${kind}"]`);
if (!button) return;
const countdown = kind === 'register' ? registerCountdown : forgotCountdown;
if (busy) button.textContent = '验证中...';
else if (countdown > 0) button.textContent = `已发送 ${countdown} s`;
else button.textContent = currentLanguage() === 'en' ? 'Get code' : '获取验证码';
button.disabled = busy || countdown > 0;
};
const startCodeCountdown = (kind) => {
if (kind === 'register' && registerCountdownTimer) window.clearInterval(registerCountdownTimer);
if (kind === 'forgot' && forgotCountdownTimer) window.clearInterval(forgotCountdownTimer);
if (kind === 'register') registerCountdown = 60;
else forgotCountdown = 60;
updateCodeButton(kind);
const timer = window.setInterval(() => {
if (kind === 'register') registerCountdown -= 1;
else forgotCountdown -= 1;
if ((kind === 'register' ? registerCountdown : forgotCountdown) <= 0) {
if (kind === 'register') registerCountdown = 0;
else forgotCountdown = 0;
window.clearInterval(timer);
}
updateCodeButton(kind);
}, 1000);
if (kind === 'register') registerCountdownTimer = timer;
else forgotCountdownTimer = timer;
};
const populateTelSelects = () => {
modal.querySelectorAll('select[name="register_region"], select[name="forgot_region"]').forEach((select) => {
const selected = select.value || '86';
select.replaceChildren(...telCodes.map((item) => {
const option = document.createElement('option');
option.value = String(item.code);
option.textContent = `${item.zh} + ${item.code}`;
if (option.value === selected) option.selected = true;
return option;
}));
if (!select.value) select.value = '86';
});
};
const loadTelCodes = async () => {
populateTelSelects();
if (telCodesLoaded) { populateTelSelects(); return; }
try {
const response = await apiRequest('/wp/v2/tel/code', { auth: false });
if (Array.isArray(response) && response.length) telCodes = response;
telCodesLoaded = true;
} catch (_) {
telCodes = [{ en: 'China', zh: '中国', locale: 'CN', code: '86' }];
}
populateTelSelects();
};
const ensureAliyunCaptchaElements = () => {
const existingElement = document.getElementById('gooood-aliyun-captcha-element');
if (!existingElement) {
const element = document.createElement('div');
element.id = 'gooood-aliyun-captcha-element';
document.body.appendChild(element);
} else {
existingElement.removeAttribute('style');
}
if (!document.getElementById('gooood-aliyun-captcha-button')) {
const button = document.createElement('button');
button.id = 'gooood-aliyun-captcha-button';
button.type = 'button';
button.style.position = 'fixed';
button.style.left = '0';
button.style.top = '0';
button.style.width = '1px';
button.style.height = '1px';
button.style.opacity = '0.01';
button.style.zIndex = '1';
document.body.appendChild(button);
}
};
const normalizeCaptchaParam = (payload) => {
if (typeof payload === 'string') return payload;
if (!payload || typeof payload !== 'object') return '';
const value = payload.captchaVerifyParam || payload.CaptchaVerifyParam || payload.verifyParam || payload.token || payload.verifyToken || payload.data;
if (typeof value === 'string') return value;
if (value && typeof value === 'object') return JSON.stringify(value);
return JSON.stringify(payload);
};
const loadAliyunCaptchaScript = () => {
window.AliyunCaptchaConfig = { region: 'cn', prefix: 'svsmdc' };
if (typeof window.initAliyunCaptcha === 'function') return Promise.resolve();
if (aliyunCaptchaScriptPromise) return aliyunCaptchaScriptPromise;
aliyunCaptchaScriptPromise = new Promise((resolve, reject) => {
let settled = false;
let existingGuard = 0;
const settleResolve = () => {
if (settled) return;
settled = true;
if (existingGuard) window.clearTimeout(existingGuard);
resolve();
};
const settleReject = (stage, error, details = {}) => {
if (settled) return;
settled = true;
if (existingGuard) window.clearTimeout(existingGuard);
logCaptchaDiagnostic(stage, details);
reject(error);
};
const injectScript = () => {
const script = document.createElement('script');
script.src = ALIYUN_CAPTCHA_SRC;
script.async = true;
script.onload = () => {
if (typeof window.initAliyunCaptcha === 'function') {
settleResolve();
return;
}
settleReject('script_load_without_init', new Error('人机验证加载失败,请刷新后重试'));
};
script.onerror = () => {
settleReject('script_load_error', new Error('人机验证加载失败,请刷新后重试'));
};
document.head.appendChild(script);
};
const existing = document.querySelector(`script[src="/type/museum-culture-architecture/${ALIYUN_CAPTCHA_SRC}"]`);
if (existing) {
existing.addEventListener('load', () => {
if (typeof window.initAliyunCaptcha === 'function') settleResolve();
else settleReject('script_existing_without_init', new Error('人机验证加载失败,请刷新后重试'), { reason: 'load_without_init' });
}, { once: true });
existing.addEventListener('error', () => {
settleReject('script_existing_error', new Error('人机验证加载失败,请刷新后重试'));
}, { once: true });
existingGuard = window.setTimeout(() => {
if (typeof window.initAliyunCaptcha === 'function') settleResolve();
else settleReject('script_existing_missing_init', new Error('人机验证加载失败,请刷新后重试'));
}, CAPTCHA_EXISTING_SCRIPT_TIMEOUT_MS);
return;
}
injectScript();
});
return aliyunCaptchaScriptPromise;
};
const captchaErrorMessage = (error, fallback = '人机验证失败,请重试') => {
if (error && error.message) return error.message;
if (error && typeof error === 'object') {
const code = error.code ? String(error.code) : '';
const msg = error.msg || error.message;
if (msg) return code ? `${code}: ${String(msg)}` : String(msg);
}
return fallback;
};
const ensureGoooodAliyunCaptchaSms = () => {
if (window.goooodAliyunCaptchaSms) return;
let instance = null;
let loading = false;
let waiters = [];
let pending = null;
let pendingFail = null;
let pendingTimer = 0;
let initGuard = 0;
let loadErrorGuard = 0;
let visibleActionGuard = 0;
let preferVisible = false;
let reinitializeBeforeNextRun = false;
let instanceReadyAt = 0;
const clearVisibleActionGuard = () => {
if (visibleActionGuard) window.clearTimeout(visibleActionGuard);
visibleActionGuard = 0;
};
const clearPendingTimer = () => {
if (pendingTimer) window.clearTimeout(pendingTimer);
pendingTimer = 0;
};
const armVisibleActionGuard = () => {
clearVisibleActionGuard();
visibleActionGuard = window.setTimeout(() => {
if (!pending) return;
reinitializeBeforeNextRun = true;
rejectPending(new Error('人机验证加载超时,请重新点击获取验证码'));
}, CAPTCHA_VISIBLE_ACTION_TIMEOUT_MS);
};
const armPendingTimer = () => {
clearPendingTimer();
pendingTimer = window.setTimeout(() => {
rejectPending(new Error('captcha_callback_timeout'));
}, CAPTCHA_VERIFY_TIMEOUT_MS);
};
const clearInitGuard = () => {
if (initGuard) window.clearTimeout(initGuard);
initGuard = 0;
if (loadErrorGuard) window.clearTimeout(loadErrorGuard);
loadErrorGuard = 0;
};
const finish = (payload) => {
const value = normalizeCaptchaParam(payload);
if (!value) {
rejectPending(new Error('人机验证结果无效,请重新点击获取验证码'));
return;
}
clearPendingTimer();
clearVisibleActionGuard();
clearInitGuard();
loading = false;
reinitializeBeforeNextRun = true;
resolveWaiters();
const callback = pending;
pending = null;
pendingFail = null;
if (callback) callback(value);
};
const handleChallengeFailure = () => {
// Aliyun refreshes a failed challenge in-place. Keep this request pending so
// the user can complete the refreshed challenge without clicking SMS again.
};
const handleLoadError = (error) => {
reinitializeBeforeNextRun = true;
logCaptchaDiagnostic(instance ? 'sdk_on_error_after_instance' : 'sdk_on_error_before_instance', {
message: captchaErrorMessage(error, '')
});
if (instance) {
if (pending) rejectPending(error || new Error('人机验证网络请求超时,请重新点击获取验证码'));
return;
}
// The SDK can report a transient resource timeout before getInstance arrives.
// Give that late initialization a short recovery window instead of showing a
// terminal business error while the challenge is still appearing.
if (!loading || instance) return;
if (loadErrorGuard) window.clearTimeout(loadErrorGuard);
loadErrorGuard = window.setTimeout(() => {
loadErrorGuard = 0;
if (!loading || instance) return;
loading = false;
if (initGuard) window.clearTimeout(initGuard);
initGuard = 0;
rejectWaiters(error || new Error('人机验证加载超时,请刷新后重试'));
}, CAPTCHA_LOAD_ERROR_GRACE_MS);
};
const rejectPending = (error) => {
clearVisibleActionGuard();
clearPendingTimer();
clearInitGuard();
loading = false;
const callback = pendingFail;
pending = null;
pendingFail = null;
if (callback) callback(error);
};
const resolveWaiters = () => {
const queue = waiters.slice();
waiters = [];
queue.forEach(({ resolve }) => resolve());
};
const rejectWaiters = (error) => {
const queue = waiters.slice();
waiters = [];
queue.forEach(({ reject }) => reject(error));
};
const ready = (resolve, reject) => {
ensureAliyunCaptchaElements();
loadAliyunCaptchaScript().then(() => {
if (instance) { resolve(); return; }
waiters.push({ resolve, reject });
if (loading) return;
loading = true;
initGuard = window.setTimeout(() => {
if (!loading) return;
loading = false;
rejectWaiters(new Error('captcha_init_timeout'));
}, CAPTCHA_INIT_TIMEOUT_MS);
try {
window.initAliyunCaptcha({
SceneId: '1rjb4fqs',
mode: 'popup',
element: '#gooood-aliyun-captcha-element',
button: '#gooood-aliyun-captcha-button',
success: finish,
fail: handleChallengeFailure,
onError: handleLoadError,
getInstance: (captcha) => {
clearInitGuard();
instance = captcha;
instanceReadyAt = Date.now();
logCaptchaDiagnostic('sdk_instance_ready', {
hasStartTracelessVerification: typeof captcha.startTracelessVerification === 'function',
hasShow: typeof captcha.show === 'function',
resources: aliyunResourceDiagnostics()
});
loading = false;
resolveWaiters();
},
slideStyle: { width: 360, height: 40 },
timeout: CAPTCHA_REQUEST_TIMEOUT_MS,
rem: CAPTCHA_UI_SCALE,
showErrorTip: false,
language: 'cn'
});
} catch (error) {
clearInitGuard();
loading = false;
rejectWaiters(error);
}
}).catch(reject);
};
window.goooodAliyunCaptchaPreferVisible = () => {
preferVisible = true;
};
const resetCompletedCaptcha = () => {
if (!reinitializeBeforeNextRun) return;
try {
const result = instance && instance.hide ? instance.hide() : null;
if (result && typeof result.then === 'function') {
Promise.resolve(result).catch(() => {});
}
} catch (_) {
// A completed V3 instance is discarded even if its best-effort hide fails.
}
instance = null;
instanceReadyAt = 0;
reinitializeBeforeNextRun = false;
};
const clickCaptchaButton = () => {
const button = document.getElementById('gooood-aliyun-captcha-button');
if (!button) return false;
try {
button.click();
} catch (error) {
rejectPending(error);
}
return true;
};
const runPendingCaptchaAction = () => {
if (!pending) return;
armPendingTimer();
try {
preferVisible = false;
logCaptchaDiagnostic('sdk_button_click', {
instanceAgeMs: instanceReadyAt ? Date.now() - instanceReadyAt : 0,
hasStartTracelessVerification: typeof (instance && instance.startTracelessVerification) === 'function',
hasShow: typeof (instance && instance.show) === 'function'
});
if (clickCaptchaButton()) {
armVisibleActionGuard();
return;
}
throw new Error('captcha_button_missing');
} catch (error) {
rejectPending(error);
}
};
window.goooodAliyunCaptchaSms = (ok, fail) => {
pending = ok;
pendingFail = fail || null;
resetCompletedCaptcha();
ready(() => {
if (!pending) return;
const warmupRemaining = Math.max(0, CAPTCHA_MIN_WARMUP_MS - (Date.now() - instanceReadyAt));
if (warmupRemaining > 0) {
window.setTimeout(runPendingCaptchaAction, warmupRemaining);
return;
}
runPendingCaptchaAction();
}, rejectPending);
};
window.setTimeout(() => {
ready(() => {}, () => {});
}, 0);
};
const runSmsCaptcha = async () => {
ensureGoooodAliyunCaptchaSms();
return new Promise((resolve, reject) => {
window.goooodAliyunCaptchaSms(resolve, (error) => {
reject(new Error(captchaErrorMessage(error, '人机验证超时,请刷新后重试')));
});
});
};
const preferVisibleSmsCaptcha = () => {
ensureGoooodAliyunCaptchaSms();
if (window.goooodAliyunCaptchaPreferVisible) window.goooodAliyunCaptchaPreferVisible();
};
const isCaptchaVerifyFailure = (source) => {
const records = [source];
if (source && typeof source === 'object' && source.data) records.push(source.data);
return records.some((item) => {
if (!item) return false;
if (typeof item === 'string') return item.includes('captcha_verify_failed') || item.includes('人机验证');
if (item instanceof Error) return isCaptchaVerifyFailure(item.message);
if (typeof item !== 'object') return false;
return [item.code, item.msg, item.message, item.error].some((value) => {
const text = String(value || '');
return text.includes('captcha_verify_failed') || text.includes('人机验证') || text.includes('captcha');
});
});
};
const requestSmsCodeWithCaptcha = async (request, failureMessage) => {
setFeedback('正在加载人机验证...');
const captchaVerifyParam = await runSmsCaptcha();
try {
const response = await request(captchaVerifyParam);
if (!responseSucceeded(response)) {
if (isCaptchaVerifyFailure(response)) {
preferVisibleSmsCaptcha();
throw new Error('人机验证未通过,请再次点击获取验证码完成安全验证。');
}
throw new Error(responseMessage(response, failureMessage));
}
return response;
} catch (error) {
if (isCaptchaVerifyFailure(error)) {
preferVisibleSmsCaptcha();
throw new Error('人机验证未通过,请再次点击获取验证码完成安全验证。');
}
throw error;
}
};
const setForgotMethod = (method) => {
forgotMethod = method;
modal.querySelectorAll('[data-forgot-method-panel]').forEach((panel) => {
const active = authMode === 'forgot' && panel.dataset.forgotMethodPanel === method;
panel.hidden = !active;
panel.querySelectorAll('input, select').forEach((control) => {
control.disabled = !active;
control.required = active;
});
});
if (authMode === 'forgot') {
titleNode.dataset.zh = method === 'phones' ? '重置密码' : '忘记密码';
titleNode.dataset.en = method === 'phones' ? 'Reset Password' : 'Forgot Password';
submitButton.dataset.zh = method === 'phones' ? '保存修改' : 'Get New Password';
submitButton.dataset.en = method === 'phones' ? 'Save changes' : 'Get New Password';
}
if (method === 'phones') loadTelCodes();
applyModalLanguage();
};
const setMode = (mode, keepFeedback = false) => {
authMode = mode;
setError('');
if (!keepFeedback) setFeedback('');
modal.querySelectorAll('[data-auth-panel]').forEach((panel) => {
const active = panel.dataset.authPanel === mode;
panel.hidden = !active;
panel.querySelectorAll('input, select').forEach((input) => {
const nestedForgotPanel = input.closest('[data-forgot-method-panel]');
input.disabled = !active || (nestedForgotPanel && nestedForgotPanel.dataset.forgotMethodPanel !== forgotMethod);
input.required = active && (!nestedForgotPanel || nestedForgotPanel.dataset.forgotMethodPanel === forgotMethod);
});
});
if (mode !== 'forgot') setForgotMethod('email');
if (mode === 'login') {
titleNode.dataset.zh = '会员登录';
titleNode.dataset.en = 'Member Login';
submitButton.dataset.zh = '登录';
submitButton.dataset.en = 'Login';
socialBlock.hidden = false;
syncWechatLoginEntry();
} else if (mode === 'register') {
titleNode.dataset.zh = '手机号注册';
titleNode.dataset.en = 'mobiles Register';
submitButton.dataset.zh = '注册';
submitButton.dataset.en = 'Register';
socialBlock.hidden = true;
loadTelCodes();
} else {
setForgotMethod(forgotMethod);
socialBlock.hidden = true;
}
modal.querySelectorAll('[data-auth-mode-button]').forEach((button) => {
const target = button.dataset.authModeButton;
if (target === 'register') {
button.dataset.zh = mode === 'register' ? '返回登录' : '手机号注册 »';
button.dataset.en = mode === 'register' ? 'Back to login' : 'mobiles register »';
button.dataset.authModeTarget = mode === 'register' ? 'login' : 'register';
} else {
button.dataset.zh = mode === 'forgot' ? '返回登录' : '忘记密码?';
button.dataset.en = mode === 'forgot' ? 'Back to login' : 'Forgot password?';
button.dataset.authModeTarget = mode === 'forgot' ? 'login' : 'forgot';
}
});
applyModalLanguage();
updateCodeButton('register');
updateCodeButton('forgot');
};
const openModal = (mode = 'login') => {
ensureGoooodAliyunCaptchaSms();
setMode(mode);
modal.hidden = false;
document.body.classList.add('gooood-auth-open');
const focusTarget = authMode === 'register' ? form.elements.register_mobile : authMode === 'forgot' ? form.elements.reset_login : form.elements.username;
window.setTimeout(() => focusTarget && focusTarget.focus(), 0);
};
const closeModal = () => { modal.hidden = true; document.body.classList.remove('gooood-auth-open'); setError(''); setFeedback(''); };
const favoriteContains = (favorites, postId) => Array.isArray(favorites) && favorites.some((entry) => typeof entry === 'number' || typeof entry === 'string' ? Number(entry) === postId : Number(entry && entry.post_id) === postId);
const setFavoriteState = (trigger, active) => {
if (!trigger) return;
trigger.classList.toggle('is-favorited', active);
trigger.setAttribute('aria-pressed', String(active));
const icon = trigger.querySelector('.action-icon');
const label = trigger.querySelector('[data-zh][data-en]');
if (icon) icon.textContent = active ? '★' : '☆';
if (label) { label.dataset.zh = active ? '已收藏' : '收藏'; label.dataset.en = active ? 'Saved' : 'Favorites'; label.textContent = currentLanguage() === 'en' ? label.dataset.en : label.dataset.zh; }
};
const toggleFavorite = async (favorite) => {
const { trigger, postId, typeId } = favorite || {};
if (!trigger || !postId || !typeId) { showToast('文章收藏信息尚未加载,请稍后重试', 'Favorite data is still loading. Please try again.'); return; }
trigger.classList.add('is-pending');
try {
const response = await apiRequest('/wp/v2/fav', { method: 'POST', body: { post_id: postId, type_id: typeId } });
const active = favoriteContains(response.favorites, postId);
setFavoriteState(trigger, active);
showToast(active ? '已收藏' : '已取消收藏', active ? 'Saved' : 'Removed from favorites');
} catch (error) {
if (isConfirmedAuthInvalidation(error)) { clearToken(); pendingFavorite = favorite; openModal(); }
else showToast(error.message || '收藏失败', error.message || 'Unable to save');
} finally { trigger.classList.remove('is-pending'); }
};
const articleFavoriteFrom = (trigger) => {
const article = trigger.closest('.article');
return { trigger, postId: Number(article && article.dataset.backendPostId), typeId: Number(article && article.dataset.backendPrimaryTypeId) };
};
const normalizeBrokenTargets = () => {
document.querySelectorAll('a[href]').forEach((link) => {
let pathname = '';
try { pathname = new URL(link.href, window.location.href).pathname.replace(/\/$/, '') || '/'; } catch (_) {}
const isLoginHash = link.hash === '#login';
if (!BROKEN_ACCOUNT_PATHS.has(pathname) && !isLoginHash) return;
link.href='/type/museum-culture-architecture/%23login';
if (link.classList.contains('favorite-action') || link.closest('.article-actions')) { link.dataset.goooodFavoriteTrigger = '1'; link.setAttribute('role', 'button'); }
else { link.dataset.goooodLoginTrigger = '1'; link.setAttribute('role', 'button'); }
});
};
modal.querySelector('.gooood-auth-close').addEventListener('click', closeModal);
document.querySelectorAll('[data-lang-toggle]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
switchLanguage(currentPathLanguage() === 'en' ? 'zh' : 'en');
}, true);
});
modal.addEventListener('click', (event) => { if (event.target === modal) closeModal(); });
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape') return;
if (!modal.hidden) closeModal();
closeAccountMenus();
});
codeButtons.forEach((button) => {
button.addEventListener('click', async () => {
const kind = button.dataset.codeKind || 'register';
setError(''); setFeedback('');
const mobiles = kind === 'forgot' ? form.elements.forgot_mobile.value.trim() : form.elements.register_mobile.value.trim();
const region = kind === 'forgot' ? form.elements.forgot_region.value : form.elements.register_region.value;
if (!mobiles) { setError('请输入手机号'); return; }
if ((kind === 'forgot' ? forgotCountdown : registerCountdown) > 0) return;
updateCodeButton(kind, true);
try {
const response = await requestSmsCodeWithCaptcha((captchaVerifyParam) => apiRequest('/wp/v2/sms202601', {
method: 'POST',
auth: false,
body: {
region_number: region || '86',
mobiles,
event: kind === 'forgot' ? 'editpass' : 'register',
captchaVerifyParam,
CaptchaVerifyParam: captchaVerifyParam
}
}), '验证码发送失败,请稍后重试。');
setFeedback(responseMessage(response, '验证码已发送,请查收短信。'));
startCodeCountdown(kind);
} catch (error) { setFeedback(''); setError(error.message || '验证码发送失败,请稍后重试。'); }
finally { updateCodeButton(kind, false); }
});
});
modal.querySelectorAll('[data-auth-mode-button]').forEach((button) => {
button.addEventListener('click', () => setMode(button.dataset.authModeTarget || 'login'));
});
modal.querySelectorAll('[data-auth-forgot-method]').forEach((button) => {
button.addEventListener('click', () => {
setError('');
setFeedback('');
setForgotMethod(button.dataset.authForgotMethod || 'email');
});
});
form.addEventListener('submit', async (event) => {
event.preventDefault(); setError(''); setFeedback(''); submitButton.disabled = true;
try {
if (authMode === 'register') {
const mobiles = form.elements.register_mobile.value.trim();
const region = form.elements.register_region.value || '86';
const password = form.elements.register_password.value;
const password2 = form.elements.register_password2.value;
if (password !== password2) throw new Error('两次输入的密码必须一致');
const response = await apiRequest('/wp/v2/user/register', { method: 'POST', auth: false, body: { region_number: region, user_mobile: mobiles, sms_code: form.elements.register_code.value.trim(), user_pass: password, user_pass2: password2, from_source: 2 } });
if (!responseSucceeded(response)) throw new Error(responseMessage(response, '注册失败,请检查信息后重试。'));
form.elements.username.value = mobiles;
form.elements.password.value = '';
form.elements.register_code.value = '';
form.elements.register_password.value = '';
form.elements.register_password2.value = '';
setFeedback(responseMessage(response, '注册成功,请使用手机号和密码登录。'));
setMode('login', true);
} else if (authMode === 'forgot') {
if (forgotMethod === 'phones') {
const mobiles = form.elements.forgot_mobile.value.trim();
const smsCode = form.elements.forgot_code.value.trim();
const password = form.elements.forgot_password.value;
const password2 = form.elements.forgot_password2.value;
if (!mobiles) throw new Error('请输入手机号');
if (!smsCode) throw new Error('请输入验证码');
if (!password) throw new Error('请输入新密码');
if (password !== password2) throw new Error('两次输入的密码必须一致');
const response = await apiRequest('/wp/v2/account/editpass', { method: 'POST', auth: false, body: { password: password2, mobiles, sms_code: smsCode } });
if (!responseSucceeded(response)) throw new Error(responseMessage(response, '密码修改失败,请检查信息后重试。'));
form.elements.username.value = mobiles;
form.elements.password.value = '';
form.elements.forgot_code.value = '';
form.elements.forgot_password.value = '';
form.elements.forgot_password2.value = '';
setFeedback(responseMessage(response, '密码已修改,请返回登录。'));
setMode('login', true);
} else {
const response = await apiRequest('/wp/v2/account/forgotpwd', { method: 'POST', auth: false, body: { user_login: form.elements.reset_login.value.trim() } });
if (!responseSucceeded(response)) throw new Error(responseMessage(response, '提交失败,请稍后重试。'));
setFeedback(responseMessage(response, '如果账号存在,重设密码邮件将发送到账号邮箱。'));
}
} else {
const response = await apiRequest('/jwt-auth/v1/token', { method: 'POST', auth: false, body: { username: form.elements.username.value.trim(), password: form.elements.password.value } });
if (!response.token) throw new Error('登录响应缺少 token');
saveToken(response.token, response.expire);
const user = await apiRequest('/wp/v2/users/me', { token: response.token });
updateMemberUI(user);
closeModal(); form.reset(); showToast(`登录成功${resolveDisplayName(user) ? `,欢迎 ${resolveDisplayName(user)}` : ''}`, 'Logged in');
if (pendingFavorite) { const favorite = pendingFavorite; pendingFavorite = null; await toggleFavorite(favorite); }
}
} catch (error) {
if (authMode === 'login') clearToken();
setError(error.message || (authMode === 'register' ? '注册失败,请检查信息后重试。' : authMode === 'forgot' ? '提交失败,请稍后重试。' : '登录失败'));
}
finally { submitButton.disabled = false; }
});
wechatButton.addEventListener('click', async () => {
if (isExternalMobileWechatLogin()) {
syncWechatLoginEntry();
setFeedback('请在微信内打开本页后,再使用微信登录。');
return;
}
setError(''); wechatButton.disabled = true;
try {
const response = await apiRequest('/wp/v2/account/oauth', { method: 'POST', auth: false, body: { action: 'login', target: 'wechat', back: window.locations.pathname + window.locations.search } });
const destination = response.url || response.redirect || response.locations;
if (!destination) throw new Error('微信登录暂时不可用');
window.locations.href = destination;
} catch (error) { setError(error.message || '微信登录启动失败'); wechatButton.disabled = false; }
});
document.addEventListener('click', async (event) => {
const accountToggle = event.target.closest('[data-gooood-account-toggle]');
if (accountToggle) {
event.preventDefault();
const account = accountToggle.closest('.gooood-account');
const opening = !account.classList.contains('is-open');
closeAccountMenus(account);
account.classList.toggle('is-open', opening);
accountToggle.setAttribute('aria-expanded', String(opening));
return;
}
const accountLogout = event.target.closest('[data-gooood-account-logout]');
if (accountLogout) {
event.preventDefault();
try { await apiRequest('/wp/v2/users/logout', { method: 'POST' }); } catch (_) {}
clearToken();
updateMemberUI(null);
showToast('已退出登录', 'Logged out');
return;
}
if (!event.target.closest('.gooood-account')) closeAccountMenus();
const favoriteTrigger = event.target.closest('[data-gooood-favorite-trigger], .favorite-action');
if (favoriteTrigger) {
event.preventDefault();
const favorite = articleFavoriteFrom(favoriteTrigger);
if (!cookieValue('auth_token')) { pendingFavorite = favorite; openModal(); } else await toggleFavorite(favorite);
return;
}
const loginTrigger = event.target.closest('[data-gooood-login-trigger], a[href="/type/museum-culture-architecture/%23login"], a[href$="#login"]');
if (!loginTrigger) return;
event.preventDefault();
if (!cookieValue('auth_token')) { openModal(); return; }
try { const user = await apiRequest('/wp/v2/users/me'); updateMemberUI(user); showToast('当前已登录;可直接点击文章卡片上的收藏', 'You are logged in. Use Favorites on an article card.'); }
catch (error) {
if (isConfirmedAuthInvalidation(error)) { clearToken(); updateMemberUI(null); openModal(); }
else { retainAuthAfterTemporaryFailure(); showToast('登录状态暂时无法确认,请稍后重试', 'Unable to confirm your sign-in right now. Please try again shortly.'); }
}
});
normalizeBrokenTargets();
new MutationObserver(normalizeBrokenTargets).observe(document.body, { childList: true, subtree: true });
setAuthState('checking', null);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => { checkAndRestoreAuth(); });
} else {
checkAndRestoreAuth();
}
})();
公开资讯滚动更新。建议通过站内栏目继续浏览,核对最新条目。