项目初始化
This commit is contained in:
王宏豪
2026-09-03 16:49:59 +08:00
commit d7fc8c8982
40 changed files with 2688 additions and 0 deletions

76
utils/api.js Normal file
View File

@@ -0,0 +1,76 @@
//const BASE_URL = 'https://muguaclub.cn';
//const BASE_URL = "http://127.0.0.1:8000";
const BASE_URL = 'https://m.muguaclub.cn/clock';
const STATIC_BASE_URL = 'https://m.muguaclub.cn';
let redirectingProfile = false;
function goProfile() {
if (redirectingProfile) {
return;
}
const pages = getCurrentPages();
const current = pages.length ? pages[pages.length - 1] : null;
const route = current && current.route;
if (route === 'pages/profile/profile') {
return;
}
redirectingProfile = true;
wx.switchTab({
url: '/pages/profile/profile',
complete() {
setTimeout(() => {
redirectingProfile = false;
}, 1200);
}
});
}
function request(options) {
const { url, method = 'GET', data = {}, auth = true } = options;
const isIdempotent = method === 'GET' || method === 'DELETE';
let retried = false;
return new Promise((resolve, reject) => {
const doRequest = () => {
const header = { 'Content-Type': 'application/json' };
const token = wx.getStorageSync('token');
if (auth && token) {
header['Authorization'] = 'Bearer ' + token;
}
wx.request({
url: BASE_URL + url,
method,
data,
header,
success(res) {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data);
} else if (res.statusCode === 401) {
wx.removeStorageSync('token');
if (auth) {
goProfile();
}
reject(res.data || { error: '未登录' });
} else {
reject(res.data || { error: '请求失败' });
}
},
fail(err) {
// 冷启动时首个请求可能因网络栈未就绪而失败,空闲时对幂等请求自动重试一次
const msg = (err && err.errMsg) || '';
const domainError = /url not in domain list|domain not/i.test(msg);
if (!retried && isIdempotent && !domainError) {
retried = true;
setTimeout(doRequest, 800);
return;
}
reject(err);
}
});
};
doRequest();
});
}
module.exports = { request, BASE_URL, STATIC_BASE_URL };

109
utils/auth.js Normal file
View File

@@ -0,0 +1,109 @@
const api = require('./api');
let loginPromise = null;
function getToken() {
return wx.getStorageSync('token') || '';
}
function setToken(token) {
wx.setStorageSync('token', token);
}
function clearToken() {
wx.removeStorageSync('token');
}
function wxLogin(retries) {
const remain = typeof retries === 'number' ? retries : 3;
return new Promise((resolve, reject) => {
wx.login({
success(res) {
if (res && res.code) {
resolve(res.code);
} else {
reject(new Error((res && res.errMsg) || '微信登录失败'));
}
},
fail: reject
});
}).catch((err) => {
// 冷启动时微信登录服务可能尚未就绪,指数退避重试
if (remain > 0) {
const delay = 400 + (4 - remain) * 200; // 首次 600ms,依次 800ms、1000ms
return new Promise((resolve) => setTimeout(resolve, delay))
.then(() => wxLogin(remain - 1));
}
throw err;
});
}
// 整个微信登录流程失败时重新取 code 再试一次,覆盖冷启动首个网络请求偶发失败
function wechatLogin(retries) {
const remain = typeof retries === 'number' ? retries : 1;
return wxLogin()
.then((code) => api.request({
url: '/api/v1/auth/wechat-login',
method: 'POST',
data: { code },
auth: false
}))
.catch((err) => {
if (remain > 0) {
return new Promise((resolve) => setTimeout(resolve, 500)).then(() => wechatLogin(remain - 1));
}
throw err;
});
}
// 单例登录:App.onLaunch 与首屏页面 onShow 可能几乎同时调用,
// 共享同一个进行中的登录 Promise,避免重复 wx.login / wechat-login 造成竞态。
// 关键:登录失败时必须清空单例,让下一次调用能重新发起,
// 否则冷启动 wx.login 偶发失败后,后续页面永远拿不到 token。
function ensureLogin(refresh) {
const existing = getToken();
if (existing && !refresh) {
return Promise.resolve(existing);
}
if (loginPromise) {
return loginPromise;
}
const p = wechatLogin()
.then((res) => {
if (res && res.success) {
setToken(res.token);
return res.token;
}
// need_phone:未绑定手机号时 resolve(null),由页面引导去「我的」绑定
return null;
})
.finally(() => {
// 无论成功失败,完成后都清掉单例,让下一次调用可以重新发起
if (loginPromise === p) {
loginPromise = null;
}
});
loginPromise = p;
return p;
}
function loginWithPhone(phoneCode) {
return wxLogin()
.then((code) => api.request({
url: '/api/v1/auth/bind-phone',
method: 'POST',
data: { code, phone_code: phoneCode },
auth: false
}))
.then((res) => {
if (res && res.success) {
setToken(res.token);
return res;
}
const error = new Error((res && res.error) || '手机号登录失败');
error.error = (res && res.error) || '手机号登录失败';
throw error;
});
}
module.exports = { getToken, setToken, clearToken, wxLogin, ensureLogin, loginWithPhone };