This commit is contained in:
2026-06-25 18:24:29 +08:00
commit a1190d7d9a
90 changed files with 5459 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
// pages/dog-edit/dog-edit — 0b 建档/编辑复用docs/05 R2
// 仅名字、犬种必填。未知犬种(中华田园犬/其他)需手动选体型档位。?id= 进编辑模式。
const { call } = require('../../utils/cloud.js');
const store = require('../../utils/store.js');
const { SIZE_LABELS, sizeOfBreed } = require('../../utils/dogStep.js');
const { chooseAndUpload } = require('../../utils/media.js');
const BREEDS = [
'柯基', '比熊', '泰迪', '柴犬', '金毛', '拉布拉多',
'哈士奇', '边境牧羊犬', '萨摩耶', '法斗', '中华田园犬', '其他',
];
const AGE_STAGES = [
{ key: 'puppy', label: '幼犬' },
{ key: 'adult', label: '成犬' },
{ key: 'senior', label: '老年犬' },
];
Page({
data: {
isEdit: false,
dogId: '',
breeds: BREEDS,
ageStages: AGE_STAGES,
sizeLabels: SIZE_LABELS,
breedIndex: -1,
needSize: false, // 未知犬种时需手动选体型
form: {
name: '',
breed: '',
sizeLevel: '',
gender: '',
ageStage: '',
weight: '',
avatar: '',
},
submitting: false,
},
onLoad(options) {
const id = options.id;
if (id) {
const dog = (store.getState().dogs || []).find((d) => d._id === id);
if (dog) {
wx.setNavigationBarTitle({ title: '编辑资料' });
this.setData({
isEdit: true,
dogId: id,
breedIndex: Math.max(0, BREEDS.indexOf(dog.breed)),
needSize: !sizeOfBreed(dog.breed),
form: {
name: dog.name || '',
breed: dog.breed || '',
sizeLevel: dog.sizeLevel || '',
gender: dog.gender || '',
ageStage: dog.ageStage || '',
weight: dog.weight != null ? String(dog.weight) : '',
avatar: dog.avatar || '',
},
});
}
}
},
onNameInput(e) { this.setData({ 'form.name': e.detail.value }); },
onWeightInput(e) { this.setData({ 'form.weight': e.detail.value }); },
async onPickAvatar() {
const fileID = await chooseAndUpload('avatars');
if (fileID) this.setData({ 'form.avatar': fileID });
},
onBreedChange(e) {
const idx = Number(e.detail.value);
const breed = BREEDS[idx];
const mapped = sizeOfBreed(breed);
this.setData({
breedIndex: idx,
'form.breed': breed,
needSize: !mapped, // 未知犬种 → 手动选
'form.sizeLevel': mapped || '',
});
},
onSizeTap(e) { this.setData({ 'form.sizeLevel': e.currentTarget.dataset.v }); },
onGenderTap(e) {
const v = e.currentTarget.dataset.v;
this.setData({ 'form.gender': this.data.form.gender === v ? '' : v });
},
onAgeTap(e) {
const v = e.currentTarget.dataset.v;
this.setData({ 'form.ageStage': this.data.form.ageStage === v ? '' : v });
},
async onSubmit() {
const { form, isEdit, dogId, needSize, submitting } = this.data;
if (submitting) return;
if (!form.name.trim()) return wx.showToast({ title: '请填写名字', icon: 'none' });
if (!form.breed) return wx.showToast({ title: '请选择犬种', icon: 'none' });
if (needSize && !form.sizeLevel) return wx.showToast({ title: '请选择体型档位', icon: 'none' });
const ageText = (AGE_STAGES.find((a) => a.key === form.ageStage) || {}).label || '';
const payload = {
name: form.name.trim(),
breed: form.breed,
sizeLevel: form.sizeLevel || null, // 已知犬种服务端会覆盖
gender: form.gender || null,
ageStage: form.ageStage || null,
ageText,
weight: form.weight ? Number(form.weight) : null,
avatar: form.avatar || '',
};
this.setData({ submitting: true });
const res = await call('dogManage', {
action: isEdit ? 'update' : 'create',
dog: isEdit ? { _id: dogId, ...payload } : payload,
}, { loading: true, loadingText: '保存中' });
this.setData({ submitting: false });
if (!res.ok) return;
const dogs = (store.getState().dogs || []).slice();
const i = dogs.findIndex((d) => d._id === res.data._id);
if (i >= 0) dogs[i] = res.data; else dogs.push(res.data);
store.set({ dogs, currentDogId: res.data._id });
if (isEdit) wx.navigateBack();
else wx.switchTab({ url: '/pages/home/home' });
},
});

View File

@@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "认识你的狗狗"
}

View File

@@ -0,0 +1,70 @@
<view class="page">
<!-- 头像(可选) -->
<view class="avatar-block" bindtap="onPickAvatar">
<image wx:if="{{form.avatar}}" class="avatar" src="{{form.avatar}}" mode="aspectFill" />
<view wx:else class="avatar">🐶</view>
<view class="avatar-tip">点击上传头像(可选)</view>
</view>
<!-- 名字 * -->
<view class="field">
<view class="flabel">名字 <text class="req">*</text></view>
<input class="finput" placeholder="给狗狗起个名字" value="{{form.name}}" bindinput="onNameInput" maxlength="12" />
</view>
<!-- 犬种 * -->
<view class="field">
<view class="flabel">犬种 <text class="req">*</text></view>
<picker mode="selector" range="{{breeds}}" value="{{breedIndex}}" bindchange="onBreedChange">
<view class="fpicker {{form.breed ? '' : 'placeholder'}}">
{{form.breed || '请选择犬种'}}
<text class="chev"></text>
</view>
</picker>
</view>
<!-- 体型档位(未知犬种必选,用于狗步换算) -->
<view wx:if="{{needSize}}" class="field">
<view class="flabel">体型档位 <text class="req">*</text></view>
<view class="chips">
<view wx:for="{{sizeLabels}}" wx:key="key"
class="chip {{form.sizeLevel===item.key?'sel':''}}"
data-v="{{item.key}}" bindtap="onSizeTap">{{item.label}}</view>
</view>
<view class="size-tip">找不到品种时,按腿长/体型选一个,用于估算狗步</view>
</view>
<!-- 性别 -->
<view class="field">
<view class="flabel">性别</view>
<view class="chips">
<view class="chip {{form.gender==='male'?'sel':''}}" data-v="male" bindtap="onGenderTap">公</view>
<view class="chip {{form.gender==='female'?'sel':''}}" data-v="female" bindtap="onGenderTap">母</view>
</view>
</view>
<!-- 年龄(可选) -->
<view class="field">
<view class="flabel">年龄(可选)</view>
<view class="chips">
<view wx:for="{{ageStages}}" wx:key="key"
class="chip {{form.ageStage===item.key?'sel':''}}"
data-v="{{item.key}}" bindtap="onAgeTap">{{item.label}}</view>
</view>
</view>
<!-- 体重(可选) -->
<view class="field last">
<view class="flabel">体重(可选,用于估算狗步)</view>
<view class="weight-row">
<input class="finput" type="digit" placeholder="如 12" value="{{form.weight}}" bindinput="onWeightInput" />
<text class="unit">kg</text>
</view>
</view>
<view class="submit">
<button class="btn primary" bindtap="onSubmit" loading="{{submitting}}">
{{isEdit ? '保存' : '完成,开始遛狗'}}
</button>
</view>
</view>

View File

@@ -0,0 +1,70 @@
.page { min-height: 100vh; padding-bottom: calc(48rpx + env(safe-area-inset-bottom)); }
.avatar-block {
display: flex; flex-direction: column; align-items: center;
padding: 36rpx 0 8rpx;
}
.avatar {
width: 156rpx; height: 156rpx; border-radius: 50%;
background: var(--surface-2);
display: flex; align-items: center; justify-content: center;
font-size: 72rpx;
border: 1rpx solid var(--line);
}
.avatar-tip { margin-top: 16rpx; font-size: 22rpx; color: var(--ink-2); }
.field {
padding: 22rpx 32rpx;
border-bottom: 1rpx solid var(--line);
}
.field.last { border-bottom: none; }
.flabel { font-size: var(--form-label-size); color: var(--ink-2); margin-bottom: 12rpx; }
.req { color: var(--danger); }
.finput {
height: var(--input-height);
min-height: var(--input-height);
font-size: var(--input-font-size);
color: var(--ink);
background: var(--surface-2);
border-radius: var(--input-radius);
padding: var(--input-padding-y) var(--input-padding-x);
line-height: 1.4;
}
.fpicker {
min-height: var(--input-height);
font-size: var(--input-font-size);
color: var(--ink);
background: var(--surface-2);
border-radius: var(--input-radius);
padding: var(--input-padding-y) var(--input-padding-x);
line-height: 1.4;
display: flex; align-items: center; justify-content: space-between;
}
.fpicker.placeholder { color: var(--ink-3); }
.fpicker .chev { color: var(--ink-3); font-size: var(--icon-size-sm); line-height: 1; }
.chips { display: flex; flex-wrap: wrap; gap: 16rpx; }
.chip {
font-size: var(--chip-font-size);
padding: var(--chip-padding-y) var(--chip-padding-x);
border-radius: var(--radius-pill);
border: 1rpx solid var(--line-2);
background: var(--surface-2);
color: var(--ink);
line-height: 1.4;
}
.chip.sel {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent-deep);
font-weight: 600;
}
.size-tip { font-size: 21rpx; color: var(--ink-3); margin-top: 12rpx; }
.weight-row { display: flex; align-items: center; gap: 16rpx; }
.weight-row .finput { flex: 1; }
.weight-row .unit { font-size: var(--input-font-size); color: var(--ink-2); }
.submit { padding: 40rpx 32rpx 0; }