2026-08-23 22:32:39 +08:00
|
|
|
|
import Taro from '@tarojs/taro';
|
|
|
|
|
|
import { request } from './api';
|
|
|
|
|
|
|
2026-08-25 20:22:46 +08:00
|
|
|
|
/* 用户端认证(统一账号):登录/资料走平台 /auth(单一 users + 平台 JWT);
|
|
|
|
|
|
手机绑定走培训 /api/auth(阶段3 后同样读写平台 users + 验平台 JWT)。
|
|
|
|
|
|
与 web 端一致,见 小程序同步约束.md */
|
|
|
|
|
|
|
|
|
|
|
|
/** pine_user 契约字段(各页面读取):username/name/avatar/phone/phoneBound/status/topics/source */
|
|
|
|
|
|
function pickUser(d) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
username: d.username,
|
|
|
|
|
|
name: d.name || d.nickname || d.username || '微信用户',
|
|
|
|
|
|
avatar: d.avatar || '',
|
|
|
|
|
|
phone: d.phone || '',
|
|
|
|
|
|
phoneBound: !!d.phoneBound,
|
2026-08-27 20:31:04 +08:00
|
|
|
|
wxBound: !!d.wxBound,
|
|
|
|
|
|
wxMiniBound: !!d.wxMiniBound,
|
|
|
|
|
|
account_type_label: d.account_type_label || d.role || '',
|
|
|
|
|
|
role: d.role || '',
|
2026-08-25 20:22:46 +08:00
|
|
|
|
status: d.status || '',
|
|
|
|
|
|
topics: Array.isArray(d.topics) ? d.topics : [],
|
|
|
|
|
|
source: d.source || ''
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveUser(d) {
|
|
|
|
|
|
Taro.setStorageSync('pine_user', pickUser(d));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-23 22:32:39 +08:00
|
|
|
|
export async function sendCode(phone) {
|
2026-08-25 20:22:46 +08:00
|
|
|
|
const data = await request('POST', '/auth/send-code', { phone });
|
|
|
|
|
|
return { ...data, debugCode: data.stub_code ?? data.debugCode };
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function phoneLogin(phone, code) {
|
2026-08-25 20:22:46 +08:00
|
|
|
|
const data = await request('POST', '/auth/phone-login', { phone, code });
|
2026-08-23 22:32:39 +08:00
|
|
|
|
if (data.token) {
|
|
|
|
|
|
Taro.setStorageSync('pine_token', data.token);
|
2026-08-25 20:22:46 +08:00
|
|
|
|
saveUser(data);
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isAuthed() {
|
|
|
|
|
|
return !!Taro.getStorageSync('pine_token');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getUser() {
|
|
|
|
|
|
return Taro.getStorageSync('pine_user') || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function logout() {
|
|
|
|
|
|
Taro.removeStorageSync('pine_token');
|
|
|
|
|
|
Taro.removeStorageSync('pine_user');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-25 20:22:46 +08:00
|
|
|
|
/* ---------------- 微信登录 / 我的资料(平台 /auth) ---------------- */
|
|
|
|
|
|
/** 微信一键登录:wx.login 拿 code → 平台 /auth/wx-login(需后端配 PINEAGENTS_WECHAT_LOGIN=true) */
|
2026-08-23 22:32:39 +08:00
|
|
|
|
export async function wxLogin({ code, nickName, avatarUrl }) {
|
2026-08-25 20:22:46 +08:00
|
|
|
|
const data = await request('POST', '/auth/wx-login', { code, nickName, avatarUrl });
|
2026-08-23 22:32:39 +08:00
|
|
|
|
if (data.token) {
|
|
|
|
|
|
Taro.setStorageSync('pine_token', data.token);
|
2026-08-25 20:22:46 +08:00
|
|
|
|
saveUser(data);
|
2026-08-26 12:00:09 +08:00
|
|
|
|
return data;
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-26 12:00:09 +08:00
|
|
|
|
/** 小程序扫码登录其它端口:扫跨端登录页二维码 → 用 wx.login code 确认 → 目标端口轮询取令牌 */
|
|
|
|
|
|
export async function mpQrConfirm(scene, code) {
|
|
|
|
|
|
const data = await request('POST', '/auth/mp-qr/confirm', { scene, code });
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-25 20:22:46 +08:00
|
|
|
|
/** 拉取当前账号资料(刷新昵称/头像/手机绑定状态)——平台 /auth/me 返回扁平 profile */
|
|
|
|
|
|
export async function getMe() {
|
|
|
|
|
|
const data = await request('GET', '/auth/me', {}, true);
|
|
|
|
|
|
if (data && data.username) {
|
2026-08-23 22:32:39 +08:00
|
|
|
|
const u = getUser() || {};
|
2026-08-25 20:22:46 +08:00
|
|
|
|
const picked = pickUser(data);
|
|
|
|
|
|
Taro.setStorageSync('pine_user', { ...u, ...picked });
|
|
|
|
|
|
return { ok: true, user: picked };
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-25 20:22:46 +08:00
|
|
|
|
/** 更新当前用户资料(昵称/头像 + 报名资料 status/topics/source)——平台 /auth/update-profile */
|
|
|
|
|
|
export async function updateProfile(body) {
|
|
|
|
|
|
const data = await request('POST', '/auth/update-profile', body, true);
|
|
|
|
|
|
if (data && data.username) {
|
|
|
|
|
|
const u = getUser() || {};
|
|
|
|
|
|
const picked = pickUser(data);
|
|
|
|
|
|
Taro.setStorageSync('pine_user', { ...u, ...picked });
|
|
|
|
|
|
return { ok: true, user: picked };
|
|
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-26 12:15:41 +08:00
|
|
|
|
/* ---------------- 手机绑定(平台 /auth,统一账号;绑定即按手机号合并去重) ---------------- */
|
|
|
|
|
|
/** 绑定手机:若该手机号已被其它账号占用,后端会按手机号把两个账号合并为同一账号,
|
|
|
|
|
|
* 并返回合并后账号的新令牌 —— 这里必须切换到新令牌,避免用已失效的旧令牌。 */
|
2026-08-25 20:22:46 +08:00
|
|
|
|
export async function bindPhone(phone, code) {
|
2026-08-26 12:15:41 +08:00
|
|
|
|
const data = await request('POST', '/auth/bind-phone', { phone, code }, true);
|
|
|
|
|
|
if (data.token) {
|
|
|
|
|
|
Taro.setStorageSync('pine_token', data.token);
|
|
|
|
|
|
Taro.setStorageSync('pine_user', JSON.stringify(pickUser(data)));
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
2026-08-26 12:15:41 +08:00
|
|
|
|
return { ok: true, phone, phoneBound: true, ...data };
|
2026-08-23 22:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 微信官方手机号一键绑定(getPhoneNumber 回调 code) */
|
|
|
|
|
|
export async function wxPhoneBind(code) {
|
|
|
|
|
|
const data = await request('POST', '/api/auth/wx-phone', { code }, true);
|
|
|
|
|
|
if (data.ok && data.phone) {
|
|
|
|
|
|
const u = getUser() || {};
|
|
|
|
|
|
Taro.setStorageSync('pine_user', { ...u, phone: data.phone, phoneBound: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|