3fc3cbc7d2
## 通用简易表单
洗护/清洁那 15 种只需要「什么时候 + 备注 + 可选照片」。弹层现在是 28 个
wx:elif 手写分支(402 行),各写一个会推到 700 行,而且每加一种类型都要发版。
加一个 wx:elif="{{isSimple}}",isSimple 由 JS 按后端返回的 form 字段算,
和上面那 9 种永不重叠。插在 photo 之后、提醒中心之前,没动任何现有分支的顺序。
备注给了逐类型的具体占位提示(「用了什么沐浴露、有没有吹干」),
比「请输入备注」有用得多——用户看到提示才知道这栏该写什么。
## 一个不写就会静默出错的地方
dayToISO():后端用 time.Parse(time.RFC3339) 解 occurred_at,只给日期
("2026-07-30")它解不出来会**静默回退成 time.Now()**——用户选「昨天洗澡」
会存成今天,而且不报错。
时区必须带真实偏移:转成 UTC 的 Z 形式,落库再转回本地时会把边界日期挪一天
(首页任务重复生成那个 bug 就是这么来的)。取正午同样是为了远离日界。
## 四处写死的类型列表全删
RECORD_ITEMS record.js 记录页 9 宫格
QUICK_ITEMS home.js 首页 8 宫格
TYPE_TONE record.js 时间轴色调(9 种写死,新类型会全掉到橙色一片分不清)
TASK_SHEETS 弹层 任务关联记录类型的下拉(只有 6 种,加了类型选不到)
全部改成读 utils/recordTypes.js 这份共用缓存。缓存 load() 只真正请求一次,
并发调用共用同一个 promise;isSimple/label/groups 是同步的,弹层打开时
不该再等一次网络。
首页 8 宫格改成取后端前 8 个(按分组顺序再按 sort),后台调 sort 就能换首屏
露出哪几个;全部 24 种在记录页。
分组色系(daily橙/health绿/care紫/clean蓝)留在前端,后端只存 group key——
改配色不用动数据。
## 联调(预生产库实跑)
15 种新类型 全部落库,且选「昨天」没被存成今天(15/15)
原有 9 种 逐一回归,写库 emoji 原样;weight 回写宠物档案 → 4.5kg ✓
时间轴 24 条混排,色调分布 6/7/5/6,没有一条查不到类型
体重趋势 仍只有 1 个点(没被 24 条记录污染)
周报/首页汇总 正常
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
6.7 KiB
JavaScript
144 lines
6.7 KiB
JavaScript
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() {
|
||
return new Promise((resolve, reject) => {
|
||
wx.login({
|
||
success: (r) => (r.code ? resolve(r.code) : reject(new Error('wx.login 未返回 code'))),
|
||
fail: reject,
|
||
});
|
||
});
|
||
}
|
||
|
||
// 登录:优先微信 code2session,失败回退开发态 Mock 登录(后端 dev_login=true)
|
||
async function 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);
|
||
}
|
||
}
|
||
|
||
const api = {
|
||
login,
|
||
uploadFile,
|
||
|
||
// 用户
|
||
getProfile: () => request({ url: '/api/user/profile' }),
|
||
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}` }),
|
||
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}` }),
|
||
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' }),
|
||
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;
|