/**
* Shared helpers for page configuration, login state, ads, links and safe HTML.
* Depends on config.js, ApiClient.js, DateUtil.js and layui.
*/
const CommonUtil = {
get $() {
return layui.$;
},
getAuthToken() {
const raw = localStorage.getItem('token');
if (!raw) return null;
try {
return JSON.parse(raw);
} catch (e) {
return raw;
}
},
getToken() {
return typeof ApiClient !== 'undefined' && ApiClient.getToken
? ApiClient.getToken()
: this.getAuthToken();
},
escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
isLocalDevHost() {
return /^(localhost|127\.0\.0\.1|0\.0\.0\.0)$/i.test(window.location.hostname);
},
freeArticleHref(articleId) {
const id = encodeURIComponent(articleId || '');
if (!id) return 'javascript:void(0)';
if (this.isLocalDevHost()) return `mianfei.html?id=${id}`;
return `/mianfei/${id}.html`;
},
authHeaders() {
const token =
typeof ApiClient !== 'undefined' && ApiClient.getToken
? ApiClient.getToken()
: this.getAuthToken();
const tenantId =
typeof ApiClient !== 'undefined' && ApiClient.tenantId
? ApiClient.tenantId
: '000000';
const headers = { tenantId };
if (token) headers.Authorization = `Bearer ${token}`;
return headers;
},
requireAuth(callback) {
if (this.getAuthToken()) {
callback();
return;
}
layui.use('layer', function () {
layui.layer.confirm(
'请先登录',
{ btn: ['去登录', '取消'] },
function (index) {
layui.layer.close(index);
window.location.href = 'login.html';
}
);
});
},
getUserLevelText(user = {}) {
const truthy = (value) =>
value === true ||
value === 1 ||
value === '1' ||
String(value).toLowerCase() === 'true';
const expertStatus = String(
user.expertStatus || user.expert_status || user.expertLevel || user.expert_level || ''
).toLowerCase();
const isRegularExpert =
truthy(user.regularExpert) ||
truthy(user.isRegularExpert) ||
truthy(user.is_regular_expert) ||
truthy(user.formalExpert) ||
truthy(user.isFormalExpert) ||
/正式|regular|formal/.test(expertStatus);
const isInternExpert =
truthy(user.internExpert) ||
truthy(user.isInternExpert) ||
truthy(user.is_intern_expert) ||
truthy(user.trialExpert) ||
truthy(user.isTrialExpert) ||
truthy(user.is_expert) ||
truthy(user.isExpert) ||
truthy(user.expert) ||
/实习|intern|trial/.test(expertStatus);
if (isRegularExpert) return '正式专家';
if (isInternExpert) return '实习专家';
return '会员';
},
getUserLevelTextLegacy(user = {}) {
if (user.regularExpert || user.isExpert === '1' || user.expertStatus === 'regular') {
return '正式专家';
}
if (user.internExpert || user.isInternExpert || user.expertStatus === 'intern') {
return '实习专家';
}
return '会员';
},
applyTopbarUserLevel(user = {}) {
const $ = this.$;
const text = this.getUserLevelText(user);
$('#userLevel, #headerUserLevel').text(text);
$('.topbar__ubadge, .uc-role').text(text);
},
applyWebConfigs(configs) {
const $ = this.$;
if (!configs) return;
if (!Array.isArray(configs)) {
configs = Object.keys(configs).map((key, index) => ({
type: key,
content: configs[key],
sort: index,
}));
}
configs = configs
.filter((item) => item && item.isEnabled !== '0' && item.isEnabled !== 0 && item.isEnabled !== false)
.map((item, index) => ({
...item,
sort: item.sort === undefined || item.sort === null ? index : Number(item.sort),
}));
const copyrightItems = [];
configs.forEach((item) => {
if (item.type === 'web_title') {
$('#page-title').text(item.content || '');
if (item.content) document.title = item.content;
} else if (item.type === 'web_keywords') {
this.setMeta('keywords', item.content || '');
} else if (item.type === 'web_introduce' || item.type === 'web_description') {
this.setMeta('description', item.content || '');
} else if (item.type === 'web_icon') {
this.setIcon(item.content || '');
} else if (item.type === 'web_copyright') {
copyrightItems.push(item);
}
});
this.applyFooterCopyright(copyrightItems);
},
applyFooterCopyright(items) {
const $ = this.$;
if (!Array.isArray(items) || !items.length) return;
const list = items
.filter((item) => item && item.content)
.sort((a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0));
if (!list.length) return;
const footerInfo = list.find((item) => Number(item.sort) === 3) || list[list.length - 1];
if (footerInfo?.content) $('#copyright-info').text(footerInfo.content.trim());
const $meta = $('.site-footer__meta');
if (!$meta.length) return;
$meta.empty();
list
.filter((item) => item !== footerInfo)
.forEach((item) => {
const content = String(item.content || '').trim();
if (!content) return;
const isIcp = /ICP备|工信部/.test(content);
const isPolice = /公网安备|公安/.test(content);
if (isIcp) {
$('', {
id: 'beian',
href: 'https://beian.miit.gov.cn/',
target: '_blank',
text: content,
}).appendTo($meta);
} else {
$('', {
id: isPolice ? 'police-record' : undefined,
text: content,
}).appendTo($meta);
}
});
if (!$meta.children().length && footerInfo?.content) {
$('', {
id: 'additional-info',
text: footerInfo.content.trim(),
}).appendTo($meta);
}
},
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')
);
},
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.installWechatLoginEntry();
this.installWechatAccountBindEntry();
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);
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('后端未返回微信授权地址');
window.location.href = 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('后端未返回微信授权地址');
window.location.href = 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) => /\*/.test(String(value || ''));
const normalizeRecommend = (value) =>
String(value || '')
.replace(/[,、]/g, ',')
.replace(/[||]/g, '+')
.replace(/\s+/g, ',')
.replace(/,+/g, ',')
.replace(/\+,/g, '+')
.replace(/,\+/g, '+')
.replace(/^,|,$/g, '');
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 getWrongNumbers = (item) => {
const codeResultWrong = (typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [])
.filter((result) => result && typeof result === 'object' && result.correct === false)
.map((result) => result.code || result.value || result.number || result.result || '')
.filter(Boolean);
const raw =
item.wrongNumbers ||
item.wrongCode ||
item.errorNumbers ||
item.errorCode ||
item.missNumbers ||
item.missCode ||
item.reverseNumbers ||
item.reverseCode ||
item.falseNumbers ||
item.falseCode ||
item.fanZhi ||
item.fanzhi ||
codeResultWrong.join(',');
const groups = splitNumbers(Array.isArray(raw) ? raw.join(',') : raw);
const values = groups.flat();
return new Set(
values.flatMap((num) => {
const text = String(num);
return [text, text.padStart(2, '0'), normalizeNumberKey(text)];
})
);
};
const getHitNumbers = (item) => {
const codeResultHit = (typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [])
.filter((result) => result && typeof result === 'object' && result.correct === true)
.map((result) => result.code || result.value || result.number || result.result || '')
.filter(Boolean);
const raw =
item.hitNumbers ||
item.hitCode ||
item.hitCodes ||
item.correctNumbers ||
item.correctCode ||
item.correctCodes ||
item.winNumbers ||
item.winCode ||
item.winCodes ||
codeResultHit.join(',');
const groups = splitNumbers(Array.isArray(raw) ? raw.join(',') : raw);
const values = groups.flat();
return new Set(
values.flatMap((num) => {
const text = String(num);
return [text, text.padStart(2, '0'), normalizeNumberKey(text)];
})
);
};
const getCodeResultMarks = (item, recommend) => {
const recommendGroups = splitNumbers(recommend);
const codeResults =
typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [];
if (!recommendGroups.length || !codeResults.length) return null;
const flatCount = recommendGroups.reduce((total, group) => total + group.length, 0);
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 null;
if (marks.length < flatCount) return null;
let offset = 0;
return recommendGroups.map((group) => {
const groupMarks = marks.slice(offset, offset + group.length);
offset += group.length;
return groupMarks;
});
};
const getPositionMarkerSets = (item, recommend) => {
const recommendGroups = splitNumbers(recommend);
if (!recommendGroups.length) return null;
const codeResults = typeof ApiClient !== 'undefined' ? ApiClient.asArray(item && item.codeResults) : [];
if (codeResults.length === recommendGroups.length) {
return codeResults.map((result) => {
if (result && typeof result === 'object' && result.correct !== false) {
return new Set();
}
const value =
result && typeof result === 'object'
? result.code || result.value || result.number || result.result || ''
: result;
const values = splitNumbers(value).flat();
return new Set(
values.flatMap((num) => {
const text = String(num);
return [text, text.padStart(2, '0'), normalizeNumberKey(text)];
})
);
});
}
const rawStatus = String(
item.status ||
item.resultText ||
item.statusText ||
item.drawStatusText ||
item.openStatusText ||
(typeof item.result === 'string' ? item.result : '')
).trim();
const resultValue = item.result ?? item.winStatus;
const isMissResult =
/全错|错误|未中|错/.test(rawStatus) ||
resultValue === 0 ||
resultValue === '0' ||
resultValue === false;
if (!isMissResult) return null;
const openGroups = splitNumbers(item.openCode || item.lotteryResult || item.resultCode || item.openResult || '');
const openValues = openGroups.flat();
if (openValues.length === recommendGroups.length) {
return openValues.map((num) => {
const text = String(num);
return new Set([text, text.padStart(2, '0'), normalizeNumberKey(text)]);
});
}
return null;
};
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.markerSets || 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 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 codeResultMark =
Array.isArray(codeResultMarks) &&
codeResultMarks[groupIndex] &&
codeResultMarks[groupIndex][numberIndex] !== undefined
? codeResultMarks[groupIndex][numberIndex]
: null;
const isExplicitHit =
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 = [
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('');
return `${balls}`;
})
.join('');
};
const fullBody = buildBody();
const shortBody = buildBody(5);
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] = `