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:
@@ -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' })
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user