feat: 养护模板/社区运营/体重趋势/计划日历 + 雪花ID重构 + 文件表与图片上传

后端
- 主键改雪花字符串 ID(pkg/idgen + Base.BeforeCreate),全表外键/JWT/中间件随之调整
- 新增 files 表:/api/upload 按 MD5 去重,返回 {id,url,md5},服务端只收图片
- 头像/记录附图/帖子图改 file_id 关联,读取解析为 URL
- 养护模板(物种×阶段)后台可配 + AI 生成草稿;建档按模板生成任务/计划/提醒
- 社区 AI 运营:虚拟账号池 + 每日定时/手动生成,帖子带 AI 标
- 计划路线图节点带真实日期;首页周历与计划日历同源;新增 day-plan 当日安排
- 体重趋势接口带备注;记录列表分页

小程序
- 公共图片上传 utils/upload.js(仅图片);记录拍照/我的头像/社区发图三处接入
- 首页日历点选查当日任务;记录页体重趋势可点看备注 + 时间轴分页折叠
- 计划页日历点选查当日计划;自定义 tabBar 高度调整

后台(React)
- 新增「养护模板」「社区运营」页;用户管理加机器人筛选

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-06 08:48:15 +08:00
parent 609f7d06cf
commit 2f5dd6eefc
83 changed files with 2253 additions and 355 deletions
+41 -25
View File
@@ -1,5 +1,6 @@
const store = require('../../utils/store.js');
const api = require('../../utils/api.js');
const upload = require('../../utils/upload.js');
function fmtDate(iso) {
if (!iso) return '';
@@ -39,8 +40,10 @@ Component({
optSel: {},
dateVals: { vaccine: '2026-07-21', deworm: '2026-07-15' },
wInput: '',
wNote: '',
costAmount: '',
postContent: '',
postImages: [],
addName: '',
addBirthday: '',
addWeight: '',
@@ -81,6 +84,7 @@ Component({
patch.optSel = {};
patch.costAmount = '';
patch.postContent = '';
patch.postImages = [];
patch.addName = '';
patch.addBirthday = '';
patch.addWeight = '';
@@ -90,7 +94,10 @@ Component({
patch.riskData = null;
patch.saving = false;
}
if (type === 'weight') patch.wInput = (pet.weight || '').replace('kg', '');
if (type === 'weight') {
patch.wInput = (pet.weight || '').replace('kg', '');
patch.wNote = '';
}
if (type === 'editPet') {
// 预填当前宠物档案
patch.addName = pet.name || '';
@@ -185,8 +192,26 @@ Component({
this.setData({ [`dateVals.${e.currentTarget.dataset.key}`]: e.detail.value });
},
onWInput(e) { this.setData({ wInput: e.detail.value }); },
onWNote(e) { this.setData({ wNote: e.detail.value }); },
onCostInput(e) { this.setData({ costAmount: e.detail.value }); },
onPostInput(e) { this.setData({ postContent: e.detail.value }); },
onPickPostImages() {
const left = 9 - this.data.postImages.length;
if (left <= 0) return wx.showToast({ title: '最多 9 张', icon: 'none' });
upload
.chooseAndUploadImages(left)
.then((list) => this.setData({ postImages: this.data.postImages.concat(list) }))
.catch((e) => {
if (e && e.canceled) return;
wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' });
});
},
onRemovePostImage(e) {
const i = e.currentTarget.dataset.index;
const arr = this.data.postImages.slice();
arr.splice(i, 1);
this.setData({ postImages: arr });
},
onAddName(e) { this.setData({ addName: e.detail.value }); },
onAddBirthday(e) { this.setData({ addBirthday: e.detail.value }); },
onAddWeight(e) { this.setData({ addWeight: e.detail.value }); },
@@ -290,33 +315,24 @@ Component({
if (!w) w = (this.data.pet.weight || '').replace('kg', '');
const num = parseFloat(w) || 0;
const weight = w.includes('kg') ? w : w + 'kg';
this.createAndClose({ type: 'weight', icon: '⚖️', title: '体重 ' + weight, num_value: num });
this.createAndClose({ type: 'weight', icon: '⚖️', title: '体重 ' + weight, num_value: num, description: (this.data.wNote || '').trim() });
},
onSavePhoto() {
const id = store.currentPetId();
if (!id) return this.close();
wx.chooseMedia({
count: 1,
mediaType: ['image'],
success: (res) => {
const path = res.tempFiles[0].tempFilePath;
wx.showLoading({ title: '上传中...' });
api
.uploadFile(path)
.then((up) =>
api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_url: up.url }),
)
.then((saved) => {
wx.hideLoading();
this.triggerEvent('saved', saved);
this.close();
})
.catch((e) => {
wx.hideLoading();
wx.showToast({ title: e.message || '上传失败', icon: 'none' });
});
},
});
upload
.chooseAndUploadImage()
.then((f) =>
api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_file_id: f.id, image_url: f.url }),
)
.then((saved) => {
this.triggerEvent('saved', saved);
this.close();
})
.catch((e) => {
if (e && e.canceled) return;
wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' });
});
},
// 组装宠物档案表单
@@ -416,7 +432,7 @@ Component({
identity,
content,
tags: [tag],
images: [],
image_file_ids: this.data.postImages.map((i) => i.id),
});
this.triggerEvent('posted');
this.close();