You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
8.1 KiB
205 lines
8.1 KiB
/*
|
|
* 统一数据访问层
|
|
*
|
|
* 默认走本地数据层,方便网页端离线演示。
|
|
* 页面带 ?demo=0 时切换到真实后端,供之后继续联调。
|
|
*/
|
|
(() => {
|
|
'use strict';
|
|
|
|
const API_BASE = `${location.origin}/api`;
|
|
const SOURCE_KEY = 'youthome:web:dataSource';
|
|
const SOURCE_VERSION_KEY = 'youthome:web:sourceDefault:v2';
|
|
const TOKEN_KEY = 'youthome:web:accessToken';
|
|
const USER_KEY = 'youthome:web:authUser';
|
|
|
|
const demoMode = new URLSearchParams(location.search).get('demo');
|
|
if (demoMode === '0') {
|
|
localStorage.setItem(SOURCE_KEY, 'api');
|
|
localStorage.setItem(SOURCE_VERSION_KEY, 'true');
|
|
if ((sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY) || '').startsWith('local-')) {
|
|
[localStorage, sessionStorage].forEach((storage) => {
|
|
storage.removeItem(TOKEN_KEY);
|
|
storage.removeItem(USER_KEY);
|
|
});
|
|
}
|
|
} else {
|
|
if (demoMode === '1' || localStorage.getItem(SOURCE_VERSION_KEY) !== 'true') {
|
|
localStorage.setItem(SOURCE_KEY, 'local');
|
|
localStorage.setItem(SOURCE_VERSION_KEY, 'true');
|
|
}
|
|
if (localStorage.getItem(SOURCE_KEY) === 'local') {
|
|
const existingToken = sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY) || '';
|
|
if (existingToken && !existingToken.startsWith('local-')) {
|
|
[localStorage, sessionStorage].forEach((storage) => {
|
|
storage.removeItem(TOKEN_KEY);
|
|
storage.removeItem(USER_KEY);
|
|
});
|
|
}
|
|
window.YouthomeLocal?.useDemoAccount();
|
|
}
|
|
}
|
|
|
|
const source = () => (localStorage.getItem(SOURCE_KEY) === 'local' ? 'local' : 'api');
|
|
const isLocal = () => source() === 'local';
|
|
const token = () => sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
|
const currentUser = () => {
|
|
try { return JSON.parse(sessionStorage.getItem(USER_KEY) || localStorage.getItem(USER_KEY) || 'null'); }
|
|
catch (error) { return null; }
|
|
};
|
|
const applicationKey = () => `youthome:web:activityApplications:${currentUser()?.id || 'guest'}`;
|
|
const applicationRecords = () => {
|
|
try { return JSON.parse(localStorage.getItem(applicationKey()) || '[]'); }
|
|
catch (error) { return []; }
|
|
};
|
|
const clearSession = () => {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
localStorage.removeItem(USER_KEY);
|
|
sessionStorage.removeItem(TOKEN_KEY);
|
|
sessionStorage.removeItem(USER_KEY);
|
|
};
|
|
|
|
const request = async (path, options = {}) => {
|
|
if (isLocal()) {
|
|
if (!window.YouthomeLocal) throw new Error('本地数据层未加载,请刷新页面重试');
|
|
return window.YouthomeLocal.request(path, options);
|
|
}
|
|
const headers = { ...(options.headers || {}) };
|
|
if (token()) headers.access_token = token();
|
|
const isForm = options.body instanceof FormData;
|
|
if (options.body && !isForm) headers['Content-Type'] = 'application/json';
|
|
let response;
|
|
try {
|
|
response = await fetch(`${API_BASE}${path}`, {
|
|
...options,
|
|
headers,
|
|
signal: AbortSignal.timeout(isForm ? 600000 : 20000)
|
|
});
|
|
} catch (error) {
|
|
throw new Error(error.name === 'TimeoutError' ? '请求超时,请稍后重试' : '无法连接后端,请稍后重试');
|
|
}
|
|
let result;
|
|
try { result = await response.json(); }
|
|
catch (error) { throw new Error(`请求失败(HTTP ${response.status})`); }
|
|
if (response.status === 401) {
|
|
clearSession();
|
|
throw new Error('登录已失效,请重新登录');
|
|
}
|
|
if (!response.ok || (typeof result.code === 'number' && result.code !== 200)) {
|
|
throw new Error(result.message || `请求失败(HTTP ${response.status})`);
|
|
}
|
|
return Object.prototype.hasOwnProperty.call(result, 'data') ? result.data : result;
|
|
};
|
|
|
|
// 后端头像字段是资源 key(如 man1),不是可直接访问的 URL
|
|
const avatarUrl = (value) => {
|
|
if (!value || typeof value !== 'string') return '';
|
|
if (/^(https?:|data:image\/|\.\.\/|<)/.test(value)) return value;
|
|
return '';
|
|
};
|
|
|
|
const me = () => currentUser();
|
|
|
|
window.YouthomeApi = {
|
|
source,
|
|
isLocal,
|
|
setSource(value) {
|
|
localStorage.setItem(SOURCE_KEY, value === 'local' ? 'local' : 'api');
|
|
localStorage.setItem(SOURCE_VERSION_KEY, 'true');
|
|
},
|
|
token,
|
|
currentUser,
|
|
applicationRecords,
|
|
saveApplication(record) {
|
|
const records = applicationRecords().filter((item) => item.activityId !== record.activityId);
|
|
records.push(record);
|
|
localStorage.setItem(applicationKey(), JSON.stringify(records));
|
|
},
|
|
removeApplication(activityId) {
|
|
localStorage.setItem(applicationKey(), JSON.stringify(applicationRecords().filter((item) => item.activityId !== activityId)));
|
|
},
|
|
clearSession,
|
|
request,
|
|
get: (path) => request(path),
|
|
post: (path, body) => request(path, { method: 'POST', ...(body === undefined ? {} : { body: JSON.stringify(body) }) }),
|
|
delete: (path) => request(path, { method: 'DELETE' }),
|
|
avatarUrl,
|
|
updateSessionUser(patch) {
|
|
const storage = sessionStorage.getItem(USER_KEY) ? sessionStorage : localStorage;
|
|
const merged = { ...(currentUser() || {}), ...patch };
|
|
storage.setItem(USER_KEY, JSON.stringify(merged));
|
|
return merged;
|
|
},
|
|
|
|
async profile() {
|
|
if (isLocal()) return window.YouthomeLocal?.account?.() || null;
|
|
const user = me();
|
|
if (!user) return null;
|
|
return request(`/users/profile?userId=${encodeURIComponent(user.id)}`);
|
|
},
|
|
async points() {
|
|
if (isLocal()) return Number(window.YouthomeLocal?.account?.()?.points || 0);
|
|
const data = await request('/profile/points');
|
|
return Number(data?.points ?? 0);
|
|
},
|
|
async checkIn() {
|
|
if (isLocal()) return null;
|
|
return request('/campus/points/check-in', { method: 'POST' });
|
|
},
|
|
async followStats() {
|
|
if (isLocal()) {
|
|
const account = window.YouthomeLocal?.account?.();
|
|
return { followingCount: window.YouthomeLocal?.following?.().length || 0, followersCount: account?.fans || 0 };
|
|
}
|
|
const user = me();
|
|
if (!user) return { followingCount: 0, followersCount: 0 };
|
|
return request(`/users/follow-status?userId=${encodeURIComponent(user.id)}`);
|
|
},
|
|
async favorites() {
|
|
if (isLocal()) return window.YouthomeLocal.bucketMoments('favorites');
|
|
const data = await request('/campus/moments/favorites?page=1&size=50');
|
|
return data?.items || [];
|
|
},
|
|
async following() {
|
|
if (isLocal()) return window.YouthomeLocal.following().map((nickname) => ({ nickname }));
|
|
const user = me();
|
|
if (!user) return [];
|
|
const data = await request(`/users/following?userId=${encodeURIComponent(user.id)}&page=1&size=50`);
|
|
return data?.items || [];
|
|
},
|
|
async toggleFollow(userId) {
|
|
if (isLocal() || !userId) return null;
|
|
return request(`/users/follow?userId=${encodeURIComponent(userId)}`, { method: 'POST' });
|
|
},
|
|
async joinedActivities() {
|
|
if (isLocal()) return window.YouthomeLocal.joinedActivities();
|
|
const data = await request('/campus/activities/joined?page=1&size=50');
|
|
return data?.items || [];
|
|
},
|
|
async registrations() {
|
|
if (isLocal()) return [];
|
|
const data = await request('/campus/activities/registrations/mine');
|
|
return Array.isArray(data) ? data : (data?.items || []);
|
|
},
|
|
async cancelRegistration(id) {
|
|
return request(`/campus/activities/registrations/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
},
|
|
async joinActivity(id, remark = '') {
|
|
if (isLocal()) return null;
|
|
const query = remark ? `?remark=${encodeURIComponent(remark)}` : '';
|
|
return request(`/campus/activities/${encodeURIComponent(id)}/applications${query}`, { method: 'POST' });
|
|
},
|
|
async quitActivity(id) {
|
|
if (isLocal()) return window.YouthomeLocal.leaveActivity(id);
|
|
return request(`/campus/activities/quit?id=${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
},
|
|
async mallProducts() {
|
|
if (isLocal()) return [];
|
|
const data = await request('/campus/mall/products');
|
|
return data?.items || [];
|
|
},
|
|
async purchase(productId) {
|
|
return request('/campus/mall/purchase', { method: 'POST', body: JSON.stringify({ productId }) });
|
|
}
|
|
};
|
|
})();
|
|
|