feat: support shared pet care tasks and smooth daily page return
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user