以下是 **「毛球手账」** 的完整技术实现方案,包含数据库 Schema、页面规划和云函数框架。 --- # 一、数据库 Schema(uniCloud DB Schema) 在 `uniCloud/database/` 目录下创建以下 `.schema.json` 文件。 ## 1. users.schema.json ```json { "bsonType": "object", "required": ["wx_openid", "createdAt"], "permission": { "read": "doc._id == auth.uid", "create": "auth.uid != null", "update": "doc._id == auth.uid", "delete": false }, "properties": { "_id": { "description": "用户ID,系统自动生成", "bsonType": "string" }, "wx_openid": { "bsonType": "string", "description": "微信openid", "trim": "both" }, "wx_unionid": { "bsonType": "string", "description": "微信unionid" }, "nickName": { "bsonType": "string", "description": "微信昵称", "trim": "both" }, "avatarUrl": { "bsonType": "string", "description": "微信头像URL" }, "phone": { "bsonType": "string", "description": "绑定手机号" }, "membership": { "bsonType": "object", "properties": { "type": { "bsonType": "string", "enum": ["free", "premium"], "default": "free" }, "expireAt": { "bsonType": "timestamp", "description": "会员过期时间" } } }, "settings": { "bsonType": "object", "properties": { "defaultPetId": { "bsonType": "string", "description": "默认选中宠物ID" }, "reminderEnabled": { "bsonType": "bool", "default": true } } }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } }, "updatedAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` ## 2. pets.schema.json ```json { "bsonType": "object", "required": ["user_id", "name", "type", "createdAt"], "permission": { "read": "doc.user_id == auth.uid", "create": "auth.uid != null", "update": "doc.user_id == auth.uid", "delete": "doc.user_id == auth.uid" }, "properties": { "_id": { "bsonType": "string" }, "user_id": { "bsonType": "string", "description": "关联用户ID", "foreignKey": "users._id" }, "name": { "bsonType": "string", "description": "宠物昵称", "minLength": 1, "maxLength": 10, "trim": "both" }, "type": { "bsonType": "string", "enum": ["cat", "dog", "other"], "description": "宠物类型" }, "breed": { "bsonType": "string", "description": "品种" }, "gender": { "bsonType": "string", "enum": ["male", "female", "unknown"], "default": "unknown" }, "birthday": { "bsonType": "timestamp", "description": "出生日期" }, "isBirthdayEstimated": { "bsonType": "bool", "default": false, "description": "生日是否为估算" }, "color": { "bsonType": "string", "description": "毛色/特征" }, "avatar": { "bsonType": "string", "description": "宠物头像云存储URL" }, "weight": { "bsonType": "array", "description": "体重历史记录", "items": { "bsonType": "object", "properties": { "value": { "bsonType": "double", "description": "体重kg" }, "date": { "bsonType": "timestamp" }, "note": { "bsonType": "string" } } } }, "neutered": { "bsonType": "bool", "default": false }, "allergies": { "bsonType": "array", "items": { "bsonType": "string" } }, "chronicDiseases": { "bsonType": "array", "items": { "bsonType": "string" } }, "emergencyContact": { "bsonType": "object", "properties": { "name": { "bsonType": "string" }, "phone": { "bsonType": "string" } } }, "status": { "bsonType": "string", "enum": ["active", "archived"], "default": "active" }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } }, "updatedAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` ## 3. health_records.schema.json ```json { "bsonType": "object", "required": ["pet_id", "user_id", "type", "date", "createdAt"], "permission": { "read": "doc.user_id == auth.uid", "create": "auth.uid != null", "update": "doc.user_id == auth.uid", "delete": "doc.user_id == auth.uid" }, "properties": { "_id": { "bsonType": "string" }, "pet_id": { "bsonType": "string", "foreignKey": "pets._id" }, "user_id": { "bsonType": "string", "foreignKey": "users._id" }, "type": { "bsonType": "string", "enum": ["vaccine", "deworm", "medical", "checkup", "other"], "description": "记录类型:疫苗/驱虫/病历/体检/其他" }, "title": { "bsonType": "string", "description": "记录标题" }, "vaccineName": { "bsonType": "string", "description": "疫苗名称" }, "batchNumber": { "bsonType": "string", "description": "疫苗批次号" }, "dewormType": { "bsonType": "string", "enum": ["internal", "external", "both"], "description": "驱虫类型" }, "brand": { "bsonType": "string", "description": "品牌(驱虫药/疫苗品牌)" }, "hospital": { "bsonType": "string", "description": "医院名称" }, "doctor": { "bsonType": "string" }, "date": { "bsonType": "timestamp", "description": "发生日期" }, "nextDate": { "bsonType": "timestamp", "description": "下次提醒日期" }, "description": { "bsonType": "string", "description": "描述/主诉" }, "diagnosis": { "bsonType": "string", "description": "诊断结果" }, "medications": { "bsonType": "array", "items": { "bsonType": "object", "properties": { "name": { "bsonType": "string" }, "dosage": { "bsonType": "string" }, "frequency": { "bsonType": "string" } } } }, "images": { "bsonType": "array", "description": "云存储图片URL数组", "items": { "bsonType": "string" } }, "cost": { "bsonType": "double", "description": "费用" }, "tags": { "bsonType": "array", "items": { "bsonType": "string" } }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } }, "updatedAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` ## 4. schedules.schema.json ```json { "bsonType": "object", "required": ["pet_id", "user_id", "title", "scheduledDate", "createdAt"], "permission": { "read": "doc.user_id == auth.uid", "create": "auth.uid != null", "update": "doc.user_id == auth.uid", "delete": "doc.user_id == auth.uid" }, "properties": { "_id": { "bsonType": "string" }, "pet_id": { "bsonType": "string", "foreignKey": "pets._id" }, "user_id": { "bsonType": "string", "foreignKey": "users._id" }, "type": { "bsonType": "string", "enum": ["vaccine", "deworm", "checkup", "custom"], "description": "日程类型" }, "title": { "bsonType": "string", "description": "提醒标题" }, "scheduledDate": { "bsonType": "timestamp", "description": "计划日期" }, "frequency": { "bsonType": "string", "enum": ["once", "daily", "weekly", "monthly", "yearly"], "default": "once" }, "interval": { "bsonType": "int", "default": 1, "description": "每N个frequency" }, "status": { "bsonType": "string", "enum": ["pending", "completed", "overdue", "skipped"], "default": "pending" }, "reminderDays": { "bsonType": "array", "items": { "bsonType": "int" }, "default": [7, 3, 1], "description": "提前提醒天数" }, "relatedRecordId": { "bsonType": "string", "foreignKey": "health_records._id", "description": "关联的健康记录" }, "note": { "bsonType": "string" }, "lastRemindedAt": { "bsonType": "timestamp", "description": "上次提醒时间" }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } }, "updatedAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` ## 5. hospitals.schema.json ```json { "bsonType": "object", "required": ["name", "location", "createdAt"], "permission": { "read": true, "create": "auth.uid != null", "update": false, "delete": false }, "properties": { "_id": { "bsonType": "string" }, "name": { "bsonType": "string", "description": "医院名称" }, "address": { "bsonType": "string" }, "location": { "bsonType": "object", "description": "GeoJSON Point", "properties": { "type": { "bsonType": "string", "enum": ["Point"], "default": "Point" }, "coordinates": { "bsonType": "array", "items": { "bsonType": "double" }, "description": "[longitude, latitude]" } } }, "phone": { "bsonType": "string" }, "hours": { "bsonType": "string", "description": "营业时间" }, "is24Hour": { "bsonType": "bool", "default": false }, "tags": { "bsonType": "array", "items": { "bsonType": "string" }, "description": "特色标签" }, "rating": { "bsonType": "double", "minimum": 0, "maximum": 5 }, "reviewCount": { "bsonType": "int", "default": 0 }, "verified": { "bsonType": "bool", "default": false, "description": "是否官方认证" }, "status": { "bsonType": "string", "enum": ["active", "pending", "rejected"], "default": "pending" }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` ## 6. articles.schema.json(养宠百科) ```json { "bsonType": "object", "required": ["title", "category", "status", "createdAt"], "permission": { "read": true, "create": false, "update": false, "delete": false }, "properties": { "_id": { "bsonType": "string" }, "title": { "bsonType": "string" }, "category": { "bsonType": "string", "enum": ["vaccine", "deworm", "disease", "nutrition", "behavior", "other"] }, "petType": { "bsonType": "string", "enum": ["cat", "dog", "all"], "default": "all" }, "cover": { "bsonType": "string" }, "content": { "bsonType": "string", "description": "富文本内容" }, "summary": { "bsonType": "string" }, "tags": { "bsonType": "array", "items": { "bsonType": "string" } }, "viewCount": { "bsonType": "int", "default": 0 }, "status": { "bsonType": "string", "enum": ["published", "draft"], "default": "draft" }, "createdAt": { "bsonType": "timestamp", "defaultValue": { "$env": "now" } } } } ``` --- # 二、核心页面前端组件结构(uni-app) ## 目录结构 ``` uni-app-project/ ├── pages/ │ ├── index/ │ │ └── index.vue # 首页(Dashboard) │ ├── pet/ │ │ ├── list.vue # 宠物列表/切换 │ │ ├── detail.vue # 宠物档案详情 │ │ ├── edit.vue # 创建/编辑宠物 │ │ └── weight.vue # 体重记录 │ ├── record/ │ │ ├── list.vue # 健康记录列表(时间轴) │ │ ├── detail.vue # 记录详情 │ │ ├── edit.vue # 创建/编辑记录 │ │ └── type-select.vue # 记录类型选择 │ ├── schedule/ │ │ ├── list.vue # 日程列表 │ │ └── edit.vue # 创建/编辑提醒 │ ├── discovery/ │ │ ├── index.vue # 发现页(医院+百科) │ │ ├── hospital-list.vue # 附近医院列表 │ │ ├── hospital-detail.vue # 医院详情 │ │ ├── article-list.vue # 百科列表 │ │ └── article-detail.vue # 百科详情 │ └── mine/ │ ├── index.vue # 我的页面 │ ├── settings.vue # 设置 │ └── member.vue # 会员中心 ├── components/ │ ├── pet-selector.vue # 顶部宠物切换器 │ ├── schedule-card.vue # 日程卡片 │ ├── record-timeline.vue # 时间轴组件 │ ├── weight-chart.vue # 体重曲线图(echarts/uni-chart) │ ├── quick-action.vue # 底部快速操作栏 │ ├── empty-state.vue # 空状态插画 │ └── share-card.vue # 分享卡片生成 ├── static/ │ └── images/ # 静态资源 ├── uniCloud/ │ └── cloudfunctions/ # 云函数 └── App.vue ``` ## 关键页面组件详解 ### 1. 首页 `pages/index/index.vue` ```vue ``` ### 2. 宠物切换器组件 `components/pet-selector.vue` ```vue ``` ### 3. 时间轴记录列表 `pages/record/list.vue` ```vue ``` --- # 三、云函数代码框架 在 `uniCloud/cloudfunctions/` 目录下创建以下云函数。 ## 1. login(微信登录) ```javascript // uniCloud/cloudfunctions/login/index.js const db = uniCloud.database() exports.main = async (event, context) => { const { code, userInfo } = event try { // 1. 获取openid const loginRes = await uniCloud.getWXContext() const { OPENID, UNIONID } = loginRes if (!OPENID) { return { code: -1, message: '获取openid失败' } } // 2. 查询或创建用户 const userCollection = db.collection('users') let user = await userCollection.where({ wx_openid: OPENID }).get() let userId if (user.data.length === 0) { // 新用户 const createRes = await userCollection.add({ wx_openid: OPENID, wx_unionid: UNIONID || '', nickName: userInfo?.nickName || '', avatarUrl: userInfo?.avatarUrl || '', phone: '', membership: { type: 'free', expireAt: null }, settings: { reminderEnabled: true }, createdAt: Date.now(), updatedAt: Date.now() }) userId = createRes.id } else { // 更新用户信息 userId = user.data[0]._id await userCollection.doc(userId).update({ nickName: userInfo?.nickName || user.data[0].nickName, avatarUrl: userInfo?.avatarUrl || user.data[0].avatarUrl, updatedAt: Date.now() }) } // 3. 生成自定义登录token(可选,用于后续请求鉴权) const token = await uniCloud.createToken({ uid: userId }) return { code: 0, message: '登录成功', data: { uid: userId, token, isNewUser: user.data.length === 0 } } } catch (err) { return { code: -1, message: err.message } } } ``` ## 2. petCrud(宠物增删改查) ```javascript // uniCloud/cloudfunctions/petCrud/index.js const db = uniCloud.database() const $ = db.command.aggregate exports.main = async (event, context) => { const { action, data, petId } = event const { uid } = context // 通过uni-id获取当前用户ID const petCollection = db.collection('pets') try { switch (action) { case 'create': { const res = await petCollection.add({ ...data, user_id: uid, createdAt: Date.now(), updatedAt: Date.now() }) return { code: 0, data: { id: res.id } } } case 'list': { const res = await petCollection.where({ user_id: uid, status: 'active' }).orderBy('createdAt', 'desc').get() return { code: 0, data: res.data } } case 'detail': { const res = await petCollection.doc(petId).get() if (res.data[0].user_id !== uid) { return { code: 403, message: '无权限' } } return { code: 0, data: res.data[0] } } case 'update': { const pet = await petCollection.doc(petId).get() if (pet.data[0].user_id !== uid) { return { code: 403, message: '无权限' } } await petCollection.doc(petId).update({ ...data, updatedAt: Date.now() }) return { code: 0, message: '更新成功' } } case 'delete': { // 软删除 await petCollection.doc(petId).update({ status: 'archived', updatedAt: Date.now() }) return { code: 0, message: '删除成功' } } case 'stats': { // 获取宠物统计信息 const pet = await petCollection.doc(petId).get() const petData = pet.data[0] // 最新体重 const latestWeight = petData.weight?.length > 0 ? petData.weight[petData.weight.length - 1].value : 0 // 查询下次疫苗和驱虫(从schedules表) const scheduleCollection = db.collection('schedules') const now = Date.now() const nextVaccine = await scheduleCollection.where({ pet_id: petId, user_id: uid, type: 'vaccine', status: 'pending', scheduledDate: db.command.gte(now) }).orderBy('scheduledDate', 'asc').limit(1).get() const nextDeworm = await scheduleCollection.where({ pet_id: petId, user_id: uid, type: 'deworm', status: 'pending', scheduledDate: db.command.gte(now) }).orderBy('scheduledDate', 'asc').limit(1).get() const calcDays = (date) => { if (!date) return '--' const days = Math.ceil((date - now) / (1000 * 60 * 60 * 24)) return days } return { code: 0, data: { latestWeight, nextVaccineDays: calcDays(nextVaccine.data[0]?.scheduledDate), nextDewormDays: calcDays(nextDeworm.data[0]?.scheduledDate) } } } default: return { code: -1, message: '未知操作' } } } catch (err) { return { code: -1, message: err.message } } } ``` ## 3. recordCrud(健康记录增删改查) ```javascript // uniCloud/cloudfunctions/recordCrud/index.js const db = uniCloud.database() exports.main = async (event, context) => { const { action, data, recordId, petId, type } = event const { uid } = context const recordCollection = db.collection('health_records') const scheduleCollection = db.collection('schedules') try { switch (action) { case 'create': { const recordData = { ...data, user_id: uid, createdAt: Date.now(), updatedAt: Date.now() } const res = await recordCollection.add(recordData) // 如果有nextDate,自动创建日程提醒 if (data.nextDate) { await scheduleCollection.add({ pet_id: data.pet_id, user_id: uid, type: data.type, title: this.getScheduleTitle(data), scheduledDate: data.nextDate, status: 'pending', relatedRecordId: res.id, createdAt: Date.now() }) } return { code: 0, data: { id: res.id } } } case 'list': { let where = { user_id: uid } if (petId) where.pet_id = petId if (type) where.type = type const res = await recordCollection.where(where) .orderBy('date', 'desc') .get() // 关联宠物名称 const petIds = [...new Set(res.data.map(r => r.pet_id))] const petCollection = db.collection('pets') const pets = await petCollection.where({ _id: db.command.in(petIds) }).get() const petMap = {} pets.data.forEach(p => petMap[p._id] = p.name) const records = res.data.map(r => ({ ...r, petName: petMap[r.pet_id] || '未知' })) return { code: 0, data: records } } case 'detail': { const res = await recordCollection.doc(recordId).get() if (res.data[0].user_id !== uid) { return { code: 403, message: '无权限' } } return { code: 0, data: res.data[0] } } case 'update': { const record = await recordCollection.doc(recordId).get() if (record.data[0].user_id !== uid) { return { code: 403, message: '无权限' } } await recordCollection.doc(recordId).update({ ...data, updatedAt: Date.now() }) // 同步更新关联的schedule if (data.nextDate && record.data[0].relatedRecordId) { await scheduleCollection.where({ relatedRecordId: recordId }).update({ scheduledDate: data.nextDate, updatedAt: Date.now() }) } return { code: 0, message: '更新成功' } } case 'delete': { const record = await recordCollection.doc(recordId).get() if (record.data[0].user_id !== uid) { return { code: 403, message: '无权限' } } await recordCollection.doc(recordId).remove() // 删除关联schedule if (record.data[0].relatedRecordId) { await scheduleCollection.where({ relatedRecordId: recordId }).remove() } return { code: 0, message: '删除成功' } } default: return { code: -1, message: '未知操作' } } } catch (err) { return { code: -1, message: err.message } } } // 辅助方法 function getScheduleTitle(data) { const titles = { vaccine: `${data.vaccineName || '疫苗'}接种`, deworm: `${data.brand || ''}驱虫`, checkup: '体检', medical: '复查' } return titles[data.type] || '健康提醒' } ``` ## 4. scheduleManager(日程管理) ```javascript // uniCloud/cloudfunctions/scheduleManager/index.js const db = uniCloud.database() exports.main = async (event, context) => { const { action, data, scheduleId, petId } = event const { uid } = context const scheduleCollection = db.collection('schedules') const now = Date.now() try { switch (action) { case 'create': { const res = await scheduleCollection.add({ ...data, user_id: uid, status: 'pending', createdAt: now, updatedAt: now }) return { code: 0, data: { id: res.id } } } case 'list': { let where = { user_id: uid } if (petId) where.pet_id = petId const res = await scheduleCollection.where(where) .orderBy('scheduledDate', 'asc') .get() return { code: 0, data: res.data } } case 'today': { // 获取今日及逾期的日程 const startOfDay = new Date().setHours(0, 0, 0, 0) const endOfDay = new Date().setHours(23, 59, 59, 999) const res = await scheduleCollection.where({ user_id: uid, status: db.command.in(['pending', 'overdue']), scheduledDate: db.command.lte(endOfDay) }).orderBy('scheduledDate', 'asc').get() return { code: 0, data: res.data } } case 'upcoming': { // 获取未来7天的日程 const endOfWeek = now + 7 * 24 * 60 * 60 * 1000 const res = await scheduleCollection.where({ user_id: uid, status: 'pending', scheduledDate: db.command.gte(now).and(db.command.lte(endOfWeek)) }).orderBy('scheduledDate', 'asc').get() return { code: 0, data: res.data } } case 'complete': { await scheduleCollection.doc(scheduleId).update({ status: 'completed', updatedAt: now }) return { code: 0, message: '已完成' } } case 'postpone': { const { days } = data const schedule = await scheduleCollection.doc(scheduleId).get() const newDate = schedule.data[0].scheduledDate + days * 24 * 60 * 60 * 1000 await scheduleCollection.doc(scheduleId).update({ scheduledDate: newDate, status: 'pending', updatedAt: now }) return { code: 0, message: `已推迟${days}天` } } case 'skip': { await scheduleCollection.doc(scheduleId).update({ status: 'skipped', updatedAt: now }) return { code: 0, message: '已忽略' } } case 'delete': { await scheduleCollection.doc(scheduleId).remove() return { code: 0, message: '删除成功' } } default: return { code: -1, message: '未知操作' } } } catch (err) { return { code: -1, message: err.message } } } ``` ## 5. reminderPush(定时提醒触发器) ```javascript // uniCloud/cloudfunctions/reminderPush/index.js // 配置:在uniCloud后台设置定时触发器,每天上午9:00执行 const db = uniCloud.database() exports.main = async (event, context) => { const now = Date.now() const today = new Date() today.setHours(0, 0, 0, 0) const tomorrow = today.getTime() + 24 * 60 * 60 * 1000 try { const scheduleCollection = db.collection('schedules') const userCollection = db.collection('users') // 1. 标记逾期 await scheduleCollection.where({ status: 'pending', scheduledDate: db.command.lt(today) }).update({ status: 'overdue', updatedAt: now }) // 2. 查找今天需要提醒的日程 // 提醒逻辑:在 reminderDays 中的某一天,且今天就是提醒日 const schedules = await scheduleCollection.where({ status: db.command.in(['pending', 'overdue']), scheduledDate: db.command.gte(today).and(db.command.lt(tomorrow + 7 * 24 * 60 * 60 * 1000)) }).get() // 3. 按用户分组,批量发送订阅消息 const userSchedules = {} schedules.data.forEach(s => { if (!userSchedules[s.user_id]) { userSchedules[s.user_id] = [] } userSchedules[s.user_id].push(s) }) for (const userId in userSchedules) { const user = await userCollection.doc(userId).get() const openid = user.data[0]?.wx_openid if (!openid) continue // 获取该用户今天需要提醒的日程 const todaySchedules = userSchedules[userId].filter(s => { const scheduledDate = new Date(s.scheduledDate) scheduledDate.setHours(0, 0, 0, 0) const diffDays = Math.ceil((scheduledDate - today) / (24 * 60 * 60 * 1000)) return s.reminderDays.includes(diffDays) && diffDays >= 0 }) if (todaySchedules.length === 0) continue // 构建订阅消息(需先在微信公众平台申请模板) const templateId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // 你的模板ID for (const schedule of todaySchedules) { try { await uniCloud.openapi.weixin.subscribeMessage.send({ touser: openid, templateId: templateId, page: `pages/schedule/list?petId=${schedule.pet_id}`, data: { thing1: { value: schedule.title }, // 事项 time2: { value: formatDate(schedule.scheduledDate) }, // 时间 thing3: { value: schedule.status === 'overdue' ? '已逾期,请尽快处理' : '即将到期' } // 备注 }, miniprogramState: 'formal' }) // 更新最后提醒时间 await scheduleCollection.doc(schedule._id).update({ lastRemindedAt: now }) } catch (err) { console.error(`发送失败: ${userId}`, err) } } } return { code: 0, message: '提醒处理完成', data: { totalChecked: schedules.data.length, notifiedUsers: Object.keys(userSchedules).length } } } catch (err) { return { code: -1, message: err.message } } } function formatDate(timestamp) { const d = new Date(timestamp) return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日` } ``` **定时触发器配置**(在 `reminderPush` 云函数的 `package.json` 中): ```json { "name": "reminderPush", "version": "1.0.0", "description": "每日健康提醒推送", "main": "index.js", "extensions": { "uni-cloud-jql": {} }, "cloudfunction-config": { "concurrency": 1, "triggers": [ { "name": "dailyReminder", "type": "timer", "config": "0 0 9 * * * *" } ] } } ``` > 说明:`0 0 9 * * * *` 表示每天上午9:00执行(UTC+8 时区)。 ## 6. nearbyHospitals(附近医院查询) ```javascript // uniCloud/cloudfunctions/nearbyHospitals/index.js const db = uniCloud.database() exports.main = async (event, context) => { const { longitude, latitude, maxDistance = 5000, limit = 20 } = event // maxDistance 单位:米,默认5km try { const hospitalCollection = db.collection('hospitals') const res = await hospitalCollection.where({ status: 'active', location: db.command.geoNear({ geometry: db.Geo.Point(longitude, latitude), maxDistance: maxDistance, distanceField: 'distance' }) }).limit(limit).get() // 格式化距离 const hospitals = res.data.map(h => ({ ...h, distance: h.distance ? Math.round(h.distance) : null, distanceText: h.distance ? (h.distance < 1000 ? `${Math.round(h.distance)}m` : `${(h.distance / 1000).toFixed(1)}km`) : '未知' })) return { code: 0, data: hospitals } } catch (err) { return { code: -1, message: err.message } } } ``` --- # 四、关键配置补充 ## 1. 地理位置索引(必须) 在 uniCloud 控制台 → 数据库 → `hospitals` 集合 → 索引管理,创建: ```json { "location": "2dsphere" } ``` ## 2. manifest.json 小程序配置 ```json { "mp-weixin": { "appid": "你的小程序appid", "setting": { "urlCheck": false }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于查找附近宠物医院" } }, "requiredPrivateInfos": [ "getLocation" ] } } ``` ## 3. 云存储目录规划 ``` cloudstorage/ ├── pet-avatars/ # 宠物头像 ├── record-images/ # 病历/疫苗证照片 ├── article-covers/ # 百科文章封面 └── share-cards/ # 生成的分享卡片 ``` --- 这套框架覆盖了 **MVP 阶段 90% 的核心功能**。建议你先实现 **login → petCrud → recordCrud → scheduleManager → reminderPush** 这个主线,跑通"创建宠物 → 记录疫苗 → 生成提醒 → 推送通知"的完整闭环,再逐步扩展发现页和会员功能。 需要我针对某个具体模块(如**微信支付开通会员**、**订阅消息授权引导**、**图片上传云存储**)继续展开吗?