This commit is contained in:
刘正航
2026-04-21 22:45:19 +08:00
commit b5237f9038
159 changed files with 7769 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
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 })
}
}
})