', { class: 'site-footer__copyright-list' }).appendTo($brand);
}
$brand
.find('.site-footer__legal, .site-footer__meta, #copyright-info, #additional-info')
.remove();
$list.empty();
list.forEach((item) => {
const content = String(item.content || '').trim();
if (!content) return;
const isIcp = /ICP备|工信部/.test(content);
const isPolice = /公网安备|公安/.test(content);
const isCopyright = /©|版权|copyright/i.test(content);
const isDescription = /本站|历史数据|数据分析|投注建议|不涉及|仅供/i.test(content);
const attributes = {
class: 'site-footer__copyright-row',
text: content,
};
if (isIcp) {
$('
', {
...attributes,
id: 'beian',
href: 'https://beian.miit.gov.cn/',
target: '_blank',
class: 'site-footer__copyright-row index-style-02',
}).appendTo($list);
} else {
if (isPolice) attributes.id = 'police-record';
else if (isCopyright) attributes.id = 'copyright-info';
else if (isDescription) attributes.id = 'additional-info';
$('', attributes).appendTo($list);
}
});
});
},
setMeta(name, content) {
if (!content) return;
let meta = document.querySelector(`meta[name="${name}"]`);
if (!meta) {
meta = document.createElement('meta');
meta.setAttribute('name', name);
document.head.appendChild(meta);
}
meta.setAttribute('content', content);
},
setIcon(url) {
if (!url) return;
let icon = document.querySelector('link[rel="icon"]');
if (!icon) {
icon = document.createElement('link');
icon.setAttribute('rel', 'icon');
document.head.appendChild(icon);
}
icon.setAttribute('href', url);
},
isHomePageDom() {
if (typeof document === 'undefined') return false;
return Boolean(
document.querySelector('#bannerBox, #resultsGrid, #articleGroups, #lotteryNavGrid')
);
},
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;
if (!value || typeof value !== 'object') return [];
if (Array.isArray(value.records)) return value.records;
if (Array.isArray(value.rows)) return value.rows;
if (Array.isArray(value.list)) return value.list;
return [];
},
pickData(result, fallback) {
if (!result || typeof result !== 'object') return fallback;
return result.data === undefined || result.data === null ? fallback : result.data;
},
loadPageConfig(options = {}) {
if (typeof ApiClient === 'undefined') return;
this.installUnifiedPagination();
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();
request
.then((result) => {
const data = result.data || {};
if (data.webConfigs) this.applyWebConfigs(data.webConfigs);
else if (data.siteConfig) this.applyWebConfigs(data.siteConfig);
else if (data.config) this.applyWebConfigs(data.config);
else if (result.data) this.applyWebConfigs(result.data);
this.applyCopyrightConfigs(data.copyrightConfigs);
if (!loadHomeData && options.ads !== false && data.adList) {
this.processAndRenderAds(data.adList, options.adPositions);
}
this.applyCaiExpertListLabels();
this.installWechatLoginEntry();
this.installWechatAccountBindEntry();
if (options.links !== false) this.loadLinks();
if (typeof options.onSuccess === 'function') options.onSuccess(result);
})
.catch((error) => {
if (typeof options.onError === 'function') options.onError(error);
});
},
installWechatLoginEntry() {
if (typeof window === 'undefined' || window.__WECHAT_LOGIN_ENTRY_INSTALLED__) return;
const $entry = this.$('.wxdl');
if (!$entry.length || typeof ApiClient === 'undefined') return;
window.__WECHAT_LOGIN_ENTRY_INSTALLED__ = true;
$entry.find('a').attr({
href: 'javascript:void(0)',
title: '微信登录',
});
$entry.on('click.wechatLogin', 'a, .wx_Box', (event) => {
event.preventDefault();
event.stopPropagation();
this.startWechatLogin();
});
},
getWechatLoginDomain() {
if (typeof window === 'undefined' || !window.location || window.location.protocol === 'file:') {
return window.CURRENT_CONFIG && window.CURRENT_CONFIG.SITE_DOMAIN
? window.CURRENT_CONFIG.SITE_DOMAIN
: 'http://47.108.24.205:9100';
}
return window.location.origin;
},
extractWechatAuthUrl(res) {
const data = res && res.data;
if (typeof data === 'string') return data;
if (data && typeof data === 'object') {
return data.url || data.authUrl || data.authorizeUrl || data.redirectUrl || data.loginUrl || '';
}
return '';
},
startWechatLogin() {
const $ = this.$;
const layer = typeof layui !== 'undefined' && layui.layer;
const $agreement = $('#checkbox, #agreementCheckbox').first();
if ($agreement.length && !$agreement.prop('checked')) {
if (layer) layer.msg('请先阅读并同意用户协议和隐私政策', { icon: 2 });
return;
}
const $loading = $('#loadingOverlay');
const $button = $('.wxdl');
$loading.addClass('is-active');
$button.addClass('is-loading');
ApiClient.get(
ApiClient.API.authWechatOpen,
{ domain: this.getWechatLoginDomain() },
{
publicRequest: true,
headers: { clientid: ApiClient.CLIENT_ID },
}
)
.then((res) => {
if (!ApiClient.isSuccess(res)) {
throw new Error((res && res.msg) || '微信授权地址获取失败');
}
const authUrl = this.extractWechatAuthUrl(res);
if (!authUrl) throw new Error('后端未返回微信授权地址');
NavigationUtil.open(authUrl);
})
.catch((error) => {
const message = error.message || '微信登录暂不可用,请稍后再试';
if (layer) layer.msg(message, { icon: 2 });
else alert(message);
})
.finally(() => {
$loading.removeClass('is-active');
$button.removeClass('is-loading');
});
},
installWechatAccountBindEntry() {
if (typeof window === 'undefined') return;
const button = document.getElementById('wechat-bind-button');
if (!button || typeof ApiClient === 'undefined') return;
this.normalizeWechatAccountBindPanel();
if (window.__WECHAT_ACCOUNT_BIND_ENTRY_INSTALLED__) return;
window.__WECHAT_ACCOUNT_BIND_ENTRY_INSTALLED__ = true;
button.setAttribute('title', '绑定微信');
button.addEventListener(
'click',
(event) => {
event.preventDefault();
event.stopPropagation();
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
this.startWechatAccountBind();
},
true
);
this.observeWechatAccountBindPanel();
},
isWechatAccountBindPanelBound() {
const $ = this.$;
const descText = ($('#wechat-bind-desc').text() || '').trim();
const buttonText = ($('#wechat-bind-button').text() || '').trim();
return (
this.hasWechatAccountBindSuccessMark() ||
$('#wechat-bind-card').hasClass('is-bound') ||
descText.indexOf('已绑定') > -1 ||
buttonText.indexOf('已绑定') > -1
);
},
hasWechatAccountBindSuccessMark() {
if (typeof window === 'undefined') return false;
try {
return sessionStorage.getItem('wechatAccountBindSuccess') === '1';
} catch (e) {
return false;
}
},
applyWechatAccountBindBoundPanel() {
const $button = this.$('#wechat-bind-button');
if (!$button.length) return;
this.$('#wechat-bind-desc').text('微信已绑定');
this.$('#wechat-bind-card').removeClass('is-pending is-ready').addClass('is-bound');
$button
.text('已绑定')
.attr('title', '微信已绑定')
.removeClass('is-pending')
.addClass('is-bound')
.prop('disabled', true);
},
normalizeWechatAccountBindPanel() {
const $button = this.$('#wechat-bind-button');
if (!$button.length) return;
if (this.hasWechatAccountBindSuccessMark()) {
this.applyWechatAccountBindBoundPanel();
return;
}
if (this.isWechatAccountBindPanelBound()) return;
this.$('#wechat-bind-desc').text('未绑定微信');
this.$('#wechat-bind-card').removeClass('is-pending').addClass('is-ready');
$button
.text('绑定微信')
.attr('title', '绑定微信')
.removeClass('is-bound is-pending')
.prop('disabled', false);
},
observeWechatAccountBindPanel() {
if (typeof window === 'undefined' || window.__WECHAT_ACCOUNT_BIND_OBSERVER__) return;
const target = document.getElementById('wechat-bind-card') || document.getElementById('wechat-bind-button');
if (!target || typeof MutationObserver === 'undefined') return;
window.__WECHAT_ACCOUNT_BIND_OBSERVER__ = new MutationObserver(() => {
window.clearTimeout(window.__WECHAT_ACCOUNT_BIND_NORMALIZE_TIMER__);
window.__WECHAT_ACCOUNT_BIND_NORMALIZE_TIMER__ = window.setTimeout(() => {
this.normalizeWechatAccountBindPanel();
}, 0);
});
window.__WECHAT_ACCOUNT_BIND_OBSERVER__.observe(target, {
attributes: true,
childList: true,
subtree: true,
});
},
rememberWechatAccountBindMode() {
if (typeof window === 'undefined') return;
const returnUrl =
`${window.location.pathname}${window.location.search}${window.location.hash}` || '/html/usercenter.html';
const pairs = {
wechatSocialMode: 'account_bind',
wechatSocialReturnUrl: returnUrl,
wechatSocialStartedAt: String(Date.now()),
};
Object.keys(pairs).forEach((key) => {
try {
sessionStorage.setItem(key, pairs[key]);
} catch (e) {}
try {
localStorage.setItem(key, pairs[key]);
} catch (e) {}
});
},
startWechatAccountBind() {
const layer = typeof layui !== 'undefined' && layui.layer;
const token = typeof ApiClient !== 'undefined' && ApiClient.token ? ApiClient.token() : '';
if (!token) {
if (layer) layer.msg('请先登录后再绑定微信', { icon: 2 });
return;
}
const $button = this.$('#wechat-bind-button');
this.rememberWechatAccountBindMode();
$button.addClass('is-loading').prop('disabled', true);
ApiClient.get(ApiClient.API.authWechatOpen, { domain: this.getWechatLoginDomain() })
.then((res) => {
if (!ApiClient.isSuccess(res)) {
throw new Error((res && res.msg) || '微信授权地址获取失败');
}
const authUrl = this.extractWechatAuthUrl(res);
if (!authUrl) throw new Error('后端未返回微信授权地址');
NavigationUtil.open(authUrl);
})
.catch((error) => {
const message = error.message || '微信绑定暂不可用,请稍后再试';
if (layer) layer.msg(message, { icon: 2 });
else alert(message);
})
.finally(() => {
$button.removeClass('is-loading').prop('disabled', false);
});
},
installUnifiedPagination() {
if (typeof window === 'undefined' || typeof document === 'undefined') return;
this.injectPaginationStyles();
this.patchLaypageDefaults();
this.applyCaiinfoCompat();
},
applyCaiinfoCompat() {
if (window.__cxzCaiinfoDisableCompat) return;
if (window.__cxzCaiinfoCompatScheduled) return;
if (!/\/caiinfo\.html(?:$|\?)/.test(window.location.pathname + window.location.search)) return;
window.__cxzCaiinfoCompatScheduled = true;
const run = () => {
const $ = window.layui && layui.$ ? layui.$ : window.jQuery;
if (!$ || !document.getElementById('articleBody')) return;
const escapeHtml = (value) =>
String(value || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const escapeJs = (value) => escapeHtml(value).replace(/\\/g, '\\\\');
const isMasked = (value) => {
const text = String(value || '').trim();
return (
!text ||
/\*/.test(text) ||
text.indexOf('购买后可见') >= 0 ||
text.indexOf('登录后购买可见') >= 0 ||
text.indexOf('待发布') >= 0
);
};
const normalizeRecommend = (value) =>
String(value || '')
.replace(/[,、]/g, ',')
.replace(/[||]/g, '+')
.replace(/\s+/g, ',')
.replace(/,+/g, ',')
.replace(/\+,/g, '+')
.replace(/,\+/g, '+')
.replace(/^,|,$/g, '');
const formatIssueText = (issue) => {
const value = String(issue || '').trim();
return /^\d{7,}$/.test(value) ? value.slice(-3) : value;
};
const splitNumbers = (value) => {
const text = normalizeRecommend(value);
if (!text || isMasked(text)) return [];
return text
.split('+')
.map((group) => group.split(',').map((num) => num.trim()).filter(Boolean))
.filter((group) => group.length);
};
const normalizeNumberKey = (value) => {
const text = String(value || '').trim();
return /^\d+$/.test(text) ? String(Number(text)) : text;
};
const toNumberSet = (values) =>
new Set(
values.flatMap((num) => {
const text = String(num);
return [text, text.padStart(2, '0'), normalizeNumberKey(text)];
})
);
const getRecommendGroupSeparator = (value) => (String(value || '').includes('|') ? '|' : '+');
const splitRecommendValues = (value) => splitNumbers(value).flat();
const getArticleRecommendValue = (item) =>
(item && (item.predictedCode || item.recommendCode || item.latestRecommend || item.recommend || item.content)) || '';
const getArticleIssueValue = (item) =>
(item && (item.issue || item.latestIssue || item.expect || item.period)) || '';
const isMaskedRecommendText = (value) => isMasked(value);
const isTruthyFlag = (value) =>
value === true || value === 1 || value === '1' || String(value).toLowerCase() === 'true';
const isArticlePrePurchased = (item) =>
Boolean(
item &&
(isTruthyFlag(item.prePurchased) ||
isTruthyFlag(item.pre_purchased) ||
isTruthyFlag(item.prepaid) ||
isTruthyFlag(item.prepaidArticle))
);
const isArticlePrePurchase = (item) =>
Boolean(
item &&
(isTruthyFlag(item.prePurchase) ||
isTruthyFlag(item.pre_purchase) ||
isTruthyFlag(item.canPrePurchase) ||
isTruthyFlag(item.preOrder))
);
const getArticleCodeNumbers = (item, fields) => {
let value = '';
fields.some((field) => {
if (item && item[field] !== undefined && item[field] !== null && item[field] !== '') {
value = item[field];
return true;
}
return false;
});
if (Array.isArray(value)) {
return value.flatMap((entry) =>
splitRecommendValues(
entry && typeof entry === 'object'
? entry.code || entry.value || entry.number || entry.result || ''
: entry
)
);
}
return splitRecommendValues(value);
};
const getCodeResultNumbers = (item, correctValue) =>
(typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [])
.filter((result) => result && typeof result === 'object' && result.correct === correctValue)
.flatMap((result) => splitRecommendValues(result.code || result.value || result.number || result.result || ''));
const getArticleHitNumbers = (item) => {
const fields = [
'hitCodes',
'hitCode',
'hitNumbers',
'hits',
'rightCodes',
'rightCode',
'rightNumbers',
'correctCodes',
'correctCode',
'correctNumbers',
];
const hitNumbers = getArticleCodeNumbers(item, fields);
return hitNumbers.length ? hitNumbers : getCodeResultNumbers(item, true);
};
const getArticleMissedNumbers = (item) => {
const fields = [
'missCodes',
'missCode',
'missNumbers',
'missedCodes',
'missedCode',
'missedNumbers',
'wrongCodes',
'wrongCode',
'wrongNumbers',
'errorCodes',
'errorCode',
'errorNumbers',
];
const missedNumbers = getArticleCodeNumbers(item, fields);
return missedNumbers.length ? missedNumbers : getCodeResultNumbers(item, false);
};
const getCodeResultCorrectGroups = (item, recommendGroups) => {
const codeResults = typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [];
if (!codeResults.length || !recommendGroups || !recommendGroups.length) return [];
const marks = codeResults.map((result) => {
if (!result || typeof result !== 'object') return null;
return result.correct === true ? true : result.correct === false ? false : null;
});
if (!marks.some((mark) => mark === true || mark === false)) return [];
const recommendCount = recommendGroups.reduce((total, group) => total + group.length, 0);
if (marks.length < recommendCount) return [];
let offset = 0;
return recommendGroups.map((group) => {
const groupMarks = marks.slice(offset, offset + group.length);
offset += group.length;
return groupMarks;
});
};
const getCodeResultMarkerGroups = (item, correctValue) => {
const codeResults = typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [];
if (!codeResults.length) return [];
return codeResults
.map((result) => {
if (Array.isArray(result)) return [];
if (result && typeof result === 'object' && result.correct !== correctValue) return [];
const value =
result && typeof result === 'object'
? result.code || result.value || result.number || result.result || ''
: result;
return String(value || '').trim() ? [String(value).trim()] : [];
})
.filter((group) => group.length > 0);
};
const getArticleHitMarkerGroups = (item, recommendGroups) => {
if (!recommendGroups || !recommendGroups.length) return [];
const hitNumbers = getArticleHitNumbers(item);
if (hitNumbers.length === recommendGroups.length) return hitNumbers.map((num) => splitRecommendValues(num));
const codeResultGroups = getCodeResultMarkerGroups(item, true);
return codeResultGroups.length === recommendGroups.length ? codeResultGroups : [];
};
const getArticleMissedMarkerGroups = (item, recommendGroups) => {
if (!recommendGroups || recommendGroups.length <= 1) return [];
const missedNumbers = getArticleMissedNumbers(item);
if (missedNumbers.length === recommendGroups.length) return missedNumbers.map((num) => splitRecommendValues(num));
const codeResultGroups = getCodeResultMarkerGroups(item, false);
return codeResultGroups.length === recommendGroups.length ? codeResultGroups : [];
};
const isWinStatusText = (value) => /全对|正确|命中|中奖|中/.test(String(value || ''));
const isLoseStatusText = (value) => /全错|错误|错/.test(String(value || ''));
const isArticleDrawn = (item, openCode) => {
if (openCode && String(openCode).trim()) return true;
const text = getExplicitStatusText(item);
if (/正确|错误|已开奖|开奖号|开奖/.test(text) && !/未开奖|待开奖/.test(text)) return true;
if (isWinStatusText(text) || isLoseStatusText(text)) return true;
return Boolean(
item &&
(item.isDrawn === true ||
item.drawn === true ||
item.opened === true ||
item.openStatus === 1 ||
item.openStatus === '1' ||
item.drawStatus === 1 ||
item.drawStatus === '1')
);
};
const isLatestArticle = (item, list) => {
if (item && isTruthyFlag(item.isLatest)) return true;
const currentId = getArticleId(item);
const firstId = getArticleId(Array.isArray(list) ? list[0] : null);
if (currentId && firstId) return String(currentId) === String(firstId);
const currentIssue = Number(getArticleIssueValue(item));
const issues = (Array.isArray(list) ? list : [])
.map((row) => Number(getArticleIssueValue(row)))
.filter((issue) => Number.isFinite(issue));
return issues.length > 0 && Number.isFinite(currentIssue) && currentIssue === Math.max(...issues);
};
const isArticleMasked = (item, recommend) =>
Boolean(
item &&
(item.masked === true ||
item.masked === 1 ||
item.masked === '1' ||
item.masked === 'true' ||
isMaskedRecommendText(recommend))
);
const canShowArticleRecommend = (item, isLatestIssue, hasDrawResult, unlockedByVisibleRecommend, recommend) => {
if (!isArticleMasked(item, recommend)) return true;
if (hasDrawResult) return true;
if (!isLatestIssue) return true;
return Boolean((ApiClient.getToken ? ApiClient.getToken() : this.getToken()) && (isPurchased(item) || unlockedByVisibleRecommend));
};
const shouldShowBuyButton = (item, isLatestIssue, hasDrawResult, purchased, recommend) =>
Boolean(isLatestIssue && !hasDrawResult && isArticleMasked(item, recommend) && !purchased);
const shouldShowPrePurchaseButton = (item, purchased) =>
Boolean(
!purchased &&
!isArticlePrePurchased(item) &&
(isArticlePrePurchase(item) || (!getArticleId(item) && getExplicitStatusText(item) === '待发布'))
);
const getRecommendLockedText = () =>
ApiClient.getToken && ApiClient.getToken() ? '购买后可见' : '登录后购买可见';
const applyRecommendHitOptions = (options, item, statusText) => {
const recommendGroups = splitNumbers(getArticleRecommendValue(item));
const codeResultGroupMarks = getCodeResultCorrectGroups(item, recommendGroups);
const hitGroups = getArticleHitMarkerGroups(item, recommendGroups);
const missedGroups = getArticleMissedMarkerGroups(item, recommendGroups);
const hitNumbers = hitGroups.length ? hitGroups.flat() : getArticleHitNumbers(item);
const missedNumbers = missedGroups.length ? missedGroups.flat() : getArticleMissedNumbers(item);
const isFinalStatus = isWinStatusText(statusText) || isLoseStatusText(statusText);
options.codeResultMarks = codeResultGroupMarks.length ? codeResultGroupMarks : null;
options.hitSet = codeResultGroupMarks.length || (hitNumbers.length > 0 && isFinalStatus) ? toNumberSet(hitNumbers) : new Set();
options.hitGroupSets =
hitGroups.length === recommendGroups.length ? hitGroups.map((group) => toNumberSet(group)) : null;
options.wrongSet = !codeResultGroupMarks.length && missedNumbers.length > 0 && isFinalStatus ? toNumberSet(missedNumbers) : new Set();
options.wrongGroupSets =
!codeResultGroupMarks.length && missedGroups.length === recommendGroups.length
? missedGroups.map((group) => toNumberSet(group))
: null;
return options;
};
const renderBalls = (value, options = {}) => {
const groups = splitNumbers(value);
if (!groups.length) return '--';
const wrongSet = options.wrongSet || new Set();
const hitSet = options.hitSet || new Set();
const markerSets = options.wrongGroupSets || null;
const hitGroupSets = options.hitGroupSets || null;
const codeResultMarks = options.codeResultMarks || null;
const isRecommend = options.type === 'recommend';
const isOpen = options.type === 'open';
const count = groups.reduce((total, group) => total + group.length, 0);
const groupSeparator = getRecommendGroupSeparator(value);
const buildBody = (limit) => {
let rendered = 0;
return groups
.map((group, groupIndex) => {
const nums = typeof limit === 'number' ? group.slice(0, Math.max(0, limit - rendered)) : group;
rendered += nums.length;
if (!nums.length) return '';
const balls = group
.slice(0, nums.length)
.map((num, numberIndex) => {
const isTextToken = /[^\d]/.test(String(num));
const normalized = String(num).padStart(2, '0');
const markerSet = Array.isArray(markerSets) ? markerSets[groupIndex] : null;
const hitGroupSet = Array.isArray(hitGroupSets) ? hitGroupSets[groupIndex] : null;
const codeResultMark =
Array.isArray(codeResultMarks) &&
codeResultMarks[groupIndex] &&
codeResultMarks[groupIndex][numberIndex] !== undefined
? codeResultMarks[groupIndex][numberIndex]
: null;
const isExplicitHit =
(hitGroupSet
? hitGroupSet.has(normalized) || hitGroupSet.has(String(num)) || hitGroupSet.has(normalizeNumberKey(num))
: hitSet.has(normalized) || hitSet.has(String(num)) || hitSet.has(normalizeNumberKey(num)));
const isHit = codeResultMark === true || isExplicitHit;
const isWrong = isHit
? false
: codeResultMark === false
? true
: markerSet
? markerSet.has(normalized) || markerSet.has(String(num)) || markerSet.has(normalizeNumberKey(num))
: wrongSet.has(normalized) || wrongSet.has(String(num)) || wrongSet.has(normalizeNumberKey(num));
const isBlueGroup =
(isOpen && groupIndex > 0) ||
(Number.isInteger(options.blueStartGroupIndex) && groupIndex >= options.blueStartGroupIndex);
const classes = isRecommend
? [
'recommend-number-text',
isBlueGroup ? 'recommend-number-blue' : '',
isWrong ? 'recommend-number-missed' : '',
isHit ? 'recommend-number-hit' : '',
]
.filter(Boolean)
.join(' ')
: [
isTextToken ? 'cxz-number-text' : isBlueGroup ? 'bule-q' : 'red-q',
isTextToken ? '' : 'cxz-number-ball',
isWrong ? 'cxz-number-ball--wrong' : '',
isHit ? 'cxz-number-ball--hit' : '',
]
.filter(Boolean)
.join(' ');
const title = isWrong ? '标记号码' : options.type === 'open' ? '开奖号码' : '推荐号码';
return `${escapeHtml(num)}`;
})
.join(isRecommend ? ',' : '');
return `${balls}`;
})
.filter(Boolean)
.join(isRecommend ? `${escapeHtml(groupSeparator)}` : '');
};
const fullBody = buildBody();
const shortBody = buildBody(4);
const body = isRecommend ? fullBody : buildBody();
const classes = [
'cxz-number-wrap',
isOpen ? 'cxz-number-wrap--open' : '',
isOpen && options.openRows === 2 ? 'cxz-number-wrap--open-2' : '',
isOpen && options.openRows === 1 ? 'cxz-number-wrap--open-1' : '',
isRecommend && count > 4 ? 'cxz-number-wrap--recommend' : '',
isRecommend && wrongSet.size ? 'cxz-number-wrap--checked' : '',
]
.filter(Boolean)
.join(' ');
let meta = '';
if (isRecommend && count > 4) {
window.__cxzNumberPopups = window.__cxzNumberPopups || {};
const popupId = `cxz-number-${Date.now()}-${Math.random().toString(36).slice(2)}`;
window.__cxzNumberPopups[popupId] = `
`;
meta += '点击查看全部';
return ``;
}
return `${body}${meta}
`;
};
const adjustRecommendNumbers = () => {
$('.cxz-number-wrap--recommend').each(function () {
const preview = this.querySelector('.cxz-number-preview--full');
if (!preview) return;
this.classList.remove('cxz-number-wrap--overflow', 'cxz-number-wrap--fit');
this.classList.add('cxz-number-wrap--fit');
const available = Math.min(this.parentElement ? this.parentElement.clientWidth : preview.clientWidth, 280);
const fits = preview.scrollWidth <= available + 1;
this.classList.remove('cxz-number-wrap--fit');
this.classList.add(fits ? 'cxz-number-wrap--fit' : 'cxz-number-wrap--overflow');
});
};
window.showCaiinfoNumbers = function (popupId) {
const content = window.__cxzNumberPopups && window.__cxzNumberPopups[popupId];
if (!content) return;
if (window.layui && layui.layer) {
const width = Math.min(520, Math.max(320, window.innerWidth - 32));
layui.layer.open({
type: 1,
title: false,
area: [`${width}px`, 'auto'],
maxWidth: 560,
shadeClose: true,
content,
});
return;
}
alert($(content).text());
};
const getArticleId = (item) =>
item.id || item.articleId || item.payArticleId || item.latestArticleId || item.contentId || item.recordId || '';
const isCollected = (value) => value === 1 || value === 2 || value === true || value === '1' || value === '2';
const isPurchased = (item) =>
item.isBuy === 1 ||
item.isBuy === 2 ||
item.isBuy === '1' ||
item.isBuy === '2' ||
item.buyStatus === 1 ||
item.buyStatus === '1' ||
item.purchased === true ||
item.purchased === 1 ||
item.purchased === '1' ||
item.isPurchased === true ||
item.isPurchased === 1 ||
item.isPurchased === '1' ||
item.bought === true ||
item.bought === 1 ||
item.bought === '1' ||
item.unlocked === true ||
item.unlocked === 1 ||
item.unlocked === '1' ||
item.canView === true ||
item.canView === 1 ||
item.canView === '1' ||
item.payStatus === 1 ||
item.payStatus === '1' ||
item.subscribed === true ||
item.subscribed === 1 ||
item.subscribed === '1' ||
item.subscribed === 'true' ||
item.subscribeStatus === 1 ||
item.subscribeStatus === '1';
const isNumericStatusValue = (value) =>
value !== undefined &&
value !== null &&
value !== '' &&
/^-?\d+(?:\.\d+)?$/.test(String(value).trim());
const getExplicitStatusText = (item) =>
String(
(item &&
(item.resultText ||
item.statusText ||
item.drawStatusText ||
item.openStatusText ||
(typeof item.result === 'string' && !isNumericStatusValue(item.result)
? item.result
: '') ||
(typeof item.status === 'string' && !isNumericStatusValue(item.status)
? item.status
: ''))) ||
''
).trim();
const getNumericStatus = (item) => {
if (!item || !isNumericStatusValue(item.status)) return null;
const value = Number(item.status);
return Number.isFinite(value) ? value : null;
};
const getStatusMatchText = (item, extraText = '') =>
[
extraText,
window.__cxzCaiinfoMenuMeta &&
window.__cxzCaiinfoMenuMeta.lottery &&
window.__cxzCaiinfoMenuMeta.lottery.name,
window.__cxzCaiinfoMenuMeta &&
window.__cxzCaiinfoMenuMeta.current &&
window.__cxzCaiinfoMenuMeta.current.name,
window.__cxzCaiinfoMenuMeta &&
window.__cxzCaiinfoMenuMeta.current &&
window.__cxzCaiinfoMenuMeta.current.parent &&
window.__cxzCaiinfoMenuMeta.current.parent.name,
item && item.lotteryName,
item && item.name,
item && item.codeName,
item && item.code,
item && item.lotteryCode,
item && item.suoxie,
item && item.playName,
item && item.playType,
item && item.typeName,
item && item.parentTypeName,
item && item.menuName,
item && item.title,
item && item.articleTitle,
item && item.latestTitle,
]
.filter(Boolean)
.join(' ')
.toLowerCase();
const getDisplayPlayTypeName = (lotteryName, typeName, item) => {
const matchText = getStatusMatchText(item, typeName);
const lotteryText = `${lotteryName || ''} ${matchText}`.toLowerCase();
if (/七乐彩|qlc/.test(lotteryText) && (matchText.includes('组选') || /^\d+码$/.test(String(typeName || '').trim()))) {
return '组选';
}
if (/大乐透|cjdlt|\bdlt\b/.test(lotteryText) && matchText.includes('复式')) {
return '复式';
}
if (/双色球|ssq/.test(lotteryText) && matchText.includes('复式')) {
return '复式';
}
if (/快乐8|kl8/.test(lotteryText) && matchText.includes('胆码')) {
return '胆码';
}
return typeName || '玩法';
};
const isStatusLoseByPlay = (item, text, typeName = '') => {
const matchText = getStatusMatchText(item, typeName);
const isQlc = /七乐彩|qlc/.test(matchText);
const isThreeDigit = /福彩3d|fc3d|排列三|排列3|pl3/.test(matchText);
const isKl8 = /快乐8|kl8/.test(matchText);
const isSsqOrDlt = /双色球|ssq|大乐透|cjdlt|\bdlt\b/.test(matchText);
const isGroup = matchText.indexOf('组选') >= 0;
const isDuplex = matchText.indexOf('复式') >= 0;
const isDanma = matchText.indexOf('胆码') >= 0;
if (isQlc && isGroup && /^中[01]$/.test(text)) return true;
if (isThreeDigit && isGroup && /^中[012]$/.test(text)) return true;
if (isKl8 && isDanma && text === '中0') return true;
if (isSsqOrDlt && isDuplex && /^中(?:1\+0|0\+0)$/.test(text)) return true;
return false;
};
const buildFrontBackStatusHtml = (statusText) => {
const match = String(statusText || '').trim().match(/^中(\d+)\+(\d+)$/);
if (!match) return escapeHtml(statusText);
return [
'中',
`${escapeHtml(match[1])}`,
'+',
`${escapeHtml(match[2])}`,
].join('');
};
const getDisplayStatusInfo = (item, statusText, className, typeName = '') => {
const text = String(statusText || '').trim();
const matchText = getStatusMatchText(item, typeName);
const isSsqOrDlt = /双色球|ssq|大乐透|cjdlt|\bdlt\b/.test(matchText);
const isDuplex = matchText.indexOf('复式') >= 0;
const nextClassName = isStatusLoseByPlay(item, text, typeName)
? 'caiinfo-status-lose'
: className || 'caiinfo-status-pending';
return {
text,
className: nextClassName,
html: isSsqOrDlt && isDuplex ? buildFrontBackStatusHtml(text) : escapeHtml(text),
};
};
const getStatus = (item, openCode, typeName = '') => {
const raw = getExplicitStatusText(item);
const numericStatus = getNumericStatus(item);
const resultValue = item.result ?? item.winStatus;
const codeResults = typeof ApiClient !== 'undefined' ? ApiClient.asArray(item.codeResults) : [];
const allCorrect =
codeResults.length > 0 && codeResults.every((result) => result && result.correct === true);
if (!raw && numericStatus !== null) {
const text = `中${numericStatus}`;
return getDisplayStatusInfo(item, text, 'caiinfo-status-win', typeName);
}
if (/全对|正确|命中|中奖|中/.test(raw) || allCorrect || resultValue === 1 || resultValue === '1' || resultValue === true) {
const text = raw || '全对';
return getDisplayStatusInfo(item, text, 'caiinfo-status-win', typeName);
}
if (/全错|错误|错/.test(raw) || resultValue === 0 || resultValue === '0' || resultValue === false) {
return getDisplayStatusInfo(item, raw === '错误' ? '错' : raw || '错', 'caiinfo-status-lose', typeName);
}
if (/待|未开奖/.test(raw)) return getDisplayStatusInfo(item, raw, 'caiinfo-status-pending', typeName);
if (raw) return getDisplayStatusInfo(item, raw, 'caiinfo-status-pending', typeName);
if (openCode) return getDisplayStatusInfo(item, '待判定', 'caiinfo-status-pending', typeName);
return getDisplayStatusInfo(item, raw || '待开奖', 'caiinfo-status-pending', typeName);
};
const pageParams = new URLSearchParams(window.location.search);
const currentCode = pageParams.get('code') || '';
const currentMenuId = pageParams.get('menuId') || pageParams.get('parentId') || '';
const findMenuMeta = (menus) => {
let lottery = null;
let current = null;
const walk = (items, parent, root) => {
(items || []).forEach((item) => {
const itemRoot = root || item;
if (item.code === currentCode || item.suoxie === currentCode) lottery = itemRoot;
if (String(item.id) === String(currentMenuId)) {
current = { ...item, parent };
lottery = itemRoot;
}
if (item.children) walk(item.children, item, itemRoot);
});
};
walk(menus || [], null, null);
return { lottery, current };
};
if (!window.__cxzCaiinfoMenuLoading && !window.__cxzCaiinfoMenuMeta && typeof ApiClient !== 'undefined') {
window.__cxzCaiinfoMenuLoading = true;
ApiClient.loadMenuCompat()
.then((res) => {
window.__cxzCaiinfoMenuMeta = findMenuMeta(res.data || []);
if (typeof window.loadArticles === 'function') window.loadArticles();
})
.catch(() => {
window.__cxzCaiinfoMenuMeta = { lottery: null, current: null };
});
}
$('.cxz-table-title').text('预测方案列表');
$('.cxz-data-table thead tr').html(
''
);
window.renderArticles = function (list) {
const $body = $('#articleBody');
$body.empty();
if (!Array.isArray(list) || !list.length) {
$body.html('
');
return;
}
list.forEach((item, index) => {
const menuMeta = window.__cxzCaiinfoMenuMeta || {};
const metaLottery = menuMeta.lottery || {};
const metaPlay = menuMeta.current || {};
const lotteryName =
item.lotteryName ||
metaLottery.name ||
item.lottery ||
item.categoryName ||
item.codeName ||
'彩票';
const rawPlayName =
metaPlay.name ||
item.playName ||
item.playType ||
(item.menuName && item.menuName !== lotteryName ? item.menuName : '') ||
item.typeName ||
item.parentTypeName ||
item.planName ||
'--';
const playName = getDisplayPlayTypeName(lotteryName, rawPlayName, item);
const nameText = lotteryName;
const issue = item.issue || item.latestIssue || item.expect || item.period || '';
const openCode = item.openCode || item.lotteryResult || item.resultCode || item.openResult || '';
const recommend = item.predictedCode || item.recommendCode || item.latestRecommend || item.recommend || '';
const openRows = /快乐8|kl8/i.test(`${lotteryName}${currentCode}`) ? 2 : 1;
const status = getStatus(item, openCode, playName);
const price = Number(item.price || item.amount || item.points || 0);
const articleId = getArticleId(item);
const blueStartGroupIndex = /大乐透|双色球|dlt|ssq/i.test(`${lotteryName}${currentCode}`) ? 1 : undefined;
const hasDrawResult = isArticleDrawn(item, openCode);
const isLatestIssue = isLatestArticle(item, list);
const hasVisibleRecommend = Boolean(recommend && !isMaskedRecommendText(recommend));
const unlockedByVisibleRecommend =
isLatestIssue && !hasDrawResult && price > 0 && !isArticleMasked(item, recommend) && hasVisibleRecommend;
const purchased = isPurchased(item) || unlockedByVisibleRecommend;
const prePurchased = isArticlePrePurchased(item);
const canShowRecommend = canShowArticleRecommend(
item,
isLatestIssue,
hasDrawResult,
unlockedByVisibleRecommend,
recommend
);
const canCopy = canShowRecommend && hasVisibleRecommend;
const collectText = isCollected(item.isCollect) ? '已收藏' : '收藏';
const collectClass = isCollected(item.isCollect) ? ' collected' : '';
let actionPrefix = '';
if (prePurchased) {
actionPrefix = '
';
} else if (shouldShowPrePurchaseButton(item, purchased) && typeof window.openPrePurchaseDialog === 'function') {
actionPrefix = `
`;
} else if (shouldShowBuyButton(item, isLatestIssue, hasDrawResult, purchased, recommend) && articleId) {
actionPrefix = `
';
}
const recommendOptions = applyRecommendHitOptions(
{
type: 'recommend',
blueStartGroupIndex,
},
item,
status.text
);
let recommendHtml = '';
if (String(recommend || '').trim() === '待发布') {
recommendHtml = '
';
} else if (canCopy) {
recommendHtml = renderBalls(recommend, recommendOptions);
} else if (recommend) {
recommendHtml = `
`
);
});
window.setTimeout(adjustRecommendNumbers, 0);
};
window.updateStats = function (list, summary) {
const rows = Array.isArray(list) ? list : [];
const total = Number(summary && (summary.totalCount || summary.total)) || rows.length;
const hit =
Number(summary && (summary.correctCount || summary.hitCount)) ||
rows.filter((item) => {
const playName = getDisplayPlayTypeName(
item.lotteryName || item.categoryName || item.codeName || '',
item.playName ||
item.playType ||
item.menuName ||
item.typeName ||
item.parentTypeName ||
item.planName ||
'',
item
);
return getStatus(item, item.openCode, playName).className === 'caiinfo-status-win';
}).length;
const rate =
summary && summary.successRate !== undefined
? Number(summary.successRate)
: total > 0
? (hit / total) * 100
: 0;
$('#statTotal').text(total);
$('#statHit').text(hit);
$('#statRate').text(`${(Number.isFinite(rate) ? rate : 0).toFixed(1)}%`);
};
if (!window.__cxzCaiinfoCompatReloaded && typeof window.loadArticles === 'function') {
window.__cxzCaiinfoCompatReloaded = true;
window.loadArticles();
}
};
document.addEventListener('DOMContentLoaded', () => {
[0, 400, 1000].forEach((delay) => window.setTimeout(run, delay));
});
[600, 1400].forEach((delay) => window.setTimeout(run, delay));
},
injectPaginationStyles() {
if (document.getElementById('cxz-unified-pagination-style')) return;
const script =
document.querySelector('script[src$="utils/CommonUtil.js"], script[src*="utils/CommonUtil.js?"]') ||
document.currentScript;
const href = script && script.src
? script.src.replace(/utils\/CommonUtil\.js(?:\?.*)?$/, 'public/css/pagination.css')
: '../public/css/pagination.css';
const link = document.createElement('link');
link.id = 'cxz-unified-pagination-style';
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
},
patchLaypageDefaults() {
if (window.__cxzLaypageDefaultsPatched) return;
if (typeof layui === 'undefined' || !layui.use) return;
const applyPatch = () => {
if (!layui.laypage || !layui.laypage.render || layui.laypage.__cxzPatched) return;
const rawRender = layui.laypage.render.bind(layui.laypage);
layui.laypage.render = function (options) {
const isCaiExpertPager =
/\/cai\.html(?:$|\?)/.test(window.location.pathname + window.location.search) &&
options &&
options.elem === 'pagination';
const nextOptions = {
prev: '上一页',
next: '下一页',
groups: window.innerWidth <= 768 ? 3 : 5,
theme: '#102b6a',
hide: true,
...options,
};
if (isCaiExpertPager) {
nextOptions.limit = 10;
}
const instance = rawRender(nextOptions);
setTimeout(() => {
document.querySelectorAll('.layui-laypage-prev').forEach((el) => {
el.textContent = el.textContent.replace(/[<>]/g, '').trim() || '上一页';
});
document.querySelectorAll('.layui-laypage-next').forEach((el) => {
el.textContent = el.textContent.replace(/[<>]/g, '').trim() || '下一页';
});
}, 0);
return instance;
};
layui.laypage.__cxzPatched = true;
window.__cxzLaypageDefaultsPatched = true;
};
applyPatch();
layui.use('laypage', applyPatch);
},
applyCaiExpertListLabels() {
if (typeof window === 'undefined' || typeof document === 'undefined') return;
if (!/\/cai\.html(?:$|\?)/.test(window.location.pathname + window.location.search)) return;
const replaceText = () => {
const title = document.getElementById('tableTitle');
if (title && title.textContent.indexOf('预测列表') >= 0) {
title.textContent = title.textContent.replace('预测列表', '专家列表');
}
const breadcrumb = document.getElementById('breadcrumbLottery');
if (breadcrumb && breadcrumb.textContent.indexOf('预测推荐') >= 0) {
breadcrumb.textContent = breadcrumb.textContent.replace('预测推荐', '专家推荐');
}
};
replaceText();
[0, 350, 800].forEach((delay) => window.setTimeout(replaceText, delay));
},
async loadBasePageData(options = {}) {
const [configList, config, copyright, siteConfig, ads] = await Promise.all([
ApiClient.get('/api/web/config/list').catch(() => ({ data: [] })),
ApiClient.get('/api/web/config').catch(() => ({ data: {} })),
ApiClient.get(ApiClient.API.configCopyright).catch(() => ({ data: [] })),
ApiClient.get('/api/web/config/site').catch(() => ({ data: {} })),
options.ads === false
? Promise.resolve({ data: [] })
: ApiClient.get('/api/web/ad/list', { inTime: true }).catch(() => ({ data: [] })),
]);
return {
code: 0,
success: true,
data: {
config: config.data || {},
siteConfig: siteConfig.data || {},
webConfigs: this.asArray(this.pickData(configList, [])).length
? this.asArray(this.pickData(configList, []))
: ApiClient.configObjectToList
? ApiClient.configObjectToList(config.data || {})
: config.data || {},
copyrightConfigs: this.asArray(this.pickData(copyright, [])),
adList: this.asArray(this.pickData(ads, [])),
},
};
},
async loadHomePageData() {
if (ApiClient.loadHomeIndexCompat) {
return ApiClient.loadHomeIndexCompat();
}
const [configList, config, copyright, siteConfig, menus, banners, notices, ads, freeArticles, latest] =
await Promise.all([
ApiClient.get('/api/web/config/list').catch(() => ({ data: [] })),
ApiClient.get('/api/web/config').catch(() => ({ data: {} })),
ApiClient.get(ApiClient.API.configCopyright).catch(() => ({ data: [] })),
ApiClient.get('/api/web/config/site').catch(() => ({ data: {} })),
ApiClient.get('/api/web/menu/home-nav').catch(() => ({ data: [] })),
ApiClient.get('/api/web/banner/list').catch(() => ({ data: [] })),
ApiClient.get('/api/web/notice/list', { count: 10 }).catch(() => ({ data: [] })),
ApiClient.get('/api/web/ad/list', { inTime: true }).catch(() => ({ data: [] })),
ApiClient.get('/api/web/free-article/recommend', { articleCount: 5 }).catch(() => ({
data: [],
})),
ApiClient.get('/api/web/lottery/result/latest').catch(() => ({ data: [] })),
]);
const lotteryList = this.asArray(this.pickData(latest, []));
const latestMap = {};
lotteryList.forEach((item) => {
latestMap[item.code] = {
...item,
expect: item.expect,
name: item.expect,
time: item.time,
openCode: item.openCode,
code: item.openCode,
red: item.redNumbers,
blue: item.blueNumbers,
};
});
const articleGroups = this.asArray(this.pickData(freeArticles, []));
const articleMap = {};
articleGroups.forEach((group) => {
if (Array.isArray(group.articles)) {
articleMap[group.code] = group.articles.map((article) => ({
...article,
code: article.code || group.code,
lotteryName: article.lotteryName || group.lotteryName,
accountAvatar: article.accountAvatar || article.expertAvatar || article.avatar,
createUserName:
article.createUserName || article.author || article.nickName || article.issuer,
}));
return;
}
const code = group.code || group.suoxie;
if (!code) return;
if (!articleMap[code]) articleMap[code] = [];
articleMap[code].push({
...group,
accountAvatar: group.accountAvatar || group.expertAvatar || group.avatar,
createUserName: group.createUserName || group.author || group.nickName || group.issuer,
});
});
const navList = this
.asArray(this.pickData(menus, []))
.filter((item) => item.type === 'lottery')
.map((menu) => ({
...menu,
suoxie: menu.suoxie || menu.code,
url: menu.url || menu.path,
lotteryResult: latestMap[menu.suoxie || menu.code],
freeArticleList: articleMap[menu.suoxie || menu.code] || [],
}));
return {
code: 0,
success: true,
data: {
config: config.data || {},
webConfigs: this.asArray(this.pickData(configList, [])).length
? this.asArray(this.pickData(configList, []))
: ApiClient.configObjectToList
? ApiClient.configObjectToList(config.data || {})
: config.data || {},
copyrightConfigs: this.asArray(this.pickData(copyright, [])),
siteConfig: siteConfig.data || {},
bannerList: this.asArray(this.pickData(banners, [])),
noticeList: this.asArray(this.pickData(notices, [])),
adList: this.asArray(this.pickData(ads, [])),
freeArticleData: articleGroups,
lotteryList,
navList,
menuList: navList,
},
};
},
filterValidAds(adList, position) {
if (!Array.isArray(adList)) return [];
const now = new Date();
return adList.filter((item) => {
const enabled = item.isEnabled !== false && item.delFlag !== '1';
const typeOk = item.adType === undefined || item.adType === 1;
const positionOk = position === undefined || String(item.position) === String(position);
const startOk = !item.startTime || new Date(item.startTime) <= now;
const endOk = !item.endTime || new Date(item.endTime) >= now;
return enabled && typeOk && positionOk && startOk && endOk;
});
},
processAndRenderAds(adList, positions) {
const validAds = this.filterValidAds(adList);
const $wrap = this.$('#sidebarAdWrap');
if (!$wrap.length || !validAds.length) return;
$wrap.empty();
const groupSize = 8;
for (let i = 0; i < validAds.length; i += groupSize) {
const group = validAds.slice(i, i + groupSize);
const $section = this.$('