const store = require('../../utils/store.js'); const api = require('../../utils/api.js'); const upload = require('../../utils/upload.js'); function fmtDate(iso) { if (!iso) return ''; const d = new Date(iso); return d.getMonth() + 1 + '月' + d.getDate() + '日'; } function ageFromBirthday(bday) { if (!bday) return ''; const b = new Date(bday); const now = new Date(); let months = (now.getFullYear() - b.getFullYear()) * 12 + (now.getMonth() - b.getMonth()); if (months < 0) months = 0; return months < 24 ? months + '个月' : Math.floor(months / 12) + '岁'; } const GENDERS = ['男孩', '女孩', '不确定']; const STAGES = ['刚到家 0-30 天', '幼年期', '成年期', '老年期']; // 任务可关联的记录弹层类型 const TASK_SHEETS = [ { key: '', label: '不关联' }, { key: 'weight', label: '体重' }, { key: 'poop', label: '便便' }, { key: 'food', label: '饮食' }, { key: 'symptom', label: '异常' }, { key: 'vaccine', label: '疫苗' }, { key: 'medicine', label: '用药' }, ]; const TASK_SHEET_LABELS = TASK_SHEETS.map((s) => s.label); const POOP = ['正常', '软便', '拉稀']; const FOOD = ['正常', '偏少', '不吃']; const COST = ['食品', '医疗', '用品']; const IDENTITY = ['petName', 'anonymous', 'official']; const PTAG = ['晒宠', '求助', '经验', '避坑']; Component({ options: { addGlobalClass: true }, properties: { show: { type: Boolean, value: false }, type: { type: String, value: '' }, postId: { type: Number, value: 0 }, }, data: { innerType: '', pet: {}, segSel: {}, optSel: {}, dateVals: { vaccine: '2026-07-21', deworm: '2026-07-15' }, wInput: '', wNote: '', costAmount: '', postContent: '', postImages: [], manageTasks: [], taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 }, taskSheetLabels: TASK_SHEET_LABELS, addName: '', addBirthday: '', addWeight: '', addColor: '', addBreed: '', commentText: '', aiInput: '', aiMessages: [], riskData: null, comments: [], reminders: [], poster: null, reportDetail: null, proInfo: null, saving: false, }, observers: { show: function (show) { // 自定义 tabBar 会盖住弹层底部按钮;打开弹层时把它藏起来,关闭时恢复 // 用 getTabBar().hidden 开关,不用 wx.showTabBar(真机上会额外冒出原生 tabBar) this.toggleTabBar(!!show); }, 'show, type': function (show, type) { if (show && type) this.setType(type, true); }, }, lifetimes: { detached() { this.toggleTabBar(false); }, }, methods: { setType(type, fresh) { const pet = store.getPet(); const patch = { innerType: type, pet }; if (fresh) { patch.segSel = {}; patch.optSel = {}; patch.costAmount = ''; patch.postContent = ''; patch.postImages = []; patch.manageTasks = []; patch.taskForm = { id: '', title: '', description: '', priority: '', sheetIdx: 0 }; patch.addName = ''; patch.addBirthday = ''; patch.addWeight = ''; patch.addColor = ''; patch.addBreed = ''; patch.commentText = ''; patch.riskData = null; patch.saving = false; } if (type === 'weight') { patch.wInput = (pet.weight || '').replace('kg', ''); patch.wNote = ''; } if (type === 'editPet') { // 预填当前宠物档案 patch.addName = pet.name || ''; patch.addColor = pet.color || ''; patch.addBreed = pet.breed || ''; patch.addWeight = (pet.weight || '').replace('kg', ''); patch.addBirthday = pet.birthday ? String(pet.birthday).slice(0, 10) : ''; patch.segSel = { addType: pet.type === '狗狗' ? 1 : 0, addGender: Math.max(0, GENDERS.indexOf(pet.gender)), }; patch.optSel = { addStage: Math.max(0, STAGES.indexOf(pet.stage)) }; } if (type === 'ai') { patch.aiMessages = [ { role: 'ai', text: `我是 ${pet.name || '毛孩子'} 的养宠助手。我会优先基于档案、计划和历史记录回答,而不是泛泛聊天。`, }, ]; patch.aiInput = ''; } this.setData(patch); this.loadSheetData(type); }, // 按弹层类型拉取真实数据 loadSheetData(type) { const id = store.currentPetId(); if (type === 'manageTasks') { this.loadManageTasks(); } else if (type === 'comments' && this.data.postId) { api.listComments(this.data.postId).then((page) => this.setData({ comments: page.list || [] })).catch(() => {}); } else if (type === 'reminders' && id) { api .getReminders(id) .then((list) => this.setData({ reminders: (list || []).map((r) => ({ title: r.title, when: r.next_due_date ? fmtDate(r.next_due_date) : r.frequency || '', })), }), ) .catch(() => {}); } else if (type === 'poster' && id) { api.getPoster(id).then((p) => this.setData({ poster: p })).catch(() => {}); } else if (type === 'reportDetail' && id) { api.weeklyReport(id).then((r) => this.setData({ reportDetail: r })).catch(() => {}); } else if (type === 'pro') { api.getPro().then((p) => this.setData({ proInfo: p })).catch(() => {}); } }, onActivatePro() { api .activatePro() .then((p) => { this.setData({ proInfo: p }); wx.showToast({ title: '已开通 Pro', icon: 'success' }); setTimeout(() => this.close(), 800); }) .catch((e) => wx.showToast({ title: e.message || '开通失败', icon: 'none' })); }, goSheet(e) { this.setType(e.currentTarget.dataset.type, true); }, close() { this.triggerEvent('close'); }, onMaskTap() { this.close(); }, noop() {}, // 藏/显当前 tab 页的自定义 tabBar(非 tab 页无 getTabBar,安全跳过) toggleTabBar(hidden) { const pages = getCurrentPages(); const page = pages[pages.length - 1]; if (page && typeof page.getTabBar === 'function' && page.getTabBar()) { page.getTabBar().setData({ hidden }); } }, onSeg(e) { const { group, index } = e.currentTarget.dataset; const segSel = Object.assign({}, this.data.segSel, { [group]: Number(index) }); this.setData({ segSel }); }, onOpt(e) { const { group, index } = e.currentTarget.dataset; const optSel = Object.assign({}, this.data.optSel, { [group]: Number(index) }); this.setData({ optSel }); }, onDate(e) { this.setData({ [`dateVals.${e.currentTarget.dataset.key}`]: e.detail.value }); }, onWInput(e) { this.setData({ wInput: e.detail.value }); }, onWNote(e) { this.setData({ wNote: e.detail.value }); }, onCostInput(e) { this.setData({ costAmount: e.detail.value }); }, onPostInput(e) { this.setData({ postContent: e.detail.value }); }, // ---- 管理任务 ---- loadManageTasks() { const id = store.currentPetId(); if (!id) return; api.getTasks(id).then((tasks) => this.setData({ manageTasks: tasks || [] })).catch(() => {}); }, onTaskTitle(e) { this.setData({ 'taskForm.title': e.detail.value }); }, onTaskDesc(e) { this.setData({ 'taskForm.description': e.detail.value }); }, onTaskPriority() { this.setData({ 'taskForm.priority': this.data.taskForm.priority === '重要' ? '' : '重要' }); }, onTaskSheet(e) { this.setData({ 'taskForm.sheetIdx': Number(e.detail.value) }); }, onEditTaskItem(e) { const t = this.data.manageTasks[e.currentTarget.dataset.index]; let sheetIdx = TASK_SHEETS.findIndex((s) => s.key === (t.sheet_type || '')); if (sheetIdx < 0) sheetIdx = 0; this.setData({ taskForm: { id: t.id, title: t.title, description: t.description || '', priority: t.priority || '', sheetIdx }, }); }, onSaveTask() { const f = this.data.taskForm; const title = (f.title || '').trim(); if (!title) return wx.showToast({ title: '填个任务名', icon: 'none' }); const body = { title, description: f.description, priority: f.priority, sheet_type: TASK_SHEETS[f.sheetIdx].key }; const id = store.currentPetId(); const p = f.id ? api.updateTask(f.id, body) : api.createTask(id, body); p.then(() => { this.setData({ taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 } }); this.loadManageTasks(); this.triggerEvent('saved'); }).catch((e) => wx.showToast({ title: e.message || '保存失败', icon: 'none' })); }, onDeleteTaskItem(e) { const t = this.data.manageTasks[e.currentTarget.dataset.index]; wx.showModal({ title: '删除任务', content: '确定删除「' + t.title + '」?', success: (r) => { if (!r.confirm) return; api.deleteTask(t.id).then(() => { this.loadManageTasks(); this.triggerEvent('saved'); }).catch((err) => wx.showToast({ title: err.message || '删除失败', icon: 'none' })); }, }); }, onPickPostImages() { const left = 9 - this.data.postImages.length; if (left <= 0) return wx.showToast({ title: '最多 9 张', icon: 'none' }); upload .chooseAndUploadImages(left) .then((list) => this.setData({ postImages: this.data.postImages.concat(list) })) .catch((e) => { if (e && e.canceled) return; wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' }); }); }, onRemovePostImage(e) { const i = e.currentTarget.dataset.index; const arr = this.data.postImages.slice(); arr.splice(i, 1); this.setData({ postImages: arr }); }, onAddName(e) { this.setData({ addName: e.detail.value }); }, onAddBirthday(e) { this.setData({ addBirthday: e.detail.value }); }, onAddWeight(e) { this.setData({ addWeight: e.detail.value }); }, onAddColor(e) { this.setData({ addColor: e.detail.value }); }, onAddBreed(e) { this.setData({ addBreed: e.detail.value }); }, onCommentInput(e) { this.setData({ commentText: e.detail.value }); }, segIdx(group) { const v = this.data.segSel[group]; return v === undefined ? 0 : v; }, optIdx(group) { const v = this.data.optSel[group]; return v === undefined ? 0 : v; }, // 异常观察 → 生成风险评估(真调后端 AI,失败回退) onGenRisk() { const id = store.currentPetId(); const symptoms = ['呕吐', '拉稀', '精神差', '皮肤红']; const durations = ['刚刚', '半天', '1天以上']; const spirits = ['正常', '一般', '明显变差']; const body = { symptoms: [symptoms[this.optIdx('symp')]], duration: durations[this.segIdx('dur')], spirit: spirits[this.segIdx('spirit')], }; if (!id) { this.setData({ innerType: 'risk', riskData: null }); return; } wx.showLoading({ title: '分析中...' }); api .assessSymptom(id, body) .then((res) => { wx.hideLoading(); this.setData({ innerType: 'risk', riskData: res }); }) .catch(() => { wx.hideLoading(); this.setData({ innerType: 'risk', riskData: null }); }); }, // 依据当前弹层类型组装一条健康记录 buildRecord() { const t = this.data.innerType; switch (t) { case 'poop': { const s = POOP[this.segIdx('poopState')]; return { type: 'poop', icon: '💩', title: '排便记录:' + s, category: s }; } case 'food': { const a = FOOD[this.segIdx('food')]; return { type: 'food', icon: '🍽️', title: '饮食记录:食欲' + a }; } case 'medicine': return { type: 'medicine', icon: '💊', title: '用药记录' }; case 'cost': { const c = COST[this.segIdx('cost')]; const amt = parseFloat(this.data.costAmount) || 0; return { type: 'cost', icon: '💰', title: '消费记录:¥' + amt, num_value: amt, category: c }; } case 'vaccine': return { type: 'vaccine', icon: '💉', title: '疫苗提醒:' + this.data.dateVals.vaccine }; case 'deworm': return { type: 'deworm', icon: '🛡️', title: '驱虫提醒:' + this.data.dateVals.deworm }; case 'risk': return { type: 'symptom', icon: '🤒', title: '异常观察:风险中', category: '中' }; case 'vetSummary': return { type: 'note', icon: '📄', title: '生成就医前摘要' }; default: return null; } }, async createAndClose(rec) { const id = store.currentPetId(); if (!rec || !id) { this.close(); return; } if (this.data.saving) return; this.setData({ saving: true }); try { const saved = await api.createRecord(id, rec); if (rec.type === 'weight') store.patchCurrent({ weight: rec.title.replace('体重 ', '') }); this.triggerEvent('saved', saved); this.close(); } catch (e) { wx.showToast({ title: e.message || '保存失败', icon: 'none' }); this.setData({ saving: false }); } }, onSave() { this.createAndClose(this.buildRecord()); }, onSaveWeight() { let w = (this.data.wInput || '').toString().replace(/\s+/g, ''); if (!w) w = (this.data.pet.weight || '').replace('kg', ''); const num = parseFloat(w) || 0; const weight = w.includes('kg') ? w : w + 'kg'; this.createAndClose({ type: 'weight', icon: '⚖️', title: '体重 ' + weight, num_value: num, description: (this.data.wNote || '').trim() }); }, onSavePhoto() { const id = store.currentPetId(); if (!id) return this.close(); upload .chooseAndUploadImage() .then((f) => api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_file_id: f.id, image_url: f.url }), ) .then((saved) => { wx.showToast({ title: '照片已保存', icon: 'success' }); this.triggerEvent('saved', saved); this.close(); }) .catch((e) => { if (e && e.canceled) return; wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' }); }); }, // 组装宠物档案表单 petFormBody() { const isDog = this.segIdx('addType') === 1; return { name: (this.data.addName || '').trim(), type: isDog ? '狗狗' : '猫猫', emoji: isDog ? '🐶' : '🐱', gender: GENDERS[this.segIdx('addGender')], birthday: this.data.addBirthday, weight: this.data.addWeight, color: (this.data.addColor || '').trim(), breed: (this.data.addBreed || '').trim(), stage: STAGES[this.optIdx('addStage')], age: ageFromBirthday(this.data.addBirthday), }; }, // 添加/编辑 共用提交入口 onPetSubmit() { if (this.data.innerType === 'editPet') return this.onEditPetSubmit(); return this.onAddPetSubmit(); }, async onAddPetSubmit() { const body = this.petFormBody(); if (!body.name) return wx.showToast({ title: '请输入名字', icon: 'none' }); if (this.data.saving) return; this.setData({ saving: true }); try { await api.createPet(body); await store.loadPets(); this.triggerEvent('petadded'); this.close(); } catch (e) { wx.showToast({ title: e.message || '添加失败', icon: 'none' }); this.setData({ saving: false }); } }, async onEditPetSubmit() { const body = this.petFormBody(); if (!body.name) return wx.showToast({ title: '请输入名字', icon: 'none' }); const id = store.currentPetId(); if (!id) return this.close(); if (this.data.saving) return; this.setData({ saving: true }); try { await api.updatePet(id, body); await store.loadPets(); this.triggerEvent('petadded'); this.close(); } catch (e) { wx.showToast({ title: e.message || '保存失败', icon: 'none' }); this.setData({ saving: false }); } }, onDeletePet() { const id = store.currentPetId(); const name = this.data.pet.name || '这只宠物'; if (!id) return; wx.showModal({ title: '删除宠物', content: `删除「${name}」后,它的记录、计划、提醒都会一并移除,且不可恢复。确定删除吗?`, confirmText: '删除', confirmColor: '#EE7D73', success: (res) => { if (!res.confirm) return; api .deletePet(id) .then(async () => { await store.loadPets(); this.triggerEvent('petadded'); this.close(); if (store.getPets().length === 0) { wx.reLaunch({ url: '/pages/onboarding/onboarding' }); } }) .catch((e) => wx.showToast({ title: e.message || '删除失败', icon: 'none' })); }, }); }, // 发帖 async onCreatePost() { const content = (this.data.postContent || '').trim(); if (!content) return wx.showToast({ title: '写点什么吧', icon: 'none' }); const identity = IDENTITY[this.segIdx('ident')]; const tag = PTAG[this.data.optSel.ptag === undefined ? 0 : this.data.optSel.ptag]; if (this.data.saving) return; this.setData({ saving: true }); try { await api.createPost({ pet_id: identity === 'petName' ? store.currentPetId() : null, identity, content, tags: [tag], image_file_ids: this.data.postImages.map((i) => i.id), }); this.triggerEvent('posted'); this.close(); } catch (e) { wx.showToast({ title: e.message || '发布失败', icon: 'none' }); this.setData({ saving: false }); } }, // 评论 async onSendComment() { const text = (this.data.commentText || '').trim(); if (!text || !this.data.postId) return this.close(); try { await api.createComment(this.data.postId, text); this.triggerEvent('commented'); } catch (e) { wx.showToast({ title: e.message || '评论失败', icon: 'none' }); } this.close(); }, // AI 聊天 onAiInput(e) { this.setData({ aiInput: e.detail.value }); }, sendAI(e) { const preset = e.currentTarget.dataset.text; const text = (preset || this.data.aiInput || '').trim(); if (!text) return; const msgs = this.data.aiMessages.concat([{ role: 'user', text }]); this.setData({ aiMessages: msgs, aiInput: '' }); api .aiChat({ pet_id: store.currentPetId() || null, session: 'sheet', text }) .then((res) => { this.setData({ aiMessages: this.data.aiMessages.concat([{ role: 'ai', text: res.reply }]) }); }) .catch(() => { this.setData({ aiMessages: this.data.aiMessages.concat([{ role: 'ai', text: '网络异常,请稍后再试。' }]), }); }); }, }, });