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:
@@ -57,6 +57,7 @@ page{scrollbar-width:none;-ms-overflow-style:none}
|
||||
.link{
|
||||
color:var(--primary-dark);font-weight:900;font-size:26rpx;
|
||||
}
|
||||
.head-links{display:flex;align-items:center;gap:26rpx}
|
||||
|
||||
/* 页面标题 */
|
||||
.page-title{margin:4rpx 0 28rpx}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -15,10 +15,8 @@ module.exports.isUrl = function (s) { return s && s.indexOf('http') === 0; }
|
||||
class="feed-tab {{feedIdx === index ? 'active' : ''}}" data-index="{{index}}" bindtap="switchFeedTab">{{item}}</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="compose-card" data-type="createPost" bindtap="openSheet">
|
||||
<view class="post-avatar">{{pet.emoji}}</view>
|
||||
<view class="compose-input">分享一下今天和 {{pet.name}} 的日常...</view>
|
||||
<button class="btn btn-primary btn-small" catchtap="openSheet" data-type="createPost">发布</button>
|
||||
<view class="compose-card">
|
||||
<button class="btn btn-primary btn-block" bindtap="openSheet" data-type="createPost">+ 发布图文</button>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{posts}}" wx:key="id" class="post-card">
|
||||
|
||||
@@ -10,15 +10,7 @@
|
||||
}
|
||||
.feed-tab.active{color:var(--primary-dark);background:#FFF4E4;border-color:#FFD39A}
|
||||
|
||||
.compose-card{
|
||||
display:flex;gap:20rpx;align-items:center;background:#fff;border-radius:44rpx;
|
||||
box-shadow:var(--shadow);padding:24rpx;margin-bottom:28rpx;
|
||||
}
|
||||
.compose-input{
|
||||
flex:1;height:76rpx;line-height:76rpx;border-radius:999rpx;background:#FBFAF8;
|
||||
border:1rpx solid var(--line);color:var(--muted);padding:0 26rpx;font-size:26rpx;
|
||||
overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
|
||||
}
|
||||
.compose-card{margin-bottom:28rpx}
|
||||
|
||||
.post-card{background:#fff;border-radius:48rpx;box-shadow:var(--shadow);padding:30rpx;margin-bottom:28rpx}
|
||||
.post-head{display:flex;align-items:center;gap:20rpx;margin-bottom:22rpx}
|
||||
|
||||
@@ -133,7 +133,10 @@ Page({
|
||||
this.setData({ sheetShow: false });
|
||||
this.loadAll();
|
||||
},
|
||||
onSheetSave() {},
|
||||
onSheetSave() {
|
||||
this.loadTasks();
|
||||
this.loadSummary();
|
||||
},
|
||||
onAddPet() {
|
||||
this.openSheetType('addPet');
|
||||
},
|
||||
|
||||
@@ -42,8 +42,11 @@
|
||||
<view class="card">
|
||||
<view class="section-head">
|
||||
<view class="sh-title">{{taskLabel}}</view>
|
||||
<view wx:if="{{selectedDate && selectedDate !== todayDate}}" class="link" bindtap="backToToday">回今天</view>
|
||||
<view wx:else class="link" bindtap="completeTasks">全部完成</view>
|
||||
<view class="head-links">
|
||||
<view class="link" data-type="manageTasks" bindtap="openSheet">管理</view>
|
||||
<view wx:if="{{selectedDate && selectedDate !== todayDate}}" class="link" bindtap="backToToday">回今天</view>
|
||||
<view wx:else class="link" bindtap="completeTasks">全部完成</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="task-list">
|
||||
<view wx:for="{{tasks}}" wx:key="title" class="task {{item.done ? 'done' : ''}}" data-index="{{index}}" bindtap="onTapTask">
|
||||
|
||||
@@ -19,6 +19,7 @@ function mapRecord(r) {
|
||||
icon: r.icon || '✍️',
|
||||
title: r.title,
|
||||
desc: fmtTime(r.occurred_at) + (r.description ? '|' + r.description : ''),
|
||||
image: r.image_url || '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,6 +152,10 @@ Page({
|
||||
const i = e.currentTarget.dataset.index;
|
||||
this.setData({ selDot: this.data.trendDots[i], selIdx: i });
|
||||
},
|
||||
previewImage(e) {
|
||||
const src = e.currentTarget.dataset.src;
|
||||
if (src) wx.previewImage({ urls: [src], current: src });
|
||||
},
|
||||
openSheet(e) {
|
||||
this.openSheetType(e.currentTarget.dataset.type);
|
||||
},
|
||||
|
||||
@@ -53,7 +53,10 @@
|
||||
<view class="health-timeline">
|
||||
<view wx:for="{{timeline}}" wx:key="index" class="health-event">
|
||||
<view class="event-dot">{{item.icon}}</view>
|
||||
<view><text class="he-b">{{item.title}}</text><view class="he-p">{{item.desc}}</view></view>
|
||||
<view class="he-body">
|
||||
<text class="he-b">{{item.title}}</text><view class="he-p">{{item.desc}}</view>
|
||||
<image wx:if="{{item.image}}" class="he-img" src="{{item.image}}" mode="aspectFill" bindtap="previewImage" data-src="{{item.image}}"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{timeline.length === 0}}" class="tl-empty">还没有记录,用上面的按钮记一条吧</view>
|
||||
</view>
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
}
|
||||
.he-b{font-size:28rpx;font-weight:700}
|
||||
.he-p{color:var(--muted);font-size:24rpx;line-height:1.45;margin-top:6rpx}
|
||||
.he-body{flex:1;min-width:0}
|
||||
.he-img{margin-top:14rpx;width:220rpx;height:220rpx;border-radius:20rpx}
|
||||
|
||||
/* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/
|
||||
page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
|
||||
|
||||
@@ -58,6 +58,9 @@ const api = {
|
||||
|
||||
// 任务
|
||||
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' }),
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 后端地址。本地联调:微信开发者工具需在「详情 → 本地设置」勾选「不校验合法域名」。
|
||||
// 上线时改为你的 https 域名,并在微信公众平台配置 request 合法域名。
|
||||
const BASE_URL = 'http://192.168.31.4:8080';
|
||||
const BASE_URL = 'http://192.168.31.3:8080';
|
||||
|
||||
module.exports = { BASE_URL };
|
||||
|
||||
Reference in New Issue
Block a user