英亚体育最新资讯正式成为英超联赛官方合作伙伴 - 英亚体育最新资讯
近期发布
英亚体育最新资讯正式成为英超联赛官方合作伙伴
让孩子在自然中探索英亚体育最新资讯正式成为英超联赛官方合作伙伴
英亚体育最新资讯正式成为英超联赛官方合作伙伴
新旧英亚体育最新资讯正式成为英超联赛官方合作伙伴并置,共筑历史城区中的英亚体育最新资讯正式成为英超联赛官方合作伙伴新空间
英亚体育最新资讯正式成为英超联赛官方合作伙伴
慕尼黑工业大学垂直木结构幼儿园
英亚体育最新资讯正式成为英超联赛官方合作伙伴
社区记忆中的孩子们的栖居地
英亚体育最新资讯正式成为英超联赛官方合作伙伴
传承街景、孕育面向未来之学的园舍
英亚体育最新资讯正式成为英超联赛官方合作伙伴
三叶交织,聚合无界
英亚体育最新资讯正式成为英超联赛官方合作伙伴
儿童感知的空间场域
英亚体育最新资讯正式成为英超联赛官方合作伙伴
为儿童尺度而生的空间体系
英亚体育最新资讯正式成为英超联赛官方合作伙伴
由模块构成的“城堡”
英亚体育最新资讯正式成为英超联赛官方合作伙伴
在树影中生长的幼儿园
英亚体育最新资讯正式成为英超联赛官方合作伙伴
可负担住宅、社区公园与公共设施的城市重构
英亚体育最新资讯正式成为英超联赛官方合作伙伴
面向所有人的设计
英亚体育最新资讯正式成为英超联赛官方合作伙伴
树屋部落
英亚体育最新资讯正式成为英超联赛官方合作伙伴
儿童与家庭中心
英亚体育最新资讯正式成为英超联赛官方合作伙伴
闲置空间的再生
英亚体育最新资讯正式成为英超联赛官方合作伙伴
一座由记忆与想象构成的儿童村落
英亚体育最新资讯正式成为英超联赛官方合作伙伴
从古代聚落到当代成长空间
英亚体育最新资讯正式成为英超联赛官方合作伙伴
从英亚体育最新资讯正式成为英超联赛官方合作伙伴英亚体育最新资讯正式成为英超联赛官方合作伙伴到可居住的英亚体育最新资讯正式成为英超联赛官方合作伙伴场所
相关文章
本页围绕「英亚体育最新资讯正式成为英超联赛官方合作伙伴」整理公开信息,便于结合栏目动态与后续阅读。 相关文章
移动端与电脑端入口保持一致。 同题参见如某条暂不可用,可改走栏目列表继续查找。
列表适合快速定位,正文适合核对表述。 相关文章两者都保留在站内即可形成完整阅读路径。
栏目入口
工作/招聘 · 复合检索 · 每个人 · 先锋专辑 · 长江 · 合集
`).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 = `/__xh__/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(`/__xh__/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 = `/__xh__/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('/__xh__/dashboard.gooood.cn/api/api-jobs-index.php', { credentials: 'omit' }),
fetch(`/__xh__/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(`/__xh__/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 ? '/__xh__/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: '/__xh__/dashboard.gooood.cn/api/api-blogjobs-number.php',
frontendBaseUrl: '/',
corsAllowedOrigin: '/'
};
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 ? `/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/kindergarten/${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/kindergarten/%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/kindergarten/%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();
}
})();
公开资讯滚动更新。建议通过站内栏目继续浏览,核对最新条目。