fix: 修复评论/资料/导航胶囊/宠物档案/定位等多处问题,补全相关云函数

- 新增 addComment/getComments/getFollowList/followUser/getUser/updateProfile/updatePet/deletePet 云函数并完成部署
- 统一通过 app.globalData.navBarInfo 避让系统胶囊按钮(编辑资料/编辑宠物等页面保存按钮被遮挡问题)
- 编辑资料页所在城市改为省份 picker 选择
- 修复"我的"页动态为空(getUserPosts 应传 openid 而非数据库 _id)
- 重构编辑宠物页:品种改用 picker 滚轮(替代有数量上限的 actionSheet),标签改用 selectedTagSet 映射 + 自定义标签输入(5字以内、仅中英文/数字、最多8个)
- 接通宠物保存/删除真实云函数调用并同步本地缓存
- 移除 feed 页对不存在的 getStories 云函数调用,修复模拟器卡死
- 移除 post 页对不存在的 wx.reverseGeocoder API 的调用
- 统一扩展 AppGlobalData 类型并修正所有 getApp 调用的泛型,解决 TS 报错
This commit is contained in:
2026-06-07 15:59:21 +08:00
parent 24891d9004
commit 3d21a9d297
49 changed files with 937 additions and 195 deletions

View File

@@ -1,15 +1,26 @@
import { api } from '../../utils/api'
import { BREED_OPTIONS } from '../../utils/constants'
import { getPetEmoji } from '../../utils/format'
import { AppGlobalData } from '../../types/index'
const SPECIES_LABELS: Record<string, string> = { dog: '狗狗', cat: '猫猫', other: '其他' }
const PET_TAGS = ['活泼', '温顺', '黏人', '独立', '爱玩', '怕生', '聪明', '贪吃', '爱运动', '安静']
const app = getApp<{ userInfo: any }>()
const PRESET_TAGS = ['活泼', '温顺', '黏人', '独立', '爱玩', '怕生', '聪明', '贪吃', '爱运动', '安静']
const app = getApp<{ globalData: AppGlobalData }>()
// 根据已选标签数组生成 {tag: boolean} 映射,供 WXML 直接判断选中态
function buildTagSet(tags: string[]): Record<string, boolean> {
const set: Record<string, boolean> = {}
tags.forEach(t => { set[t] = true })
return set
}
Page({
data: {
statusBarHeight: 0,
navbarHeight: 44,
navbarPaddingRight: 0,
isEdit: false,
petId: '',
form: {
@@ -23,12 +34,24 @@ Page({
emoji: '🐶',
},
speciesLabel: '狗狗',
allTags: PET_TAGS,
// 品种
currentBreeds: BREED_OPTIONS['dog'],
breedIndex: -1,
// 标签
allTags: [...PRESET_TAGS] as string[],
selectedTagSet: {} as Record<string, boolean>,
customTagInput: '',
},
onLoad(query: { id?: string }) {
const info = wx.getSystemInfoSync()
this.setData({ statusBarHeight: info.statusBarHeight })
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
this.setData({
statusBarHeight,
navbarHeight,
navbarPaddingRight,
currentBreeds: BREED_OPTIONS['dog'],
})
if (query.id) {
this.setData({ isEdit: true, petId: query.id })
@@ -40,6 +63,15 @@ Page({
const user = app.globalData?.userInfo
const pet = user?.pets?.find((p: any) => p._id === id)
if (!pet) return
const breeds = BREED_OPTIONS[pet.species] || BREED_OPTIONS['dog']
const breedIndex = breeds.indexOf(pet.breed)
const petTags: string[] = pet.tags || []
// 把宠物已有的自定义标签合并进 allTags
const merged = [...PRESET_TAGS]
petTags.forEach(t => { if (!merged.includes(t)) merged.push(t) })
this.setData({
form: {
name: pet.name,
@@ -48,10 +80,14 @@ Page({
gender: pet.gender,
birthday: pet.birthday || '',
bio: pet.bio || '',
tags: pet.tags || [],
tags: petTags,
emoji: pet.emoji,
},
speciesLabel: SPECIES_LABELS[pet.species] || '狗狗',
currentBreeds: breeds,
breedIndex,
allTags: merged,
selectedTagSet: buildTagSet(petTags),
})
},
@@ -65,27 +101,27 @@ Page({
itemList: ['狗狗', '猫猫', '其他'],
success: res => {
const species = (['dog', 'cat', 'other'] as const)[res.tapIndex]
const breeds = BREED_OPTIONS[species]
this.setData({
'form.species': species,
'form.breed': '',
speciesLabel: SPECIES_LABELS[species],
'form.emoji': getPetEmoji(species),
speciesLabel: SPECIES_LABELS[species],
currentBreeds: breeds,
breedIndex: -1,
})
},
})
},
onBreed() {
const breeds = BREED_OPTIONS[this.data.form.species] || BREED_OPTIONS.dog
wx.showActionSheet({
itemList: breeds,
success: res => {
const breed = breeds[res.tapIndex]
this.setData({
'form.breed': breed,
'form.emoji': getPetEmoji(this.data.form.species, breed),
})
},
// 品种 picker 选中回调
onBreedChange(e: WechatMiniprogram.CustomEvent) {
const idx = Number(e.detail.value)
const breed = this.data.currentBreeds[idx]
this.setData({
breedIndex: idx,
'form.breed': breed,
'form.emoji': getPetEmoji(this.data.form.species, breed),
})
},
@@ -102,13 +138,64 @@ Page({
this.setData({ 'form.birthday': dob, 'form.age': age })
},
// 预设标签 — 点击切换
onTagToggle(e: WechatMiniprogram.CustomEvent) {
const tag = e.currentTarget.dataset.tag as string
const tags = [...this.data.form.tags]
const idx = tags.indexOf(tag)
if (idx >= 0) tags.splice(idx, 1)
else if (tags.length < 5) tags.push(tag)
this.setData({ 'form.tags': tags })
if (idx >= 0) {
tags.splice(idx, 1)
} else {
if (tags.length >= 8) {
wx.showToast({ title: '最多选8个标签', icon: 'none' })
return
}
tags.push(tag)
}
this.setData({ 'form.tags': tags, selectedTagSet: buildTagSet(tags) })
},
// 自定义标签输入
onCustomTagInput(e: WechatMiniprogram.CustomEvent) {
this.setData({ customTagInput: e.detail.value })
},
// 添加自定义标签
onAddCustomTag() {
const raw = this.data.customTagInput.trim()
if (!raw) return
// 仅允许中英文和数字最多5个字符
if ([...raw].length > 5) {
wx.showToast({ title: '标签最多5个字', icon: 'none' })
return
}
if (!/^[一-龥a-zA-Z0-9]+$/.test(raw)) {
wx.showToast({ title: '只能输入文字或字母', icon: 'none' })
return
}
const tags = [...this.data.form.tags]
if (tags.includes(raw)) {
this.setData({ customTagInput: '' })
return
}
if (tags.length >= 8) {
wx.showToast({ title: '最多选8个标签', icon: 'none' })
return
}
tags.push(raw)
const allTags = this.data.allTags.includes(raw)
? this.data.allTags
: [...this.data.allTags, raw]
this.setData({
'form.tags': tags,
selectedTagSet: buildTagSet(tags),
allTags,
customTagInput: '',
})
},
async onChoosePhoto() {
@@ -140,27 +227,51 @@ Page({
}
wx.showLoading({ title: '保存中...' })
try {
await api.updatePet(isEdit ? { ...form, _id: petId } : form)
const saved = await api.updatePet(isEdit ? { ...form, _id: petId } : form)
// 同步本地缓存,让"我的"页面立即看到最新的宠物档案
const user = app.globalData.userInfo
if (user) {
const pets = Array.isArray(user.pets) ? [...user.pets] : []
const idx = pets.findIndex((p: any) => p._id === saved._id)
if (idx >= 0) pets[idx] = saved
else pets.push(saved)
user.pets = pets
}
wx.hideLoading()
wx.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => wx.navigateBack(), 800)
} catch {
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '保存失败', icon: 'none' })
const message = e instanceof Error ? e.message : '保存失败'
wx.showToast({ title: message, icon: 'none' })
}
},
onDelete() {
const { petId } = this.data
wx.showModal({
title: '删除宠物档案',
content: '确定要删除吗?',
confirmText: '删除',
confirmColor: '#ff4f91',
success: res => {
if (res.confirm) {
wx.showToast({ title: '删除', icon: 'success' })
setTimeout(() => wx.navigateBack(), 800)
}
if (!res.confirm) return
wx.showLoading({ title: '删除中...' })
api.deletePet(petId)
.then(() => {
const user = app.globalData.userInfo
if (user && Array.isArray(user.pets)) {
user.pets = user.pets.filter((p: any) => p._id !== petId)
}
wx.hideLoading()
wx.showToast({ title: '已删除', icon: 'success' })
setTimeout(() => wx.navigateBack(), 800)
})
.catch((e: unknown) => {
wx.hideLoading()
const message = e instanceof Error ? e.message : '删除失败'
wx.showToast({ title: message, icon: 'none' })
})
},
})
},

View File

@@ -1,6 +1,9 @@
<view class="page">
<!-- 状态栏占位 -->
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
<view class="nav">
<!-- 导航栏:动态高度 + 右侧胶囊避让 -->
<view class="nav" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px; box-sizing: border-box;">
<view class="nav-back" bindtap="onBack">取消</view>
<view class="nav-title">{{isEdit ? '编辑宠物' : '添加宠物'}}</view>
<view class="nav-save" bindtap="onSave">保存</view>
@@ -17,8 +20,10 @@
<text class="avatar-tip">点击更换照片</text>
</view>
<!-- 名字 -->
<!-- 基础信息 -->
<view class="field-group">
<!-- 名字 -->
<view class="field-item">
<text class="field-label">宠物名字 *</text>
<input class="field-input" value="{{form.name}}" bindinput="onField" data-key="name" placeholder="给TA起个名字" maxlength="10" />
@@ -33,13 +38,15 @@
</view>
</view>
<!-- 品种 -->
<view class="field-item" bindtap="onBreed">
<!-- 品种 — picker 滚轮 -->
<view class="field-item">
<text class="field-label">品种</text>
<view class="field-select">
<text>{{form.breed || '选择品种'}}</text>
<text class="select-arrow"></text>
</view>
<picker mode="selector" range="{{currentBreeds}}" value="{{breedIndex}}" bindchange="onBreedChange">
<view class="field-select">
<text class="{{form.breed ? '' : 'placeholder-text'}}">{{form.breed || '选择品种'}}</text>
<text class="select-arrow"></text>
</view>
</picker>
</view>
<!-- 性别 -->
@@ -56,7 +63,7 @@
<text class="field-label">生日</text>
<picker mode="date" value="{{form.birthday}}" bindchange="onBirthday">
<view class="field-select">
<text>{{form.birthday || '选择生日'}}</text>
<text class="{{form.birthday ? '' : 'placeholder-text'}}">{{form.birthday || '选择生日'}}</text>
<text class="select-arrow"></text>
</view>
</picker>
@@ -80,22 +87,41 @@
<!-- 性格标签 -->
<view class="tags-section">
<text class="tags-title">性格标签</text>
<view class="tags-header">
<text class="tags-title">性格标签</text>
<text class="tags-count">已选 {{form.tags.length}}/8</text>
</view>
<!-- 预设 + 自定义标签网格 -->
<view class="tag-grid">
<view
wx:for="{{allTags}}"
wx:key="index"
class="tag-chip {{form.tags.includes(item) ? 'tag-on' : ''}}"
class="tag-chip {{selectedTagSet[item] ? 'tag-on' : ''}}"
bindtap="onTagToggle"
data-tag="{{item}}"
>{{item}}</view>
</view>
<!-- 自定义标签输入 -->
<view class="custom-tag-row">
<input
class="custom-tag-input"
value="{{customTagInput}}"
bindinput="onCustomTagInput"
bindconfirm="onAddCustomTag"
placeholder="自定义标签最多5字"
maxlength="5"
confirm-type="done"
/>
<view class="custom-tag-add {{customTagInput ? '' : 'add-disabled'}}" bindtap="onAddCustomTag">添加</view>
</view>
</view>
<!-- 删除 -->
<view wx:if="{{isEdit}}" class="delete-btn" bindtap="onDelete">删除这只宠物</view>
<view style="height: 60rpx;"></view>
<view style="height: 80rpx;"></view>
</view>
</scroll-view>
</view>

View File

@@ -26,10 +26,19 @@
.gender-btn { background: rgba(255,255,255,0.80); border: 1rpx solid rgba(43,37,61,0.12); border-radius: 999rpx; padding: 12rpx 24rpx; font-size: 26rpx; color: #9b8fa8; font-weight: 700; }
.g-active { background: #ffe5f0; border-color: #ffb6d0; color: #a91d5b; }
.placeholder-text { color: #c4b8d0; }
.tags-section { margin-bottom: 28rpx; }
.tags-title { display: block; font-size: 26rpx; font-weight: 900; color: #9b8fa8; margin-bottom: 16rpx; }
.tag-grid { display: flex; gap: 14rpx; flex-wrap: wrap; }
.tags-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
.tags-title { font-size: 26rpx; font-weight: 900; color: #9b8fa8; }
.tags-count { font-size: 22rpx; color: #c4b8d0; }
.tag-grid { display: flex; gap: 14rpx; flex-wrap: wrap; margin-bottom: 20rpx; }
.tag-chip { background: rgba(255,255,255,0.80); border: 1rpx solid rgba(43,37,61,0.10); border-radius: 999rpx; padding: 12rpx 24rpx; font-size: 26rpx; color: #6a6178; font-weight: 700; }
.tag-on { background: #f0e8ff; border-color: #d4b8ff; color: #8c5cff; }
.custom-tag-row { display: flex; gap: 14rpx; align-items: center; margin-top: 4rpx; }
.custom-tag-input { flex: 1; height: 68rpx; background: rgba(255,255,255,0.80); border: 1rpx solid rgba(43,37,61,0.10); border-radius: 999rpx; padding: 0 24rpx; font-size: 26rpx; color: #272235; }
.custom-tag-add { background: linear-gradient(135deg, #ff4f91, #ff9f1c); color: #fff; border-radius: 999rpx; padding: 14rpx 30rpx; font-size: 26rpx; font-weight: 800; flex-shrink: 0; }
.add-disabled { background: #e8e4f0; color: #9b8fa8; }
.delete-btn { text-align: center; font-size: 28rpx; color: #ff4f91; background: rgba(255,79,145,0.08); border-radius: 999rpx; padding: 24rpx; margin-top: 40rpx; }