feat: support shared pet care tasks and smooth daily page return
ci-cd / verify (push) Successful in 2m18s
ci-cd / deploy (push) Skipped

This commit is contained in:
2026-10-05 20:20:05 +08:00
parent fbc1eda561
commit 3175936266
16 changed files with 196 additions and 119 deletions
@@ -1 +0,0 @@
Component({});
@@ -1 +0,0 @@
{ "component": true }
@@ -1,9 +0,0 @@
<view class="skeleton" aria-label="正在准备首页内容">
<view class="hero-placeholder"><view class="bone line short"></view><view class="bone line title"></view><view class="bone line"></view><view class="bone art"></view></view>
<view class="bone heading"></view>
<view class="card-placeholder"><view class="bone line title"></view><view class="bone line"></view><view class="bone action"></view></view>
<view class="stats-placeholder"><view class="bone"></view><view class="bone"></view><view class="bone"></view></view>
<view class="bone heading"></view>
<view class="card-placeholder"><view class="bone line"></view><view class="bone line short"></view></view>
<view class="loading-copy">正在准备今天的小日常…</view>
</view>
@@ -1,73 +0,0 @@
.bone {
background: linear-gradient(100deg, #f3e7dc 20%, #fff8ef 45%, #f3e7dc 70%);
background-size: 220% 100%;
animation: shimmer 1.6s ease-in-out infinite;
border-radius: 15rpx;
}
.hero-placeholder {
position: relative;
min-height: 330rpx;
background: #fff0e2;
border-radius: 38rpx;
padding: 45rpx 35rpx;
box-sizing: border-box;
}
.line {
height: 22rpx;
width: 65%;
margin: 24rpx 0;
}
.short {
width: 25%;
}
.title {
height: 38rpx;
width: 48%;
}
.art {
position: absolute;
right: 35rpx;
top: 62rpx;
width: 170rpx;
height: 190rpx;
border-radius: 40rpx;
}
.heading {
width: 230rpx;
height: 33rpx;
margin: 48rpx 0 28rpx;
}
.card-placeholder {
padding: 28rpx;
background: white;
border-radius: 35rpx;
}
.action {
height: 80rpx;
margin-top: 42rpx;
border-radius: 24rpx;
}
.stats-placeholder {
display: flex;
gap: 14rpx;
margin-top: 20rpx;
}
.stats-placeholder view {
height: 140rpx;
flex: 1;
border-radius: 28rpx;
}
.loading-copy {
text-align: center;
color: #a48d7e;
font-size: 22rpx;
margin-top: 32rpx;
}
@keyframes shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: -100% 0;
}
}
+24
View File
@@ -6,6 +6,10 @@ function dateLabel() {
const now = new Date();
return `${now.getMonth() + 1}月${now.getDate()}日 周${"日一二三四五六"[now.getDay()]}`;
}
function accountKey() {
const state = getApp().globalData || {};
return (state.user && state.user.id) || state.token || "";
}
Page({
data: {
locationName: "当前位置",
@@ -23,13 +27,32 @@ Page({
phase: "auth",
error: "",
},
onLoad() {
const cached = getApp().homeSnapshot;
if (cached && cached.account === accountKey() && cached.date === localDate()) {
this.setData({ ...cached.data, ready: true, phase: "ready", loading: false });
}
},
onShow() {
this.applyEnvironment(environment.cached());
this.load();
},
onHide() {
this.rememberHome();
},
onUnload() {
this.rememberHome();
this.disposed = true;
},
rememberHome() {
if (!this.data.ready || !accountKey()) return;
const { pets, selected, tasks, undoneCount, walkCount, name } = this.data;
getApp().homeSnapshot = {
account: accountKey(),
date: localDate(),
data: { pets, selected, tasks, undoneCount, walkCount, name },
};
},
updateEnvironmentLabel() {
this.setData({
headerSubtitle:
@@ -112,6 +135,7 @@ Page({
ready: true,
phase: "ready",
});
this.rememberHome();
// Location permission and weather never hold up the first usable screen.
this.refreshEnvironment();
} catch (error) {
+1 -2
View File
@@ -3,7 +3,6 @@
"usingComponents": {
"tab-bar": "/components/tab-bar/index",
"pet-picker": "/components/pet-picker/index",
"startup-state": "/components/startup-state/index",
"home-skeleton": "/components/home-skeleton/index"
"startup-state": "/components/startup-state/index"
}
}
+3 -5
View File
@@ -1,8 +1,7 @@
<startup-state wx:if="{{phase==='auth' || phase==='auth-error'}}" error="{{error}}" bind:retry="load" />
<startup-state wx:if="{{!ready && (!error || phase==='auth-error')}}" error="{{error}}" bind:retry="load" />
<page-layout wx:else id="page-layout" title="{{locationName}}" subtitle="{{headerSubtitle}}" location="{{true}}" bind:locationtap="refreshEnvironment">
<view class="screen">
<home-skeleton wx:if="{{loading && !ready}}" />
<view wx:elif="{{!ready && error}}" class="card home-error">
<view wx:if="{{!ready && error}}" class="card home-error">
<app-icon kind="paw" size="54" />
<view class="error-title">暂时没能准备好首页</view>
<view class="muted">{{error}}</view>
@@ -10,7 +9,6 @@
</view>
<block wx:else>
<view wx:if="{{error}}" class="refresh-error"><text>更新失败,保留上次的日常记录</text><text class="link" bindtap="load">重试</text></view>
<view wx:elif="{{loading}}" class="refresh-hint">正在更新今天的小日常…</view>
<view class="hero">
<view class="hero-copy">
<text class="hero-small">今日的主角</text>
@@ -91,7 +89,7 @@
</view>
<view class="task-main">
<text class="task-name">{{item.title}}</text>
<text class="muted">{{item.time_of_day}} · {{item.completed?'已完成':taskBusyIds[0]===item.id?'正在记录…':'点击完成'}}</text>
<text class="muted">{{item.all_pets?'全部宠物 · ':''}}{{item.time_of_day}} · {{item.completed?'已完成':taskBusyIds[0]===item.id?'正在记录…':'点击完成'}}</text>
</view>
</view>
<view wx:if="{{!tasks.length}}" class="empty">
-6
View File
@@ -189,12 +189,6 @@
border-radius: 22rpx;
margin-bottom: 20rpx;
}
.refresh-hint {
text-align: center;
font-size: 22rpx;
color: #a48d7e;
margin-bottom: 20rpx;
}
.first-pet {
padding-top: 30rpx;
}
+7 -3
View File
@@ -4,6 +4,7 @@ const { localDate, shiftDate } = require("../../utils/date");
Page({
data: {
pets: [],
petOptions: [],
tasks: [],
taskBusyIds: [],
completingAll: false,
@@ -32,10 +33,11 @@ Page({
if (date !== this.data.date) return;
const tasks = result.items.map((t) => ({
...t,
petName: (pets.find((p) => p.id === t.pet_id) || {}).name || "宠物",
petName: t.all_pets ? "全部宠物" : (pets.find((p) => p.id === t.pet_id) || {}).name || "宠物",
}));
this.setData({
pets,
petOptions: [...pets, { id: "", name: "全部宠物" }],
tasks,
undoneCount: tasks.filter((t) => !t.completed).length,
done: tasks.filter((t) => t.completed).length,
@@ -78,7 +80,7 @@ Page({
showForm: true,
editingId: task.id,
title: task.title,
petIndex: Math.max(petIndex, 0),
petIndex: task.all_pets ? this.data.pets.length : Math.max(petIndex, 0),
category: task.category,
time_of_day: task.time_of_day,
repeat_rule: task.repeat_rule,
@@ -125,7 +127,9 @@ Page({
this.data.editingId ? "/tasks/" + this.data.editingId : "/tasks",
this.data.editingId ? "PUT" : "POST",
{
pet_id: this.data.pets[this.data.petIndex].id,
...(this.data.petIndex === this.data.pets.length
? { all_pets: true }
: { 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,
+2 -2
View File
@@ -71,12 +71,12 @@
<view class="field">
<view class="field-label">照顾谁</view>
<picker
range="{{pets}}"
range="{{petOptions}}"
range-key="name"
value="{{petIndex}}"
bindchange="petChange"
>
<view class="picker">{{pets[petIndex].name||'请先添加宠物'}}</view>
<view class="picker">{{petOptions[petIndex].name||'请先添加宠物'}}</view>
</picker>
</view>
<view class="field">