feat: 小程序连接服务器IP + 同步未提交开发
- miniprogram/config.js: BASE_URL 指向 http://192.227.237.8:3001 - 动作标题中文化(nameZh),搜索匹配 name/nameZh/nameEn - 新增计划/收藏/我的 等页面与组件 - 后端 Docker 化(Dockerfile/.dockerignore)与翻译脚本 - .gitignore 补充 .DS_Store
This commit is contained in:
@@ -1,20 +1,115 @@
|
||||
// 极简登录占位:仅演示 wx.login 拿到 code,不请求真实后端服务端。
|
||||
// 生产环境中,应将 code 发送到后端换取 openid / session。
|
||||
function getOpenid() {
|
||||
return new Promise((resolve) => {
|
||||
// 登录与用户态管理:封装 wx.login + 后端 /api/auth/login。
|
||||
// 后端在首次登录时自动创建用户;开发模式(未配置 WX_APPID)接受任意 code
|
||||
// 并返回稳定的虚拟 openid。
|
||||
const { request, setUnauthorizedHandler } = require('./request');
|
||||
|
||||
const TOKEN_KEY = 'access_token';
|
||||
const OPENID_KEY = 'openid';
|
||||
const PROFILE_KEY = 'user_profile';
|
||||
|
||||
// 模块加载时注册 401 自动重新登录:成功后返回新的 token,失败返回 null。
|
||||
setUnauthorizedHandler(async () => {
|
||||
try {
|
||||
await login();
|
||||
return wx.getStorageSync(TOKEN_KEY);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
function login() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success(res) {
|
||||
if (res.code) {
|
||||
// 真实实现:wx.request({ url: BASE_URL + '/api/auth/login', data: { code } })
|
||||
console.log('[auth] wx.login code =', res.code);
|
||||
}
|
||||
resolve(res.code || '');
|
||||
const code = res.code || 'tourist';
|
||||
request({ url: '/api/auth/login', method: 'POST', data: { code } })
|
||||
.then((r) => {
|
||||
const token = r.token;
|
||||
const user = r.user || {};
|
||||
wx.setStorageSync(TOKEN_KEY, token);
|
||||
if (user.openid) wx.setStorageSync(OPENID_KEY, user.openid);
|
||||
const profile = { nickname: user.nickname || '', avatar: user.avatar || '' };
|
||||
wx.setStorageSync(PROFILE_KEY, profile);
|
||||
resolve(profile);
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' });
|
||||
reject(err);
|
||||
});
|
||||
},
|
||||
fail() {
|
||||
resolve('');
|
||||
// 拿不到 code 时降级为游客 code
|
||||
request({ url: '/api/auth/login', method: 'POST', data: { code: 'tourist' } })
|
||||
.then((r) => {
|
||||
const token = r.token;
|
||||
const user = r.user || {};
|
||||
wx.setStorageSync(TOKEN_KEY, token);
|
||||
if (user.openid) wx.setStorageSync(OPENID_KEY, user.openid);
|
||||
const profile = { nickname: user.nickname || '', avatar: user.avatar || '' };
|
||||
wx.setStorageSync(PROFILE_KEY, profile);
|
||||
resolve(profile);
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' });
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { getOpenid };
|
||||
// 确保已登录:有 token 直接返回,否则执行登录。
|
||||
async function ensureLogin() {
|
||||
const token = getToken();
|
||||
if (token) return token;
|
||||
await login();
|
||||
return getToken();
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return wx.getStorageSync(TOKEN_KEY) || '';
|
||||
}
|
||||
|
||||
function getProfile() {
|
||||
return wx.getStorageSync(PROFILE_KEY) || { nickname: '', avatar: '' };
|
||||
}
|
||||
|
||||
function isLogin() {
|
||||
return !!getToken();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
wx.removeStorageSync(TOKEN_KEY);
|
||||
wx.removeStorageSync(OPENID_KEY);
|
||||
wx.removeStorageSync(PROFILE_KEY);
|
||||
}
|
||||
|
||||
function setProfile(p) {
|
||||
const profile = Object.assign({}, getProfile(), p || {});
|
||||
wx.setStorageSync(PROFILE_KEY, profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
// 更新昵称 / 头像,写回后端并刷新本地状态。
|
||||
function updateProfile(dto) {
|
||||
return request({ url: '/api/users/me', method: 'PATCH', data: dto })
|
||||
.then((user) => {
|
||||
const profile = {
|
||||
nickname: (user && user.nickname) || getProfile().nickname,
|
||||
avatar: (user && user.avatar) || getProfile().avatar
|
||||
};
|
||||
wx.setStorageSync(PROFILE_KEY, profile);
|
||||
return profile;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
ensureLogin,
|
||||
getToken,
|
||||
getProfile,
|
||||
isLogin,
|
||||
logout,
|
||||
setProfile,
|
||||
updateProfile
|
||||
};
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
// 统一的网络请求封装:所有请求都经过此包装器。
|
||||
// 后端为公开 API,无需鉴权头;失败时统一 toast 提示。
|
||||
// 受保护接口自动携带 Authorization: Bearer <token>;遇到 401 时由注册的
|
||||
// 未授权处理器(通常是自动重新登录)处理一次,然后重试原请求。
|
||||
const { BASE_URL } = require('../config');
|
||||
|
||||
function request({ url, method = 'GET', data = {} }) {
|
||||
let unauthorizedHandler = null;
|
||||
|
||||
// 注册「未授权」处理回调(由 auth 模块注入,用于自动重新登录)
|
||||
function setUnauthorizedHandler(fn) {
|
||||
unauthorizedHandler = fn;
|
||||
}
|
||||
|
||||
function request({ url, method = 'GET', data = {}, _retry = false }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('access_token');
|
||||
wx.request({
|
||||
url: BASE_URL + url,
|
||||
method,
|
||||
data,
|
||||
header: { 'content-type': 'application/json' },
|
||||
header: {
|
||||
'content-type': 'application/json',
|
||||
...(token ? { Authorization: 'Bearer ' + token } : {})
|
||||
},
|
||||
success(res) {
|
||||
// 401:尝试一次自动重新登录后重试
|
||||
if (res.statusCode === 401 && !_retry && unauthorizedHandler) {
|
||||
return unauthorizedHandler()
|
||||
.then((t) => (t ? resolve(request({ url, method, data, _retry: true })) : reject(res)))
|
||||
.catch(() => reject(res));
|
||||
}
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '请求失败 (' + res.statusCode + ')',
|
||||
icon: 'none'
|
||||
});
|
||||
wx.showToast({ title: '请求失败 (' + res.statusCode + ')', icon: 'none' });
|
||||
reject(res);
|
||||
}
|
||||
},
|
||||
@@ -28,4 +43,4 @@ function request({ url, method = 'GET', data = {} }) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { request };
|
||||
module.exports = { request, setUnauthorizedHandler };
|
||||
|
||||
Reference in New Issue
Block a user