Files
PetCommunity/miniprogram/pages/pet-edit/pet-edit.ts
chenwu 3d21a9d297 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 报错
2026-06-07 15:59:21 +08:00

281 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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: {
name: '',
species: 'dog' as 'dog' | 'cat' | 'other',
breed: '',
gender: 'female' as 'female' | 'male',
birthday: '',
bio: '',
tags: [] as string[],
emoji: '🐶',
},
speciesLabel: '狗狗',
// 品种
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()
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 })
this.loadPet(query.id)
}
},
loadPet(id: string) {
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,
species: pet.species,
breed: pet.breed,
gender: pet.gender,
birthday: pet.birthday || '',
bio: pet.bio || '',
tags: petTags,
emoji: pet.emoji,
},
speciesLabel: SPECIES_LABELS[pet.species] || '狗狗',
currentBreeds: breeds,
breedIndex,
allTags: merged,
selectedTagSet: buildTagSet(petTags),
})
},
onField(e: WechatMiniprogram.CustomEvent) {
const key = e.currentTarget.dataset.key as string
this.setData({ [`form.${key}`]: e.detail.value })
},
onSpecies() {
wx.showActionSheet({
itemList: ['狗狗', '猫猫', '其他'],
success: res => {
const species = (['dog', 'cat', 'other'] as const)[res.tapIndex]
const breeds = BREED_OPTIONS[species]
this.setData({
'form.species': species,
'form.breed': '',
'form.emoji': getPetEmoji(species),
speciesLabel: SPECIES_LABELS[species],
currentBreeds: breeds,
breedIndex: -1,
})
},
})
},
// 品种 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),
})
},
onGender(e: WechatMiniprogram.CustomEvent) {
this.setData({ 'form.gender': e.currentTarget.dataset.v })
},
onBirthday(e: WechatMiniprogram.CustomEvent) {
const dob = e.detail.value as string
const diffMs = Date.now() - new Date(dob).getTime()
const years = Math.floor(diffMs / (365.25 * 24 * 3600 * 1000))
const months = Math.floor((diffMs % (365.25 * 24 * 3600 * 1000)) / (30.44 * 24 * 3600 * 1000))
const age = years > 0 ? `${years}${months > 0 ? months + '个月' : ''}` : `${months}个月`
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 >= 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() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: async res => {
const path = res.tempFiles[0].tempFilePath
wx.showLoading({ title: '上传中...' })
try {
const cloudPath = `pet-avatars/${Date.now()}.jpg`
const r = await wx.cloud.uploadFile({ cloudPath, filePath: path })
this.setData({ 'form.avatarUrl': r.fileID })
wx.hideLoading()
} catch {
wx.hideLoading()
wx.showToast({ title: '上传失败', icon: 'none' })
}
},
})
},
async onSave() {
const { form, isEdit, petId } = this.data
if (!form.name.trim()) {
wx.showToast({ title: '请填写宠物名字', icon: 'none' })
return
}
wx.showLoading({ title: '保存中...' })
try {
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 (e) {
wx.hideLoading()
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) 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' })
})
},
})
},
onBack() { wx.navigateBack() },
})