commit 0e43ccec72fe7f4aed7d9e6f4298627d1cf613c8 Author: chenwu Date: Thu Jun 18 14:33:50 2026 +0800 v0.0.4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9e7645 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.env +.env.* + diff --git a/.swc/plugins/v7_windows_x86_64_0.104.8/a21de3c9b20ee3f62c37754794ecc3b8637569af96a5d8798cbee4ea25679ab2 b/.swc/plugins/v7_windows_x86_64_0.104.8/a21de3c9b20ee3f62c37754794ecc3b8637569af96a5d8798cbee4ea25679ab2 new file mode 100644 index 0000000..71d9455 Binary files /dev/null and b/.swc/plugins/v7_windows_x86_64_0.104.8/a21de3c9b20ee3f62c37754794ecc3b8637569af96a5d8798cbee4ea25679ab2 differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..53222de --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# 汪圈 Taro 微信小程序 + +本项目已从 `refrence_html/` 的静态 HTML 参考稿迁移为 Taro + React + TypeScript 微信小程序,并按微信云开发模式保留云函数目录。 + +## 运行 + +```bash +npm install +npm run dev:weapp +``` + +构建: + +```bash +npm run build:weapp +``` + +类型检查: + +```bash +npm run typecheck +``` + +## 微信开发者工具 + +1. 打开微信开发者工具。 +2. 导入当前项目根目录。 +3. `miniprogramRoot` 已配置为 `dist/`。 +4. `cloudfunctionRoot` 已配置为 `cloudfunctions/`。 +5. `project.config.json` 当前使用 `touristappid`,正式开发时替换为你的小程序 AppID。 + +## 云开发环境 + +客户端通过 `TARO_APP_CLOUD_ENV` 初始化云环境: + +```bash +$env:TARO_APP_CLOUD_ENV="你的云环境ID" +npm run dev:weapp +``` + +未配置云环境时,页面会自动使用本地 mock 数据,方便先验收视觉和交互。 + +## 页面 + +- `src/pages/plaza`:广场信息流 +- `src/pages/nearby`:附近地图与可拖拽底部列表 +- `src/pages/publish`:发布动态 +- `src/pages/messages`:消息列表 +- `src/pages/profile`:我的主页 + +设计与架构说明见 [docs/taro-miniapp-design.md](docs/taro-miniapp-design.md)。 + diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..5d59b5f --- /dev/null +++ b/babel.config.js @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + [ + 'taro', + { + framework: 'react', + ts: true + } + ] + ] +} + diff --git a/cloudbaserc.json b/cloudbaserc.json new file mode 100644 index 0000000..cb9c77b --- /dev/null +++ b/cloudbaserc.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://static.cloudbase.net/cli/cloudbaserc.schema.json", + "envId": "cloud1-d4g697lte499543d8", + "functionRoot": "cloudfunctions", + "functions": [ + { "name": "login", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "feedList", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "postCreate", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "postLike", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "postFavorite", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "draftSave", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "draftGet", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "nearbyPets", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "locationUpdate", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "matchLike", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "messageList", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "messageSend", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "messageThread", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "conversationRead", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "followToggle", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "followList", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "userList", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "profileGet", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "profileUpdate", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "petList", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "petSave", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 }, + { "name": "petDelete", "runtime": "Nodejs16.13", "handler": "index.main", "timeout": 10, "memorySize": 128 } + ] +} diff --git a/cloudfunctions/conversationRead/index.js b/cloudfunctions/conversationRead/index.js new file mode 100644 index 0000000..5f6a9c3 --- /dev/null +++ b/cloudfunctions/conversationRead/index.js @@ -0,0 +1,19 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!event.conversationId) throw new Error('conversationId is required') + + await db.collection('conversations').doc(event.conversationId).update({ + data: { + unreadOpenids: _.pull(OPENID), + [`unreadCounts.${OPENID}`]: 0 + } + }) + + return { unreadCount: 0 } +} diff --git a/cloudfunctions/conversationRead/package.json b/cloudfunctions/conversationRead/package.json new file mode 100644 index 0000000..960e0b9 --- /dev/null +++ b/cloudfunctions/conversationRead/package.json @@ -0,0 +1,9 @@ +{ + "name": "conversationRead", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/draftGet/index.js b/cloudfunctions/draftGet/index.js new file mode 100644 index 0000000..2934e4e --- /dev/null +++ b/cloudfunctions/draftGet/index.js @@ -0,0 +1,13 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async () => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const res = await db.collection('drafts').where({ openid: OPENID }).limit(1).get() + if (!res.data.length) return { draft: null } + const record = res.data[0] + return { draft: record.draft || null, updatedAt: record.updatedAt } +} diff --git a/cloudfunctions/draftGet/package.json b/cloudfunctions/draftGet/package.json new file mode 100644 index 0000000..e737071 --- /dev/null +++ b/cloudfunctions/draftGet/package.json @@ -0,0 +1,8 @@ +{ + "name": "draftGet", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/draftSave/index.js b/cloudfunctions/draftSave/index.js new file mode 100644 index 0000000..309b1ca --- /dev/null +++ b/cloudfunctions/draftSave/index.js @@ -0,0 +1,24 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const now = Date.now() + const data = { + openid: OPENID, + draft: event.draft || {}, + updatedAt: now + } + const existed = await db.collection('drafts').where({ openid: OPENID }).limit(1).get() + + if (existed.data.length) { + await db.collection('drafts').doc(existed.data[0]._id).update({ data }) + return { draftId: existed.data[0]._id, updatedAt: now } + } + + const result = await db.collection('drafts').add({ data }) + return { draftId: result._id, updatedAt: now } +} + diff --git a/cloudfunctions/draftSave/package.json b/cloudfunctions/draftSave/package.json new file mode 100644 index 0000000..b04a468 --- /dev/null +++ b/cloudfunctions/draftSave/package.json @@ -0,0 +1,9 @@ +{ + "name": "draftSave", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/feedList/index.js b/cloudfunctions/feedList/index.js new file mode 100644 index 0000000..7976da3 --- /dev/null +++ b/cloudfunctions/feedList/index.js @@ -0,0 +1,105 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +// Query a collection that may not exist yet (created lazily on first write). +// Returns an empty result instead of throwing "collection not exists". +async function safeGet(collection, where, limit = 200) { + try { + return await db.collection(collection).where(where).limit(limit).get() + } catch (e) { + return { data: [] } + } +} + +function formatTimeText(ts) { + const diff = Date.now() - ts + const mins = Math.floor(diff / 60000) + if (mins < 1) return '刚刚' + if (mins < 60) return `${mins} 分钟前` + const hours = Math.floor(mins / 60) + if (hours < 24) return `${hours} 小时前` + const days = Math.floor(hours / 24) + if (days === 1) return '昨天' + if (days < 7) return `${days} 天前` + const d = new Date(ts) + return `${d.getMonth() + 1}月${d.getDate()}日` +} + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const pageSize = Math.min(event.pageSize || 20, 50) + const query = { visibility: 'public' } + + if (event.topic) query.topics = event.topic + + // Follow channel: only posts from users I follow (plus my own). + if (event.channel === 'follow') { + if (!OPENID) return { list: [], nextCursor: '' } + const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!meRes.data.length) return { list: [], nextCursor: '' } + const meId = meRes.data[0]._id + const followRes = await safeGet('follows', { followerId: meId }, 500) + const authorOpenids = followRes.data.map(f => f.followeeOpenid).filter(Boolean) + authorOpenids.push(OPENID) + query.authorOpenid = _.in(authorOpenids) + query.visibility = _.in(['public', 'friends']) + } + + if (event.cursor) { + query.createdAt = _.lt(Number(event.cursor)) + } + + const res = await db.collection('posts') + .where(query) + .orderBy('createdAt', 'desc') + .limit(pageSize) + .get() + + if (!res.data.length) return { list: [], nextCursor: '' } + + const postIds = res.data.map(p => p._id) + + // Batch-check liked/favorited status — skip if no valid OPENID (non-WeChat context) + let likedSet = new Set() + let favSet = new Set() + if (OPENID) { + const [likedRes, favRes] = await Promise.all([ + safeGet('postLikes', { openid: OPENID, postId: _.in(postIds) }), + safeGet('postFavorites', { openid: OPENID, postId: _.in(postIds) }) + ]) + likedSet = new Set(likedRes.data.map(l => l.postId)) + favSet = new Set(favRes.data.map(f => f.postId)) + } + + const list = res.data.map(post => ({ + _id: post._id, + author: { + id: post.authorId || '', + name: post.authorSnapshot?.name || '用户', + avatarKey: post.authorSnapshot?.avatarKey || 'gradient-avatar-1' + }, + pet: post.petSnapshot || { name: '', breed: '' }, + body: post.content || '', + topics: post.topics || [], + media: Array.isArray(post.media) + ? post.media.map(m => (typeof m === 'string' ? m : m && (m.url || m.fileId))).filter(Boolean) + : [], + mediaTone: post.mediaTone || 'pet-1', + sticker: post.sticker || undefined, + locationText: post.locationText || '', + timeText: formatTimeText(post.createdAt || Date.now()), + counts: { + likes: post.counts?.likes || 0, + comments: post.counts?.comments || 0, + favorites: post.counts?.favorites || 0 + }, + likedByMe: likedSet.has(post._id), + favoritedByMe: favSet.has(post._id) + })) + + const last = res.data[res.data.length - 1] + return { list, nextCursor: last ? String(last.createdAt) : '' } +} diff --git a/cloudfunctions/feedList/package.json b/cloudfunctions/feedList/package.json new file mode 100644 index 0000000..f445238 --- /dev/null +++ b/cloudfunctions/feedList/package.json @@ -0,0 +1,9 @@ +{ + "name": "feedList", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/followList/index.js b/cloudfunctions/followList/index.js new file mode 100644 index 0000000..bfe908d --- /dev/null +++ b/cloudfunctions/followList/index.js @@ -0,0 +1,63 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +async function fetchUsers(ids) { + if (!ids.length) return [] + const out = [] + // _.in supports up to ~100 values; chunk to be safe + for (let i = 0; i < ids.length; i += 100) { + const chunk = ids.slice(i, i + 100) + const res = await db.collection('users').where({ _id: _.in(chunk) }).limit(100).get() + out.push(...res.data) + } + return out +} + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!meRes.data.length) throw new Error('user not found') + const meId = meRes.data[0]._id + const type = event.type || 'friends' + + const safeFollows = where => + db.collection('follows').where(where).limit(500).get().catch(() => ({ data: [] })) + const [followingRes, followerRes] = await Promise.all([ + safeFollows({ followerId: meId }), + safeFollows({ followeeId: meId }) + ]) + const followingIds = followingRes.data.map(f => f.followeeId) + const followerIds = followerRes.data.map(f => f.followerId) + const followingSet = new Set(followingIds) + const followerSet = new Set(followerIds) + const friendIds = followingIds.filter(id => followerSet.has(id)) + + let ids = friendIds + if (type === 'following') ids = followingIds + else if (type === 'followers') ids = followerIds + + const users = await fetchUsers(ids) + const list = users.map(u => ({ + id: u._id, + name: u.nickname || '用户', + handle: u.handle || '', + avatarKey: u.avatarKey || 'gradient-avatar-1', + avatarUrl: u.avatarUrl || '', + bio: u.bio || '', + isFollowing: followingSet.has(u._id), + isFriend: followingSet.has(u._id) && followerSet.has(u._id) + })) + + return { + list, + counts: { + following: followingIds.length, + followers: followerIds.length, + friends: friendIds.length + } + } +} diff --git a/cloudfunctions/followList/package.json b/cloudfunctions/followList/package.json new file mode 100644 index 0000000..5e23c60 --- /dev/null +++ b/cloudfunctions/followList/package.json @@ -0,0 +1,8 @@ +{ + "name": "followList", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/followToggle/index.js b/cloudfunctions/followToggle/index.js new file mode 100644 index 0000000..f824fb0 --- /dev/null +++ b/cloudfunctions/followToggle/index.js @@ -0,0 +1,47 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const { targetUserId, follow } = event + if (!targetUserId) throw new Error('targetUserId is required') + + const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!meRes.data.length) throw new Error('user not found') + const meId = meRes.data[0]._id + if (meId === targetUserId) throw new Error('cannot follow yourself') + + const targetRes = await db.collection('users').doc(targetUserId).get().catch(() => null) + const target = targetRes && targetRes.data + if (!target) throw new Error('target user not found') + + const existing = await db.collection('follows') + .where({ followerId: meId, followeeId: targetUserId }) + .limit(1) + .get() + + if (follow && !existing.data.length) { + await db.collection('follows').add({ + data: { + followerId: meId, + followerOpenid: OPENID, + followeeId: targetUserId, + followeeOpenid: target.openid, + createdAt: Date.now() + } + }) + } else if (!follow && existing.data.length) { + await db.collection('follows').doc(existing.data[0]._id).remove() + } + + // A friendship (汪友) is a mutual follow. + const back = await db.collection('follows') + .where({ followerId: targetUserId, followeeId: meId }) + .limit(1) + .get() + + return { following: Boolean(follow), mutual: Boolean(follow) && back.data.length > 0 } +} diff --git a/cloudfunctions/followToggle/package.json b/cloudfunctions/followToggle/package.json new file mode 100644 index 0000000..82ab303 --- /dev/null +++ b/cloudfunctions/followToggle/package.json @@ -0,0 +1,8 @@ +{ + "name": "followToggle", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/locationUpdate/index.js b/cloudfunctions/locationUpdate/index.js new file mode 100644 index 0000000..faa9707 --- /dev/null +++ b/cloudfunctions/locationUpdate/index.js @@ -0,0 +1,21 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const now = Date.now() + const data = { + openid: OPENID, + location: event.location || null, + visible: Boolean(event.visible), + updatedAt: now + } + const existed = await db.collection('locations').where({ openid: OPENID }).limit(1).get() + if (existed.data.length) await db.collection('locations').doc(existed.data[0]._id).update({ data }) + else await db.collection('locations').add({ data }) + + return { ok: true } +} + diff --git a/cloudfunctions/locationUpdate/package.json b/cloudfunctions/locationUpdate/package.json new file mode 100644 index 0000000..c00649d --- /dev/null +++ b/cloudfunctions/locationUpdate/package.json @@ -0,0 +1,9 @@ +{ + "name": "locationUpdate", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/login/index.js b/cloudfunctions/login/index.js new file mode 100644 index 0000000..ac959d9 --- /dev/null +++ b/cloudfunctions/login/index.js @@ -0,0 +1,59 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +// Generate a unique handle from openid +function makeHandle(openid) { + const suffix = openid.slice(-6).toLowerCase() + return `@user_${suffix}` +} + +// Default avatar pool to randomise new user avatars +const AVATAR_KEYS = [ + 'gradient-avatar-1', 'gradient-avatar-2', 'gradient-avatar-3', + 'gradient-avatar-4', 'gradient-avatar-5', 'gradient-avatar-6' +] + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + + const now = Date.now() + const existed = await db.collection('users').where({ openid: OPENID }).limit(1).get() + + if (existed.data.length) { + const user = existed.data[0] + await db.collection('users').doc(user._id).update({ + data: { lastLoginAt: now } + }) + return { + openid: OPENID, + user: { ...user, lastLoginAt: now }, + isNew: false + } + } + + // New user — create with sensible defaults + const avatarKey = AVATAR_KEYS[Math.floor(Math.random() * AVATAR_KEYS.length)] + const user = { + openid: OPENID, + nickname: event.userInfo?.nickname || '宠物达人', + handle: makeHandle(OPENID), + avatarKey, + avatarUrl: '', + profileCompleted: false, + location: '', + yearsWithPets: 0, + level: 1, + bio: '', + verified: false, + stats: { posts: 0, following: 0, followers: 0, favorites: 0 }, + preferences: { notifications: true, nearbyVisible: true }, + createdAt: now, + lastLoginAt: now + } + + const result = await db.collection('users').add({ data: user }) + return { openid: OPENID, user: { _id: result._id, ...user }, isNew: true } +} diff --git a/cloudfunctions/login/package.json b/cloudfunctions/login/package.json new file mode 100644 index 0000000..e7d1e11 --- /dev/null +++ b/cloudfunctions/login/package.json @@ -0,0 +1,9 @@ +{ + "name": "login", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/matchLike/index.js b/cloudfunctions/matchLike/index.js new file mode 100644 index 0000000..fde42e7 --- /dev/null +++ b/cloudfunctions/matchLike/index.js @@ -0,0 +1,46 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const { petId } = event + if (!petId) throw new Error('petId is required') + + // Get current user and their pets + const userRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!userRes.data.length) throw new Error('user not found') + const userId = userRes.data[0]._id + + // Record this like (idempotent) + const existed = await db.collection('matches').where({ fromOpenid: OPENID, petId }).limit(1).get() + if (!existed.data.length) { + await db.collection('matches').add({ data: { fromOpenid: OPENID, userId, petId, createdAt: Date.now() } }) + } + + // Check mutual match: does the target pet's owner like any of current user's pets? + let matched = false + try { + const targetPetRes = await db.collection('pets').doc(petId).get() + const targetOwnerId = targetPetRes.data?.ownerId + if (targetOwnerId && targetOwnerId !== userId) { + const targetOwnerRes = await db.collection('users').doc(targetOwnerId).get() + const targetOpenid = targetOwnerRes.data?.openid + if (targetOpenid) { + const myPetsRes = await db.collection('pets').where({ ownerId: userId }).limit(20).get() + const myPetIds = myPetsRes.data.map(p => p._id) + if (myPetIds.length) { + const reverseRes = await db.collection('matches') + .where({ fromOpenid: targetOpenid, petId: _.in(myPetIds) }) + .limit(1) + .get() + matched = reverseRes.data.length > 0 + } + } + } + } catch (_) {} + + return { matched } +} diff --git a/cloudfunctions/matchLike/package.json b/cloudfunctions/matchLike/package.json new file mode 100644 index 0000000..d5dfc52 --- /dev/null +++ b/cloudfunctions/matchLike/package.json @@ -0,0 +1,9 @@ +{ + "name": "matchLike", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/messageList/index.js b/cloudfunctions/messageList/index.js new file mode 100644 index 0000000..86ae7bd --- /dev/null +++ b/cloudfunctions/messageList/index.js @@ -0,0 +1,101 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +function formatTimeText(ts) { + if (!ts) return '' + const diff = Date.now() - ts + const mins = Math.floor(diff / 60000) + if (mins < 1) return '现在' + if (mins < 60) return `${mins} 分钟前` + const hours = Math.floor(mins / 60) + if (hours < 24) return `${hours} 小时前` + const days = Math.floor(hours / 24) + if (days === 1) return '昨天' + const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] + if (days < 7) return weekdays[new Date(ts).getDay()] + const d = new Date(ts) + return `${d.getMonth() + 1}/${d.getDate()}` +} + +// Query a collection that may not exist yet (created lazily on first write). +async function safeGet(collection, where, limit = 200) { + try { + return await db.collection(collection).where(where).limit(limit).get() + } catch (e) { + return { data: [] } + } +} + +async function fetchUsers(ids) { + if (!ids.length) return [] + const out = [] + for (let i = 0; i < ids.length; i += 100) { + const chunk = ids.slice(i, i + 100) + const res = await db.collection('users').where({ _id: _.in(chunk) }).limit(100).get() + out.push(...res.data) + } + return out +} + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const tab = event.tab || 'all' + const query = { memberOpenids: OPENID } + if (tab === 'groups') query.type = 'group' + if (tab === 'unread') query.unreadOpenids = OPENID + + const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + const meId = meRes.data[0] && meRes.data[0]._id + + // Active row = my 汪友 (mutual follows) + let friendUsers = [] + if (meId) { + const [followingRes, followerRes] = await Promise.all([ + safeGet('follows', { followerId: meId }, 500), + safeGet('follows', { followeeId: meId }, 500) + ]) + const followerSet = new Set(followerRes.data.map(f => f.followerId)) + const friendIds = followingRes.data.map(f => f.followeeId).filter(id => followerSet.has(id)) + friendUsers = await fetchUsers(friendIds) + } + + let convRes + try { + convRes = await db.collection('conversations').where(query).orderBy('updatedAt', 'desc').limit(50).get() + } catch (e) { + convRes = { data: [] } + } + + const conversations = convRes.data.map(c => { + const unreadOpenids = c.unreadOpenids || [] + const unreadCounts = c.unreadCounts || {} + const unreadCount = unreadCounts[OPENID] || (unreadOpenids.includes(OPENID) ? 1 : 0) + return { + _id: c._id, + type: c.type || 'single', + title: c.title || '未知用户', + petName: c.petName || undefined, + avatarKey: c.avatarKey || 'gradient-avatar-1', + preview: c.lastMessage?.content || c.preview || '', + timeText: formatTimeText(c.updatedAt || c.createdAt), + unreadCount, + pinned: Boolean(c.pinned), + muted: Boolean(c.muted), + online: Boolean(c.online) + } + }) + + const activeUsers = friendUsers.map(u => ({ + id: u._id, + name: u.nickname || '汪友', + avatarKey: u.avatarKey || 'gradient-avatar-1', + avatarUrl: u.avatarUrl || '', + live: Boolean(u.online) + })) + + return { activeUsers, conversations } +} diff --git a/cloudfunctions/messageList/package.json b/cloudfunctions/messageList/package.json new file mode 100644 index 0000000..dbeeeba --- /dev/null +++ b/cloudfunctions/messageList/package.json @@ -0,0 +1,9 @@ +{ + "name": "messageList", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/messageSend/index.js b/cloudfunctions/messageSend/index.js new file mode 100644 index 0000000..ef49074 --- /dev/null +++ b/cloudfunctions/messageSend/index.js @@ -0,0 +1,88 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const content = String(event.content || '').trim() + if (!content) throw new Error('content is required') + + const now = Date.now() + let convId = event.conversationId + + // Create a 1:1 conversation if not provided + if (!convId) { + let { toOpenid } = event + // Allow opening by user id without exposing openids to the client. + if (!toOpenid && event.toUserId) { + const toUserById = await db.collection('users').doc(event.toUserId).get().catch(() => null) + if (toUserById && toUserById.data) toOpenid = toUserById.data.openid + } + if (!toOpenid) throw new Error('conversationId or toOpenid/toUserId is required') + + // Look for existing conversation between these two users + const existing = await db.collection('conversations') + .where({ type: 'single', memberOpenids: _.all([OPENID, toOpenid]) }) + .limit(1) + .get() + + if (existing.data.length) { + convId = existing.data[0]._id + } else { + const toUserRes = await db.collection('users').where({ openid: toOpenid }).limit(1).get() + const toUser = toUserRes.data[0] || { nickname: '用户', avatarKey: 'gradient-avatar-1' } + const newConv = await db.collection('conversations').add({ + data: { + type: 'single', + memberOpenids: [OPENID, toOpenid], + unreadOpenids: [toOpenid], + unreadCounts: { [toOpenid]: 1 }, + title: toUser.nickname, + avatarKey: toUser.avatarKey, + lastMessage: { content: content.slice(0, 50), createdAt: now }, + pinned: false, + muted: false, + createdAt: now, + updatedAt: now + } + }) + convId = newConv._id + } + } + + // Add message to messages collection + const msgResult = await db.collection('messages').add({ + data: { + conversationId: convId, + fromOpenid: OPENID, + content: content.slice(0, 1000), + type: event.msgType || 'text', + createdAt: now + } + }) + + // Update conversation: last message + unread counts for all recipients + const convRes = await db.collection('conversations').doc(convId).get() + const members = convRes.data.memberOpenids || [] + const recipients = members.filter(id => id !== OPENID) + + const updateData = { + lastMessage: { content: content.slice(0, 50), createdAt: now }, + updatedAt: now + } + // Add recipients to unreadOpenids and increment their unread counts + recipients.forEach(r => { + updateData[`unreadCounts.${r}`] = _.inc(1) + }) + + // Rebuild unreadOpenids to include all recipients (remove duplicates) + const existingUnread = convRes.data.unreadOpenids || [] + const newUnread = [...new Set([...existingUnread, ...recipients])] + updateData.unreadOpenids = newUnread + + await db.collection('conversations').doc(convId).update({ data: updateData }) + + return { messageId: msgResult._id, conversationId: convId } +} diff --git a/cloudfunctions/messageSend/package.json b/cloudfunctions/messageSend/package.json new file mode 100644 index 0000000..20f0131 --- /dev/null +++ b/cloudfunctions/messageSend/package.json @@ -0,0 +1,8 @@ +{ + "name": "messageSend", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/messageThread/index.js b/cloudfunctions/messageThread/index.js new file mode 100644 index 0000000..533ba1e --- /dev/null +++ b/cloudfunctions/messageThread/index.js @@ -0,0 +1,82 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +function timeText(ts) { + if (!ts) return '' + const d = new Date(ts) + const hh = String(d.getHours()).padStart(2, '0') + const mm = String(d.getMinutes()).padStart(2, '0') + const sameDay = new Date().toDateString() === d.toDateString() + if (sameDay) return `${hh}:${mm}` + return `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm}` +} + +async function userToPeer(u) { + return { id: u._id, name: u.nickname || '用户', avatarKey: u.avatarKey || 'gradient-avatar-1', avatarUrl: u.avatarUrl || '' } +} + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + + let convId = event.conversationId || '' + let peer = null + + // Open by target user — resolve peer and find an existing conversation. + if (!convId && event.targetUserId) { + const tRes = await db.collection('users').doc(event.targetUserId).get().catch(() => null) + const t = tRes && tRes.data + if (t) { + peer = await userToPeer(t) + const ex = await db.collection('conversations') + .where({ type: 'single', memberOpenids: _.all([OPENID, t.openid]) }) + .limit(1) + .get() + .catch(() => ({ data: [] })) + if (ex.data.length) convId = ex.data[0]._id + } + } + + // Open by conversation — resolve the other member as peer. + if (convId && !peer) { + const c = await db.collection('conversations').doc(convId).get().catch(() => null) + if (c && c.data) { + const otherOpenid = (c.data.memberOpenids || []).find(o => o !== OPENID) + if (otherOpenid) { + const oRes = await db.collection('users').where({ openid: otherOpenid }).limit(1).get() + if (oRes.data[0]) peer = await userToPeer(oRes.data[0]) + } + if (!peer) { + peer = { id: '', name: c.data.title || '对话', avatarKey: c.data.avatarKey || 'gradient-avatar-1', avatarUrl: '' } + } + } + } + + let messages = [] + if (convId) { + const res = await db.collection('messages') + .where({ conversationId: convId }) + .orderBy('createdAt', 'desc') + .limit(50) + .get() + .catch(() => ({ data: [] })) + messages = res.data.reverse().map(m => ({ + _id: m._id, + fromMe: m.fromOpenid === OPENID, + content: m.content, + type: m.type || 'text', + createdAt: m.createdAt, + timeText: timeText(m.createdAt) + })) + + // Mark this conversation read for me. + await db.collection('conversations').doc(convId).update({ + data: { unreadOpenids: _.pull(OPENID), [`unreadCounts.${OPENID}`]: 0 } + }).catch(() => {}) + } + + return { conversationId: convId, peer, messages } +} diff --git a/cloudfunctions/messageThread/package.json b/cloudfunctions/messageThread/package.json new file mode 100644 index 0000000..7520263 --- /dev/null +++ b/cloudfunctions/messageThread/package.json @@ -0,0 +1,8 @@ +{ + "name": "messageThread", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/nearbyPets/index.js b/cloudfunctions/nearbyPets/index.js new file mode 100644 index 0000000..9c20369 --- /dev/null +++ b/cloudfunctions/nearbyPets/index.js @@ -0,0 +1,70 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +function hashId(id) { + let h = 5381 + for (let i = 0; i < id.length; i++) { + h = ((h << 5) + h) ^ id.charCodeAt(i) + h = h >>> 0 // keep as unsigned 32-bit + } + return h +} + +// Generate a stable map pin position from pet id (fallback abstract layer) +function stablePin(id) { + const h = hashId(id) + const left = 10 + (h % 70) + const top = 15 + ((h >> 8) % 55) + return { left, top } +} + +// Scatter a pet deterministically within roughly ±0.9km of the user, so on a +// real map markers cluster nearby (stable per pet id across requests). +function syntheticCoord(id, userLoc) { + const h = hashId(id) + const dLat = ((h % 1600) - 800) / 100000 // ±0.008° ≈ ±0.9km + const dLng = (((h >> 8) % 1600) - 800) / 100000 + return { latitude: userLoc.latitude + dLat, longitude: userLoc.longitude + dLng } +} + +// Rough distance text from coordinate delta (fallback when no real distance) +function distanceText(petLoc, userLoc) { + if (!petLoc || !userLoc) return null + const R = 6371000 + const dLat = ((petLoc.latitude - userLoc.latitude) * Math.PI) / 180 + const dLon = ((petLoc.longitude - userLoc.longitude) * Math.PI) / 180 + const a = + Math.sin(dLat / 2) ** 2 + + Math.cos((userLoc.latitude * Math.PI) / 180) * + Math.cos((petLoc.latitude * Math.PI) / 180) * + Math.sin(dLon / 2) ** 2 + const meters = Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))) + if (meters < 1000) return `${meters}m` + return `${(meters / 1000).toFixed(1)}km` +} + +exports.main = async event => { + const filter = event.filters?.type || 'all' + const userLoc = event.location || null + const query = {} + if (filter === 'dog' || filter === 'cat') query.species = filter + if (filter === 'online') query.online = true + + const res = await db.collection('pets').where(query).limit(50).get() + + const list = res.data.map(pet => { + const realCoord = pet.location && typeof pet.location.latitude === 'number' ? pet.location : null + const coord = realCoord || (userLoc ? syntheticCoord(pet._id, userLoc) : null) + return { + ...pet, + latitude: coord ? coord.latitude : undefined, + longitude: coord ? coord.longitude : undefined, + distanceText: distanceText(coord, userLoc) || pet.distanceText || null, + pin: stablePin(pet._id) + } + }) + + return { list } +} diff --git a/cloudfunctions/nearbyPets/package.json b/cloudfunctions/nearbyPets/package.json new file mode 100644 index 0000000..6f48100 --- /dev/null +++ b/cloudfunctions/nearbyPets/package.json @@ -0,0 +1,9 @@ +{ + "name": "nearbyPets", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/petDelete/index.js b/cloudfunctions/petDelete/index.js new file mode 100644 index 0000000..af1093f --- /dev/null +++ b/cloudfunctions/petDelete/index.js @@ -0,0 +1,26 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + if (!event.petId) throw new Error('petId is required') + + const users = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!users.data.length) throw new Error('user not found') + const ownerId = users.data[0]._id + + let pet + try { + pet = await db.collection('pets').doc(event.petId).get() + } catch (_) { + return { ok: true } // already gone + } + if (!pet.data) return { ok: true } + if (pet.data.ownerId !== ownerId) throw new Error('not allowed') + + await db.collection('pets').doc(event.petId).remove() + return { ok: true } +} diff --git a/cloudfunctions/petDelete/package.json b/cloudfunctions/petDelete/package.json new file mode 100644 index 0000000..68aae04 --- /dev/null +++ b/cloudfunctions/petDelete/package.json @@ -0,0 +1,8 @@ +{ + "name": "petDelete", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/cloudfunctions/petList/index.js b/cloudfunctions/petList/index.js new file mode 100644 index 0000000..3351236 --- /dev/null +++ b/cloudfunctions/petList/index.js @@ -0,0 +1,17 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + let ownerId = event.ownerId + if (!ownerId) { + const user = await db.collection('users').where({ openid: OPENID }).limit(1).get() + ownerId = user.data[0]?._id + } + + const res = ownerId ? await db.collection('pets').where({ ownerId }).limit(20).get() : { data: [] } + return { list: res.data } +} + diff --git a/cloudfunctions/petList/package.json b/cloudfunctions/petList/package.json new file mode 100644 index 0000000..a699aa6 --- /dev/null +++ b/cloudfunctions/petList/package.json @@ -0,0 +1,9 @@ +{ + "name": "petList", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/petSave/index.js b/cloudfunctions/petSave/index.js new file mode 100644 index 0000000..8f90d6d --- /dev/null +++ b/cloudfunctions/petSave/index.js @@ -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 => { + const { OPENID } = cloud.getWXContext() + const users = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!users.data.length) throw new Error('user not found') + + const pet = { + ...(event.pet || {}), + ownerId: users.data[0]._id, + updatedAt: Date.now() + } + delete pet._id + + if (event.pet?._id) { + await db.collection('pets').doc(event.pet._id).update({ data: pet }) + return { petId: event.pet._id } + } + + pet.createdAt = Date.now() + const result = await db.collection('pets').add({ data: pet }) + return { petId: result._id } +} + diff --git a/cloudfunctions/petSave/package.json b/cloudfunctions/petSave/package.json new file mode 100644 index 0000000..d315fe4 --- /dev/null +++ b/cloudfunctions/petSave/package.json @@ -0,0 +1,9 @@ +{ + "name": "petSave", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/postCreate/index.js b/cloudfunctions/postCreate/index.js new file mode 100644 index 0000000..1dab4c3 --- /dev/null +++ b/cloudfunctions/postCreate/index.js @@ -0,0 +1,65 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +const MEDIA_TONES = ['pet-1', 'pet-2', 'pet-3'] +const PET_TONES = { dog: 'gold', cat: 'purple', other: 'green' } + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const now = Date.now() + const content = String(event.content || '').trim() + if (!content) throw new Error('content is required') + + // Get author info first (required for snapshot) + const userRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!userRes.data.length) throw new Error('user not found') + const user = userRes.data[0] + + // Get pet snapshot if petId provided + let petSnapshot = null + if (event.petId) { + try { + const petRes = await db.collection('pets').doc(event.petId).get() + if (petRes.data) { + const p = petRes.data + petSnapshot = { name: p.name, breed: p.breed, tone: PET_TONES[p.species] || 'green' } + } + } catch (_) {} + } + + // Derive mediaTone from media count so similar posts look varied + const mediaCount = Array.isArray(event.media) ? event.media.length : 0 + const mediaTone = event.mediaTone || MEDIA_TONES[(mediaCount + now) % 3] + + const post = { + authorOpenid: OPENID, + authorId: user._id, + authorSnapshot: { name: user.nickname, avatarKey: user.avatarKey }, + petId: event.petId || '', + petSnapshot, + content: content.slice(0, 2000), + topics: Array.isArray(event.topics) ? event.topics : [], + media: Array.isArray(event.media) ? event.media : [], + mediaTone, + sticker: event.sticker || null, + locationText: event.locationText || '', + location: (typeof event.latitude === 'number' && typeof event.longitude === 'number') + ? { latitude: event.latitude, longitude: event.longitude } + : null, + visibility: event.visibility || 'public', + counts: { likes: 0, comments: 0, shares: 0, favorites: 0 }, + createdAt: now, + updatedAt: now + } + + const result = await db.collection('posts').add({ data: post }) + + await db.collection('users').doc(user._id).update({ + data: { 'stats.posts': _.inc(1) } + }) + + return { postId: result._id } +} diff --git a/cloudfunctions/postCreate/package.json b/cloudfunctions/postCreate/package.json new file mode 100644 index 0000000..38b0453 --- /dev/null +++ b/cloudfunctions/postCreate/package.json @@ -0,0 +1,9 @@ +{ + "name": "postCreate", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/postFavorite/index.js b/cloudfunctions/postFavorite/index.js new file mode 100644 index 0000000..7da53d0 --- /dev/null +++ b/cloudfunctions/postFavorite/index.js @@ -0,0 +1,32 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const { postId, favorited } = event + if (!postId) throw new Error('postId is required') + + const existed = await db.collection('postFavorites').where({ postId, openid: OPENID }).limit(1).get() + let delta = 0 + + if (favorited && !existed.data.length) { + await db.collection('postFavorites').add({ data: { postId, openid: OPENID, createdAt: Date.now() } }) + delta = 1 + } + + if (!favorited && existed.data.length) { + await db.collection('postFavorites').doc(existed.data[0]._id).remove() + delta = -1 + } + + if (delta) { + await db.collection('posts').doc(postId).update({ data: { 'counts.favorites': _.inc(delta) } }) + } + + const post = await db.collection('posts').doc(postId).get() + return { favorited: Boolean(favorited), favorites: post.data.counts?.favorites || 0 } +} + diff --git a/cloudfunctions/postFavorite/package.json b/cloudfunctions/postFavorite/package.json new file mode 100644 index 0000000..0b6a291 --- /dev/null +++ b/cloudfunctions/postFavorite/package.json @@ -0,0 +1,9 @@ +{ + "name": "postFavorite", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/postLike/index.js b/cloudfunctions/postLike/index.js new file mode 100644 index 0000000..563c862 --- /dev/null +++ b/cloudfunctions/postLike/index.js @@ -0,0 +1,32 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + const { postId, liked } = event + if (!postId) throw new Error('postId is required') + + const existed = await db.collection('postLikes').where({ postId, openid: OPENID }).limit(1).get() + let delta = 0 + + if (liked && !existed.data.length) { + await db.collection('postLikes').add({ data: { postId, openid: OPENID, createdAt: Date.now() } }) + delta = 1 + } + + if (!liked && existed.data.length) { + await db.collection('postLikes').doc(existed.data[0]._id).remove() + delta = -1 + } + + if (delta) { + await db.collection('posts').doc(postId).update({ data: { 'counts.likes': _.inc(delta) } }) + } + + const post = await db.collection('posts').doc(postId).get() + return { liked: Boolean(liked), likes: post.data.counts?.likes || 0 } +} + diff --git a/cloudfunctions/postLike/package.json b/cloudfunctions/postLike/package.json new file mode 100644 index 0000000..86c7af5 --- /dev/null +++ b/cloudfunctions/postLike/package.json @@ -0,0 +1,9 @@ +{ + "name": "postLike", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/profileGet/index.js b/cloudfunctions/profileGet/index.js new file mode 100644 index 0000000..73271ed --- /dev/null +++ b/cloudfunctions/profileGet/index.js @@ -0,0 +1,40 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +// Count a collection safely — returns 0 if the collection does not exist yet. +async function safeCount(collection, where) { + try { + const res = await db.collection(collection).where(where).count() + return res.total || 0 + } catch (e) { + return 0 + } +} + +// Derive live stats from the source collections so the displayed numbers are +// always accurate instead of relying on a stored field that can drift. +async function computeStats(user) { + const [posts, favorites, following, followers] = await Promise.all([ + safeCount('posts', { authorId: user._id }), + safeCount('postFavorites', { openid: user.openid }), + safeCount('follows', { followerId: user._id }), + safeCount('follows', { followeeId: user._id }) + ]) + return { posts, following, followers, favorites } +} + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!event.userId && !OPENID) throw new Error('no openid in wx context') + const query = event.userId ? { _id: event.userId } : { openid: OPENID } + const users = await db.collection('users').where(query).limit(1).get() + const user = users.data[0] || null + if (!user) return { user: null, pets: [] } + + const pets = await db.collection('pets').where({ ownerId: user._id }).limit(20).get() + user.stats = await computeStats(user) + + return { user, pets: pets.data } +} diff --git a/cloudfunctions/profileGet/package.json b/cloudfunctions/profileGet/package.json new file mode 100644 index 0000000..6b473fb --- /dev/null +++ b/cloudfunctions/profileGet/package.json @@ -0,0 +1,9 @@ +{ + "name": "profileGet", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/profileUpdate/index.js b/cloudfunctions/profileUpdate/index.js new file mode 100644 index 0000000..a0fe125 --- /dev/null +++ b/cloudfunctions/profileUpdate/index.js @@ -0,0 +1,28 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const patch = event.patch || {} + delete patch.openid + delete patch._id + + const users = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!users.data.length) throw new Error('user not found') + + // Setting a real avatar means the user has completed WeChat profile authorization + if (patch.avatarUrl) patch.profileCompleted = true + + await db.collection('users').doc(users.data[0]._id).update({ + data: { + ...patch, + updatedAt: Date.now() + } + }) + const user = await db.collection('users').doc(users.data[0]._id).get() + return { user: user.data } +} + diff --git a/cloudfunctions/profileUpdate/package.json b/cloudfunctions/profileUpdate/package.json new file mode 100644 index 0000000..a51ce7a --- /dev/null +++ b/cloudfunctions/profileUpdate/package.json @@ -0,0 +1,9 @@ +{ + "name": "profileUpdate", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} + diff --git a/cloudfunctions/userList/index.js b/cloudfunctions/userList/index.js new file mode 100644 index 0000000..735026c --- /dev/null +++ b/cloudfunctions/userList/index.js @@ -0,0 +1,46 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +// Discover other users to follow. Optional keyword filters by nickname. +exports.main = async event => { + const { OPENID } = cloud.getWXContext() + if (!OPENID) throw new Error('no openid in wx context') + const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() + if (!meRes.data.length) throw new Error('user not found') + const meId = meRes.data[0]._id + const keyword = String(event.keyword || '').trim() + + const where = { _id: _.neq(meId) } + if (keyword) where.nickname = db.RegExp({ regexp: keyword, options: 'i' }) + + const usersRes = await db.collection('users') + .where(where) + .orderBy('lastLoginAt', 'desc') + .limit(30) + .get() + + const safeFollows = where2 => + db.collection('follows').where(where2).limit(500).get().catch(() => ({ data: [] })) + const [followingRes, followerRes] = await Promise.all([ + safeFollows({ followerId: meId }), + safeFollows({ followeeId: meId }) + ]) + const followingSet = new Set(followingRes.data.map(f => f.followeeId)) + const followerSet = new Set(followerRes.data.map(f => f.followerId)) + + const list = usersRes.data.map(u => ({ + id: u._id, + name: u.nickname || '用户', + handle: u.handle || '', + avatarKey: u.avatarKey || 'gradient-avatar-1', + avatarUrl: u.avatarUrl || '', + bio: u.bio || '', + isFollowing: followingSet.has(u._id), + isFriend: followingSet.has(u._id) && followerSet.has(u._id) + })) + + return { list } +} diff --git a/cloudfunctions/userList/package.json b/cloudfunctions/userList/package.json new file mode 100644 index 0000000..bc5fbdb --- /dev/null +++ b/cloudfunctions/userList/package.json @@ -0,0 +1,8 @@ +{ + "name": "userList", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} diff --git a/config/dev.ts b/config/dev.ts new file mode 100644 index 0000000..c4e6f57 --- /dev/null +++ b/config/dev.ts @@ -0,0 +1,7 @@ +export default { + env: { + TARO_APP_CLOUD_ENV: JSON.stringify(process.env.TARO_APP_CLOUD_ENV || 'cloud1-d4g697lte499543d8') + }, + defineConstants: {} +} + diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..9665ea1 --- /dev/null +++ b/config/index.ts @@ -0,0 +1,57 @@ +import path from 'path' +import { defineConfig, type UserConfigExport } from '@tarojs/cli' + +export default defineConfig<'webpack5'>(async (merge, { command }) => { + const baseConfig: UserConfigExport<'webpack5'> = { + projectName: 'wangquan-miniapp', + date: '2026-06-16', + designWidth: 390, + deviceRatio: { + 390: 2, + 640: 2.34, + 750: 1, + 828: 1.81 + }, + sourceRoot: 'src', + outputRoot: 'dist', + plugins: ['@tarojs/plugin-framework-react'], + defineConstants: {}, + copy: { + patterns: [{ from: 'src/custom-tab-bar', to: 'dist/custom-tab-bar' }], + options: {} + }, + framework: 'react', + compiler: 'webpack5', + cache: { + enable: false + }, + alias: { + '@': path.resolve(__dirname, '..', 'src') + }, + sass: { + data: '@use "@/styles/tokens.scss" as *;' + }, + mini: { + postcss: { + pxtransform: { + enable: true, + config: {} + }, + cssModules: { + enable: false, + config: { + namingPattern: 'module', + generateScopedName: '[name]__[local]___[hash:base64:5]' + } + } + } + }, + h5: {} + } + + if (command === 'build') { + return merge({}, baseConfig, (await import('./prod')).default) + } + + return merge({}, baseConfig, (await import('./dev')).default) +}) diff --git a/config/prod.ts b/config/prod.ts new file mode 100644 index 0000000..c4e6f57 --- /dev/null +++ b/config/prod.ts @@ -0,0 +1,7 @@ +export default { + env: { + TARO_APP_CLOUD_ENV: JSON.stringify(process.env.TARO_APP_CLOUD_ENV || 'cloud1-d4g697lte499543d8') + }, + defineConstants: {} +} + diff --git a/dist/app-origin.wxss b/dist/app-origin.wxss new file mode 100644 index 0000000..4fb9772 --- /dev/null +++ b/dist/app-origin.wxss @@ -0,0 +1 @@ +@-webkit-keyframes slideUp{from{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes slideUp{from{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes pulse{0%{opacity:.9;-webkit-transform:scale(.2);transform:scale(.2)}80%{opacity:0;-webkit-transform:scale(2.2);transform:scale(2.2)}100%{opacity:0;-webkit-transform:scale(2.2);transform:scale(2.2)}}@keyframes pulse{0%{opacity:.9;-webkit-transform:scale(.2);transform:scale(.2)}80%{opacity:0;-webkit-transform:scale(2.2);transform:scale(2.2)}100%{opacity:0;-webkit-transform:scale(2.2);transform:scale(2.2)}}@-webkit-keyframes softAppear{from{opacity:0;-webkit-transform:translateY(16rpx);transform:translateY(16rpx)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes softAppear{from{opacity:0;-webkit-transform:translateY(16rpx);transform:translateY(16rpx)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.wx-icon-text{font-family:Inter,system-ui,-apple-system,PingFang SC,Microsoft YaHei,sans-serif;line-height:1}.no-scrollbar{scrollbar-width:none}.no-scrollbar::-webkit-scrollbar{display:none}.gradient-avatar-1{background:linear-gradient(135deg,#ffb3c8,#ffd9a8)}.gradient-avatar-2{background:linear-gradient(135deg,#d8b4ff,#a4d4ff)}.gradient-avatar-3{background:linear-gradient(135deg,#8fe5b5,#d8b4ff)}.gradient-avatar-4{background:linear-gradient(135deg,#ffd9a8,#ffb3c8)}.gradient-avatar-5{background:linear-gradient(135deg,#a4d4ff,#ffd9a8)}.gradient-avatar-6{background:linear-gradient(135deg,#ffb3c8,#d8b4ff)}page{background:#14102b;color:#f0e9ff;font-family:Inter,system-ui,-apple-system,PingFang SC,Microsoft YaHei,sans-serif;min-height:100%}button{background:transparent;border:0;color:inherit;line-height:inherit;padding:0}button::after{border:0}scroll-view{-webkit-box-sizing:border-box;box-sizing:border-box} \ No newline at end of file diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 0000000..61bf46e --- /dev/null +++ b/dist/app.js @@ -0,0 +1,2 @@ +/*! For license information please see app.js.LICENSE.txt */ +require("./common"),require("./vendors"),require("./taro"),require("./runtime"),(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[524],{2951:function(e,n,t){"use strict";t(2457);var r=t(7860),l=t(1291),a=t(758),u=t.n(a);function i(e){var n=e.children;return(0,a.useLaunch)(function(){var e="cloud1-d4g697lte499543d8";e&&u().cloud&&u().cloud.init({env:e,traceUser:!0})}),n}var o=i,s=t(6540),c=t.t(s,2),f=t(7260),d={pages:["pages/plaza/index","pages/nearby/index","pages/publish/index","pages/messages/index","pages/profile/index","pages/profile-edit/index","pages/pet-edit/index","pages/chat/index","pages/contacts/index"],window:{navigationStyle:"custom",backgroundColor:"#14102b",backgroundTextStyle:"dark"},tabBar:{custom:!0,color:"#8a7aab",selectedColor:"#d8b4ff",backgroundColor:"#14102b",borderStyle:"black",list:[{pagePath:"pages/plaza/index",text:"\u5e7f\u573a"},{pagePath:"pages/nearby/index",text:"\u9644\u8fd1"},{pagePath:"pages/messages/index",text:"\u6d88\u606f"},{pagePath:"pages/profile/index",text:"\u6211\u7684"}]},permission:{"scope.userLocation":{desc:"\u7528\u4e8e\u53d1\u73b0\u9644\u8fd1\u7684\u6bdb\u5b69\u5b50\uff0c\u4ee5\u53ca\u5728\u53d1\u5e03\u52a8\u6001\u65f6\u6807\u8bb0\u4f4d\u7f6e"}},requiredPrivateInfos:["chooseLocation","getLocation"],lazyCodeLoading:"requiredComponents"};r.mw.__taroAppConfig=d;App((0,l.ND)(o,c,f.Ay,d));(0,a.initPxTransform)({designWidth:390,deviceRatio:{390:2,640:2.34,750:1,828:1.81},baseFontSize:20,unitPrecision:void 0,targetUnit:void 0})},735:function(e,n){"use strict";n.DefaultEventPriority=16},2935:function(e,n,t){e.exports=function(e){var n={},r=t(6540),l=t(9982),a=Object.assign;function u(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,t=1;ti||l[u]!==a[i]){var o="\n"+l[u].replace(" at new "," at ");return e.displayName&&o.includes("")&&(o=o.replace("",e.displayName)),o}}while(1<=u&&0<=i);break}}}finally{tn=!1,Error.prepareStackTrace=t}return(e=e?e.displayName||e.name:"")?nn(e):""}var ln=Object.prototype.hasOwnProperty,an=[],un=-1;function on(e){return{current:e}}function sn(e){0>un||(e.current=an[un],an[un]=null,un--)}function cn(e,n){un++,an[un]=e.current,e.current=n}var fn={},dn=on(fn),pn=on(!1),hn=fn;function mn(e,n){var t=e.type.contextTypes;if(!t)return fn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===n)return r.__reactInternalMemoizedMaskedChildContext;var l,a={};for(l in t)a[l]=n[l];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=a),a}function gn(e){return e=e.childContextTypes,null!==e&&void 0!==e}function vn(){sn(pn),sn(dn)}function bn(e,n,t){if(dn.current!==fn)throw Error(u(168));cn(dn,n),cn(pn,t)}function yn(e,n,t){var r=e.stateNode;if(n=n.childContextTypes,"function"!==typeof r.getChildContext)return t;for(var l in r=r.getChildContext(),r)if(!(l in n))throw Error(u(108,z(e)||"Unknown",l));return a({},t,r)}function Sn(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||fn,hn=dn.current,cn(dn,e),cn(pn,pn.current),!0}function kn(e,n,t){var r=e.stateNode;if(!r)throw Error(u(169));t?(e=yn(e,n,hn),r.__reactInternalMemoizedMergedChildContext=e,sn(pn),sn(dn),cn(dn,e)):sn(pn),cn(pn,t)}var xn=Math.clz32?Math.clz32:Nn,wn=Math.log,zn=Math.LN2;function Nn(e){return e>>>=0,0===e?32:31-(wn(e)/zn|0)|0}var En=64,Pn=4194304;function Cn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194240&e;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return 130023424&e;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function _n(e,n){var t=e.pendingLanes;if(0===t)return 0;var r=0,l=e.suspendedLanes,a=e.pingedLanes,u=268435455&t;if(0!==u){var i=u&~l;0!==i?r=Cn(i):(a&=u,0!==a&&(r=Cn(a)))}else u=t&~l,0!==u?r=Cn(u):0!==a&&(r=Cn(a));if(0===r)return 0;if(0!==n&&n!==r&&0===(n&l)&&(l=r&-r,a=n&-n,l>=a||16===l&&0!==(4194240&a)))return n;if(0!==(4&r)&&(r|=16&t),n=e.entangledLanes,0!==n)for(e=e.entanglements,n&=r;0t;t++)n.push(e);return n}function Un(e,n,t){e.pendingLanes|=n,536870912!==n&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,n=31-xn(n),e[n]=t}function Mn(e,n){var t=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=n,e.mutableReadLanes&=n,e.entangledLanes&=n,n=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0>=u,l-=u,pt=1<<32-xn(n)+l|t<d?(p=f,f=null):p=f.sibling;var h=g(l,f,i[d],o);if(null===h){null===f&&(f=p);break}e&&f&&null===h.alternate&&n(l,f),u=a(h,u,d),null===c?s=h:c.sibling=h,c=h,f=p}if(d===i.length)return t(l,f),kt&&mt(l,d),s;if(null===f){for(;dp?(h=d,d=null):h=d.sibling;var y=g(l,d,b.value,s);if(null===y){null===d&&(d=h);break}e&&d&&null===y.alternate&&n(l,d),i=a(y,i,p),null===f?c=y:f.sibling=y,f=y,d=h}if(b.done)return t(l,d),kt&&mt(l,p),c;if(null===d){for(;!b.done;p++,b=o.next())b=m(l,b.value,s),null!==b&&(i=a(b,i,p),null===f?c=b:f.sibling=b,f=b);return kt&&mt(l,p),c}for(d=r(l,d);!b.done;p++,b=o.next())b=v(d,l,p,b.value,s),null!==b&&(e&&null!==b.alternate&&d.delete(null===b.key?p:b.key),i=a(b,i,p),null===f?c=b:f.sibling=b,f=b);return e&&d.forEach(function(e){return n(l,e)}),kt&&mt(l,p),c}function k(e,r,a,u){if("object"===typeof a&&null!==a&&a.type===c&&null===a.key&&(a=a.props.children),"object"===typeof a&&null!==a){switch(a.$$typeof){case o:e:{for(var f=a.key,d=r;null!==d;){if(d.key===f){if(f=a.type,f===c){if(7===d.tag){t(e,d.sibling),r=l(d,a.props.children),r.return=e,e=r;break e}}else if(d.elementType===f||"object"===typeof f&&null!==f&&f.$$typeof===y&&mr(f)===d.type){t(e,d.sibling),r=l(d,a.props),r.ref=pr(e,d,a),r.return=e,e=r;break e}t(e,d);break}n(e,d),d=d.sibling}a.type===c?(r=xi(a.props.children,e.mode,u,a.key),r.return=e,e=r):(u=ki(a.type,a.key,a.props,null,e.mode,u),u.ref=pr(e,r,a),u.return=e,e=u)}return i(e);case s:e:{for(d=a.key;null!==r;){if(r.key===d){if(4===r.tag&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){t(e,r.sibling),r=l(r,a.children||[]),r.return=e,e=r;break e}t(e,r);break}n(e,r),r=r.sibling}r=Ni(a,e.mode,u),r.return=e,e=r}return i(e);case y:return d=a._init,k(e,r,d(a._payload),u)}if(T(a))return b(e,r,a,u);if(x(a))return S(e,r,a,u);hr(e,a)}return"string"===typeof a&&""!==a||"number"===typeof a?(a=""+a,null!==r&&6===r.tag?(t(e,r.sibling),r=l(r,a),r.return=e,e=r):(t(e,r),r=zi(a,e.mode,u),r.return=e,e=r),i(e)):t(e,r)}return k}var vr=gr(!0),br=gr(!1),yr={},Sr=on(yr),kr=on(yr),xr=on(yr);function wr(e){if(e===yr)throw Error(u(174));return e}function zr(e,n){cn(xr,n),cn(kr,e),cn(Sr,yr),e=F(n),sn(Sr),cn(Sr,e)}function Nr(){sn(Sr),sn(kr),sn(xr)}function Er(e){var n=wr(xr.current),t=wr(Sr.current);n=U(t,e.type,n),t!==n&&(cn(kr,e),cn(Sr,n))}function Pr(e){kr.current===e&&(sn(Sr),sn(kr))}var Cr=on(0);function _r(e){for(var n=e;null!==n;){if(13===n.tag){var t=n.memoizedState;if(null!==t&&(t=t.dehydrated,null===t||Me(t)||De(t)))return n}else if(19===n.tag&&void 0!==n.memoizedProps.revealOrder){if(0!==(128&n.flags))return n}else if(null!==n.child){n.child.return=n,n=n.child;continue}if(n===e)break;for(;null===n.sibling;){if(null===n.return||n.return===e)return null;n=n.return}n.sibling.return=n.return,n=n.sibling}return null}var Ir=[];function Lr(){for(var e=0;et?t:4,e(!0);var r=Rr.transition;Rr.transition={};try{e(!1),n()}finally{Qn=t,Rr.transition=r}}function Sl(){return qr().memoizedState}function kl(e,n,t){var r=Bu(e);if(t={lane:r,action:t,hasEagerState:!1,eagerState:null,next:null},wl(e))zl(n,t);else if(t=Gt(e,n,t,r),null!==t){var l=Hu();Wu(t,e,r,l),Nl(t,n,r)}}function xl(e,n,t){var r=Bu(e),l={lane:r,action:t,hasEagerState:!1,eagerState:null,next:null};if(wl(e))zl(n,l);else{var a=e.alternate;if(0===e.lanes&&(null===a||0===a.lanes)&&(a=n.lastRenderedReducer,null!==a))try{var u=n.lastRenderedState,i=a(u,t);if(l.hasEagerState=!0,l.eagerState=i,Zn(i,u)){var o=n.interleaved;return null===o?(l.next=l,Yt(n)):(l.next=o.next,o.next=l),void(n.interleaved=l)}}catch(e){}t=Gt(e,n,l,r),null!==t&&(l=Hu(),Wu(t,e,r,l),Nl(t,n,r))}}function wl(e){var n=e.alternate;return e===Ur||null!==n&&n===Ur}function zl(e,n){Hr=Qr=!0;var t=e.pending;null===t?n.next=n:(n.next=t.next,t.next=n),e.pending=n}function Nl(e,n,t){if(0!==(4194240&t)){var r=n.lanes;r&=e.pendingLanes,t|=r,n.lanes=t,Dn(e,t)}}var El={readContext:Vt,useCallback:jr,useContext:jr,useEffect:jr,useImperativeHandle:jr,useInsertionEffect:jr,useLayoutEffect:jr,useMemo:jr,useReducer:jr,useRef:jr,useState:jr,useDebugValue:jr,useDeferredValue:jr,useTransition:jr,useMutableSource:jr,useSyncExternalStore:jr,useId:jr,unstable_isNewReconciler:!1},Pl={readContext:Vt,useCallback:function(e,n){return Vr().memoizedState=[e,void 0===n?null:n],e},useContext:Vt,useEffect:sl,useImperativeHandle:function(e,n,t){return t=null!==t&&void 0!==t?t.concat([e]):null,il(4194308,4,pl.bind(null,n,e),t)},useLayoutEffect:function(e,n){return il(4194308,4,e,n)},useInsertionEffect:function(e,n){return il(4,2,e,n)},useMemo:function(e,n){var t=Vr();return n=void 0===n?null:n,e=e(),t.memoizedState=[e,n],e},useReducer:function(e,n,t){var r=Vr();return n=void 0!==t?t(n):n,r.memoizedState=r.baseState=n,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},r.queue=e,e=e.dispatch=kl.bind(null,Ur,e),[r.memoizedState,e]},useRef:function(e){var n=Vr();return e={current:e},n.memoizedState=e},useState:ll,useDebugValue:ml,useDeferredValue:function(e){return Vr().memoizedState=e},useTransition:function(){var e=ll(!1),n=e[0];return e=yl.bind(null,e[1]),Vr().memoizedState=e,[n,e]},useMutableSource:function(){},useSyncExternalStore:function(e,n,t){var r=Ur,l=Vr();if(kt){if(void 0===t)throw Error(u(407));t=t()}else{if(t=n(),null===du)throw Error(u(349));0!==(30&Fr)||Zr(r,n,t)}l.memoizedState=t;var a={value:t,getSnapshot:n};return l.queue=a,sl(nl.bind(null,r,a,e),[e]),r.flags|=2048,al(9,el.bind(null,r,a,t,n),void 0,null),t},useId:function(){var e=Vr(),n=du.identifierPrefix;if(kt){var t=ht,r=pt;t=(r&~(1<<32-xn(r)-1)).toString(32)+t,n=":"+n+"R"+t,t=Br++,0Nu&&(n.flags|=128,r=!0,va(l,!1),n.lanes=4194304)}else{if(!r)if(e=_r(a),null!==e){if(n.flags|=128,r=!0,e=e.updateQueue,null!==e&&(n.updateQueue=e,n.flags|=4),va(l,!0),null===l.tail&&"hidden"===l.tailMode&&!a.alternate&&!kt)return ba(n),null}else 2*On()-l.renderingStartTime>Nu&&1073741824!==t&&(n.flags|=128,r=!0,va(l,!1),n.lanes=4194304);l.isBackwards?(a.sibling=n.child,n.child=a):(e=l.last,null!==e?e.sibling=a:n.child=a,l.last=a)}return null!==l.tail?(n=l.tail,l.rendering=n,l.tail=n.sibling,l.renderingStartTime=On(),n.sibling=null,e=Cr.current,cn(Cr,r?1&e|2:1&e),n):(ba(n),null);case 22:case 23:return Ju(),t=null!==n.memoizedState,null!==e&&null!==e.memoizedState!==t&&(n.flags|=8192),t&&0!==(1&n.mode)?0!==(1073741824&mu)&&(ba(n),Y&&6&n.subtreeFlags&&(n.flags|=8192)):ba(n),null;case 24:return null;case 25:return null}throw Error(u(156,n.tag))}function Sa(e,n){switch(bt(n),n.tag){case 1:return gn(n.type)&&vn(),e=n.flags,65536&e?(n.flags=-65537&e|128,n):null;case 3:return Nr(),sn(pn),sn(dn),Lr(),e=n.flags,0!==(65536&e)&&0===(128&e)?(n.flags=-65537&e|128,n):null;case 5:return Pr(n),null;case 13:if(sn(Cr),e=n.memoizedState,null!==e&&null!==e.dehydrated){if(null===n.alternate)throw Error(u(340));Lt()}return e=n.flags,65536&e?(n.flags=-65537&e|128,n):null;case 19:return sn(Cr),null;case 4:return Nr(),null;case 10:return $t(n.type._context),null;case 22:case 23:return Ju(),null;case 24:return null;default:return null}}var ka=!1,xa=!1,wa="function"===typeof WeakSet?WeakSet:Set,za=null;function Na(e,n){var t=e.ref;if(null!==t)if("function"===typeof t)try{t(null)}catch(t){ci(e,n,t)}else t.current=null}function Ea(e,n,t){try{t()}catch(t){ci(e,n,t)}}var Pa=!1;function Ca(e,n){for(M(e.containerInfo),za=n;null!==za;)if(e=za,n=e.child,0!==(1028&e.subtreeFlags)&&null!==n)n.return=e,za=n;else for(;null!==za;){e=za;try{var t=e.alternate;if(0!==(1024&e.flags))switch(e.tag){case 0:case 11:case 15:break;case 1:if(null!==t){var r=t.memoizedProps,l=t.memoizedState,a=e.stateNode,i=a.getSnapshotBeforeUpdate(e.elementType===e.type?r:Mt(e.type,r),l);a.__reactInternalSnapshotBeforeUpdate=i}break;case 3:Y&&Ne(e.stateNode.containerInfo);break;case 5:case 6:case 4:case 17:break;default:throw Error(u(163))}}catch(n){ci(e,e.return,n)}if(n=e.sibling,null!==n){n.return=e.return,za=n;break}za=e.return}return t=Pa,Pa=!1,t}function _a(e,n,t){var r=n.updateQueue;if(r=null!==r?r.lastEffect:null,null!==r){var l=r=r.next;do{if((l.tag&e)===e){var a=l.destroy;l.destroy=void 0,void 0!==a&&Ea(n,t,a)}l=l.next}while(l!==r)}}function Ia(e,n){if(n=n.updateQueue,n=null!==n?n.lastEffect:null,null!==n){var t=n=n.next;do{if((t.tag&e)===e){var r=t.create;t.destroy=r()}t=t.next}while(t!==n)}}function La(e){var n=e.ref;if(null!==n){var t=e.stateNode;switch(e.tag){case 5:e=R(t);break;default:e=t}"function"===typeof n?n(e):n.current=e}}function Ta(e){var n=e.alternate;null!==n&&(e.alternate=null,Ta(n)),e.child=null,e.deletions=null,e.sibling=null,5===e.tag&&(n=e.stateNode,null!==n&&ee(n)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Ra(e){return 5===e.tag||3===e.tag||4===e.tag}function Fa(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||Ra(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(2&e.flags)continue e;if(null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function Ua(e,n,t){var r=e.tag;if(5===r||6===r)e=e.stateNode,n?ve(t,e,n):de(t,e);else if(4!==r&&(e=e.child,null!==e))for(Ua(e,n,t),e=e.sibling;null!==e;)Ua(e,n,t),e=e.sibling}function Ma(e,n,t){var r=e.tag;if(5===r||6===r)e=e.stateNode,n?ge(t,e,n):fe(t,e);else if(4!==r&&(e=e.child,null!==e))for(Ma(e,n,t),e=e.sibling;null!==e;)Ma(e,n,t),e=e.sibling}var Da=null,Qa=!1;function Ha(e,n,t){for(t=t.child;null!==t;)Ba(e,n,t),t=t.sibling}function Ba(e,n,t){if(Jn&&"function"===typeof Jn.onCommitFiberUnmount)try{Jn.onCommitFiberUnmount(Gn,t)}catch(e){}switch(t.tag){case 5:xa||Na(t,n);case 6:if(Y){var r=Da,l=Qa;Da=null,Ha(e,n,t),Da=r,Qa=l,null!==Da&&(Qa?ye(Da,t.stateNode):be(Da,t.stateNode))}else Ha(e,n,t);break;case 18:Y&&null!==Da&&(Qa?Ke(Da,t.stateNode):Je(Da,t.stateNode));break;case 4:Y?(r=Da,l=Qa,Da=t.stateNode.containerInfo,Qa=!0,Ha(e,n,t),Da=r,Qa=l):(G&&(r=t.stateNode.containerInfo,l=Pe(r),Ie(r,l)),Ha(e,n,t));break;case 0:case 11:case 14:case 15:if(!xa&&(r=t.updateQueue,null!==r&&(r=r.lastEffect,null!==r))){l=r=r.next;do{var a=l,u=a.destroy;a=a.tag,void 0!==u&&(0!==(2&a)||0!==(4&a))&&Ea(t,n,u),l=l.next}while(l!==r)}Ha(e,n,t);break;case 1:if(!xa&&(Na(t,n),r=t.stateNode,"function"===typeof r.componentWillUnmount))try{r.props=t.memoizedProps,r.state=t.memoizedState,r.componentWillUnmount()}catch(e){ci(t,n,e)}Ha(e,n,t);break;case 21:Ha(e,n,t);break;case 22:1&t.mode?(xa=(r=xa)||null!==t.memoizedState,Ha(e,n,t),xa=r):Ha(e,n,t);break;default:Ha(e,n,t)}}function Wa(e){var n=e.updateQueue;if(null!==n){e.updateQueue=null;var t=e.stateNode;null===t&&(t=e.stateNode=new wa),n.forEach(function(n){var r=hi.bind(null,e,n);t.has(n)||(t.add(n),n.then(r,r))})}}function ja(e,n){var t=n.deletions;if(null!==t)for(var r=0;r";case Ka:return":has("+(lu(e)||"")+")";case Xa:return'[role="'+e.value+'"]';case eu:return'"'+e.value+'"';case Za:return'[data-testname="'+e.value+'"]';default:throw Error(u(365))}}function au(e,n){var t=[];e=[e,0];for(var r=0;rl&&(l=i),r&=~a}if(r=l,r=On()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*iu(r/1960))-r,10e?16:e,null===Ru)var r=!1;else{if(e=Ru,Ru=null,Fu=0,0!==(6&fu))throw Error(u(331));var l=fu;for(fu|=4,za=e.current;null!==za;){var a=za,i=a.child;if(0!==(16&za.flags)){var o=a.deletions;if(null!==o){for(var s=0;sOn()-zu?Ku(e,0):ku|=t),ju(e,n)}function di(e,n){0===n&&(0===(1&e.mode)?n=1:(n=Pn,Pn<<=1,0===(130023424&Pn)&&(Pn=4194304)));var t=Hu();e=Jt(e,n),null!==e&&(Un(e,n,t),ju(e,t))}function pi(e){var n=e.memoizedState,t=0;null!==n&&(t=n.retryLane),di(e,t)}function hi(e,n){var t=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;null!==l&&(t=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(u(314))}null!==r&&r.delete(n),di(e,t)}function mi(e,n){return Bn(e,n)}function gi(e,n,t,r){this.tag=e,this.key=t,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=n,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function vi(e,n,t,r){return new gi(e,n,t,r)}function bi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function yi(e){if("function"===typeof e)return bi(e)?1:0;if(void 0!==e&&null!==e){if(e=e.$$typeof,e===m)return 11;if(e===b)return 14}return 2}function Si(e,n){var t=e.alternate;return null===t?(t=vi(e.tag,n,e.key,e.mode),t.elementType=e.elementType,t.type=e.type,t.stateNode=e.stateNode,t.alternate=e,e.alternate=t):(t.pendingProps=n,t.type=e.type,t.flags=0,t.subtreeFlags=0,t.deletions=null),t.flags=14680064&e.flags,t.childLanes=e.childLanes,t.lanes=e.lanes,t.child=e.child,t.memoizedProps=e.memoizedProps,t.memoizedState=e.memoizedState,t.updateQueue=e.updateQueue,n=e.dependencies,t.dependencies=null===n?null:{lanes:n.lanes,firstContext:n.firstContext},t.sibling=e.sibling,t.index=e.index,t.ref=e.ref,t}function ki(e,n,t,r,l,a){var i=2;if(r=e,"function"===typeof e)bi(e)&&(i=1);else if("string"===typeof e)i=5;else e:switch(e){case c:return xi(t.children,l,a,n);case f:i=8,l|=8;break;case d:return e=vi(12,t,n,2|l),e.elementType=d,e.lanes=a,e;case g:return e=vi(13,t,n,l),e.elementType=g,e.lanes=a,e;case v:return e=vi(19,t,n,l),e.elementType=v,e.lanes=a,e;case S:return wi(t,l,a,n);default:if("object"===typeof e&&null!==e)switch(e.$$typeof){case p:i=10;break e;case h:i=9;break e;case m:i=11;break e;case b:i=14;break e;case y:i=16,r=null;break e}throw Error(u(130,null==e?e:typeof e,""))}return n=vi(i,t,n,l),n.elementType=e,n.type=r,n.lanes=a,n}function xi(e,n,t,r){return e=vi(7,e,r,n),e.lanes=t,e}function wi(e,n,t,r){return e=vi(22,e,r,n),e.elementType=S,e.lanes=t,e.stateNode={isHidden:!1},e}function zi(e,n,t){return e=vi(6,e,null,n),e.lanes=t,e}function Ni(e,n,t){return n=vi(4,null!==e.children?e.children:[],e.key,n),n.lanes=t,n.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},n}function Ei(e,n,t,r,l){this.tag=n,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=V,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fn(0),this.expirationTimes=Fn(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fn(0),this.identifierPrefix=r,this.onRecoverableError=l,J&&(this.mutableSourceEagerHydrationData=null)}function Pi(e,n,t,r,l,a,u,i,o){return e=new Ei(e,n,t,i,o),1===n?(n=1,!0===a&&(n|=8)):n=0,a=vi(3,null,null,n),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:t,cache:null,transitions:null,pendingSuspenseBoundaries:null},Xt(a),e}function Ci(e){if(!e)return fn;e=e._reactInternals;e:{if(N(e)!==e||1!==e.tag)throw Error(u(170));var n=e;do{switch(n.tag){case 3:n=n.stateNode.context;break e;case 1:if(gn(n.type)){n=n.stateNode.__reactInternalMemoizedMergedChildContext;break e}}n=n.return}while(null!==n);throw Error(u(171))}if(1===e.tag){var t=e.type;if(gn(t))return yn(e,t,n)}return n}function _i(e){var n=e._reactInternals;if(void 0===n){if("function"===typeof e.render)throw Error(u(188));throw e=Object.keys(e).join(","),Error(u(268,e))}return e=C(n),null===e?null:e.stateNode}function Ii(e,n){if(e=e.memoizedState,null!==e&&null!==e.dehydrated){var t=e.retryLane;e.retryLane=0!==t&&t=c&&a>=d&&l<=f&&i<=p){e.splice(n,1);break}if(!(r!==c||t.width!==s.width||pi)){d>a&&(s.height+=d-a,s.y=a),pl)){c>r&&(s.width+=c-r,s.x=r),ft&&(t=i)),i ")+"\n\nNo matching component was found for:\n "+e.join(" > ")}return null},n.getPublicRootInstance=function(e){if(e=e.current,!e.child)return null;switch(e.child.tag){case 5:return R(e.child.stateNode);default:return e.child.stateNode}},n.injectIntoDevTools=function(e){if(e={bundleType:e.bundleType,version:e.version,rendererPackageName:e.rendererPackageName,rendererConfig:e.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setErrorHandler:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:i.ReactCurrentDispatcher,findHostInstanceByFiber:Ti,findFiberByHostInstance:e.findFiberByHostInstance||Ri,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null,reconcilerVersion:"18.2.0"},"undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)e=!1;else{var n=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(n.isDisabled||!n.supportsFiber)e=!0;else{try{Gn=n.inject(e),Jn=n}catch(e){}e=!!n.checkDCE}}return e},n.isAlreadyRendering=function(){return!1},n.observeVisibleRects=function(e,n,t,r){if(!re)throw Error(u(363));e=uu(e,n);var l=ce(e,t,r).disconnect;return{disconnect:function(){l()}}},n.registerMutableSourceForHydration=function(e,n){var t=n._getVersion;t=t(n._source),null==e.mutableSourceEagerHydrationData?e.mutableSourceEagerHydrationData=[n,t]:e.mutableSourceEagerHydrationData.push(n,t)},n.runWithPriority=function(e,n){var t=Qn;try{return Qn=e,n()}finally{Qn=t}},n.shouldError=function(){return null},n.shouldSuspend=function(){return!1},n.updateContainer=function(e,n,t,r){var l=n.current,a=Hu(),u=Bu(l);return t=Ci(t),null===n.context?n.context=t:n.pendingContext=t,n=er(a,u),n.payload={element:e},r=void 0===r?null:r,null!==r&&(n.callback=r),e=nr(l,n,u),null!==e&&(Wu(e,l,u,a),tr(e,l,u)),u},n}},772:function(e,n,t){"use strict";e.exports=t(735)},4845:function(e,n,t){"use strict";e.exports=t(2935)},7463:function(e,n,t){"use strict";var r=t(4490)["navigator"];function l(e,n){var t=e.length;e.push(n);e:for(;0>>1,l=e[r];if(!(0>>1;ri(o,t))si(c,o)?(e[r]=c,e[s]=t,r=s):(e[r]=o,e[u]=t,r=u);else{if(!(si(c,t)))break e;e[r]=c,e[s]=t,r=s}}}return n}function i(e,n){var t=e.sortIndex-n.sortIndex;return 0!==t?t:e.id-n.id}if("object"===typeof performance&&"function"===typeof performance.now){var o=performance;n.unstable_now=function(){return o.now()}}else{var s=Date,c=s.now();n.unstable_now=function(){return s.now()-c}}var f=[],d=[],p=1,h=null,m=3,g=!1,v=!1,b=!1,y="function"===typeof setTimeout?setTimeout:null,S="function"===typeof clearTimeout?clearTimeout:null,k="undefined"!==typeof setImmediate?setImmediate:null;function x(e){for(var n=a(d);null!==n;){if(null===n.callback)u(d);else{if(!(n.startTime<=e))break;u(d),n.sortIndex=n.expirationTime,l(f,n)}n=a(d)}}function w(e){if(b=!1,x(e),!v)if(null!==a(f))v=!0,U(z);else{var n=a(d);null!==n&&M(w,n.startTime-e)}}function z(e,t){v=!1,b&&(b=!1,S(C),C=-1),g=!0;var r=m;try{for(x(t),h=a(f);null!==h&&(!(h.expirationTime>t)||e&&!L());){var l=h.callback;if("function"===typeof l){h.callback=null,m=h.priorityLevel;var i=l(h.expirationTime<=t);t=n.unstable_now(),"function"===typeof i?h.callback=i:h===a(f)&&u(f),x(t)}else u(f);h=a(f)}if(null!==h)var o=!0;else{var s=a(d);null!==s&&M(w,s.startTime-t),o=!1}return o}finally{h=null,m=r,g=!1}}"undefined"!==typeof r&&void 0!==r.scheduling&&void 0!==r.scheduling.isInputPending&&r.scheduling.isInputPending.bind(r.scheduling);var N,E=!1,P=null,C=-1,_=5,I=-1;function L(){return!(n.unstable_now()-I<_)}function T(){if(null!==P){var e=n.unstable_now();I=e;var t=!0;try{t=P(!0,e)}finally{t?N():(E=!1,P=null)}}else E=!1}if("function"===typeof k)N=function(){k(T)};else if("undefined"!==typeof MessageChannel){var R=new MessageChannel,F=R.port2;R.port1.onmessage=T,N=function(){F.postMessage(null)}}else N=function(){y(T,0)};function U(e){P=e,E||(E=!0,N())}function M(e,t){C=y(function(){e(n.unstable_now())},t)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(e){e.callback=null},n.unstable_continueExecution=function(){v||g||(v=!0,U(z))},n.unstable_forceFrameRate=function(e){0>e||125u?(e.sortIndex=r,l(d,e),null===a(f)&&e===a(d)&&(b?(S(C),C=-1):b=!0,M(w,r-u))):(e.sortIndex=i,l(f,e),v||g||(v=!0,U(z))),e},n.unstable_shouldYield=L,n.unstable_wrapCallback=function(e){var n=m;return function(){var t=m;m=n;try{return e.apply(this,arguments)}finally{m=t}}}},9982:function(e,n,t){"use strict";e.exports=t(7463)}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[907,96],function(){return n(2951)});e.O()}]); \ No newline at end of file diff --git a/dist/app.js.LICENSE.txt b/dist/app.js.LICENSE.txt new file mode 100644 index 0000000..ba737b0 --- /dev/null +++ b/dist/app.js.LICENSE.txt @@ -0,0 +1,29 @@ +/** + * @license React + * react-reconciler-constants.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @license React + * react-reconciler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @license React + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/dist/app.json b/dist/app.json new file mode 100644 index 0000000..3689b9c --- /dev/null +++ b/dist/app.json @@ -0,0 +1 @@ +{"pages":["pages/plaza/index","pages/nearby/index","pages/publish/index","pages/messages/index","pages/profile/index","pages/profile-edit/index","pages/pet-edit/index","pages/chat/index","pages/contacts/index"],"window":{"navigationStyle":"custom","backgroundColor":"#14102b","backgroundTextStyle":"dark"},"tabBar":{"custom":true,"color":"#8a7aab","selectedColor":"#d8b4ff","backgroundColor":"#14102b","borderStyle":"black","list":[{"pagePath":"pages/plaza/index","text":"广场"},{"pagePath":"pages/nearby/index","text":"附近"},{"pagePath":"pages/messages/index","text":"消息"},{"pagePath":"pages/profile/index","text":"我的"}]},"permission":{"scope.userLocation":{"desc":"用于发现附近的毛孩子,以及在发布动态时标记位置"}},"requiredPrivateInfos":["chooseLocation","getLocation"],"lazyCodeLoading":"requiredComponents"} \ No newline at end of file diff --git a/dist/app.wxss b/dist/app.wxss new file mode 100644 index 0000000..6462ef6 --- /dev/null +++ b/dist/app.wxss @@ -0,0 +1 @@ +@import "./app-origin.wxss";@import "./common.wxss"; \ No newline at end of file diff --git a/dist/base.wxml b/dist/base.wxml new file mode 100644 index 0000000..bfdb19e --- /dev/null +++ b/dist/base.wxml @@ -0,0 +1,671 @@ + +