Add one-way task completion and exportable pet identity cards
ci-cd / verify (push) Successful in 1m31s
ci-cd / deploy (push) Skipped

This commit is contained in:
2026-10-05 11:32:32 +08:00
parent f0ee63e642
commit cb4ffa13e9
21 changed files with 999 additions and 117 deletions
+14 -13
View File
@@ -1,4 +1,5 @@
const { secure, toast } = require("../../utils/api");
const completion = require("../../utils/task-completion");
const { secure } = require("../../utils/api");
const { localDate } = require("../../utils/date");
const environment = require("../../utils/environment");
function dateLabel() {
@@ -12,6 +13,9 @@ Page({
pets: [],
selected: [],
tasks: [],
taskBusyIds: [],
completingAll: false,
undoneCount: 0,
walkCount: 0,
name: "铲屎官",
loading: true,
@@ -100,6 +104,7 @@ Page({
pets,
selected: chosen.filter((id) => pets.some((p) => p.id === id)),
tasks: tasks.items,
undoneCount: tasks.items.filter((t) => !t.completed).length,
walkCount: walks.filter((w) => w.started_at.slice(0, 7) === month)
.length,
name: me.nickname || "毛孩家长",
@@ -138,17 +143,13 @@ Page({
goWalks() {
wx.navigateTo({ url: "/pages/walk-records/walk-records" });
},
async complete(e) {
const id = e.currentTarget.dataset.id,
task = this.data.tasks.find((t) => t.id === id);
try {
await secure("/tasks/" + id + "/completion", "PUT", {
date: localDate(),
completed: !task.completed,
});
await this.load();
} catch (err) {
toast(err);
}
complete(e) {
return completion.complete(
this,
this.data.tasks.find((t) => t.id === e.currentTarget.dataset.id),
);
},
completeAll() {
return completion.completeAll(this);
},
});
+8 -3
View File
@@ -70,23 +70,28 @@
<text class="section-title">今天要做的事</text>
<text class="link" bindtap="goTasks">全部任务 ›</text>
</view>
<view wx:if="{{tasks.length}}" class="completion-toolbar">
<text>{{undoneCount ? '还有 '+undoneCount+' 项小事' : '这一份日常,已全部照顾好'}}</text>
<button wx:if="{{undoneCount}}" class="finish-all" bindtap="completeAll" loading="{{completingAll}}" disabled="{{completingAll || taskBusyIds.length}}">一键完成</button>
<text wx:else class="all-done">全部完成</text>
</view>
<view class="card tasks">
<view
wx:for="{{tasks}}"
wx:key="id"
class="task"
class="task {{item.completed?'task-completed':''}}"
data-id="{{item.id}}"
bindtap="complete"
>
<view class="check {{item.completed?'done':''}}">
{{item.completed?'✓':''}}
{{item.completed?'✓':taskBusyIds[0]===item.id?'…':''}}
</view>
<view class="task-icon">
<app-icon kind="{{item.category}}" size="36" />
</view>
<view class="task-main">
<text class="task-name">{{item.title}}</text>
<text class="muted">{{item.time_of_day}}</text>
<text class="muted">{{item.time_of_day}} · {{item.completed?'已完成':taskBusyIds[0]===item.id?'正在记录…':'点击完成'}}</text>
</view>
</view>
<view wx:if="{{!tasks.length}}" class="empty">
+22
View File
@@ -48,3 +48,25 @@
font-size: 24rpx;
margin-top: 20rpx;
}
.field input {
height: 104rpx;
min-height: 104rpx;
padding: 0 26rpx;
line-height: normal;
}
.picker {
min-height: 104rpx;
padding: 30rpx 26rpx;
}
.field radio-group {
min-height: 104rpx;
padding: 26rpx;
}
.field textarea {
height: 210rpx;
padding: 26rpx;
}
.field {
margin: 28rpx 0;
}
+1 -1
View File
@@ -16,7 +16,7 @@ Page({
},
edit(e) {
wx.navigateTo({
url: "/pages/pet-edit/pet-edit?id=" + e.currentTarget.dataset.id,
url: "/pages/pet-edit/pet-edit?id=" + e.detail.id,
});
},
});
+2 -1
View File
@@ -1,6 +1,7 @@
{
"disableScroll": true,
"usingComponents": {
"tab-bar": "/components/tab-bar/index"
"tab-bar": "/components/tab-bar/index",
"pet-id-card": "/components/pet-id-card/index"
}
}
+1 -25
View File
@@ -11,31 +11,7 @@
<text class="section-title">宠物档案 · {{pets.length}}</text>
<text class="add" bindtap="add">+ 添加</text>
</view>
<view
wx:for="{{pets}}"
wx:key="id"
class="card pet"
data-id="{{item.id}}"
bindtap="edit"
>
<view class="pet-avatar">
<image
wx:if="{{item.avatar_url}}"
src="{{item.avatar_url}}"
mode="aspectFill"
/>
<app-icon wx:else size="70" />
</view>
<view class="pet-info">
<strong>{{item.name}}</strong>
<text>
{{item.breed||'小可爱'}} ·
{{item.gender==='male'?'男孩':item.gender==='female'?'女孩':'未知'}}
</text>
<text>体重 {{item.weight_kg||'—'}} kg</text>
</view>
<text class="arrow">›</text>
</view>
<pet-id-card wx:for="{{pets}}" wx:key="id" pet="{{item}}" bind:edit="edit" />
<view wx:if="{{!pets.length}}" class="empty">给第一只狗狗建个档案吧 </view>
<button class="primary add-button" bindtap="add">+ 新建宠物档案</button>
</view>
-37
View File
@@ -33,43 +33,6 @@
font-weight: 600;
color: #b95738;
}
.pet {
display: flex;
align-items: center;
gap: 22rpx;
}
.pet-avatar {
width: 125rpx;
height: 125rpx;
flex: none;
border-radius: 28rpx;
background: #f1d2a3;
display: grid;
place-items: center;
font-size: 63rpx;
overflow: hidden;
}
.pet-avatar image {
width: 100%;
height: 100%;
}
.pet-info {
display: flex;
flex-direction: column;
gap: 6rpx;
flex: 1;
}
.pet-info strong {
font-size: 32rpx;
}
.pet-info text {
font-size: 23rpx;
color: #886c5f;
}
.arrow {
font-size: 42rpx;
color: #886c5f;
}
.add-button {
margin-top: 25rpx;
}
+29 -19
View File
@@ -1,9 +1,13 @@
const completion = require("../../utils/task-completion");
const { secure, toast } = require("../../utils/api");
const { localDate, shiftDate } = require("../../utils/date");
Page({
data: {
pets: [],
tasks: [],
taskBusyIds: [],
completingAll: false,
undoneCount: 0,
date: localDate(),
showForm: false,
editingId: "",
@@ -33,23 +37,21 @@ Page({
this.setData({
pets,
tasks,
undoneCount: tasks.filter((t) => !t.completed).length,
done: tasks.filter((t) => t.completed).length,
});
} catch (e) {
toast(e);
}
},
async toggle(e) {
const t = this.data.tasks[e.currentTarget.dataset.index];
try {
await secure("/tasks/" + t.id + "/completion", "PUT", {
date: this.data.date,
completed: !t.completed,
});
await this.load();
} catch (err) {
toast(err);
}
toggle(e) {
return completion.complete(
this,
this.data.tasks[e.currentTarget.dataset.index],
);
},
completeAll() {
return completion.completeAll(this);
},
open() {
if (!this.data.pets.length) {
@@ -83,10 +85,14 @@ Page({
});
},
changeDay(e) {
this.setData({ date: shiftDate(this.data.date, Number(e.currentTarget.dataset.days)) });
if (this.data.completingAll || this.data.taskBusyIds.length) return;
this.setData({
date: shiftDate(this.data.date, Number(e.currentTarget.dataset.days)),
});
this.load();
},
showToday() {
if (this.data.completingAll || this.data.taskBusyIds.length) return;
this.setData({ date: localDate() });
this.load();
},
@@ -113,13 +119,17 @@ Page({
}
this.setData({ saving: true });
try {
await secure(this.data.editingId ? "/tasks/" + this.data.editingId : "/tasks", this.data.editingId ? "PUT" : "POST", {
pet_id: this.data.pets[this.data.petIndex].id,
title: this.data.title.trim(),
category: this.data.category,
time_of_day: this.data.time_of_day,
repeat_rule: this.data.repeat_rule,
});
await secure(
this.data.editingId ? "/tasks/" + this.data.editingId : "/tasks",
this.data.editingId ? "PUT" : "POST",
{
pet_id: this.data.pets[this.data.petIndex].id,
title: this.data.title.trim(),
category: this.data.category,
time_of_day: this.data.time_of_day,
repeat_rule: this.data.repeat_rule,
},
);
this.setData({ showForm: false, editingId: "" });
await this.load();
} catch (e) {
+8 -3
View File
@@ -19,14 +19,19 @@
<text bindtap="showToday">回到今天</text>
<text data-days="1" bindtap="changeDay">后一天 ›</text>
</view>
<view wx:if="{{tasks.length}}" class="completion-toolbar">
<text>{{undoneCount ? '还有 '+undoneCount+' 项小事' : '这一份日常,已全部照顾好'}}</text>
<button wx:if="{{undoneCount}}" class="finish-all" bindtap="completeAll" loading="{{completingAll}}" disabled="{{completingAll || taskBusyIds.length}}">一键完成</button>
<text wx:else class="all-done">全部完成</text>
</view>
<view class="card list">
<view wx:for="{{tasks}}" wx:key="id" class="task">
<view wx:for="{{tasks}}" wx:key="id" class="task {{item.completed?'task-completed':''}}">
<view
class="check {{item.completed?'done':''}}"
data-index="{{index}}"
bindtap="toggle"
>
{{item.completed?'✓':''}}
{{item.completed?'✓':taskBusyIds[0]===item.id?'…':''}}
</view>
<view class="icon">
<app-icon kind="{{item.category}}" size="36" />
@@ -37,7 +42,7 @@
{{item.petName}} · {{item.time_of_day}} ·
{{item.repeat_rule==='daily'?'每天':item.repeat_rule==='weekdays'?'工作日':'周末'}}
</text>
<text>点此编辑</text>
<text>{{item.completed?'已完成 · 点此编辑任务':taskBusyIds[0]===item.id?'正在记录…':'点击左侧完成 · 点此编辑'}}</text>
</view>
<text class="remove" data-index="{{index}}" bindtap="remove">×</text>
</view>