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,152 @@
const { request } = require('../../utils/request')
const emptyForm = {
name: '',
category: '轻食',
description: '',
calories: '',
protein: '',
fat: '',
carbs: '',
fiber: '',
tagsText: ''
}
Page({
data: {
keyword: '',
recipes: [],
loading: false,
editId: null,
form: { ...emptyForm },
importText: '[{"name":"低脂鸡肉饭","category":"减脂轻食","calories":360,"protein":30,"fat":10,"carbs":34,"fiber":5,"tags":["减脂"]}]'
},
onShow() {
this.fetchRecipes()
},
onInput(e) {
const field = e.currentTarget.dataset.field
this.setData({ [field]: e.detail.value })
},
onFormInput(e) {
const field = e.currentTarget.dataset.field
this.setData({ [`form.${field}`]: e.detail.value })
},
async fetchRecipes() {
this.setData({ loading: true })
try {
const data = await request({ url: `/recipes?keyword=${encodeURIComponent(this.data.keyword)}&page=1&page_size=100` })
const recipes = (data.items || []).map((item) => ({
...item,
tags_text: (item.tags || []).join('、')
}))
this.setData({ recipes })
} catch (err) {
// handled
} finally {
this.setData({ loading: false })
}
},
startCreate() {
this.setData({ editId: null, form: { ...emptyForm } })
},
startEdit(e) {
const id = Number(e.currentTarget.dataset.id)
const row = this.data.recipes.find((item) => item.id === id)
if (!row) return
this.setData({
editId: row.id,
form: {
name: row.name || '',
category: row.category || '轻食',
description: row.description || '',
calories: String(row.calories || ''),
protein: String(row.protein || ''),
fat: String(row.fat || ''),
carbs: String(row.carbs || ''),
fiber: String(row.fiber || ''),
tagsText: (row.tags || []).join(',')
}
})
},
buildRecipePayload() {
const f = this.data.form
return {
name: f.name,
category: f.category,
description: f.description,
calories: Number(f.calories),
protein: Number(f.protein),
fat: Number(f.fat),
carbs: Number(f.carbs),
fiber: Number(f.fiber),
tags: (f.tagsText || '').split(',').map(i => i.trim()).filter(Boolean)
}
},
async saveRecipe() {
const payload = this.buildRecipePayload()
if (!payload.name) {
wx.showToast({ title: '请填写食谱名称', icon: 'none' })
return
}
try {
if (this.data.editId) {
await request({ url: `/recipes/${this.data.editId}`, method: 'PUT', data: payload })
wx.showToast({ title: '食谱已更新', icon: 'success' })
} else {
await request({ url: '/recipes', method: 'POST', data: payload })
wx.showToast({ title: '食谱已创建', icon: 'success' })
}
this.fetchRecipes()
} catch (err) {
// handled
}
},
async deleteRecipe(e) {
const id = e.currentTarget.dataset.id
wx.showModal({
title: '删除食谱',
content: `确定删除食谱 ID ${id} 吗?`,
success: async (res) => {
if (!res.confirm) return
try {
await request({ url: `/recipes/${id}`, method: 'DELETE' })
wx.showToast({ title: '删除成功', icon: 'success' })
this.fetchRecipes()
} catch (err) {
// handled
}
}
})
},
async importDefaultSeed() {
try {
await request({ url: '/recipes/import', method: 'POST', data: {} })
wx.showToast({ title: '默认食谱导入成功', icon: 'success' })
this.fetchRecipes()
} catch (err) {
// handled
}
},
async importByJSON() {
try {
const items = JSON.parse(this.data.importText)
await request({ url: '/recipes/import', method: 'POST', data: { items } })
wx.showToast({ title: '批量导入成功', icon: 'success' })
this.fetchRecipes()
} catch (err) {
wx.showToast({ title: '导入 JSON 格式错误', icon: 'none' })
}
}
})

View File

@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "管理员-食谱管理"
}

View File

@@ -0,0 +1,53 @@
<view class="container">
<view class="hero">
<view class="hero-title">轻食食谱信息管理</view>
<view class="hero-sub">支持导入 / 查找 / 编辑 / 删除食谱信息</view>
</view>
<view class="card">
<view class="card-title">食谱查找</view>
<input class="input" data-field="keyword" value="{{keyword}}" bindinput="onInput" placeholder="按名称搜索" />
<button class="btn btn-primary" loading="{{loading}}" bindtap="fetchRecipes">搜索食谱</button>
<button class="btn btn-ghost" bindtap="importDefaultSeed">导入默认食谱种子</button>
</view>
<view class="card">
<view class="card-title">{{editId ? ('编辑食谱 #' + editId) : '新增食谱'}}</view>
<input class="input" data-field="name" value="{{form.name}}" bindinput="onFormInput" placeholder="食谱名称" />
<input class="input" data-field="category" value="{{form.category}}" bindinput="onFormInput" placeholder="分类" />
<input class="input" data-field="description" value="{{form.description}}" bindinput="onFormInput" placeholder="描述" />
<input class="input" data-field="calories" type="digit" value="{{form.calories}}" bindinput="onFormInput" placeholder="热量" />
<input class="input" data-field="protein" type="digit" value="{{form.protein}}" bindinput="onFormInput" placeholder="蛋白质" />
<input class="input" data-field="fat" type="digit" value="{{form.fat}}" bindinput="onFormInput" placeholder="脂肪" />
<input class="input" data-field="carbs" type="digit" value="{{form.carbs}}" bindinput="onFormInput" placeholder="碳水" />
<input class="input" data-field="fiber" type="digit" value="{{form.fiber}}" bindinput="onFormInput" placeholder="膳食纤维" />
<input class="input" data-field="tagsText" value="{{form.tagsText}}" bindinput="onFormInput" placeholder="标签,逗号分隔" />
<button class="btn btn-primary" bindtap="saveRecipe">保存食谱</button>
<button class="btn btn-ghost" bindtap="startCreate">清空表单</button>
</view>
<view class="card">
<view class="card-title">批量导入 JSON</view>
<textarea class="input textarea" data-field="importText" value="{{importText}}" bindinput="onInput" />
<button class="btn btn-accent" bindtap="importByJSON">执行导入</button>
</view>
<view class="card">
<view class="card-title">食谱列表</view>
<view wx:if="{{!recipes.length}}" class="empty">暂无食谱</view>
<view wx:for="{{recipes}}" wx:key="id" class="item">
<view class="row">
<view class="item-title">{{item.name}}</view>
<view class="value">{{item.calories}} kcal</view>
</view>
<view class="item-sub">蛋白{{item.protein}}g / 脂肪{{item.fat}}g / 碳水{{item.carbs}}g</view>
<view class="item-sub">{{item.category}} · {{item.tags_text}}</view>
<view class="row">
<button class="btn btn-ghost" size="mini" data-id="{{item.id}}" bindtap="startEdit">编辑</button>
<button class="btn btn-accent" size="mini" data-id="{{item.id}}" bindtap="deleteRecipe">删除</button>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,4 @@
.item .row .btn {
margin-top: 10rpx;
width: 44%;
}