feat: rebuild pet app and backend around core care flows
This commit is contained in:
+54
-159
@@ -1,167 +1,62 @@
|
||||
const { request, uploadFile, setToken, setRefreshToken } = require('./request.js');
|
||||
|
||||
// 登录成功后统一落盘 access + refresh 两个令牌
|
||||
function saveTokens(data) {
|
||||
setToken(data.token);
|
||||
setRefreshToken(data.refresh_token);
|
||||
return data;
|
||||
}
|
||||
|
||||
// wx.login 取 code
|
||||
function wxLogin() {
|
||||
const { baseURL } = require("./config");
|
||||
function request(path, method = "GET", data, auth = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success: (r) => (r.code ? resolve(r.code) : reject(new Error('wx.login 未返回 code'))),
|
||||
const token = getApp().globalData.token || wx.getStorageSync("pet_token");
|
||||
wx.request({
|
||||
url: baseURL + path,
|
||||
method,
|
||||
data,
|
||||
header: token && auth ? { Authorization: "Bearer " + token } : {},
|
||||
success(res) {
|
||||
if (res.data && res.data.code === 0) resolve(res.data.data);
|
||||
else {
|
||||
if (res.statusCode === 401) {
|
||||
wx.removeStorageSync("pet_token");
|
||||
getApp().globalData.token = "";
|
||||
}
|
||||
const error = new Error((res.data && res.data.message) || "请求失败");
|
||||
error.status = res.statusCode;
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 登录:优先微信 code2session,失败回退开发态 Mock 登录(后端 dev_login=true)
|
||||
async function login() {
|
||||
async function secure(path, method, data) {
|
||||
await getApp().login();
|
||||
try {
|
||||
const code = await wxLogin();
|
||||
// noAuthRetry:登录接口失败时也会返回 40100,不能再触发「续期→重新登录」,否则会自己套自己
|
||||
const data = await request({ url: '/api/auth/wechat', method: 'POST', data: { code }, noAuthRetry: true });
|
||||
return saveTokens(data);
|
||||
} catch (e) {
|
||||
console.warn('[api] 微信登录失败,回退 Mock 登录:', e && e.message);
|
||||
const data = await request({
|
||||
url: '/api/auth/login',
|
||||
method: 'POST',
|
||||
data: { nickname: '开发用户' },
|
||||
noAuthRetry: true,
|
||||
});
|
||||
return saveTokens(data);
|
||||
return await request(path, method, data);
|
||||
} catch (error) {
|
||||
if (error.status === 401) {
|
||||
await getApp().login();
|
||||
return request(path, method, data);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const api = {
|
||||
login,
|
||||
uploadFile,
|
||||
|
||||
// 用户
|
||||
getProfile: () => request({ url: '/api/user/profile' }),
|
||||
bindPhone: (code) => request({ url: '/api/user/phone', method: 'POST', data: { code } }),
|
||||
updateProfile: (body) => request({ url: '/api/user/profile', method: 'PUT', data: body }),
|
||||
|
||||
// 用户
|
||||
userSummary: () => request({ url: '/api/user/summary' }),
|
||||
// 记录类型(后台可配)。分组好的,前端直接渲染
|
||||
recordTypes: () => request({ url: '/api/record-types' }),
|
||||
updateDecoration: (body) => request({ url: '/api/user/decoration', method: 'PUT', data: body }),
|
||||
|
||||
// 宠物
|
||||
getPets: () => request({ url: '/api/pets' }),
|
||||
homeSummary: (id) => request({ url: `/api/pets/${id}/home-summary` }),
|
||||
getPet: (id) => request({ url: `/api/pets/${id}` }),
|
||||
// 养护方案:可选列表 + 套用到某个月
|
||||
careTemplates: (species, stage) =>
|
||||
request({ url: '/api/care-templates?species=' + species + '&stage=' + encodeURIComponent(stage) }),
|
||||
applyTemplate: (petId, templateID, monthOffset) =>
|
||||
request({ url: `/api/pets/${petId}/apply-template`, method: 'POST',
|
||||
data: { template_id: templateID, month_offset: monthOffset || 0 } }),
|
||||
|
||||
// 生命阶段的划分标准。判定和展示用同一份数据 —— 不另写一份说明文案,
|
||||
// 否则改了阈值忘了改文案,用户看到的依据和实际行为对不上
|
||||
lifeStages: (species, size) =>
|
||||
request({ url: `/api/life-stages?species=${species}&size=${size || ''}` }),
|
||||
petIDCard: (id) => request({ url: `/api/pets/${id}/id-card` }),
|
||||
petStageDrift: (petId) => request({ url: `/api/pets/${petId}/stage-drift` }),
|
||||
|
||||
// 品种(后台可配),按首字母分组
|
||||
breeds: (species) => request({ url: '/api/breeds?species=' + species }),
|
||||
createPet: (body) => request({ url: '/api/pets', method: 'POST', data: body }),
|
||||
updatePet: (id, body) => request({ url: `/api/pets/${id}`, method: 'PUT', data: body }),
|
||||
deletePet: (id) => request({ url: `/api/pets/${id}`, method: 'DELETE' }),
|
||||
onboarding: (body) => request({ url: '/api/onboarding', method: 'POST', data: body }),
|
||||
|
||||
// 记录
|
||||
getRecords: (id, { type, page, pageSize } = {}) => {
|
||||
const qs = [];
|
||||
if (type) qs.push(`type=${type}`);
|
||||
if (page) qs.push(`page=${page}`);
|
||||
if (pageSize) qs.push(`page_size=${pageSize}`);
|
||||
return request({ url: `/api/pets/${id}/records${qs.length ? '?' + qs.join('&') : ''}` });
|
||||
},
|
||||
createRecord: (id, body) => request({ url: `/api/pets/${id}/records`, method: 'POST', data: body }),
|
||||
deleteRecord: (recordId) => request({ url: `/api/records/${recordId}`, method: 'DELETE' }),
|
||||
petInsights: (id) => request({ url: `/api/pets/${id}/insights` }),
|
||||
weightTrend: (id) => request({ url: `/api/pets/${id}/records/weight-trend` }),
|
||||
|
||||
// 任务
|
||||
getTasks: (id, date) => request({ url: `/api/pets/${id}/tasks${date ? `?date=${date}` : ''}` }),
|
||||
createTask: (id, body) => request({ url: `/api/pets/${id}/tasks`, method: 'POST', data: body }),
|
||||
updateTask: (taskId, body) => request({ url: `/api/tasks/${taskId}`, method: 'PUT', data: body }),
|
||||
deleteTask: (taskId) => request({ url: `/api/tasks/${taskId}`, method: 'DELETE' }),
|
||||
toggleTask: (taskId) => request({ url: `/api/tasks/${taskId}/toggle`, method: 'POST' }),
|
||||
completeAllTasks: (id) => request({ url: `/api/pets/${id}/tasks/complete-all`, method: 'POST' }),
|
||||
|
||||
// 计划
|
||||
getPlan: (id) => request({ url: `/api/pets/${id}/plan` }),
|
||||
planCalendar: (id, month) =>
|
||||
request({ url: `/api/pets/${id}/plan/calendar${month ? `?month=${month}` : ''}` }),
|
||||
dayPlan: (id, date) => request({ url: `/api/pets/${id}/day-plan?date=${date}` }),
|
||||
// AI 相关:用户端入口已全部下掉,后端 10 个路由和配额配置都还在。
|
||||
// 想开回来把这几行解注释、再把入口接上就行,不用重写。
|
||||
// createAIPlan: (id, input) => request({ url: `/api/pets/${id}/ai-plan`, method: 'POST', data: { input } }),
|
||||
// applyAIPlan: (planId) => request({ url: `/api/ai-plan/${planId}/apply`, method: 'POST' }),
|
||||
// 用户自己管理计划节点
|
||||
planMonthStatus: (petId) => request({ url: `/api/pets/${petId}/plan/month-status` }),
|
||||
addPlanTask: (petId, body) => request({ url: `/api/pets/${petId}/plan-tasks`, method: 'POST', data: body }),
|
||||
updatePlanTask: (taskId, body) => request({ url: `/api/plan-tasks/${taskId}`, method: 'PUT', data: body }),
|
||||
deletePlanTask: (taskId) => request({ url: `/api/plan-tasks/${taskId}`, method: 'DELETE' }),
|
||||
togglePlanTask: (taskId) => request({ url: `/api/plan-tasks/${taskId}/toggle`, method: 'POST' }),
|
||||
|
||||
// 提醒
|
||||
getReminders: (id) => request({ url: `/api/pets/${id}/reminders` }),
|
||||
createReminder: (id, body) => request({ url: `/api/pets/${id}/reminders`, method: 'POST', data: body }),
|
||||
updateReminder: (remId, body) => request({ url: `/api/reminders/${remId}`, method: 'PUT', data: body }),
|
||||
deleteReminder: (remId) => request({ url: `/api/reminders/${remId}`, method: 'DELETE' }),
|
||||
|
||||
// 报告
|
||||
weeklyReport: (id) => request({ url: `/api/pets/${id}/report/weekly` }),
|
||||
getBill: (id, period) => request({ url: `/api/pets/${id}/bill?period=${period || 'month'}` }),
|
||||
healthSummary: (id) => request({ url: `/api/pets/${id}/health-summary` }),
|
||||
getPoster: (id) => request({ url: `/api/pets/${id}/poster` }),
|
||||
|
||||
// 社区
|
||||
listPosts: (tab, page) =>
|
||||
request({ url: `/api/posts?tab=${encodeURIComponent(tab || '')}&page=${page || 1}&page_size=10` }),
|
||||
createPost: (body) => request({ url: '/api/posts', method: 'POST', data: body }),
|
||||
likePost: (id) => request({ url: `/api/posts/${id}/like`, method: 'POST' }),
|
||||
userCard: (userId) => request({ url: `/api/users/${userId}/profile` }),
|
||||
userPosts: (userId, page) => request({ url: `/api/users/${userId}/posts?page=${page || 1}&page_size=10` }),
|
||||
relations: (userId, kind, page) =>
|
||||
request({ url: `/api/users/${userId}/relations?kind=${kind}&page=${page || 1}&page_size=20` }),
|
||||
followUser: (userId) => request({ url: `/api/users/${userId}/follow`, method: 'POST' }),
|
||||
unfollowUser: (userId) => request({ url: `/api/users/${userId}/follow`, method: 'DELETE' }),
|
||||
deletePost: (id) => request({ url: `/api/posts/${id}`, method: 'DELETE' }),
|
||||
deleteComment: (id) => request({ url: `/api/comments/${id}`, method: 'DELETE' }),
|
||||
listComments: (id) => request({ url: `/api/posts/${id}/comments` }),
|
||||
createComment: (id, content, images, parentId) =>
|
||||
request({
|
||||
url: `/api/posts/${id}/comments`,
|
||||
method: 'POST',
|
||||
data: { content, images: images || [], parent_id: parentId || '' },
|
||||
}),
|
||||
listReplies: (commentId) => request({ url: `/api/comments/${commentId}/replies` }),
|
||||
|
||||
// 文章
|
||||
listArticles: () => request({ url: '/api/articles' }),
|
||||
submitFeedback: (body) => request({ url: '/api/feedback', method: 'POST', data: body }),
|
||||
getArticle: (id) => request({ url: `/api/articles/${id}` }),
|
||||
|
||||
// Pro
|
||||
getPro: () => request({ url: '/api/pro' }),
|
||||
activatePro: () => request({ url: '/api/pro/activate', method: 'POST' }),
|
||||
|
||||
// AI
|
||||
// aiMessages: (session, limit) =>
|
||||
// request({ url: `/api/ai/messages?session=${session || ''}&limit=${limit || 30}` }),
|
||||
// aiChat: (body) => request({ url: '/api/ai/chat', method: 'POST', data: body }),
|
||||
// assessSymptom: (id, body) =>
|
||||
// request({ url: `/api/pets/${id}/ai/assess-symptom`, method: 'POST', data: body }),
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
function toast(error) {
|
||||
wx.showToast({ title: error.message || "操作失败", icon: "none" });
|
||||
}
|
||||
function upload(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = getApp().globalData.token;
|
||||
wx.uploadFile({
|
||||
url: baseURL + "/files",
|
||||
filePath: path,
|
||||
name: "file",
|
||||
header: { Authorization: "Bearer " + token },
|
||||
success(res) {
|
||||
try {
|
||||
const body = JSON.parse(res.data);
|
||||
if (body.code === 0) resolve(body.data);
|
||||
else reject(new Error(body.message));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
module.exports = { request, secure, toast, upload };
|
||||
|
||||
Reference in New Issue
Block a user