1
This commit is contained in:
152
miniprogram/pages/admin-recipes/index.js
Normal file
152
miniprogram/pages/admin-recipes/index.js
Normal 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' })
|
||||
}
|
||||
}
|
||||
})
|
||||
3
miniprogram/pages/admin-recipes/index.json
Normal file
3
miniprogram/pages/admin-recipes/index.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "管理员-食谱管理"
|
||||
}
|
||||
53
miniprogram/pages/admin-recipes/index.wxml
Normal file
53
miniprogram/pages/admin-recipes/index.wxml
Normal 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>
|
||||
4
miniprogram/pages/admin-recipes/index.wxss
Normal file
4
miniprogram/pages/admin-recipes/index.wxss
Normal file
@@ -0,0 +1,4 @@
|
||||
.item .row .btn {
|
||||
margin-top: 10rpx;
|
||||
width: 44%;
|
||||
}
|
||||
Reference in New Issue
Block a user