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:
@@ -58,6 +58,62 @@
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "打招呼 / 发起私信"
|
||||
},
|
||||
{
|
||||
"name": "addComment",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "发表评论"
|
||||
},
|
||||
{
|
||||
"name": "getComments",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "获取帖子评论列表"
|
||||
},
|
||||
{
|
||||
"name": "getFollowList",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "获取关注/粉丝列表"
|
||||
},
|
||||
{
|
||||
"name": "followUser",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "关注/取消关注用户"
|
||||
},
|
||||
{
|
||||
"name": "getUser",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "获取用户信息"
|
||||
},
|
||||
{
|
||||
"name": "updateProfile",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "更新用户资料"
|
||||
},
|
||||
{
|
||||
"name": "updatePet",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "新增或更新宠物档案"
|
||||
},
|
||||
{
|
||||
"name": "deletePet",
|
||||
"timeout": 10,
|
||||
"runtime": "Nodejs20.19",
|
||||
"handler": "index.main",
|
||||
"description": "删除宠物档案"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
43
cloudfunctions/addComment/index.js
Normal file
43
cloudfunctions/addComment/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { postId, content } = event
|
||||
|
||||
if (!postId || !content?.trim()) {
|
||||
return { code: -1, message: '参数错误' }
|
||||
}
|
||||
|
||||
try {
|
||||
const postRes = await db.collection('posts').doc(postId).get().catch(() => null)
|
||||
if (!postRes?.data) return { code: -1, message: '帖子不存在' }
|
||||
|
||||
const comment = {
|
||||
postId,
|
||||
authorId: OPENID,
|
||||
content: content.trim(),
|
||||
likeCount: 0,
|
||||
createdAt: db.serverDate(),
|
||||
}
|
||||
const res = await db.collection('comments').add({ data: comment })
|
||||
|
||||
await db.collection('posts').doc(postId).update({
|
||||
data: { commentCount: _.inc(1) },
|
||||
})
|
||||
|
||||
const authorRes = await db.collection('users')
|
||||
.where({ openid: OPENID })
|
||||
.field({ nickName: true, avatarUrl: true, openid: true })
|
||||
.get()
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: { _id: res._id, ...comment, author: authorRes.data[0] || null },
|
||||
}
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/addComment/package.json
Normal file
1
cloudfunctions/addComment/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "addComment", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
30
cloudfunctions/deletePet/index.js
Normal file
30
cloudfunctions/deletePet/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { petId } = event || {}
|
||||
|
||||
if (!petId) return { code: -1, message: '缺少宠物ID' }
|
||||
|
||||
try {
|
||||
const userRes = await db.collection('users').where({ openid: OPENID }).get()
|
||||
if (userRes.data.length === 0) {
|
||||
return { code: -1, message: '用户不存在' }
|
||||
}
|
||||
const user = userRes.data[0]
|
||||
const pets = (Array.isArray(user.pets) ? user.pets : []).filter(p => p._id !== petId)
|
||||
|
||||
await db.collection('users').doc(user._id).update({
|
||||
data: {
|
||||
pets,
|
||||
updatedAt: db.serverDate(),
|
||||
},
|
||||
})
|
||||
|
||||
return { code: 0, data: null }
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/deletePet/package.json
Normal file
1
cloudfunctions/deletePet/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "deletePet", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
40
cloudfunctions/followUser/index.js
Normal file
40
cloudfunctions/followUser/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { targetId } = event
|
||||
if (!targetId || targetId === OPENID) return { code: -1, message: '参数错误' }
|
||||
|
||||
try {
|
||||
const existRes = await db.collection('follows')
|
||||
.where({ followerId: OPENID, followeeId: targetId })
|
||||
.count()
|
||||
|
||||
if (existRes.total > 0) {
|
||||
// unfollow
|
||||
await db.collection('follows')
|
||||
.where({ followerId: OPENID, followeeId: targetId })
|
||||
.remove()
|
||||
await Promise.all([
|
||||
db.collection('users').where({ openid: OPENID }).update({ data: { 'stats.friends': _.inc(-1) } }),
|
||||
db.collection('users').where({ openid: targetId }).update({ data: { 'stats.friends': _.inc(-1) } }),
|
||||
])
|
||||
return { code: 0, data: { following: false } }
|
||||
} else {
|
||||
// follow
|
||||
await db.collection('follows').add({
|
||||
data: { followerId: OPENID, followeeId: targetId, createdAt: db.serverDate() },
|
||||
})
|
||||
await Promise.all([
|
||||
db.collection('users').where({ openid: OPENID }).update({ data: { 'stats.friends': _.inc(1) } }),
|
||||
db.collection('users').where({ openid: targetId }).update({ data: { 'stats.friends': _.inc(1) } }),
|
||||
])
|
||||
return { code: 0, data: { following: true } }
|
||||
}
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/followUser/package.json
Normal file
1
cloudfunctions/followUser/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "followUser", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
45
cloudfunctions/getComments/index.js
Normal file
45
cloudfunctions/getComments/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { postId, page = 0 } = event
|
||||
if (!postId) return { code: -1, message: '缺少 postId' }
|
||||
|
||||
try {
|
||||
const skip = page * PAGE_SIZE
|
||||
const [totalRes, commentsRes] = await Promise.all([
|
||||
db.collection('comments').where({ postId }).count(),
|
||||
db.collection('comments')
|
||||
.where({ postId })
|
||||
.orderBy('createdAt', 'asc')
|
||||
.skip(skip)
|
||||
.limit(PAGE_SIZE)
|
||||
.get(),
|
||||
])
|
||||
|
||||
const comments = commentsRes.data
|
||||
const authorIds = [...new Set(comments.map(c => c.authorId))]
|
||||
|
||||
let authorMap = {}
|
||||
if (authorIds.length > 0) {
|
||||
const authorsRes = await db.collection('users')
|
||||
.where({ openid: _.in(authorIds) })
|
||||
.field({ nickName: true, avatarUrl: true, openid: true })
|
||||
.get()
|
||||
authorsRes.data.forEach(u => { authorMap[u.openid] = u })
|
||||
}
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
list: comments.map(c => ({ ...c, author: authorMap[c.authorId] || null })),
|
||||
total: totalRes.total,
|
||||
},
|
||||
}
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/getComments/package.json
Normal file
1
cloudfunctions/getComments/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "getComments", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
51
cloudfunctions/getFollowList/index.js
Normal file
51
cloudfunctions/getFollowList/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { type = 'following', userId } = event
|
||||
const targetId = userId || OPENID
|
||||
|
||||
try {
|
||||
let followIds = []
|
||||
|
||||
if (type === 'following') {
|
||||
const res = await db.collection('follows')
|
||||
.where({ followerId: targetId })
|
||||
.field({ followeeId: true })
|
||||
.get()
|
||||
followIds = res.data.map(f => f.followeeId)
|
||||
} else {
|
||||
const res = await db.collection('follows')
|
||||
.where({ followeeId: targetId })
|
||||
.field({ followerId: true })
|
||||
.get()
|
||||
followIds = res.data.map(f => f.followerId)
|
||||
}
|
||||
|
||||
if (followIds.length === 0) {
|
||||
return { code: 0, data: [] }
|
||||
}
|
||||
|
||||
const usersRes = await db.collection('users')
|
||||
.where({ openid: _.in(followIds) })
|
||||
.field({ nickName: true, openid: true, avatarUrl: true, bio: true, location: true, isOnline: true, lastSeen: true, pets: true, stats: true })
|
||||
.get()
|
||||
|
||||
// check if current user follows each
|
||||
const myFollowsRes = await db.collection('follows')
|
||||
.where({ followerId: OPENID, followeeId: _.in(followIds) })
|
||||
.field({ followeeId: true })
|
||||
.get()
|
||||
const myFollowSet = new Set(myFollowsRes.data.map(f => f.followeeId))
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: usersRes.data.map(u => ({ ...u, _id: u._id, isFollowing: myFollowSet.has(u.openid) })),
|
||||
}
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/getFollowList/package.json
Normal file
1
cloudfunctions/getFollowList/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "getFollowList", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
40
cloudfunctions/getUser/index.js
Normal file
40
cloudfunctions/getUser/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { userId } = event
|
||||
|
||||
try {
|
||||
let userRes
|
||||
if (userId) {
|
||||
// Look up by DB _id
|
||||
userRes = await db.collection('users').doc(userId).get().catch(() => null)
|
||||
if (!userRes?.data) {
|
||||
// Fallback: look up by openid
|
||||
const res = await db.collection('users').where({ openid: userId }).get()
|
||||
userRes = res.data.length > 0 ? { data: res.data[0] } : null
|
||||
}
|
||||
} else {
|
||||
const res = await db.collection('users').where({ openid: OPENID }).get()
|
||||
userRes = res.data.length > 0 ? { data: res.data[0] } : null
|
||||
}
|
||||
|
||||
if (!userRes?.data) return { code: -1, message: '用户不存在' }
|
||||
|
||||
const user = userRes.data
|
||||
|
||||
// Check follow status
|
||||
const followRes = await db.collection('follows')
|
||||
.where({ followerId: OPENID, followeeId: user.openid })
|
||||
.count()
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: { ...user, isFollowing: followRes.total > 0 },
|
||||
}
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/getUser/package.json
Normal file
1
cloudfunctions/getUser/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "getUser", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
72
cloudfunctions/updatePet/index.js
Normal file
72
cloudfunctions/updatePet/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
// 根据生日计算年龄展示文案
|
||||
function calcAge(birthday) {
|
||||
if (!birthday) return ''
|
||||
const dob = new Date(birthday).getTime()
|
||||
if (isNaN(dob)) return ''
|
||||
const diffMs = Date.now() - dob
|
||||
if (diffMs < 0) return ''
|
||||
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))
|
||||
return years > 0 ? `${years}岁${months > 0 ? months + '个月' : ''}` : `${months}个月`
|
||||
}
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { pet } = event || {}
|
||||
|
||||
if (!pet || !pet.name || !String(pet.name).trim()) {
|
||||
return { code: -1, message: '宠物信息不完整' }
|
||||
}
|
||||
|
||||
try {
|
||||
const userRes = await db.collection('users').where({ openid: OPENID }).get()
|
||||
if (userRes.data.length === 0) {
|
||||
return { code: -1, message: '用户不存在' }
|
||||
}
|
||||
const user = userRes.data[0]
|
||||
const pets = Array.isArray(user.pets) ? [...user.pets] : []
|
||||
|
||||
const petData = {
|
||||
name: String(pet.name).trim(),
|
||||
species: pet.species || 'dog',
|
||||
breed: pet.breed || '',
|
||||
gender: pet.gender || 'female',
|
||||
birthday: pet.birthday || '',
|
||||
age: calcAge(pet.birthday),
|
||||
bio: (pet.bio || '').trim(),
|
||||
tags: Array.isArray(pet.tags) ? pet.tags.slice(0, 8) : [],
|
||||
emoji: pet.emoji || '🐾',
|
||||
photos: pet.avatarUrl ? [pet.avatarUrl] : (Array.isArray(pet.photos) ? pet.photos : []),
|
||||
ownerId: OPENID,
|
||||
}
|
||||
|
||||
let resultPet
|
||||
if (pet._id) {
|
||||
const idx = pets.findIndex(p => p._id === pet._id)
|
||||
if (idx === -1) {
|
||||
return { code: -1, message: '宠物档案不存在' }
|
||||
}
|
||||
resultPet = { ...pets[idx], ...petData, _id: pet._id }
|
||||
pets[idx] = resultPet
|
||||
} else {
|
||||
const newId = `pet_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
resultPet = { _id: newId, ...petData }
|
||||
pets.push(resultPet)
|
||||
}
|
||||
|
||||
await db.collection('users').doc(user._id).update({
|
||||
data: {
|
||||
pets,
|
||||
updatedAt: db.serverDate(),
|
||||
},
|
||||
})
|
||||
|
||||
return { code: 0, data: resultPet }
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/updatePet/package.json
Normal file
1
cloudfunctions/updatePet/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "updatePet", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
27
cloudfunctions/updateProfile/index.js
Normal file
27
cloudfunctions/updateProfile/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { nickName, bio, location } = event
|
||||
|
||||
if (!nickName?.trim()) return { code: -1, message: '昵称不能为空' }
|
||||
|
||||
try {
|
||||
await db.collection('users')
|
||||
.where({ openid: OPENID })
|
||||
.update({
|
||||
data: {
|
||||
nickName: nickName.trim(),
|
||||
bio: (bio || '').trim(),
|
||||
location: (location || '').trim(),
|
||||
updatedAt: db.serverDate(),
|
||||
},
|
||||
})
|
||||
|
||||
return { code: 0, data: null }
|
||||
} catch (e) {
|
||||
return { code: -1, message: e.message }
|
||||
}
|
||||
}
|
||||
1
cloudfunctions/updateProfile/package.json
Normal file
1
cloudfunctions/updateProfile/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "name": "updateProfile", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }
|
||||
@@ -1,19 +1,15 @@
|
||||
import { UserProfile, GeoPoint } from './types/index'
|
||||
import { UserProfile, GeoPoint, AppGlobalData } from './types/index'
|
||||
import { getCachedUser, login } from './utils/auth'
|
||||
import { CLOUD_ENV, HEARTBEAT_INTERVAL_MS } from './utils/constants'
|
||||
import { api } from './utils/api'
|
||||
|
||||
App<{
|
||||
userInfo: UserProfile | null
|
||||
isLoggedIn: boolean
|
||||
currentLocation: GeoPoint | null
|
||||
_locationTimer: number | null
|
||||
}>({
|
||||
App<{ globalData: AppGlobalData }>({
|
||||
globalData: {
|
||||
userInfo: null,
|
||||
isLoggedIn: false,
|
||||
currentLocation: null,
|
||||
_locationTimer: null,
|
||||
navBarInfo: { statusBarHeight: 0, navbarHeight: 44, navbarPaddingRight: 0 },
|
||||
},
|
||||
|
||||
onLaunch() {
|
||||
@@ -22,6 +18,15 @@ App<{
|
||||
traceUser: true,
|
||||
})
|
||||
|
||||
// 测量系统胶囊按钮位置,供所有页面共用
|
||||
const sysInfo = wx.getSystemInfoSync()
|
||||
const capsule = wx.getMenuButtonBoundingClientRect()
|
||||
this.globalData.navBarInfo = {
|
||||
statusBarHeight: sysInfo.statusBarHeight,
|
||||
navbarHeight: (capsule.top - sysInfo.statusBarHeight) * 2 + capsule.height,
|
||||
navbarPaddingRight: sysInfo.windowWidth - capsule.left,
|
||||
}
|
||||
|
||||
const cached = getCachedUser()
|
||||
if (cached) {
|
||||
this.globalData.userInfo = cached
|
||||
|
||||
@@ -14,6 +14,7 @@ Component({
|
||||
avatarGradient: '',
|
||||
timeText: '',
|
||||
likeCount: 0,
|
||||
imageGridClass: 'post-img-grid-0',
|
||||
},
|
||||
|
||||
observers: {
|
||||
@@ -22,20 +23,25 @@ Component({
|
||||
this.setData({
|
||||
avatarGradient: getAvatarGradient(val.authorId || ''),
|
||||
timeText: formatTime(val.createdAt),
|
||||
likeCount: val.likeCount,
|
||||
likeCount: typeof val.likeCount === 'number' ? val.likeCount : 0,
|
||||
imageGridClass: this.getImageGridClass(val.images),
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getImageGridClass(images?: string[]) {
|
||||
if (!Array.isArray(images)) return 'post-img-grid-0'
|
||||
return `post-img-grid-${images.length > 3 ? 'multi' : images.length}`
|
||||
},
|
||||
|
||||
onCardTap() {
|
||||
const post = this.data.post as Post
|
||||
if (!post) return
|
||||
wx.navigateTo({ url: `/pages/post-detail/post-detail?id=${post._id}` })
|
||||
},
|
||||
|
||||
onAuthorTap(e: WechatMiniprogram.CustomEvent) {
|
||||
e.stopPropagation()
|
||||
onAuthorTap() {
|
||||
const post = this.data.post as Post
|
||||
if (!post?.authorId) return
|
||||
wx.navigateTo({ url: `/pages/pet-detail/pet-detail?userId=${post.authorId}` })
|
||||
@@ -50,8 +56,7 @@ Component({
|
||||
})
|
||||
},
|
||||
|
||||
async onLike(e: WechatMiniprogram.CustomEvent) {
|
||||
e.stopPropagation()
|
||||
async onLike() {
|
||||
const post = this.data.post as Post
|
||||
if (!post) return
|
||||
const wasLiked = post.isLiked
|
||||
@@ -72,15 +77,13 @@ Component({
|
||||
}
|
||||
},
|
||||
|
||||
onComment(e: WechatMiniprogram.CustomEvent) {
|
||||
e.stopPropagation()
|
||||
onComment() {
|
||||
const post = this.data.post as Post
|
||||
if (!post) return
|
||||
wx.navigateTo({ url: `/pages/post-detail/post-detail?id=${post._id}&focus=comment` })
|
||||
},
|
||||
|
||||
onShare(e: WechatMiniprogram.CustomEvent) {
|
||||
e.stopPropagation()
|
||||
onShare() {
|
||||
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<view class="post-card" bindtap="onCardTap">
|
||||
<!-- 作者头部 -->
|
||||
<view class="post-header">
|
||||
<view class="post-avatar" style="background: {{avatarGradient}}" bindtap="onAuthorTap" catchtap="onAuthorTap">
|
||||
<text>{{post.author.nickName[0] || '?'}}</text>
|
||||
<view class="post-avatar" style="background: {{avatarGradient}}" catchtap="onAuthorTap">
|
||||
<text>{{post.author && post.author.nickName ? post.author.nickName[0] : '?'}}</text>
|
||||
</view>
|
||||
<view class="post-meta">
|
||||
<text class="post-username">{{post.author.nickName}}</text>
|
||||
<text class="post-username">{{post.author && post.author.nickName ? post.author.nickName : '匿名用户'}}</text>
|
||||
<view class="post-loc" wx:if="{{post.location}}">
|
||||
<text class="loc-icon">📍</text>
|
||||
<text class="loc-text">{{post.location.name}}</text>
|
||||
@@ -17,19 +17,19 @@
|
||||
<!-- 图片 -->
|
||||
<view wx:if="{{post.images && post.images.length > 0}}" class="post-images-wrap">
|
||||
<!-- 单图全宽 -->
|
||||
<view wx:if="{{post.images.length === 1}}" class="post-img-single">
|
||||
<view wx:if="{{post.images && post.images.length === 1}}" class="post-img-single">
|
||||
<image
|
||||
class="img-single"
|
||||
src="{{post.images[0]}}"
|
||||
mode="aspectFill"
|
||||
lazy-load="true"
|
||||
bindtap="onImageTap"
|
||||
catchtap="onImageTap"
|
||||
data-idx="0"
|
||||
/>
|
||||
<view wx:if="{{post.mood}}" class="post-tag-chip">{{post.mood}}</view>
|
||||
</view>
|
||||
<!-- 多图网格 -->
|
||||
<view wx:else class="post-img-grid post-img-grid-{{post.images.length > 3 ? 'multi' : post.images.length}}">
|
||||
<view wx:else class="post-img-grid {{imageGridClass}}">
|
||||
<image
|
||||
wx:for="{{post.images}}"
|
||||
wx:key="index"
|
||||
@@ -38,7 +38,7 @@
|
||||
src="{{item}}"
|
||||
mode="aspectFill"
|
||||
lazy-load="true"
|
||||
bindtap="onImageTap"
|
||||
catchtap="onImageTap"
|
||||
data-idx="{{index}}"
|
||||
/>
|
||||
</view>
|
||||
@@ -55,15 +55,15 @@
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<view class="post-actions">
|
||||
<view class="action-btn {{post.isLiked ? 'liked' : ''}}" bindtap="onLike" catchtap="onLike">
|
||||
<view class="action-btn {{post.isLiked ? 'liked' : ''}}" catchtap="onLike">
|
||||
<text class="action-icon">{{post.isLiked ? '❤️' : '🤍'}}</text>
|
||||
<text class="action-count">{{likeCount}}</text>
|
||||
</view>
|
||||
<view class="action-btn" bindtap="onComment" catchtap="onComment">
|
||||
<view class="action-btn" catchtap="onComment">
|
||||
<text class="action-icon">💬</text>
|
||||
<text class="action-count">{{post.commentCount}}</text>
|
||||
<text class="action-count">{{post.commentCount || 0}}</text>
|
||||
</view>
|
||||
<view class="action-btn" bindtap="onShare" catchtap="onShare">
|
||||
<view class="action-btn" catchtap="onShare">
|
||||
<text class="action-icon">↗</text>
|
||||
<text class="action-count">转发</text>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { api } from '../../utils/api'
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const app = getApp<{ userInfo: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
|
||||
@@ -1,22 +1,45 @@
|
||||
const app = getApp<{ userInfo: any }>()
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const PROVINCES = [
|
||||
'北京', '天津', '上海', '重庆',
|
||||
'河北', '山西', '辽宁', '吉林', '黑龙江',
|
||||
'江苏', '浙江', '安徽', '福建', '江西', '山东',
|
||||
'河南', '湖北', '湖南', '广东', '海南',
|
||||
'四川', '贵州', '云南', '陕西', '甘肃', '青海',
|
||||
'内蒙古', '广西', '西藏', '宁夏', '新疆',
|
||||
'香港', '澳门', '台湾',
|
||||
]
|
||||
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
form: { nickName: '', bio: '', location: '' },
|
||||
provinces: PROVINCES,
|
||||
locationIndex: -1,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight })
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({ statusBarHeight, navbarHeight, navbarPaddingRight })
|
||||
const user = app.globalData?.userInfo
|
||||
if (user) {
|
||||
const locationIndex = PROVINCES.indexOf(user.location || '')
|
||||
this.setData({
|
||||
form: { nickName: user.nickName || '', bio: user.bio || '', location: user.location || '' },
|
||||
locationIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange(e: WechatMiniprogram.CustomEvent) {
|
||||
const idx = Number(e.detail.value)
|
||||
this.setData({ locationIndex: idx, 'form.location': PROVINCES[idx] })
|
||||
},
|
||||
|
||||
onField(e: WechatMiniprogram.CustomEvent) {
|
||||
this.setData({ [`form.${e.currentTarget.dataset.key}`]: e.detail.value })
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<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">编辑资料</view>
|
||||
<view class="nav-save" bindtap="onSave">保存</view>
|
||||
@@ -17,8 +17,12 @@
|
||||
<textarea class="field-textarea" value="{{form.bio}}" bindinput="onField" data-key="bio" placeholder="介绍一下你和你的宠物..." maxlength="80" auto-height="true" show-confirm-bar="false" />
|
||||
</view>
|
||||
<view class="field-item">
|
||||
<text class="field-label">所在城市</text>
|
||||
<input class="field-input" value="{{form.location}}" bindinput="onField" data-key="location" placeholder="所在城市" maxlength="20" />
|
||||
<text class="field-label">所在省份</text>
|
||||
<picker mode="selector" range="{{provinces}}" value="{{locationIndex}}" bindchange="onLocationChange">
|
||||
<view class="field-picker-val {{locationIndex < 0 ? 'placeholder' : ''}}">
|
||||
{{locationIndex >= 0 ? provinces[locationIndex] : '请选择省份'}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 60rpx;"></view>
|
||||
|
||||
@@ -11,3 +11,5 @@
|
||||
.field-label { font-size: 28rpx; font-weight: 700; color: #272235; flex-shrink: 0; margin-right: 20rpx; }
|
||||
.field-input { flex: 1; font-size: 28rpx; color: #272235; text-align: right; }
|
||||
.field-textarea { width: 100%; min-height: 80rpx; font-size: 28rpx; color: #272235; line-height: 1.6; }
|
||||
.field-picker-val { flex: 1; font-size: 28rpx; color: #272235; text-align: right; }
|
||||
.field-picker-val.placeholder { color: #c4b8d0; }
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Post } from '../../types/index'
|
||||
import { Post, AppGlobalData } from '../../types/index'
|
||||
import { api } from '../../utils/api'
|
||||
import { getAvatarGradient, getPetEmoji } from '../../utils/format'
|
||||
import { PAGE_SIZE } from '../../utils/constants'
|
||||
|
||||
interface FeedTab { key: 'feed' | 'nearby' | 'hot'; label: string }
|
||||
|
||||
const app = getApp<{ userInfo: any; currentLocation: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
tabBarHeight: 56,
|
||||
posts: [] as Post[],
|
||||
stories: [] as any[],
|
||||
@@ -31,12 +33,14 @@ Page({
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({
|
||||
statusBarHeight: info.statusBarHeight,
|
||||
tabBarHeight: info.windowHeight - info.safeArea.bottom + 56,
|
||||
statusBarHeight,
|
||||
navbarHeight,
|
||||
navbarPaddingRight,
|
||||
tabBarHeight: info.windowHeight - (info.safeArea?.bottom ?? info.windowHeight) + 56,
|
||||
})
|
||||
this.loadFeed(true)
|
||||
this.loadStories()
|
||||
this.syncMyInfo()
|
||||
},
|
||||
|
||||
@@ -84,18 +88,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
async loadStories() {
|
||||
try {
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'getStories',
|
||||
data: {},
|
||||
}) as any
|
||||
if (res.result?.code === 0) {
|
||||
this.setData({ stories: res.result.data || [] })
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
syncMyInfo() {
|
||||
const user = app.globalData?.userInfo
|
||||
if (!user) return
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<view class="feed-page">
|
||||
<!-- 状态栏占位 -->
|
||||
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
|
||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<view class="topbar">
|
||||
<!-- 主导航栏:与胶囊按钮同高,右侧留出胶囊空间 -->
|
||||
<view class="navbar" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px;">
|
||||
<view class="topbar-logo">汪<text class="logo-accent">圈</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 次级菜单栏:位于胶囊按钮下方,全宽可用 -->
|
||||
<view class="subbar">
|
||||
<view class="topbar-tabs">
|
||||
<view
|
||||
wx:for="{{feedTabs}}"
|
||||
|
||||
@@ -5,11 +5,20 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
.topbar {
|
||||
/* 主导航栏:与胶囊按钮同高,右侧避让胶囊 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 28rpx 12rpx;
|
||||
padding-left: 28rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 次级菜单栏:胶囊按钮下方,全宽 */
|
||||
.subbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 28rpx 12rpx;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { UserProfile } from '../../types/index'
|
||||
import { UserProfile, AppGlobalData } from '../../types/index'
|
||||
import { api } from '../../utils/api'
|
||||
import { getAvatarGradient } from '../../utils/format'
|
||||
|
||||
const app = getApp<{ userInfo: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
tabBarHeight: 56,
|
||||
activeTab: 'following' as 'following' | 'followers',
|
||||
list: [] as any[],
|
||||
@@ -18,7 +20,13 @@ Page({
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight })
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({
|
||||
statusBarHeight,
|
||||
navbarHeight,
|
||||
navbarPaddingRight,
|
||||
tabBarHeight: info.windowHeight - (info.safeArea?.bottom ?? info.windowHeight) + 56,
|
||||
})
|
||||
this.loadList()
|
||||
},
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<view class="friends-page">
|
||||
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
|
||||
<!-- 状态栏占位 -->
|
||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||
|
||||
<view class="topbar">
|
||||
<!-- 主导航栏:与胶囊等高,右侧避让胶囊 -->
|
||||
<view class="navbar" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px;">
|
||||
<view class="topbar-logo">汪<text class="logo-accent">友</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 次级菜单栏:位于胶囊下方,全宽可用 -->
|
||||
<view class="subbar">
|
||||
<view class="topbar-tabs">
|
||||
<view class="ftab {{activeTab === 'following' ? 'ftab-active' : ''}}" bindtap="switchTab" data-tab="following">
|
||||
关注 <text class="ftab-count">{{followingCount}}</text>
|
||||
|
||||
@@ -5,11 +5,20 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 28rpx 12rpx;
|
||||
padding-left: 28rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 28rpx 12rpx;
|
||||
gap: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.topbar-logo { font-size: 44rpx; font-weight: 900; color: #272235; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { login } from '../../utils/auth'
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const app = getApp<{ userInfo: any; isLoggedIn: boolean }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NearbyUser } from '../../types/index'
|
||||
import { NearbyUser, AppGlobalData } from '../../types/index'
|
||||
import { api } from '../../utils/api'
|
||||
import { formatDistance, formatLastSeen, getAvatarGradient, getPetEmoji } from '../../utils/format'
|
||||
import { requireLocation } from '../../utils/auth'
|
||||
@@ -7,11 +7,13 @@ import { NEARBY_RADIUS_KM } from '../../utils/constants'
|
||||
const DEFAULT_LAT = 31.2304
|
||||
const DEFAULT_LNG = 121.4737
|
||||
|
||||
const app = getApp<{ userInfo: any; currentLocation: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
tabBarHeight: 56,
|
||||
viewMode: 'map' as 'map' | 'list',
|
||||
mapCenter: { latitude: DEFAULT_LAT, longitude: DEFAULT_LNG },
|
||||
@@ -29,7 +31,13 @@ Page({
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight })
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({
|
||||
statusBarHeight,
|
||||
navbarHeight,
|
||||
navbarPaddingRight,
|
||||
tabBarHeight: info.windowHeight - (info.safeArea?.bottom ?? info.windowHeight) + 56,
|
||||
})
|
||||
this.initLocation()
|
||||
},
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<view class="nearby-page">
|
||||
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
|
||||
<!-- 状态栏占位 -->
|
||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||
|
||||
<!-- 顶栏 -->
|
||||
<view class="topbar">
|
||||
<!-- 主导航栏:与胶囊等高,右侧避让胶囊 -->
|
||||
<view class="navbar" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px;">
|
||||
<view class="topbar-logo">附<text class="logo-accent">近</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 次级菜单栏:位于胶囊下方,全宽可用 -->
|
||||
<view class="subbar">
|
||||
<view class="view-toggle">
|
||||
<view class="toggle-btn {{viewMode === 'map' ? 'toggle-active' : ''}}" bindtap="setViewMode" data-mode="map">🗺 地图</view>
|
||||
<view class="toggle-btn {{viewMode === 'list' ? 'toggle-active' : ''}}" bindtap="setViewMode" data-mode="list">📋 列表</view>
|
||||
|
||||
@@ -6,11 +6,21 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶栏 */
|
||||
.topbar {
|
||||
/* 主导航栏:与胶囊等高,右侧避让胶囊 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 28rpx 12rpx;
|
||||
padding-left: 28rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
/* 次级菜单栏:胶囊下方,全宽 */
|
||||
.subbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 28rpx 12rpx;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { api } from '../../utils/api'
|
||||
import { getAvatarGradient, getPetEmoji } from '../../utils/format'
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const app = getApp<{ userInfo: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -1,7 +1,43 @@
|
||||
import { api } from '../../utils/api'
|
||||
import { formatTime, getAvatarGradient } from '../../utils/format'
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const app = getApp<{ userInfo: any }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
function normalizeAuthor(author: any, authorId = '') {
|
||||
return {
|
||||
...(author || {}),
|
||||
nickName: author?.nickName || '匿名用户',
|
||||
avatarUrl: author?.avatarUrl || '',
|
||||
openid: author?.openid || authorId,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePost(post: any) {
|
||||
return {
|
||||
authorId: '',
|
||||
...(post || {}),
|
||||
author: normalizeAuthor(post?.author, post?.authorId),
|
||||
content: post?.content || '',
|
||||
images: Array.isArray(post?.images) ? post.images : [],
|
||||
hashtags: Array.isArray(post?.hashtags) ? post.hashtags : [],
|
||||
likeCount: typeof post?.likeCount === 'number' ? post.likeCount : 0,
|
||||
commentCount: typeof post?.commentCount === 'number' ? post.commentCount : 0,
|
||||
createdAt: post?.createdAt || '',
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeComment(comment: any) {
|
||||
return {
|
||||
authorId: '',
|
||||
...(comment || {}),
|
||||
author: normalizeAuthor(comment?.author, comment?.authorId),
|
||||
content: comment?.content || '',
|
||||
createdAt: comment?.createdAt || '',
|
||||
gradient: getAvatarGradient(comment?.authorId || ''),
|
||||
timeText: comment?.createdAt ? formatTime(comment.createdAt) : '',
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -14,15 +50,17 @@ Page({
|
||||
likeCount: 0,
|
||||
commentText: '',
|
||||
focusInput: false,
|
||||
pendingFocusComment: false,
|
||||
},
|
||||
|
||||
onLoad(query: { id?: string; focus?: string }) {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight, postId: query.id || '' })
|
||||
this.setData({
|
||||
statusBarHeight: info.statusBarHeight,
|
||||
postId: query.id || '',
|
||||
pendingFocusComment: query.focus === 'comment',
|
||||
})
|
||||
this.loadPost(query.id || '')
|
||||
if (query.focus === 'comment') {
|
||||
this.setData({ focusInput: true })
|
||||
}
|
||||
},
|
||||
|
||||
async loadPost(id: string) {
|
||||
@@ -32,14 +70,19 @@ Page({
|
||||
name: 'getPost',
|
||||
data: { postId: id },
|
||||
}) as any
|
||||
const post = res.result?.data
|
||||
if (!post) return
|
||||
const post = normalizePost(res.result?.data)
|
||||
if (!post?._id) return
|
||||
this.setData({
|
||||
post,
|
||||
avatarGradient: getAvatarGradient(post.authorId),
|
||||
timeText: formatTime(post.createdAt),
|
||||
avatarGradient: getAvatarGradient(post.authorId || ''),
|
||||
timeText: post.createdAt ? formatTime(post.createdAt) : '',
|
||||
likeCount: post.likeCount,
|
||||
})
|
||||
if (this.data.pendingFocusComment) {
|
||||
wx.nextTick(() => {
|
||||
this.setData({ focusInput: true, pendingFocusComment: false })
|
||||
})
|
||||
}
|
||||
this.loadComments(id)
|
||||
} catch {}
|
||||
},
|
||||
@@ -47,11 +90,7 @@ Page({
|
||||
async loadComments(postId: string) {
|
||||
try {
|
||||
const res = await api.getComments(postId)
|
||||
const comments = res.list.map((c: any) => ({
|
||||
...c,
|
||||
gradient: getAvatarGradient(c.authorId),
|
||||
timeText: formatTime(c.createdAt),
|
||||
}))
|
||||
const comments = res.list.map(normalizeComment)
|
||||
this.setData({ comments })
|
||||
} catch {}
|
||||
},
|
||||
@@ -73,6 +112,10 @@ Page({
|
||||
this.setData({ commentText: e.detail.value })
|
||||
},
|
||||
|
||||
onCommentBlur() {
|
||||
this.setData({ focusInput: false })
|
||||
},
|
||||
|
||||
async onSend() {
|
||||
const text = this.data.commentText.trim()
|
||||
if (!text || !this.data.postId) return
|
||||
@@ -81,11 +124,7 @@ Page({
|
||||
await api.addComment(this.data.postId, text)
|
||||
const comments = await api.getComments(this.data.postId)
|
||||
this.setData({
|
||||
comments: comments.list.map((c: any) => ({
|
||||
...c,
|
||||
gradient: getAvatarGradient(c.authorId),
|
||||
timeText: formatTime(c.createdAt),
|
||||
})),
|
||||
comments: comments.list.map(normalizeComment),
|
||||
})
|
||||
} catch {
|
||||
wx.showToast({ title: '发送失败', icon: 'none' })
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<!-- 作者 -->
|
||||
<view class="post-header" bindtap="onAuthorTap">
|
||||
<view class="avatar" style="background: {{avatarGradient}}">
|
||||
<text>{{post.author.nickName[0]}}</text>
|
||||
<text>{{post.author && post.author.nickName ? post.author.nickName[0] : '?'}}</text>
|
||||
</view>
|
||||
<view class="meta">
|
||||
<text class="uname">{{post.author.nickName}}</text>
|
||||
<text class="uname">{{post.author && post.author.nickName ? post.author.nickName : '匿名用户'}}</text>
|
||||
<text class="utime">{{timeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -25,11 +25,11 @@
|
||||
<text class="content">{{post.content}}</text>
|
||||
|
||||
<!-- 图片 -->
|
||||
<view wx:if="{{post.images.length > 0}}" class="images">
|
||||
<view wx:if="{{post.images && post.images.length > 0}}" class="images">
|
||||
<image
|
||||
wx:for="{{post.images}}"
|
||||
wx:key="index"
|
||||
class="img {{post.images.length === 1 ? 'img-full' : 'img-grid'}}"
|
||||
class="img {{post.images && post.images.length === 1 ? 'img-full' : 'img-grid'}}"
|
||||
src="{{item}}"
|
||||
mode="aspectFill"
|
||||
lazy-load="true"
|
||||
@@ -73,10 +73,10 @@
|
||||
class="comment-item"
|
||||
>
|
||||
<view class="c-avatar" style="background: {{item.gradient}}">
|
||||
<text>{{item.author.nickName[0]}}</text>
|
||||
<text>{{item.author && item.author.nickName ? item.author.nickName[0] : '?'}}</text>
|
||||
</view>
|
||||
<view class="c-body">
|
||||
<text class="c-name">{{item.author.nickName}}</text>
|
||||
<text class="c-name">{{item.author && item.author.nickName ? item.author.nickName : '匿名用户'}}</text>
|
||||
<text class="c-text">{{item.content}}</text>
|
||||
<text class="c-time">{{item.timeText}}</text>
|
||||
</view>
|
||||
@@ -92,7 +92,9 @@
|
||||
class="comment-input"
|
||||
id="commentInput"
|
||||
value="{{commentText}}"
|
||||
focus="{{focusInput}}"
|
||||
bindinput="onCommentInput"
|
||||
bindblur="onCommentBlur"
|
||||
placeholder="写下你的想法..."
|
||||
confirm-type="send"
|
||||
bindconfirm="onSend"
|
||||
|
||||
@@ -3,11 +3,15 @@ import { chooseAndUploadMedia } from '../../utils/auth'
|
||||
import { MOOD_OPTIONS, HOT_HASHTAGS } from '../../utils/constants'
|
||||
import { getPetEmoji } from '../../utils/format'
|
||||
|
||||
const app = getApp<{ userInfo: any; currentLocation: any }>()
|
||||
import { AppGlobalData } from '../../types/index'
|
||||
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
content: '',
|
||||
images: [] as any[],
|
||||
location: null as any,
|
||||
@@ -23,10 +27,9 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight })
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({ statusBarHeight, navbarHeight, navbarPaddingRight })
|
||||
this.loadMyPets()
|
||||
this.autoGetLocation()
|
||||
},
|
||||
|
||||
loadMyPets() {
|
||||
@@ -42,24 +45,6 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
autoGetLocation() {
|
||||
const loc = app.globalData?.currentLocation
|
||||
if (!loc) return
|
||||
wx.reverseGeocoder({
|
||||
location: { longitude: loc.longitude, latitude: loc.latitude },
|
||||
success: (res: any) => {
|
||||
const name = res.result?.address || ''
|
||||
if (name) {
|
||||
this.setData({
|
||||
location: { name, latitude: loc.latitude, longitude: loc.longitude },
|
||||
})
|
||||
this.checkCanSubmit()
|
||||
}
|
||||
},
|
||||
fail: () => {},
|
||||
} as any)
|
||||
},
|
||||
|
||||
onContentInput(e: WechatMiniprogram.CustomEvent) {
|
||||
this.setData({ content: e.detail.value })
|
||||
this.checkCanSubmit()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<view class="post-page">
|
||||
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
|
||||
<!-- 状态栏占位 -->
|
||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<view class="post-header">
|
||||
<!-- 顶部导航:高度与胶囊等高,右侧 padding 避让胶囊 -->
|
||||
<view class="post-header" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px;">
|
||||
<view class="cancel-btn" bindtap="onCancel">取消</view>
|
||||
<view class="header-title">发布动态</view>
|
||||
<view class="submit-btn {{canSubmit ? '' : 'submit-disabled'}}" bindtap="onSubmit">
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部 */
|
||||
/* 顶部:高度和右侧 padding 由 JS 动态设置,避让系统胶囊按钮 */
|
||||
.post-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 28rpx;
|
||||
padding-left: 28rpx;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.72);
|
||||
background: rgba(255, 255, 255, 0.60);
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Post } from '../../types/index'
|
||||
import { Post, AppGlobalData } from '../../types/index'
|
||||
import { api } from '../../utils/api'
|
||||
import { getAvatarGradient, getPetEmoji, formatCount } from '../../utils/format'
|
||||
|
||||
const app = getApp<{ userInfo: any; isLoggedIn: boolean }>()
|
||||
const app = getApp<{ globalData: AppGlobalData }>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 44,
|
||||
navbarPaddingRight: 0,
|
||||
tabBarHeight: 56,
|
||||
userInfo: {
|
||||
nickName: '',
|
||||
@@ -26,7 +28,13 @@ Page({
|
||||
|
||||
onLoad() {
|
||||
const info = wx.getSystemInfoSync()
|
||||
this.setData({ statusBarHeight: info.statusBarHeight })
|
||||
const { statusBarHeight, navbarHeight, navbarPaddingRight } = app.globalData.navBarInfo
|
||||
this.setData({
|
||||
statusBarHeight,
|
||||
navbarHeight,
|
||||
navbarPaddingRight,
|
||||
tabBarHeight: info.windowHeight - (info.safeArea?.bottom ?? info.windowHeight) + 56,
|
||||
})
|
||||
this.loadProfile()
|
||||
},
|
||||
|
||||
@@ -59,7 +67,7 @@ Page({
|
||||
try {
|
||||
const [_user, postsRes] = await Promise.all([
|
||||
api.getUserProfile(app.globalData?.userInfo?._id || '').catch(() => null),
|
||||
api.getUserPosts(app.globalData?.userInfo?._id || '').catch(() => ({ list: [], total: 0 })),
|
||||
api.getUserPosts(app.globalData?.userInfo?.openid || '').catch(() => ({ list: [], total: 0 })),
|
||||
])
|
||||
if (_user) {
|
||||
app.globalData.userInfo = _user
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<view class="profile-page">
|
||||
<view class="safe-top" style="height: {{statusBarHeight}}px"></view>
|
||||
<!-- 状态栏占位 -->
|
||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||
|
||||
<!-- 主导航栏:与胶囊等高,右侧避让胶囊,放置设置按钮 -->
|
||||
<view class="navbar" style="height: {{navbarHeight}}px; padding-right: {{navbarPaddingRight}}px;">
|
||||
<view class="navbar-title">我的</view>
|
||||
<view class="settings-btn" bindtap="onSettings">⚙️</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
scroll-y="true"
|
||||
@@ -10,12 +17,8 @@
|
||||
bindrefresherrefresh="onRefresh"
|
||||
refresher-triggered="{{refreshing}}"
|
||||
>
|
||||
<!-- 用户信息区 -->
|
||||
<!-- 用户信息区(移除原来的 header-topbar,已移至 navbar)-->
|
||||
<view class="profile-header">
|
||||
<!-- 头部操作栏 -->
|
||||
<view class="header-topbar">
|
||||
<view class="settings-btn" bindtap="onSettings">⚙️</view>
|
||||
</view>
|
||||
|
||||
<!-- 头像 + 基本信息 -->
|
||||
<view class="user-top">
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
.profile-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #fff8f2 0%, #fff4fb 50%, #f5f0ff 100%);
|
||||
}
|
||||
|
||||
.profile-scroll { min-height: 100vh; }
|
||||
|
||||
/* 用户信息区 */
|
||||
.profile-header {
|
||||
padding: 0 28rpx 28rpx;
|
||||
background: linear-gradient(180deg, rgba(255, 225, 90, 0.18), transparent);
|
||||
}
|
||||
|
||||
.header-topbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16rpx 0 12rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 主导航栏:与胶囊等高,右侧避让胶囊 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 28rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 900;
|
||||
color: #272235;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
@@ -29,6 +33,14 @@
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.profile-scroll { flex: 1; }
|
||||
|
||||
/* 用户信息区 */
|
||||
.profile-header {
|
||||
padding: 16rpx 28rpx 28rpx;
|
||||
background: linear-gradient(180deg, rgba(255, 225, 90, 0.18), transparent);
|
||||
}
|
||||
|
||||
.user-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -101,8 +101,16 @@ export interface TabBarItem {
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
export interface NavBarInfo {
|
||||
statusBarHeight: number
|
||||
navbarHeight: number
|
||||
navbarPaddingRight: number
|
||||
}
|
||||
|
||||
export interface AppGlobalData {
|
||||
userInfo: UserProfile | null
|
||||
isLoggedIn: boolean
|
||||
currentLocation: GeoPoint | null
|
||||
navBarInfo: NavBarInfo
|
||||
_locationTimer: number | null
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ export const api = {
|
||||
updatePet: (pet: Partial<Pet> & { _id?: string }) =>
|
||||
call<Pet>('updatePet', { pet }),
|
||||
|
||||
deletePet: (petId: string) =>
|
||||
call<void>('deletePet', { petId }),
|
||||
|
||||
uploadImage: async (filePath: string): Promise<string> => {
|
||||
const ext = filePath.split('.').pop() || 'jpg'
|
||||
const cloudPath = `posts/${Date.now()}_${Math.random().toString(36).slice(2)}.${ext}`
|
||||
|
||||
@@ -45,10 +45,41 @@ export const HOT_HASHTAGS = [
|
||||
'#周末遛狗',
|
||||
]
|
||||
|
||||
export const BREED_OPTIONS = {
|
||||
dog: ['比熊', '柴犬', '金毛', '泰迪', '边牧', '法斗', '哈士奇', '萨摩耶', '拉布拉多', '其他'],
|
||||
cat: ['英短', '布偶', '美短', '加菲', '暹罗', '橘猫', '黑猫', '其他'],
|
||||
other: ['兔子', '仓鼠', '鹦鹉', '龟', '其他'],
|
||||
// 品种数据集 — 后期直接在对应数组末尾追加即可
|
||||
export const BREED_OPTIONS: Record<string, string[]> = {
|
||||
dog: [
|
||||
// 小型犬
|
||||
'比熊', '泰迪/贵宾', '博美', '吉娃娃', '约克夏', '马尔济斯', '西施', '迷你雪纳瑞',
|
||||
'柯基', '腊肠', '法斗', '巴哥', '迷你杜宾',
|
||||
// 中型犬
|
||||
'柴犬', '边牧', '萨摩耶', '哈士奇', '阿拉斯加', '秋田',
|
||||
// 大型犬
|
||||
'金毛', '拉布拉多', '德牧', '杜宾', '大白熊', '圣伯纳',
|
||||
// 中华/混种
|
||||
'田园犬(土狗)', '串串',
|
||||
// 兜底
|
||||
'其他',
|
||||
],
|
||||
cat: [
|
||||
// 短毛
|
||||
'英短', '美短', '苏格兰折耳', '孟买', '阿比西尼亚', '俄罗斯蓝猫', '暹罗', '东方短毛',
|
||||
// 长毛
|
||||
'布偶', '波斯', '加菲', '缅因', '挪威森林', '土耳其安哥拉',
|
||||
// 中华田园
|
||||
'橘猫', '狸花猫', '黑猫', '白猫', '三花', '玳瑁', '中华田园猫',
|
||||
// 兜底
|
||||
'其他',
|
||||
],
|
||||
other: [
|
||||
// 啮齿 & 小型哺乳
|
||||
'兔子', '仓鼠', '豚鼠', '龙猫', '花鼠', '刺猬',
|
||||
// 鸟类
|
||||
'虎皮鹦鹉', '牡丹鹦鹉', '玄凤鹦鹉', '文鸟', '金丝雀',
|
||||
// 爬行 & 水生
|
||||
'乌龟', '蜥蜴', '金鱼/锦鲤', '热带鱼',
|
||||
// 兜底
|
||||
'其他',
|
||||
],
|
||||
}
|
||||
|
||||
export const NEARBY_RADIUS_KM = 5
|
||||
|
||||
Reference in New Issue
Block a user