feat: 每日任务顺延与增删改 + 计划到期续期 + 靠谱养护模板 + 图片URL修复

后端
- 每日任务惰性生成:某天为空则从最近一天顺延复制(done 重置),首天用模板
- 任务增删改 API(POST /pets/:id/tasks、PUT/DELETE /tasks/:id),改动自动顺延
- 30 天计划到期自动归档并按当前阶段生成新一轮
- 内置养护模板重写为兽医常识向:狗每阶段含遛狗/牵引/狂犬,猫含猫砂/梳毛/饮水
- 文件 URL 改为按 object_name + 当前配置动态拼接,换 IP/域名不再有旧地址

小程序
- 首页今日任务加「管理」入口:增删改任务弹层
- 记录时间轴显示照片缩略图(可全屏预览)+ 拍照成功提示
- 社区发布条精简为单个「发布图文」按钮,去掉头像/输入框误触

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-06 10:06:13 +08:00
parent 2f5dd6eefc
commit 28b95a6468
21 changed files with 478 additions and 81 deletions
@@ -20,6 +20,18 @@ function ageFromBirthday(bday) {
const GENDERS = ['男孩', '女孩', '不确定'];
const STAGES = ['刚到家 0-30 天', '幼年期', '成年期', '老年期'];
// 任务可关联的记录弹层类型
const TASK_SHEETS = [
{ key: '', label: '不关联' },
{ key: 'weight', label: '体重' },
{ key: 'poop', label: '便便' },
{ key: 'food', label: '饮食' },
{ key: 'symptom', label: '异常' },
{ key: 'vaccine', label: '疫苗' },
{ key: 'medicine', label: '用药' },
];
const TASK_SHEET_LABELS = TASK_SHEETS.map((s) => s.label);
const POOP = ['正常', '软便', '拉稀'];
const FOOD = ['正常', '偏少', '不吃'];
const COST = ['食品', '医疗', '用品'];
@@ -44,6 +56,9 @@ Component({
costAmount: '',
postContent: '',
postImages: [],
manageTasks: [],
taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 },
taskSheetLabels: TASK_SHEET_LABELS,
addName: '',
addBirthday: '',
addWeight: '',
@@ -85,6 +100,8 @@ Component({
patch.costAmount = '';
patch.postContent = '';
patch.postImages = [];
patch.manageTasks = [];
patch.taskForm = { id: '', title: '', description: '', priority: '', sheetIdx: 0 };
patch.addName = '';
patch.addBirthday = '';
patch.addWeight = '';
@@ -127,7 +144,9 @@ Component({
// 按弹层类型拉取真实数据
loadSheetData(type) {
const id = store.currentPetId();
if (type === 'comments' && this.data.postId) {
if (type === 'manageTasks') {
this.loadManageTasks();
} else if (type === 'comments' && this.data.postId) {
api.listComments(this.data.postId).then((page) => this.setData({ comments: page.list || [] })).catch(() => {});
} else if (type === 'reminders' && id) {
api
@@ -195,6 +214,53 @@ Component({
onWNote(e) { this.setData({ wNote: e.detail.value }); },
onCostInput(e) { this.setData({ costAmount: e.detail.value }); },
onPostInput(e) { this.setData({ postContent: e.detail.value }); },
// ---- 管理任务 ----
loadManageTasks() {
const id = store.currentPetId();
if (!id) return;
api.getTasks(id).then((tasks) => this.setData({ manageTasks: tasks || [] })).catch(() => {});
},
onTaskTitle(e) { this.setData({ 'taskForm.title': e.detail.value }); },
onTaskDesc(e) { this.setData({ 'taskForm.description': e.detail.value }); },
onTaskPriority() {
this.setData({ 'taskForm.priority': this.data.taskForm.priority === '重要' ? '' : '重要' });
},
onTaskSheet(e) { this.setData({ 'taskForm.sheetIdx': Number(e.detail.value) }); },
onEditTaskItem(e) {
const t = this.data.manageTasks[e.currentTarget.dataset.index];
let sheetIdx = TASK_SHEETS.findIndex((s) => s.key === (t.sheet_type || ''));
if (sheetIdx < 0) sheetIdx = 0;
this.setData({
taskForm: { id: t.id, title: t.title, description: t.description || '', priority: t.priority || '', sheetIdx },
});
},
onSaveTask() {
const f = this.data.taskForm;
const title = (f.title || '').trim();
if (!title) return wx.showToast({ title: '填个任务名', icon: 'none' });
const body = { title, description: f.description, priority: f.priority, sheet_type: TASK_SHEETS[f.sheetIdx].key };
const id = store.currentPetId();
const p = f.id ? api.updateTask(f.id, body) : api.createTask(id, body);
p.then(() => {
this.setData({ taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 } });
this.loadManageTasks();
this.triggerEvent('saved');
}).catch((e) => wx.showToast({ title: e.message || '保存失败', icon: 'none' }));
},
onDeleteTaskItem(e) {
const t = this.data.manageTasks[e.currentTarget.dataset.index];
wx.showModal({
title: '删除任务',
content: '确定删除「' + t.title + '」?',
success: (r) => {
if (!r.confirm) return;
api.deleteTask(t.id).then(() => {
this.loadManageTasks();
this.triggerEvent('saved');
}).catch((err) => wx.showToast({ title: err.message || '删除失败', icon: 'none' }));
},
});
},
onPickPostImages() {
const left = 9 - this.data.postImages.length;
if (left <= 0) return wx.showToast({ title: '最多 9 张', icon: 'none' });
@@ -326,6 +392,7 @@ Component({
api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_file_id: f.id, image_url: f.url }),
)
.then((saved) => {
wx.showToast({ title: '照片已保存', icon: 'success' });
this.triggerEvent('saved', saved);
this.close();
})
@@ -273,6 +273,34 @@ module.exports.sel = function (map, key, index, def) {
<button class="btn btn-primary btn-block" bindtap="onCreatePost">发布到宠友圈</button>
</block>
<!-- 管理任务 -->
<block wx:elif="{{innerType === 'manageTasks'}}">
<view class="sheet-h3">管理任务</view>
<view class="sheet-p">增删改今日任务,改动会自动顺延到以后每天。</view>
<view wx:for="{{manageTasks}}" wx:key="id" class="mtask-row">
<view class="mtask-body">
<text class="mtask-b">{{item.title}}<text wx:if="{{item.priority}}" class="mtask-pri">{{item.priority}}</text></text>
<view wx:if="{{item.description}}" class="mtask-p">{{item.description}}</view>
</view>
<view class="mtask-op" catchtap="onEditTaskItem" data-index="{{index}}">改</view>
<view class="mtask-op del" catchtap="onDeleteTaskItem" data-index="{{index}}">删</view>
</view>
<view wx:if="{{!manageTasks.length}}" class="sheet-p">今天还没有任务,下面加一条吧。</view>
<view class="field" style="margin-top:12rpx"><label>{{taskForm.id ? '修改任务' : '新增任务'}}</label>
<input class="input" placeholder="任务名,例如:喂驱虫药" placeholder-class="placeholder" value="{{taskForm.title}}" bindinput="onTaskTitle"/></view>
<view class="field"><input class="input" placeholder="说明(可选)" placeholder-class="placeholder" value="{{taskForm.description}}" bindinput="onTaskDesc"/></view>
<view class="field"><label>优先级 / 关联记录</label>
<view class="mtask-form-row">
<view class="seg-btn {{taskForm.priority === '重要' ? 'selected' : ''}}" catchtap="onTaskPriority">重要</view>
<picker mode="selector" range="{{taskSheetLabels}}" value="{{taskForm.sheetIdx}}" bindchange="onTaskSheet">
<view class="picker-box">关联:{{taskSheetLabels[taskForm.sheetIdx]}}</view>
</picker>
</view>
</view>
<button class="btn btn-primary btn-block" bindtap="onSaveTask">{{taskForm.id ? '保存修改' : '添加任务'}}</button>
</block>
<!-- 评论 -->
<block wx:elif="{{innerType === 'comments'}}">
<view class="sheet-h3">评论</view>
@@ -35,6 +35,18 @@
.picker-box{color:var(--text)}
.del-btn{background:var(--red-soft);color:var(--red)}
/* 管理任务 */
.mtask-row{display:flex;align-items:center;gap:16rpx;padding:20rpx 0;border-bottom:1rpx solid var(--line)}
.mtask-body{flex:1;min-width:0}
.mtask-b{font-size:28rpx;font-weight:700}
.mtask-pri{margin-left:12rpx;font-size:20rpx;font-weight:700;color:var(--primary-dark);background:#FFF2DC;padding:2rpx 12rpx;border-radius:999rpx}
.mtask-p{margin-top:6rpx;font-size:24rpx;color:var(--muted)}
.mtask-op{flex:none;font-size:26rpx;font-weight:700;color:var(--primary-dark);padding:8rpx 18rpx;background:#FBFAF8;border-radius:16rpx}
.mtask-op.del{color:var(--red)}
.mtask-form-row{display:flex;align-items:center;gap:16rpx}
.mtask-form-row .seg-btn{flex:none;padding:0 28rpx;height:72rpx;line-height:72rpx}
.mtask-form-row .picker-box{flex:1}
/* 图片选择器 */
.img-picker{display:flex;flex-wrap:wrap;gap:16rpx}
.img-thumb{position:relative;width:160rpx;height:160rpx;border-radius:20rpx;overflow:hidden}