const { request } = require('../../utils/request') const REC_TYPES = [ { key: 'current', label: '当前状态推荐', desc: '基于你最近一次饮食状态,用随机森林给出当前最匹配的食谱。', scene: '日常实时决策', requirement: '建议先填写今天的体重、体脂、摄入和消耗' }, { key: 'health', label: '健康状态推荐', desc: '按体脂、摄入和运动平衡进行规则评分,优先推荐营养结构更均衡的方案。', scene: '想吃得更健康', requirement: '推荐先有基础饮食状态数据' }, { key: 'history', label: '历史计划推荐', desc: '根据近一段时间的体重和摄入趋势,给出偏减脂或偏增肌的策略型推荐。', scene: '需要阶段性调整', requirement: '至少有 3 天历史记录' }, { key: 'goal', label: '未来目标推荐', desc: '围绕你的目标(减脂/增肌/生酮/维持)进行宏量营养匹配。', scene: '目标导向饮食', requirement: '可跟随个人资料,也可手动指定目标' }, { key: 'occupation', label: '职业行业推荐', desc: '根据职业活动强度和作息特点,推荐更贴近日常能量需求的食谱。', scene: '工作日场景优化', requirement: '可跟随个人资料,也可手动指定职业' } ] const TOP_K_OPTIONS = [ { value: 3, label: '3 条' }, { value: 5, label: '5 条' }, { value: 10, label: '10 条' } ] const GOAL_OPTIONS = [ { value: '', label: '跟随个人资料(推荐)' }, { value: 'lose_fat', label: '减脂(lose_fat)' }, { value: 'gain_muscle', label: '增肌(gain_muscle)' }, { value: 'keto', label: '生酮(keto)' }, { value: 'maintain', label: '维持(maintain)' } ] const OCCUPATION_OPTIONS = [ { value: '', label: '跟随个人资料(推荐)' }, { value: 'office', label: '办公室(office)' }, { value: 'developer', label: '程序员(developer)' }, { value: 'fitness', label: '健身行业(fitness)' }, { value: 'teacher', label: '教师(teacher)' }, { value: 'student', label: '学生(student)' }, { value: 'manual', label: '体力劳动(manual)' } ] Page({ data: { recType: 'current', recTypes: REC_TYPES, activeType: REC_TYPES[0], selectedLabel: REC_TYPES[0].label, topK: 5, topKLabel: '5 条', topKOptions: TOP_K_OPTIONS, goalValue: '', goalLabel: GOAL_OPTIONS[0].label, goalOptions: GOAL_OPTIONS, occupationValue: '', occupationLabel: OCCUPATION_OPTIONS[0].label, occupationOptions: OCCUPATION_OPTIONS, loading: false, payload: null, summary: null, errorText: '', lastUpdatedAt: '' }, onShow() { this.fetchRecommend() }, onTypeChange(e) { const idx = Number(e.detail.value) const item = this.data.recTypes[idx] if (!item) return this.setData({ recType: item.key, selectedLabel: item.label, activeType: item }) this.fetchRecommend() }, onTopKChange(e) { const idx = Number(e.detail.value) const item = this.data.topKOptions[idx] if (!item) return this.setData({ topK: item.value, topKLabel: item.label }) this.fetchRecommend() }, onGoalChange(e) { const idx = Number(e.detail.value) const item = this.data.goalOptions[idx] if (!item) return this.setData({ goalValue: item.value, goalLabel: item.label }) this.fetchRecommend() }, onOccupationChange(e) { const idx = Number(e.detail.value) const item = this.data.occupationOptions[idx] if (!item) return this.setData({ occupationValue: item.value, occupationLabel: item.label }) this.fetchRecommend() }, buildUrl() { const topK = this.data.topK || 5 const type = this.data.recType if (type === 'current') return `/recommend/current?top_k=${topK}` if (type === 'health') return `/recommend/health?top_k=${topK}` if (type === 'history') return `/recommend/plan/history?top_k=${topK}` if (type === 'goal') { const extra = this.data.goalValue ? `&goal=${encodeURIComponent(this.data.goalValue)}` : '' return `/recommend/plan/goal?top_k=${topK}${extra}` } const occ = this.data.occupationValue ? `&occupation=${encodeURIComponent(this.data.occupationValue)}` : '' return `/recommend/plan/occupation?top_k=${topK}${occ}` }, buildSummary(items = []) { if (!items.length) return null const totalCalories = items.reduce((sum, row) => sum + Number(row.calories || 0), 0) const totalProtein = items.reduce((sum, row) => sum + Number(row.protein || 0), 0) return { count: items.length, avgCalories: Math.round((totalCalories / items.length) * 10) / 10, avgProtein: Math.round((totalProtein / items.length) * 10) / 10 } }, formatNow() { const d = new Date() const pad = (n) => String(n).padStart(2, '0') return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` }, async fetchRecommend() { this.setData({ loading: true, errorText: '' }) try { const payload = await request({ url: this.buildUrl() }) const items = payload && Array.isArray(payload.items) ? payload.items : [] const normalizedItems = items.map((row, idx) => { const score = Number(row.rf_score ?? row.score ?? 0) return { ...row, rank: idx + 1, displayScore: score.toFixed(1) } }) this.setData({ payload: { ...payload, items: normalizedItems }, summary: this.buildSummary(normalizedItems), lastUpdatedAt: this.formatNow(), errorText: '' }) } catch (err) { this.setData({ payload: null, summary: null, errorText: (err && err.message) || '推荐请求失败,请稍后重试' }) } finally { this.setData({ loading: false }) } } })