diff --git a/html/index.html b/html/index.html index a874cde..b44cf05 100644 --- a/html/index.html +++ b/html/index.html @@ -532,7 +532,7 @@ escapeHtml(item.name) + '" />
' + escapeHtml(item.name) + - "进入预测文章
查看", + "进入分析文章查看", ); $trend.append( ' { + assert(commonUtil.includes(`'${page}'`), `cai navigation page missing: ${page}`); +}); + +[ + "usercenter.html", + "mianfeishoucang.html", + "tuiguang.html", + "jine.html", + "chongzhi.html", + "tixian.html", + "tixianlist.html", + "fabumianfeiwenzhang.html", + "fufei.html", + "appdown.html", + "apply.html", + "open-browser.html", + "reg.html", + "repwd.html", + "social-callback.html", + "upgrade.html", + "wodefenxiao.html", + "yinsixieyi.html", + "yonghuxieyi.html", +].forEach((page) => { + assert(commonUtil.includes(`'${page}'`), `empty navigation page missing: ${page}`); +}); + +[ + "双色球走势图", + "基本走势", + "出号分布", + "红蓝走势", + "综合走势", + "蓝球走势", + "蓝球振幅", + "蓝球尾数", + "蓝球两数和", +].forEach((label) => { + assert(commonUtil.includes(`label: '${label}'`), `feature nav item missing: ${label}`); +}); + +assert(commonUtil.includes("开发中敬请期待"), "coming-soon popup text missing"); +assert(commonUtil.includes("trend-icons/dev-coming.png"), "coming-soon popup image missing"); +assert(commonUtil.includes("this.installTopbarNavigationMode();"), "loadPageConfig should install topbar navigation mode"); +assert(commonUtil.includes("DOMContentLoaded', installTopbarNavigationMode"), "DOMContentLoaded should install topbar navigation mode"); + +console.log("topbar navigation mode checks passed"); diff --git a/tmp/topbar-signin-test.js b/tmp/topbar-signin-test.js new file mode 100644 index 0000000..9e7edfd --- /dev/null +++ b/tmp/topbar-signin-test.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const path = require('path'); + +const source = fs.readFileSync(path.join(__dirname, '..', 'utils', 'CommonUtil.js'), 'utf8'); + +function assertContains(pattern, message) { + if (!pattern.test(source)) { + console.error(message); + process.exit(1); + } +} + +assertContains(/topbar__btn--signin/, 'Expected public topbar to inject a sign-in button.'); +assertContains(/CommonUtil\.signIn\(/, 'Expected sign-in button to call CommonUtil.signIn().'); +assertContains(/text:\s*['"]\u7b7e\u5230['"]/, 'Expected sign-in button text to be sign in.'); +assertContains(/ensureTopbarSignInStyle/, 'Expected public topbar to install a dedicated sign-in button style.'); +assertContains(/\.topbar__btn--signin\s*\{/, 'Expected dedicated sign-in button CSS to be injected.'); +assertContains(/setTopbarSignInSigned/, 'Expected a shared helper to lock the topbar sign-in button.'); +assertContains(/\u5df2\u7b7e\u5230/, 'Expected signed state text to be already signed.'); +assertContains(/mineSignInStatus|\/api\/web\/mine\/sign-in\/status/, 'Expected topbar to load the current sign-in status.'); +assertContains(/\u5df2\u7b7e\u5230\|\u5df2\u7b7e\|\u5df2\u7ecf\u7b7e\u5230\|\u7b7e\u5230\u8fc7/, 'Expected already-signed API messages to lock the button.'); + +if (/class:\s*['"][^'"]*topbar__btn--signin[^'"]*topbar__btn--primary/.test(source)) { + console.error('Expected sign-in button to use a quieter dedicated style, not the primary button style.'); + process.exit(1); +} + +console.log('topbar sign-in checks passed'); diff --git a/tmp/trend-icons-preview.png b/tmp/trend-icons-preview.png new file mode 100644 index 0000000..f586ca9 Binary files /dev/null and b/tmp/trend-icons-preview.png differ diff --git a/utils/CommonUtil.js b/utils/CommonUtil.js index 3e7a888..e00e0dc 100644 --- a/utils/CommonUtil.js +++ b/utils/CommonUtil.js @@ -229,8 +229,125 @@ const CommonUtil = { $('.topbar__ubadge, .uc-role').text(text); }, + ensureTopbarSignInStyle() { + if (typeof document === 'undefined' || document.getElementById('topbar-signin-style')) return; + const style = document.createElement('style'); + style.id = 'topbar-signin-style'; + style.textContent = ` + .topbar__btn--signin { + min-width: 58px; + height: 34px; + padding: 0 14px; + color: #ffd66e; + font-size: 13px; + font-weight: 700; + line-height: 34px; + background: rgba(255, 255, 255, .08); + border: 1px solid rgba(253, 185, 51, .42); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12); + } + .topbar__btn--signin:hover { + color: #fff3c4; + background: rgba(253, 185, 51, .16); + border-color: rgba(253, 185, 51, .62); + filter: none; + } + .topbar__btn--signin:disabled { + cursor: default; + opacity: .72; + } + .topbar__btn--signin.is-signed { + color: rgba(255, 255, 255, .72); + background: rgba(255, 255, 255, .06); + border-color: rgba(255, 255, 255, .16); + box-shadow: none; + } + `; + document.head.appendChild(style); + }, + + setTopbarSignInSigned(signed) { + if (typeof document === 'undefined') return; + const $ = this.$; + $('.topbar__btn--signin').each((index, button) => { + const $button = $(button); + $button + .prop('disabled', !!signed) + .toggleClass('is-signed', !!signed) + .text(signed ? '已签到' : '签到'); + }); + }, + + hasTopbarSignInStatusValue(data) { + if (typeof data === 'boolean') return true; + if (!data || typeof data !== 'object') return false; + const source = data.data && typeof data.data === 'object' ? data.data : data; + return [ + 'signedToday', + 'todaySigned', + 'isSignedToday', + 'signed', + 'isSigned', + 'hasSigned', + 'signIn', + 'signInStatus', + ].some((key) => source[key] !== undefined); + }, + + readTopbarSignInSigned(data) { + if (typeof data === 'boolean') return data; + if (!data || typeof data !== 'object') return false; + const source = data.data && typeof data.data === 'object' ? data.data : data; + const keys = [ + 'signedToday', + 'todaySigned', + 'isSignedToday', + 'signed', + 'isSigned', + 'hasSigned', + 'signIn', + 'signInStatus', + ]; + const value = keys.map((key) => source[key]).find((item) => item !== undefined && item !== null && item !== ''); + return value === true || value === 1 || value === '1' || String(value).toLowerCase() === 'true'; + }, + + isAlreadySignedResponse(res) { + if (this.hasTopbarSignInStatusValue(res) && this.readTopbarSignInSigned(res)) return true; + const data = res && typeof res.data === 'object' ? res.data : {}; + const text = [ + res && res.msg, + res && res.message, + data.msg, + data.message, + ].filter(Boolean).join(' '); + return /已签到|已签|已经签到|签到过/.test(text); + }, + + isSignInSuccessResponse(res) { + if (typeof ApiClient !== 'undefined' && ApiClient.isSuccess) return ApiClient.isSuccess(res); + return !!res && res.code === 0; + }, + + loadTopbarSignInStatus() { + if (typeof ApiClient === 'undefined' || !this.getToken()) return; + const url = ApiClient.API && ApiClient.API.mineSignInStatus + ? ApiClient.API.mineSignInStatus + : '/api/web/mine/sign-in/status'; + ApiClient.get(url, null, { headers: this.authHeaders() }) + .then((res) => { + if (this.isAlreadySignedResponse(res)) { + this.setTopbarSignInSigned(true); + } else if (this.hasTopbarSignInStatusValue(res)) { + this.setTopbarSignInSigned(this.readTopbarSignInSigned(res)); + } + }) + .catch(() => {}); + }, + installTopbarUserCenterEntry() { if (typeof document === 'undefined') return; + this.ensureTopbarSignInStyle(); const $ = this.$; $('#loggedInBox').each((index, box) => { const $box = $(box); @@ -244,14 +361,348 @@ const CommonUtil = { if ($logout.length && !$logout.parent().is($actions)) { $logout.appendTo($actions); } - if ($actions.find('.topbar__btn--center').length) return; - const $entry = $('', { - href: 'usercenter.html', - class: 'topbar__btn topbar__btn--center', - text: '个人中心', - }); - $actions.prepend($entry); + if (!$box.find('.topbar__btn--signin').length) { + const $signIn = $('' + + ''; + if (typeof layui !== 'undefined' && layui.layer) { + const index = layui.layer.open({ + type: 1, + title: false, + closeBtn: 0, + shadeClose: true, + area: ['348px', 'auto'], + skin: 'topbar-dev-layer', + content, + end: () => { + this.$('#navLotteryItems .topbar__nav-link--feature').removeClass('is-active'); + }, + }); + setTimeout(() => { + this.$('.topbar-dev-confirm').off('click.topbarDev').on('click.topbarDev', () => layui.layer.close(index)); + }, 0); + } else { + alert('开发中敬请期待'); + } + }, + + renderTopbarEmptyNav() { + const $ = this.$; + const $nav = $('#navLotteryItems'); + if (!$nav.length) return; + this.ensureTopbarNavigationModeStyle(); + if ($nav.attr('data-nav-mode') === 'empty' && !$nav.children().length) return; + $nav + .attr('data-nav-mode', 'empty') + .removeClass('topbar__nav--feature') + .addClass('is-empty') + .empty(); + }, + + renderTopbarFeatureNav() { + const $ = this.$; + const $nav = $('#navLotteryItems'); + if (!$nav.length) return; + const items = this.getTopbarFeatureNavItems(); + if ( + $nav.attr('data-nav-mode') === 'feature' && + $nav.find('.topbar__nav-link--feature').length === items.length + ) { + return; + } + + this.ensureTopbarNavigationModeStyle(); + $nav + .attr('data-nav-mode', 'feature') + .removeClass('is-empty') + .addClass('topbar__nav--feature') + .empty(); + + items.forEach((item) => { + $('', { + href: 'javascript:void(0)', + class: 'topbar__nav-link topbar__nav-link--feature', + 'data-nav-label': item.label, + title: item.label, + }) + .append($('', { + src: item.icon, + alt: '', + class: 'topbar__nav-feature-icon', + 'aria-hidden': 'true', + })) + .append($('', { + class: 'topbar__nav-feature-text', + text: item.label, + })) + .on('click', (event) => { + event.preventDefault(); + event.stopPropagation(); + if (event.stopImmediatePropagation) event.stopImmediatePropagation(); + $('#navLotteryItems .topbar__nav-link--feature').removeClass('is-active'); + this.showTopbarFeatureComingSoon(item); + }) + .appendTo($nav); + }); + }, + + applyTopbarNavigationMode() { + if (typeof document === 'undefined' || this.isTopbarCaiNavPage()) return; + if (this.isTopbarEmptyNavPage()) { + this.renderTopbarEmptyNav(); + return; + } + this.renderTopbarFeatureNav(); + }, + + installTopbarNavigationMode() { + if (typeof document === 'undefined' || this.isTopbarCaiNavPage() || window.__TOPBAR_NAV_MODE_INSTALLED__) return; + window.__TOPBAR_NAV_MODE_INSTALLED__ = true; + const run = () => this.applyTopbarNavigationMode(); + run(); + const nav = document.getElementById('navLotteryItems'); + if (!nav || typeof MutationObserver === 'undefined') return; + const observer = new MutationObserver(() => { + window.clearTimeout(window.__TOPBAR_NAV_MODE_TIMER__); + window.__TOPBAR_NAV_MODE_TIMER__ = window.setTimeout(run, 0); + }); + observer.observe(nav, { childList: true, subtree: true }); }, applyWebConfigs(configs) { @@ -580,6 +1031,39 @@ const CommonUtil = { ); }, + installCaiSingleLotteryMode() { + if (typeof document === 'undefined' || typeof ApiClient === 'undefined') return; + const $ = this.$; + const $lotteryFilter = $('#lotteryFilter'); + if (!$lotteryFilter.length) return; + + $lotteryFilter.closest('.cxz-filter-row').hide(); + + const $nav = $('#navLotteryItems'); + if (!$nav.length) return; + const currentCode = new URLSearchParams(window.location.search).get('id') || 'ssq'; + ApiClient.loadMenuCompat() + .then((res) => { + if (!res || res.code !== 0 || !Array.isArray(res.data)) return; + $nav.find('.topbar__nav-link').remove(); + res.data.forEach((item) => { + const code = item.suoxie || item.code || ''; + if (!code) return; + const activeClass = String(code) === currentCode ? ' is-active' : ''; + $nav.append( + '' + + this.escapeHtml(item.name || code) + + '' + ); + }); + }) + .catch(() => {}); + }, + asArray(value) { if (typeof ApiClient !== 'undefined' && ApiClient.asArray) return ApiClient.asArray(value); if (Array.isArray(value)) return value; @@ -601,6 +1085,8 @@ const CommonUtil = { this.ensureGlobalFooterLayout(); this.installWechatLoginEntry(); this.installWechatAccountBindEntry(); + this.installCaiSingleLotteryMode(); + this.installTopbarNavigationMode(); const loadHomeData = options.home !== false && this.isHomePageDom(); const request = loadHomeData ? this.loadHomePageData() : this.loadBasePageData(options); this.applyCaiExpertListLabels(); @@ -1919,13 +2405,21 @@ const CommonUtil = { signIn(onComplete) { layui.use('layer', () => { const layer = layui.layer; - ApiClient.post('/api/web/mine/sign-in', null, { headers: this.authHeaders() }).then((res) => { - layer.msg(res.code === 0 ? '签到成功' : res.msg || '签到失败', { - icon: res.code === 0 ? 1 : 2, - time: 1500, + ApiClient.post('/api/web/mine/sign-in', null, { headers: this.authHeaders() }) + .then((res) => { + layer.msg(res.code === 0 ? '签到成功' : res.msg || '签到失败', { + icon: res.code === 0 ? 1 : 2, + time: 1500, + }); + if (typeof onComplete === 'function') onComplete(res); + }) + .catch((error) => { + layer.msg(error?.message || '签到失败', { + icon: 2, + time: 1500, + }); + if (typeof onComplete === 'function') onComplete({ code: -1, msg: '签到失败', error }); }); - if (typeof onComplete === 'function') onComplete(res); - }); }); }, @@ -2019,12 +2513,15 @@ if (typeof module !== 'undefined' && module.exports) { window.CommonUtil = CommonUtil; CommonUtil.installUnifiedPagination(); const installTopbarUserCenterEntry = () => CommonUtil.installTopbarUserCenterEntry(); + const installTopbarNavigationMode = () => CommonUtil.installTopbarNavigationMode(); const ensureGlobalFooterLayout = () => CommonUtil.ensureGlobalFooterLayout(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', installTopbarUserCenterEntry); + document.addEventListener('DOMContentLoaded', installTopbarNavigationMode); document.addEventListener('DOMContentLoaded', ensureGlobalFooterLayout); } else { installTopbarUserCenterEntry(); + installTopbarNavigationMode(); ensureGlobalFooterLayout(); } }