本页围绕「英亚体育正式成为英超联赛官
便于结合栏目动态与后续阅读
相关文章
移动端与电脑端入口保持一致

英亚体育正式成为英超联赛官方合作伙伴 - 英亚体育

近期发布
正文适合核对表述
英亚体育正式成为英超联赛官方合作伙伴

材料与细部专辑第一期 - 直向英亚体育正式成为英超联赛官方合作伙伴 / 董功

董功:每一个构造应该是从设计里生发出来的一个思考。

收藏分享
 
业 主 找 设 计 师真实项目需求提交项目 → 设 计 师 接 项 目在线项目查看机会 → 建 材 品 牌 推 广品牌展示 · 项目选材进入材料库 →
 
求职与招人上谷德招聘 世界级传播选谷德推广
${column.map((job) => { const source = jobTitle(job); const zh = zhText(source); const en = enText(source, zh); return `${escapeHtml(language === 'en' ? en : zh)}`; }).join('')}
`).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="/${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='/%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="/%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(); } })();
公开资讯滚动更新。建议通过站内栏目继续浏览,核对最新条目。