feat(miniprogram): 初始化 Taro 4 + React 小程序项目
- 手动创建 Taro 4.2 + React 18 + TypeScript 项目骨架 - 配置 webpack5 编译、SCSS 样式、医疗清新主题 - 实现 API 请求层(JWT 自动注入 + token 刷新) - 实现 auth store(微信登录 + 手机号绑定 + 就诊人管理) - 实现登录页(微信一键登录 + 手机号授权绑定) - 实现首页(问候栏 + 今日健康卡片 + 快捷服务 + 即将到来) - 实现我的页面(个人信息 + 功能菜单 + 退出登录) - 健康/预约/资讯占位页 - TabBar 5 个入口:首页/健康/预约/资讯/我的
This commit is contained in:
44
apps/miniprogram/src/services/auth.ts
Normal file
44
apps/miniprogram/src/services/auth.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { api } from './request';
|
||||
|
||||
export interface UserInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
avatar?: string;
|
||||
tenant_id: string;
|
||||
}
|
||||
|
||||
export interface LoginResp {
|
||||
bound: boolean;
|
||||
openid: string;
|
||||
token?: {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires_in: number;
|
||||
user: { id: string; username: string; display_name?: string; phone?: string; avatar_url?: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface PatientInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
gender?: string;
|
||||
birthday?: string;
|
||||
relation: string;
|
||||
}
|
||||
|
||||
export async function wechatLogin(code: string): Promise<LoginResp> {
|
||||
return api.post('/auth/wechat/login', { code });
|
||||
}
|
||||
|
||||
export async function wechatBindPhone(openid: string, encryptedData: string, iv: string) {
|
||||
return api.post('/auth/wechat/bind-phone', {
|
||||
openid,
|
||||
encrypted_data: encryptedData,
|
||||
iv,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPatients() {
|
||||
return api.get<PatientInfo[]>('/health/patients');
|
||||
}
|
||||
65
apps/miniprogram/src/services/request.ts
Normal file
65
apps/miniprogram/src/services/request.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
|
||||
|
||||
interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
async function getHeaders(): Promise<Record<string, string>> {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
const token = Taro.getStorageSync('access_token');
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const patientId = Taro.getStorageSync('current_patient_id');
|
||||
if (patientId) headers['X-Patient-Id'] = patientId;
|
||||
const tenantId = Taro.getStorageSync('tenant_id');
|
||||
if (tenantId) headers['X-Tenant-Id'] = tenantId;
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function tryRefreshToken(): Promise<boolean> {
|
||||
const refreshToken = Taro.getStorageSync('refresh_token');
|
||||
if (!refreshToken) return false;
|
||||
try {
|
||||
const res = await Taro.request({
|
||||
url: `${BASE_URL}/auth/refresh`,
|
||||
method: 'POST',
|
||||
data: { refresh_token: refreshToken },
|
||||
});
|
||||
if (res.statusCode === 200 && res.data?.success) {
|
||||
Taro.setStorageSync('access_token', res.data.data.access_token);
|
||||
Taro.setStorageSync('refresh_token', res.data.data.refresh_token);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
Taro.removeStorageSync('access_token');
|
||||
Taro.removeStorageSync('refresh_token');
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function request<T>(method: string, path: string, data?: unknown): Promise<T> {
|
||||
const headers = await getHeaders();
|
||||
const res = await Taro.request({ url: `${BASE_URL}${path}`, method, data, header: headers });
|
||||
|
||||
if (res.statusCode === 401) {
|
||||
const refreshed = await tryRefreshToken();
|
||||
if (refreshed) return request<T>(method, path, data);
|
||||
Taro.redirectTo({ url: '/pages/login/index' });
|
||||
throw new Error('登录已过期');
|
||||
}
|
||||
|
||||
const body = res.data as ApiResponse<T>;
|
||||
if (!body.success) throw new Error(body.message || '请求失败');
|
||||
return body.data as T;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>('GET', path),
|
||||
post: <T>(path: string, data?: unknown) => request<T>('POST', path, data),
|
||||
put: <T>(path: string, data?: unknown) => request<T>('PUT', path, data),
|
||||
delete: <T>(path: string) => request<T>('DELETE', path),
|
||||
};
|
||||
Reference in New Issue
Block a user