feat: rebuild pet app and backend around core care flows

This commit is contained in:
2026-10-01 13:11:30 +08:00
parent 51ead8f411
commit bfcf8788a4
321 changed files with 4918 additions and 25199 deletions
+88
View File
@@ -0,0 +1,88 @@
const { secure, upload, toast } = require("../../utils/api");
Page({
data: {
id: "",
name: "",
breed: "柯基",
gender: "male",
birth_date: "",
weight_kg: "",
coat: "",
allergies: "",
care_notes: "",
avatar_file_id: "",
avatar_url: "",
},
onLoad(options) {
if (options.id) {
this.setData({ id: options.id });
this.load(options.id);
}
},
async load(id) {
try {
this.setData(await secure("/pets/" + id));
} catch (e) {
toast(e);
}
},
field(e) {
this.setData({ [e.currentTarget.dataset.key]: e.detail.value });
},
date(e) {
this.setData({ birth_date: e.detail.value });
},
gender(e) {
this.setData({ gender: e.detail.value });
},
async choose() {
try {
const r = await wx.chooseMedia({ count: 1, mediaType: ["image"] });
const file = await upload(r.tempFiles[0].tempFilePath);
this.setData({ avatar_file_id: file.id, avatar_url: file.url });
} catch (e) {
toast(e);
}
},
async save() {
if (!this.data.name.trim()) {
wx.showToast({ title: "请输入名字", icon: "none" });
return;
}
const body = {
name: this.data.name,
species: "dog",
breed: this.data.breed,
gender: this.data.gender,
birth_date: this.data.birth_date || "",
weight_kg: Number(this.data.weight_kg) || 0,
coat: this.data.coat,
allergies: this.data.allergies,
care_notes: this.data.care_notes,
avatar_file_id: this.data.avatar_file_id || null,
};
try {
await secure(
"/pets" + (this.data.id ? "/" + this.data.id : ""),
this.data.id ? "PUT" : "POST",
body,
);
wx.navigateBack();
} catch (e) {
toast(e);
}
},
async archive() {
const confirm = await wx.showModal({
title: "归档宠物",
content: "归档后不会再出现在遛狗和任务中。",
});
if (!confirm.confirm) return;
try {
await secure("/pets/" + this.data.id, "DELETE");
wx.navigateBack();
} catch (e) {
toast(e);
}
},
});
+1
View File
@@ -0,0 +1 @@
{}
+64
View File
@@ -0,0 +1,64 @@
<view class="screen">
<view class="eyebrow">PET PROFILE</view>
<view class="title">{{id?'编辑档案':'认识新朋友'}} 🐶</view>
<view class="photo" bindtap="choose">
<image wx:if="{{avatar_url}}" src="{{avatar_url}}" mode="aspectFill" />
<text wx:else>🐕</text>
<view>点击上传照片</view>
</view>
<view class="field">
<view class="field-label">名字 *</view>
<input
placeholder="它叫什么名字?"
value="{{name}}"
data-key="name"
bindinput="field"
/>
</view>
<view class="field">
<view class="field-label">品种</view>
<input value="{{breed}}" data-key="breed" bindinput="field" />
</view>
<view class="field">
<view class="field-label">性别</view>
<radio-group bindchange="gender">
<label>
<radio value="male" checked="{{gender==='male'}}" color="#194f42" />
男孩
</label>
<label>
<radio value="female" checked="{{gender==='female'}}" color="#194f42" />
女孩
</label>
</radio-group>
</view>
<view class="field">
<view class="field-label">生日</view>
<picker mode="date" value="{{birth_date}}" bindchange="date">
<view class="picker">{{birth_date||'选择生日'}}</view>
</picker>
</view>
<view class="field">
<view class="field-label">体重 kg</view>
<input
type="digit"
value="{{weight_kg}}"
data-key="weight_kg"
bindinput="field"
/>
</view>
<view class="field">
<view class="field-label">毛色</view>
<input value="{{coat}}" data-key="coat" bindinput="field" />
</view>
<view class="field">
<view class="field-label">过敏提醒</view>
<input value="{{allergies}}" data-key="allergies" bindinput="field" />
</view>
<view class="field">
<view class="field-label">照顾备注</view>
<textarea value="{{care_notes}}" data-key="care_notes" bindinput="field" />
</view>
<button class="primary" bindtap="save">保存档案</button>
<button wx:if="{{id}}" class="archive" bindtap="archive">归档这只宠物</button>
</view>
+50
View File
@@ -0,0 +1,50 @@
.photo {
width: 190rpx;
height: 190rpx;
border-radius: 42rpx;
background: #f2d5aa;
display: grid;
place-items: center;
margin: 0 auto 35rpx;
position: relative;
font-size: 80rpx;
overflow: hidden;
}
.photo image {
width: 100%;
height: 100%;
}
.photo view {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #194f42b5;
color: white;
text-align: center;
font-size: 18rpx;
padding: 6rpx;
}
.field radio-group {
display: flex;
gap: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 20rpx;
}
.field label {
display: flex;
align-items: center;
gap: 5rpx;
}
.picker {
background: white;
border-radius: 20rpx;
padding: 22rpx;
}
.archive {
background: transparent;
color: #a66c5a;
font-size: 24rpx;
margin-top: 20rpx;
}