Files
2026-08-21 17:06:12 +08:00

1038 lines
40 KiB
JavaScript

const ApiClient = {
// 后端租户标识,所有接口请求都会带到 header 里。
// 后面如果租户号更换,只需要改这里的默认值,或在 config.js 里配置 CURRENT_CONFIG.TENANT_ID。
TENANT_ID: '531923',
// TENANT_ID: '000000',
API: {
authCode: '/api/web/auth/code',
authLogin: '/api/web/auth/login',
authLogout: '/api/web/auth/logout',
authRegister: '/api/web/auth/register',
authSmsCode: '/api/web/auth/sms/code',
authWechatOpen: '/api/web/auth/binding/wechat_open',
authVerifyConfig: '/api/web/auth/verify-config',
authSlideCaptcha: '/api/web/auth/slide-captcha',
authSlideCaptchaVerify: '/api/web/auth/slide-captcha/verify',
authHeartbeat: '/api/web/auth/heartbeat',
authForgotPasswordSmsCode: '/api/web/auth/forgot-password/sms-code',
authForgotPasswordReset: '/api/web/auth/forgot-password/reset',
authSocialLogin: '/api/web/auth/social-login',
authSocialCallback: '/api/web/auth/social-callback',
authSocialBind: '/api/web/auth/social-bind',
authSocialBindPhone: '/api/web/auth/social-bind-phone',
authSocialBindPhoneSmsCode: '/api/web/auth/social-bind-phone/sms-code',
appDownloadInfo: '/api/web/app/getDownloadInfo',
agreement: (type) => `/api/web/agreement/${encodeURIComponent(type || '')}`,
noticeList: '/api/web/notice/list',
noticeDetail: (id) => `/api/web/notice/${encodeURIComponent(id || '')}`,
friendLinks: '/api/web/link/friend',
otherRecommendLinks: '/api/web/link/other-recommend',
latestLotteryResult: '/api/web/lottery/result/latest',
lotteryResultList: '/api/web/lottery/result/list',
webConfigList: '/api/web/config/list',
webConfig: '/api/web/config',
configList: '/api/web/config/list',
configSite: '/api/web/config/site',
configCopyright: '/api/web/config/copyright',
bannerList: '/api/web/banner/list',
adList: '/api/web/ad/list',
freeArticleRecommend: '/api/web/free-article/recommend',
freeArticleList: '/api/web/free-article/list',
freeArticleLatest: '/api/web/free-article/latest',
referralShareInfo: '/api/web/referral/share-info',
referralSummary: '/api/web/referral/summary',
referralInviteList: '/api/web/referral/invite-list',
referralRebateList: '/api/web/referral/rebate-list',
referralBind: '/api/web/referral/bind',
accountInfo: '/api/web/account/info',
accountProfile: '/api/web/account/profile',
accountProfileAvatar: '/api/web/account/profile/avatar',
expertConfig: '/api/web/expert/config',
expertApply: '/api/web/expert/apply',
expertUploadIdCardFront: '/api/web/expert/upload-id-card-front',
expertUploadIdCardBack: '/api/web/expert/upload-id-card-back',
homeNav: '/api/web/menu/home-nav',
menuChildren: (id) => `/api/web/menu/${encodeURIComponent(id)}/children`,
payArticleExpertProfile: '/api/web/pay-article/expert-profile',
payArticleExpertList: '/api/web/pay-article/expert-list',
payArticleExpertRank: '/api/web/pay-article/expert-rank',
payArticlePublishOptions: (menuId) => `/api/web/pay-article/publish/options?menuId=${encodeURIComponent(menuId || '')}`,
payArticlePublish: '/api/web/pay-article/publish',
payArticleLatest: '/api/web/pay-article/latest',
payArticleRelated: (id) => `/api/web/pay-article/${encodeURIComponent(id || '')}/related`,
payArticleCollect: (id) => `/api/web/pay-article/article-collect/${encodeURIComponent(id)}`,
expertCollect: (expertId, accountType = 1) =>
`/api/web/pay-article/expert-collect?${new URLSearchParams({
expertId: String(expertId || ''),
accountType: String(accountType || 1),
}).toString()}`,
walletOverview: '/api/web/wallet/overview',
walletFlowList: '/api/web/wallet/flow/list',
walletFlowDelete: (id) => `/api/web/wallet/flow/${encodeURIComponent(id)}`,
walletRechargeConfigList: '/api/web/wallet/recharge/config/list',
walletRechargeOrder: '/api/web/wallet/recharge/order',
walletRechargeOrderStatus: (outTradeNo) => `/api/web/wallet/recharge/order/status/${encodeURIComponent(outTradeNo || '')}`,
walletRechargeNotify: '/api/web/wallet/recharge/notify',
walletTransferToAccount: '/api/web/wallet/transfer-to-account',
walletWithdraw: '/api/web/wallet/withdraw',
walletWithdrawList: '/api/web/wallet/withdraw/list',
walletPrepurchasePayArticle: '/api/web/wallet/prepurchase/pay-article',
walletReceiveAccountList: '/api/web/wallet/receive-account/list',
walletReceiveAccount: '/api/web/wallet/receive-account',
walletReceiveAccountUpdate: (id) => `/api/web/wallet/receive-account/${encodeURIComponent(id)}`,
walletReceiveAccountDefault: (id) => `/api/web/wallet/receive-account/${encodeURIComponent(id)}/default`,
walletReceiveAccountDelete: (id) => `/api/web/wallet/receive-account/${encodeURIComponent(id)}`,
walletReceiveAccountUploadQrcode: '/api/web/wallet/receive-account/upload-qrcode',
mineSummary: '/api/web/mine/summary',
mineFreeCollectList: '/api/web/mine/free-collect/list',
minePayCollectList: '/api/web/mine/pay-collect/list',
mineExpertCollectList: '/api/web/mine/expert-collect/list',
mineSignInList: '/api/web/mine/sign-in/list',
mineSignInStatus: '/api/web/mine/sign-in/status',
mineSignIn: '/api/web/mine/sign-in',
walletPurchasePayArticle: '/api/web/wallet/purchase/pay-article',
freeArticlePublish: '/api/web/free-article/publish',
mineFreeArticleList: '/api/web/mine/free-article/list',
mineFreeArticleDetail: (id) => `/api/web/mine/free-article/${encodeURIComponent(id || '')}`,
minePayArticleList: '/api/web/mine/pay-article/list',
minePayArticleDetail: (id) => `/api/web/mine/pay-article/${encodeURIComponent(id)}`,
minePurchasedPayArticleList: '/api/web/mine/purchased-pay-article/list',
minePurchasedPayArticleDelete: (sourceType, recordId) => `/api/web/mine/purchased-pay-article/${encodeURIComponent(sourceType)}/${encodeURIComponent(recordId)}`,
freeArticleDetail: (id) => `/api/web/free-article/${encodeURIComponent(id)}`,
freeArticleRelated: (id) => `/api/web/free-article/${encodeURIComponent(id)}/related`,
freeArticleComments: (id) => `/api/web/free-article/${encodeURIComponent(id)}/comments`,
freeArticleComment: (id) => `/api/web/free-article/${encodeURIComponent(id)}/comment`,
freeArticleLike: (id) => `/api/web/free-article/${encodeURIComponent(id)}/like`,
freeArticleCollect: (id) => `/api/web/free-article/${encodeURIComponent(id)}/collect`,
fc3dFreePredictMenus: '/api/web/fc3d/free-predict/menus',
fc3dFreePredictList: '/api/web/fc3d/free-predict/list',
fc3dSchemeList: '/api/web/fc3d/scheme/list',
fc3dSchemeDetail: (id) => `/api/web/fc3d/scheme/${encodeURIComponent(id)}`,
walletPurchaseFc3dScheme: '/api/web/wallet/purchase/fc3d-scheme',
lotteryTrendTools: '/api/web/lottery/result/trend/tools',
lotteryTrend: '/api/web/lottery/result/trend',
},
get baseUrl() {
return (window.CURRENT_CONFIG && window.CURRENT_CONFIG.API_BASE_URL) || '';
},
get clientId() {
return (
(window.CURRENT_CONFIG && window.CURRENT_CONFIG.CLIENT_ID) ||
'209ef407810e3856f40870a9f0e769d7'
);
},
get CLIENT_ID() {
return this.clientId;
},
get tenantId() {
return (
(window.CURRENT_CONFIG && window.CURRENT_CONFIG.TENANT_ID) ||
this.TENANT_ID
);
},
request(options) {
const normalized = this.normalizeLegacyRequest(options || {});
if (normalized.compose) return normalized.compose();
const method = (normalized.method || 'GET').toUpperCase();
const url = this.buildUrl(normalized.url, method === 'GET' ? normalized.data : null);
const dedupeKey = this.requestDedupeKey(method, url, normalized);
const cached = this.getDedupeCache(dedupeKey);
if (cached) return cached;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), normalized.timeout || 10000);
const baseHeaders = normalized.publicRequest ? this.publicHeaders() : this.defaultHeaders();
const config = {
method,
headers: {
...baseHeaders,
...(normalized.headers || {}),
},
signal: controller.signal,
};
if (normalized.data && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
config.body =
normalized.data instanceof FormData || typeof normalized.data === 'string'
? normalized.data
: JSON.stringify(normalized.data);
if (normalized.data instanceof FormData) delete config.headers['Content-Type'];
}
const promise = fetch(this.baseUrl + url, config)
.then(async (response) => {
clearTimeout(timeoutId);
if (!response.ok) {
if (response.status === 401) {
localStorage.removeItem('token');
NavigationUtil.open('login.html');
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = this.normalizeResponse(await this.parseResponse(response), normalized);
if (this.isAuthExpiredResponse(result)) this.handleAuthExpired();
return result;
})
.catch((error) => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') throw new Error('Request timeout');
throw error;
});
return this.setDedupeCache(dedupeKey, promise);
},
requestDedupeKey(method, url, normalized = {}) {
if (method !== 'GET' || normalized.dedupe === false) return '';
const token = this.getToken() || '';
return [method, this.baseUrl + url, token, this.clientId, this.tenantId].join(' ');
},
getDedupeStore() {
if (!this._dedupeStore) this._dedupeStore = new Map();
return this._dedupeStore;
},
getDedupeCache(key) {
if (!key) return null;
const store = this.getDedupeStore();
const item = store.get(key);
if (!item) return null;
if (item.pending) return item.promise;
if (Date.now() - item.time > 500) {
store.delete(key);
return null;
}
return item.promise;
},
setDedupeCache(key, promise) {
if (!key) return promise;
const store = this.getDedupeStore();
store.set(key, { promise, pending: true, time: Date.now() });
promise
.then(() => {
const item = store.get(key);
if (item?.promise === promise) {
item.pending = false;
item.time = Date.now();
setTimeout(() => {
if (store.get(key)?.promise === promise) store.delete(key);
}, 600);
}
})
.catch(() => {
if (store.get(key)?.promise === promise) store.delete(key);
});
return promise;
},
defaultHeaders() {
const headers = {
'Content-Type': 'application/json',
tenantId: this.tenantId,
};
const token = this.getToken();
if (token) {
headers.Authorization = `Bearer ${token}`;
headers.clientid = this.clientId;
}
return headers;
},
publicHeaders() {
return {
'Content-Type': 'application/json',
tenantId: this.tenantId,
};
},
isSuccess(res) {
return !!res && (res.code === 0 || res.code === 200 || res.success === true);
},
isAuthExpiredResponse(result) {
if (!result || typeof result !== 'object') return false;
const code = Number(result.code);
const message = String(result.msg || result.message || result.error || '').toLowerCase();
return (
code === 401 ||
code === 403 ||
code === 40101 ||
code === 40102 ||
message.indexOf('token') >= 0 && (message.indexOf('expired') >= 0 || message.indexOf('invalid') >= 0)
);
},
getToken() {
const raw = localStorage.getItem('token');
if (!raw) return '';
try {
const parsed = JSON.parse(raw);
return parsed.access_token || parsed.accessToken || parsed.token || parsed;
} catch (e) {
return raw;
}
},
token() {
return this.getToken();
},
clearToken() {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
localStorage.removeItem('user');
},
getLocalUserInfo() {
const raw = localStorage.getItem('userInfo') || localStorage.getItem('user') || '';
if (!raw) return {};
try {
return JSON.parse(raw) || {};
} catch (e) {
return {};
}
},
isJwtExpired(token) {
const parts = String(token || '').split('.');
if (parts.length < 2) return false;
try {
let payloadText = parts[1].replace(/-/g, '+').replace(/_/g, '/');
while (payloadText.length % 4) payloadText += '=';
const payload = JSON.parse(decodeURIComponent(escape(window.atob(payloadText))));
return payload.exp ? payload.exp * 1000 <= Date.now() : false;
} catch (e) {
return false;
}
},
isTokenExpired() {
const token = this.token();
if (!token) return true;
if (this.isJwtExpired(token)) return true;
const userInfo = this.getLocalUserInfo();
const expireTime = Number(userInfo.expireTime || userInfo.expire_time || userInfo.expiresAt || userInfo.expires_at || 0);
return expireTime ? expireTime <= Date.now() : false;
},
handleAuthExpired() {
if (typeof window === 'undefined') return;
this.clearToken();
const path = window.location.pathname || '';
if (/\/(login|reg|repwd)\.html$/i.test(path)) return;
const returnUrl = window.location.pathname + window.location.search;
const loginPath = '/login.html?redirect=' + encodeURIComponent(returnUrl);
NavigationUtil.open(
typeof CommonUtil !== 'undefined' && CommonUtil.siteHref
? CommonUtil.siteHref(loginPath)
: ((window.CURRENT_ENV === 'development' ? '/html/login.html?redirect=' : '/login.html?redirect=') + encodeURIComponent(returnUrl)));
},
buildUrl(url, params) {
if (!params || params instanceof FormData) return url;
const clean = this.cleanParams(params);
const query = new URLSearchParams(clean).toString();
if (!query) return url;
return url + (url.indexOf('?') >= 0 ? '&' : '?') + query;
},
cleanParams(params, omittedKeys = []) {
const clean = {};
const omitted = new Set(omittedKeys || []);
Object.keys(params || {}).forEach((key) => {
const value = params[key];
if (omitted.has(key)) return;
if (value !== undefined && value !== null && value !== '') clean[key] = value;
});
return clean;
},
async parseResponse(response) {
const contentType = response.headers.get('content-type') || '';
if (contentType.indexOf('application/json') >= 0) return response.json();
return response.text();
},
normalizeResponse(result, context = {}) {
if (!result || typeof result !== 'object') return result;
const normalized = { ...result };
if (normalized.code === 200) {
normalized.rawCode = 200;
normalized.code = 0;
}
if (typeof normalized.success === 'undefined') {
normalized.success = normalized.code === 0 || normalized.code === 200;
}
if (Array.isArray(normalized.rows)) {
normalized.data = {
records: normalized.rows,
rows: normalized.rows,
totalRow: normalized.total || 0,
total: normalized.total || 0,
};
}
if (context.url === '/api/web/pay-article/expert-rank') {
this.normalizeExpertRankResponse(normalized, context.data || {});
}
if (normalized.data && typeof normalized.data === 'object') {
if (Object.prototype.hasOwnProperty.call(normalized.data, 'captchaEnabled')) {
normalized.data.id = normalized.data.uuid || 'captcha-disabled';
normalized.data.image = normalized.data.img || this.transparentGif();
if (normalized.data.captchaEnabled === false) this.applyCaptchaDisabledState();
}
if (normalized.data.access_token && !normalized.data.token) {
normalized.data.token = normalized.data.access_token;
}
}
return normalized;
},
normalizeExpertRankResponse(result, params = {}) {
const data = result.data && typeof result.data === 'object' ? result.data : {};
const source = Array.isArray(data.records)
? data.records
: Array.isArray(data.rows)
? data.rows
: Array.isArray(result.rows)
? result.rows
: [];
const rows = source.map((item, index) => {
const successRate =
item.successRate === undefined || item.successRate === null ? '' : String(item.successRate);
const hitRate = item.hitRate || (successRate ? `${successRate}%` : '');
return {
...item,
rank: item.rank || index + 1,
createBy: item.createBy || item.expertId,
expertId: item.expertId || item.createBy,
createUserName: item.createUserName || item.nickname || item.expertName,
nickname: item.nickname || item.createUserName || item.expertName,
issue: item.issue || item.latestIssue,
title: item.title || item.latestTitle || item.latestRecommend,
hitRate,
};
});
const shouldExpandForLegacyRank =
params && !params.pageNum && Number(params.pageSize || 0) >= 50;
const records = shouldExpandForLegacyRank
? rows.flatMap((item) => {
const total = Math.max(Number(item.totalCount) || 1, 1);
const hit = Math.max(Number(item.correctCount) || 0, 0);
return Array.from({ length: total }, (_, index) => ({
...item,
status: index < hit ? '1' : '0',
}));
})
: rows;
result.data = {
...data,
records,
rows: records,
totalRow: data.totalRow || data.total || result.total || rows.length,
total: data.total || data.totalRow || result.total || rows.length,
};
},
transparentGif() {
return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
},
applyCaptchaDisabledState() {
if (typeof window === 'undefined' || !window.document) return;
setTimeout(() => {
const input = document.getElementById('captchaInput');
const hidden = document.getElementById('captchaId');
const box = document.getElementById('captchaContainer');
if (input) input.value = '0000';
if (hidden) hidden.value = 'captcha-disabled';
if (box) box.style.display = 'none';
}, 0);
},
normalizePageParams(data) {
const params = { ...(data || {}) };
if (params.page && !params.pageNum) params.pageNum = params.page;
if (params.limit && !params.pageSize) params.pageSize = params.limit;
if (params.pageNo && !params.pageNum) params.pageNum = params.pageNo;
delete params.page;
delete params.limit;
delete params.pageNo;
return params;
},
pickData(result, fallback) {
if (!result || typeof result !== 'object') return fallback;
return result.data === undefined || result.data === null ? fallback : result.data;
},
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;
if (Array.isArray(value.data)) return value.data;
return [];
},
unwrap(result, fallback) {
return this.pickData(result, fallback);
},
listFrom(source, keys = ['records', 'rows', 'list', 'data']) {
if (Array.isArray(source)) return source;
if (!source || typeof source !== 'object') return [];
for (const key of keys || []) {
const nested = this.asArray(source[key]);
if (nested.length) return nested;
}
return [];
},
normalizeLegacyRequest(options) {
const url = options.url || '';
const method = (options.method || 'GET').toUpperCase();
const data = options.data || {};
if (url === '/api/web/index') {
return { compose: () => this.loadHomeIndexCompat() };
}
if (url === '/api/web/menu/getFormatList') {
return { compose: () => this.loadMenuCompat() };
}
if (url === '/api/web/link/getList') {
return { compose: () => this.loadLinksCompat() };
}
if (url === '/api/web/QRCode') {
return { compose: () => Promise.resolve({ code: 0, success: true, data: location.origin + '/html/appdown.html' }) };
}
if (url === '/api/web/user/login') {
return { compose: () => this.loginCompat(data) };
}
if (url.indexOf('/api/user/collect/list') === 0) {
return {
...options,
url: '/api/web/mine/free-collect/list',
method: 'GET',
data: this.normalizePageParams({
...data,
page: this.queryValue(url, 'page') || data.page,
pageSize: this.queryValue(url, 'pageSize') || data.pageSize,
}),
};
}
if (url === '/api/web/lotteryResult/getList') {
return { compose: () => this.requestLegacyLotteryResultCompat(data) };
}
if (url === '/api/web/freeArticle/getPageList') {
return { compose: () => this.requestWithMenu('/api/web/free-article/list', data) };
}
if (url === '/api/web/payArticle/getPageList') {
return { compose: () => this.requestWithMenu('/api/web/pay-article/expert-rank', data) };
}
if (url === '/api/web/payArticle/getListByCreateUser') {
return {
compose: () => this.loadLegacyExpertDetailCompat(data),
};
}
const direct = this.mapSimpleLegacyUrl(url, data);
if (direct) return { ...options, ...direct };
if (url.indexOf('/api/web/agreement/getByType') === 0) {
const type = this.queryValue(url, 'type') || data.type || '1';
return { ...options, url: `/api/web/agreement/${type}`, method: 'GET', data: null };
}
if (url.indexOf('/api/web/notice/getDetail') === 0) {
const id = this.queryValue(url, 'id') || data.id;
return { ...options, url: `/api/web/notice/${id}`, method: 'GET', data: null };
}
if (url.indexOf('/api/web/freeArticle/getDetail') === 0) {
const id = this.queryValue(url, 'id') || data.id;
return { ...options, url: `/api/web/free-article/${id}`, method: 'GET', data: null };
}
return { ...options, method, data };
},
mapSimpleLegacyUrl(url, data) {
const pageParams = this.normalizePageParams(data);
const map = {
'/api/web/user/register': { url: '/api/web/auth/register', method: 'POST', data: this.normalizeAuthPayload(data) },
'/api/web/user/getCaptcha': { url: '/api/web/auth/code', method: 'GET', data: null },
'/api/web/user/logout': { url: '/api/web/auth/logout', method: 'POST', data: null },
'/api/web/user/getCurrentUserDetail': { url: '/api/web/mine/summary', method: 'GET', data: null },
'/api/web/user/getUserBalance': { url: '/api/web/wallet/overview', method: 'GET', data: null },
'/api/web/user/signIn': { url: '/api/web/mine/sign-in', method: 'POST', data: null },
'/api/web/user/signIn/getPageList': { url: '/api/web/mine/sign-in/list', method: 'GET', data: pageParams },
'/api/web/capitalFlow/getPageList': { url: '/api/web/wallet/flow/list', method: 'GET', data: pageParams },
'/api/web/payment/getConfigList': { url: '/api/web/wallet/recharge/config/list', method: 'GET', data: null },
'/api/web/payment/charge/native': {
url: '/api/web/wallet/recharge/order',
method: 'POST',
data: Object.assign({}, data || {}, {
rechargeId: data && (data.rechargeId || data.configId),
payType: 'NATIVE',
returnUrl: typeof window !== 'undefined' ? window.location.href : ''
})
},
'/api/web/freeArticle/getPageListByUser': { url: '/api/web/mine/free-article/list', method: 'GET', data: pageParams },
'/api/web/freeArticle/push': { url: '/api/web/free-article/publish', method: 'POST', data },
'/api/web/payArticle/push': {
url: '/api/web/pay-article/publish',
method: 'POST',
data: {
parentId: data.parentId,
code: data.code,
title: data.title,
predictedCode: data.predictedCode || data.predictCode || data.content,
issue: data.issue || data.expect,
},
},
'/api/web/payArticle/subscribe': { url: '/api/web/wallet/purchase/pay-article', method: 'POST', data },
'/api/web/order/getPageListByUser': { url: '/api/web/mine/purchased-pay-article/list', method: 'GET', data: pageParams },
'/api/web/rebate/getAffInfo': { url: '/api/web/referral/share-info', method: 'GET', data: null },
'/api/web/rebate/getPageList': { url: '/api/web/referral/rebate-list', method: 'GET', data: pageParams },
'/api/web/rebate/getWithdrawList': { url: '/api/web/wallet/withdraw/list', method: 'GET', data: pageParams },
'/api/web/rebate/withdraw': { url: '/api/web/wallet/withdraw', method: 'POST', data },
'/api/web/rebate/rebateToBalance': { url: '/api/web/wallet/transfer-to-account', method: 'POST', data },
'/api/web/distribution/getPageList': { url: '/api/web/referral/invite-list', method: 'GET', data: pageParams },
'/api/web/user/applyExpert': { url: '/api/web/expert/apply', method: 'POST', data },
'/api/web/user/expert/apply': { url: '/api/web/expert/apply', method: 'POST', data },
'/api/web/user/updateInfo': { url: '/api/web/account/profile', method: 'PUT', data },
'/api/web/notice/getPageList': { url: '/api/web/notice/list', method: 'GET', data: pageParams },
'/api/web/fc3d/freePredict/list': { url: '/api/web/fc3d/free-predict/list', method: 'GET', data: pageParams },
'/api/web/fc3d/scheme/list': { url: '/api/web/fc3d/scheme/list', method: 'GET', data: pageParams },
'/api/web/fc3d/scheme/buy': { url: '/api/web/wallet/purchase/fc3d-scheme', method: 'POST', data },
};
if (map[url]) return map[url];
const pathMap = [
[/^\/api\/web\/freeArticle\/like\/(.+)$/, (id) => ({ url: `/api/web/free-article/${id}/like`, method: 'POST', data: null })],
[/^\/api\/web\/freeArticle\/collect\/(.+)$/, (id) => ({ url: `/api/web/free-article/${id}/collect`, method: 'POST', data: null })],
[/^\/api\/web\/freeArticleComment\/getPageList$/, () => ({ url: `/api/web/free-article/${data.articleId}/comments`, method: 'GET', data: pageParams })],
[/^\/api\/web\/freeArticleComment\/push$/, () => ({ url: `/api/web/free-article/${data.articleId}/comment`, method: 'POST', data })],
[/^\/api\/web\/payArticle\/collect\/(.+)$/, (id) => ({ url: `/api/web/pay-article/article-collect/${id}`, method: 'POST', data: null })],
[/^\/api\/web\/expert\/collect\/(.+)$/, (id) => ({
url: `/api/web/pay-article/expert-collect?${new URLSearchParams({
expertId: id,
accountType: data.accountType || 1,
}).toString()}`,
method: 'POST',
data: null,
})],
];
for (const [pattern, build] of pathMap) {
const match = url.match(pattern);
if (match) return build(match[1]);
}
return null;
},
normalizeAuthPayload(data) {
const payload = { ...(data || {}) };
if (!payload.grantType) payload.grantType = 'password';
if (!payload.deviceType) payload.deviceType = 'WEB';
if (payload.captcha && !payload.code) payload.code = payload.captcha;
if (payload.captchaId && payload.captchaId !== 'captcha-disabled' && !payload.uuid) {
payload.uuid = payload.captchaId;
}
if (payload.captchaId === 'captcha-disabled') {
payload.verifyType = payload.verifyType || 'none';
delete payload.uuid;
delete payload.captchaId;
delete payload.captcha;
delete payload.code;
} else if (!payload.verifyType) {
payload.verifyType = payload.code ? 'captcha' : 'none';
}
return payload;
},
async loginCompat(data) {
const payload = this.normalizeAuthPayload(data);
const verifyConfig = await this.get('/api/web/auth/verify-config').catch(() => ({ data: null }));
const verifyType = verifyConfig.data && verifyConfig.data.verifyType;
if (verifyType === 'slide' || verifyConfig.data?.slideCaptchaEnabled) {
const slide = await this.get('/api/web/auth/slide-captcha').catch(() => ({ data: null }));
if (slide.data && slide.data.uuid) {
payload.verifyType = 'slide';
payload.slideUuid = slide.data.uuid;
payload.slideX = slide.data.targetX || slide.data.x || 0;
delete payload.code;
delete payload.uuid;
delete payload.captcha;
delete payload.captchaId;
}
}
return this.post('/api/web/auth/login', payload);
},
queryValue(url, key) {
const query = url.split('?')[1];
if (!query) return '';
return new URLSearchParams(query).get(key) || '';
},
fetchHomeNav() {
if (!this._homeNavPromise) {
this._homeNavPromise = this.get('/api/web/menu/home-nav').catch((error) => {
this._homeNavPromise = null;
throw error;
});
}
return this._homeNavPromise;
},
normalizeMenuList(navData, options = {}) {
return (navData || [])
.filter((item) => item.type === 'lottery')
.map((item) => ({
...item,
suoxie: item.suoxie || item.code,
url: item.url || item.path,
children: options.keepChildren && Array.isArray(item.children) ? item.children : [],
}));
},
shouldLoadMenuChildren() {
if (typeof window === 'undefined') return true;
return /\/(?:cai|caiinfo|fufei)\.html(?:$|\?)/.test(
window.location.pathname + window.location.search
);
},
async loadMenuCompat() {
if (!this._menuCompatPromise) {
this._menuCompatPromise = (async () => {
const nav = await this.fetchHomeNav();
const list = this.normalizeMenuList(nav.data, { keepChildren: true });
if (!this.shouldLoadMenuChildren()) {
return { code: 0, success: true, data: list };
}
const missingChildren = list.filter((item) => item.id && !item.children.length);
await Promise.all(
missingChildren.map((item) =>
this.get(`/api/web/menu/${item.id}/children`)
.then((res) => {
item.children = res.data || [];
})
.catch(() => {
item.children = [];
})
)
);
return { code: 0, success: true, data: list };
})().catch((error) => {
this._menuCompatPromise = null;
throw error;
});
}
return this._menuCompatPromise;
},
async loadHomeMenusCompat() {
const nav = await this.fetchHomeNav();
const list = this.normalizeMenuList(nav.data);
return { code: 0, success: true, data: list };
},
async requestWithMenu(url, data) {
const params = this.normalizePageParams(data || {});
if (url === '/api/web/pay-article/expert-rank' && !params.issueCount && params.pageSize) {
params.issueCount = params.pageSize;
if (params.pageNum) params.pageSize = params.rowPageSize || 10;
}
params.menuId =
params.menuId ||
params.parentId ||
params.typeId ||
(await this.resolveMenuId(params.code || params.suoxie));
delete params.parentId;
delete params.typeId;
delete params.code;
delete params.suoxie;
return this.get(url, params);
},
async requestLegacyLotteryResultCompat(data = {}) {
const params = this.normalizePageParams(data || {});
const code = params.code || params.suoxie || params.lotteryCode || '';
const pageSize = params.pageSize || params.limit || params.count || 100;
const normalizeResponse = (res) => {
if (!this.isSuccess(res)) return res;
const payload = this.pickData(res, {});
const list = this.asArray(payload);
return Object.assign({}, res, {
code: 0,
success: true,
data: list,
rawData: payload,
rows: list,
records: list,
});
};
if (code && this.API.lotteryTrend) {
try {
const trendRes = await this.get(this.API.lotteryTrend, {
code,
pageSize,
});
if (this.isSuccess(trendRes) && this.asArray(this.pickData(trendRes, [])).length) {
return normalizeResponse(trendRes);
}
} catch (e) {
// Fall back to the older result-list endpoint below.
}
}
const listRes = await this.requestWithMenu('/api/web/lottery/result/list', data);
return normalizeResponse(listRes);
},
async loadLegacyExpertDetailCompat(data = {}) {
const cacheKey = JSON.stringify(this.normalizePageParams(data));
if (!this._legacyExpertDetailPromises) this._legacyExpertDetailPromises = {};
if (this._legacyExpertDetailPromises[cacheKey]) {
return this._legacyExpertDetailPromises[cacheKey];
}
this._legacyExpertDetailPromises[cacheKey] = this.fetchLegacyExpertDetailCompat(data).catch(
(error) => {
delete this._legacyExpertDetailPromises[cacheKey];
throw error;
}
);
return this._legacyExpertDetailPromises[cacheKey];
},
async fetchLegacyExpertDetailCompat(data = {}) {
const params = this.normalizePageParams(data);
const expertId = params.expertId || params.createBy || '';
const accountType = params.accountType || 1;
const menuId =
params.menuId ||
params.parentId ||
params.typeId ||
(await this.resolveMenuId(params.code || params.suoxie));
const listParams = {
...params,
expertId,
accountType,
menuId,
pageNum: params.pageNum || 1,
pageSize: params.pageSize || 50,
};
delete listParams.createBy;
delete listParams.parentId;
delete listParams.typeId;
delete listParams.code;
delete listParams.suoxie;
const [profileRes, listRes] = await Promise.all([
expertId
? this.get('/api/web/pay-article/expert-profile', { expertId, accountType }).catch((error) => ({
code: 500,
msg: error.message || '获取专家资料失败',
data: {},
}))
: Promise.resolve({ code: 0, data: {} }),
this.get('/api/web/pay-article/expert-list', listParams),
]);
if (listRes.code !== 0) return listRes;
const profile = this.pickData(profileRes, {}) || {};
const listData = this.pickData(listRes, {}) || {};
const list = this.asArray(listData).map((item) => ({
...item,
id: item.id || item.articleId || item.payArticleId,
issue: item.issue || item.latestIssue || item.expect || item.period,
title: item.title || item.latestTitle,
predictedCode:
item.predictedCode ||
item.recommendCode ||
item.latestRecommend ||
item.recommend ||
item.content,
openCode: item.openCode || item.lotteryResult || item.resultCode || item.openResult,
accountType: item.accountType || accountType,
}));
return {
...listRes,
data: {
...listData,
...profile,
expertId,
createBy: expertId,
menuId,
parentId: menuId,
accountType,
avatar: profile.avatar || profile.headImg,
nickname: profile.nickname || profile.nickName || profile.expertName,
isCollect: profile.isCollect ?? profile.collected ?? profile.expertCollected,
payArticleList: list,
records: list,
rows: list,
totalRow: listData.totalRow || listData.total || listRes.total || list.length,
total: listData.total || listData.totalRow || listRes.total || list.length,
},
};
},
async resolveMenuId(code) {
if (!code) return '';
if (!this._menuIdCache) {
const nav = await this.fetchHomeNav().catch(() => ({ data: [] }));
this._menuIdCache = {};
(nav.data || []).forEach((item) => {
if (item.code && item.id) this._menuIdCache[item.code] = item.id;
});
}
return this._menuIdCache[code] || '';
},
async loadLinksCompat() {
const [friend, other] = await Promise.all([
this.get('/api/web/link/friend').catch(() => ({ data: [] })),
this.get('/api/web/link/other-recommend').catch(() => ({ data: [] })),
]);
const friendList = (friend.data || []).map((item) => ({ ...item, type: 1 }));
const otherList = (other.data || []).map((item) => ({ ...item, type: 2 }));
return { code: 0, success: true, data: friendList.concat(otherList) };
},
async loadHomeIndexCompat() {
const [configList, config, copyright, siteConfig, menus, banners, notices, ads, freeArticles, links, latest] = await Promise.all([
this.get('/api/web/config/list').catch(() => ({ data: [] })),
this.get('/api/web/config').catch(() => ({ data: {} })),
this.get(this.API.configCopyright).catch(() => ({ data: [] })),
this.get('/api/web/config/site').catch(() => ({ data: {} })),
this.loadHomeMenusCompat().catch(() => ({ data: [] })),
this.get('/api/web/banner/list').catch(() => ({ data: [] })),
this.get('/api/web/notice/list', { count: 6 }).catch(() => ({ data: [] })),
this.get('/api/web/ad/list', { inTime: true }).catch(() => ({ data: [] })),
this.get('/api/web/free-article/recommend', { articleCount: 5 }).catch(() => ({ data: [] })),
this.loadLinksCompat().catch(() => ({ data: [] })),
this.get('/api/web/lottery/result/latest').catch(() => ({ data: [] })),
]);
const latestMap = {};
const lotteryList = this.asArray(this.pickData(latest, []));
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 menuList = this.asArray(this.pickData(menus, [])).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: {
webConfigs: this.asArray(this.pickData(configList, [])).length
? this.asArray(this.pickData(configList, []))
: this.configObjectToList(config.data || {}),
copyrightConfigs: this.asArray(this.pickData(copyright, [])),
siteConfig: siteConfig.data || {},
menuList,
navList: menuList,
lotteryList,
freeArticleData: articleGroups,
bannerList: this.asArray(this.pickData(banners, [])),
noticeList: this.asArray(this.pickData(notices, [])),
adList: this.asArray(this.pickData(ads, [])),
linkList: this.asArray(this.pickData(links, [])),
},
};
},
configObjectToList(config) {
return Object.keys(config || {}).map((key, index) => ({
type: key,
content: config[key],
sort: index,
}));
},
get(url, params, options = {}) {
if (typeof options === 'function') {
return this.request({ url, method: 'GET', data: params || null }).then(options);
}
return this.request({ url, method: 'GET', data: params || null, ...options });
},
post(url, data, options = {}) {
if (typeof options === 'function') {
return this.request({ url, method: 'POST', data: data || null }).then(options);
}
return this.request({ url, method: 'POST', data: data || null, ...options });
},
put(url, data, options = {}) {
if (typeof options === 'function') {
return this.request({ url, method: 'PUT', data: data || null }).then(options);
}
return this.request({ url, method: 'PUT', data: data || null, ...options });
},
delete(url, data, options = {}) {
if (typeof options === 'function') {
return this.request({ url, method: 'DELETE', data: data || null }).then(options);
}
return this.request({ url, method: 'DELETE', data: data || null, ...options });
},
};