This commit is contained in:
Developer
2026-04-13 10:00:55 +08:00
parent a59f54567f
commit 5ce8759a18
51 changed files with 4848 additions and 575 deletions

View File

@@ -5,9 +5,10 @@
* @file api_discover.php
* @author AI Assistant
* @date 2026-04-12
* @version 1.0.0
* @version 1.1.0
* @desc 提供随机数据用于首页/发现页瀑布流,支持响应式布局
* @lastUpdate 2026-04-12 初始版本
* 同一客户端多次请求返回不重复数据30分钟后重置
* @lastUpdate 2026-04-12 添加已返回ID排除机制避免重复数据
*/
$startTime = microtime(true);
@@ -47,47 +48,52 @@ $format = ApiResponse::getFormat();
$forceRefresh = isset($params['_refresh']) && $params['_refresh'] === '1';
$clientIP = getClientIP();
$requestInfo = ApiCache::getDiscoverRequestCount($clientIP);
$shouldUseCache = !$forceRefresh && $requestInfo['count'] >= 2 && $requestInfo['data'] !== null;
if ($shouldUseCache) {
header('X-Cache: HIT');
header('X-Request-Count: ' . $requestInfo['count']);
$result = $requestInfo['data'];
$result['meta']['cache_status'] = 'cached';
$result['meta']['request_count'] = $requestInfo['count'];
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
ApiResponse::output($result, $format);
exit;
}
header('X-Cache: MISS');
$counts = calculateCounts($params);
$data = array(
'recipes' => getRandomRecipes($counts['recipe']),
'ingredients' => getRandomIngredients($counts['ingredient']),
'categories' => getRandomCategories($counts['category']),
'tags' => getRandomTags($counts['tag']),
'nutrition_types' => getNutritionTypes($counts['nutrition']),
'recipes' => getRandomRecipes($counts['recipe'], $clientIP),
'ingredients' => getRandomIngredients($counts['ingredient'], $clientIP),
'categories' => getRandomCategories($counts['category'], $clientIP),
'tags' => getRandomTags($counts['tag'], $clientIP),
'nutrition_types' => getNutritionTypes($counts['nutrition'], $clientIP),
'meal_times' => getMealTimes($counts['meal_time'])
);
$recipeIds = array_column($data['recipes'], 'id');
$ingredientIds = array_column($data['ingredients'], 'id');
$categoryIds = array_column($data['categories'], 'id');
$tagIds = array_column($data['tags'], 'id');
$nutritionNames = array_column($data['nutrition_types'], 'name');
if (!empty($recipeIds)) {
ApiCache::addDiscoverReturnedIds($clientIP, 'recipe', $recipeIds);
}
if (!empty($ingredientIds)) {
ApiCache::addDiscoverReturnedIds($clientIP, 'ingredient', $ingredientIds);
}
if (!empty($categoryIds)) {
ApiCache::addDiscoverReturnedIds($clientIP, 'category', $categoryIds);
}
if (!empty($tagIds)) {
ApiCache::addDiscoverReturnedIds($clientIP, 'tag', $tagIds);
}
if (!empty($nutritionNames)) {
ApiCache::addDiscoverReturnedIds($clientIP, 'nutrition', $nutritionNames);
}
$result = array(
'code' => 200,
'message' => 'success',
'data' => $data,
'meta' => array(
'request_count' => $requestInfo['count'] + 1,
'cache_status' => 'fresh',
'counts' => $counts
)
);
ApiCache::incrementDiscoverRequestCount($clientIP, $result);
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
ApiResponse::output($result, $format);
@@ -112,15 +118,15 @@ function getClientIP() {
* 计算各类型数量
*/
function calculateCounts($params) {
$total = isset($params['total']) ? min((int) $params['total'], 100) : 30;
$total = isset($params['total']) ? min((int) $params['total'], 100) : 50;
$ratios = array(
'recipe' => 0.25,
'ingredient' => 0.15,
'category' => 0.15,
'tag' => 0.20,
'nutrition' => 0.15,
'meal_time' => 0.10
'recipe' => 0.75,
'ingredient' => 0.03,
'category' => 0.10,
'tag' => 0.03,
'nutrition' => 0.02,
'meal_time' => 0.02
);
$counts = array();
@@ -130,7 +136,7 @@ function calculateCounts($params) {
foreach ($ratios as $type => $ratio) {
$paramName = $type === 'nutrition' ? 'nutrition' : $type;
if (isset($params[$paramName]) && (int) $params[$paramName] > 0) {
$counts[$type] = min((int) $params[$paramName], 20);
$counts[$type] = min((int) $params[$paramName], 80);
$specifiedTotal += $counts[$type];
$specifiedTypes[] = $type;
}
@@ -159,7 +165,7 @@ function calculateCounts($params) {
/**
* 获取随机菜品
*/
function getRandomRecipes($limit) {
function getRandomRecipes($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
@@ -169,6 +175,17 @@ function getRandomRecipes($limit) {
$tablePostStat = $zbp->db->dbpre . 'post_stat';
$tableIdMap = $zbp->db->dbpre . 'recipe_id_map';
$excludeIds = array();
if (!empty($clientIP)) {
$excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'recipe');
}
$whereClause = "p.log_Type = 0 AND p.log_Status = 0";
if (!empty($excludeIds)) {
$excludeIdsStr = implode(',', array_map('intval', $excludeIds));
$whereClause .= " AND p.log_ID NOT IN ($excludeIdsStr)";
}
$sql = "SELECT p.log_ID, p.log_Title, p.log_CateID, p.log_ViewNums,
c.cate_Name,
s.rate_nums, s.rate_score,
@@ -177,12 +194,17 @@ function getRandomRecipes($limit) {
LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID
LEFT JOIN $tablePostStat s ON p.log_ID = s.log_id
LEFT JOIN $tableIdMap m ON p.log_ID = m.new_log_id
WHERE p.log_Type = 0 AND p.log_Status = 0
WHERE $whereClause
ORDER BY RAND()
LIMIT $limit";
$results = $zbp->db->Query($sql);
if (empty($results) && !empty($excludeIds)) {
ApiCache::clearDiscoverReturnedIds($clientIP, 'recipe');
return getRandomRecipes($limit, $clientIP);
}
$list = array();
foreach ($results as $row) {
$rateNums = (int) ($row['rate_nums'] ?? 0);
@@ -213,7 +235,7 @@ function getRandomRecipes($limit) {
/**
* 获取随机食材
*/
function getRandomIngredients($limit) {
function getRandomIngredients($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
@@ -221,15 +243,32 @@ function getRandomIngredients($limit) {
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$tableCategory = $zbp->db->dbpre . 'category';
$excludeIds = array();
if (!empty($clientIP)) {
$excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'ingredient');
}
$whereClause = "1=1";
if (!empty($excludeIds)) {
$excludeIdsStr = implode(',', array_map('intval', $excludeIds));
$whereClause .= " AND i.ingredient_id NOT IN ($excludeIdsStr)";
}
$sql = "SELECT i.ingredient_id, i.name, i.cate_ID, i.allergen, i.introduction,
c.cate_Name
FROM $tableIngredient i
LEFT JOIN $tableCategory c ON i.cate_ID = c.cate_ID
WHERE $whereClause
ORDER BY RAND()
LIMIT $limit";
$results = $zbp->db->Query($sql);
if (empty($results) && !empty($excludeIds)) {
ApiCache::clearDiscoverReturnedIds($clientIP, 'ingredient');
return getRandomIngredients($limit, $clientIP);
}
$list = array();
foreach ($results as $row) {
$allergen = array();
@@ -261,20 +300,72 @@ function getRandomIngredients($limit) {
/**
* 获取随机分类
*/
function getRandomCategories($limit) {
function getRandomCategories($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
$tableCategory = $zbp->db->dbpre . 'category';
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$sql = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID
FROM $tableCategory
WHERE cate_ID > 10
ORDER BY RAND()
LIMIT $limit";
$excludeIds = array();
if (!empty($clientIP)) {
$excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'category');
}
$results = $zbp->db->Query($sql);
$excludeClause = '';
if (!empty($excludeIds)) {
$excludeIdsStr = implode(',', array_map('intval', $excludeIds));
$excludeClause = " AND cate_ID NOT IN ($excludeIdsStr)";
}
$recipeLimit = max(1, ceil($limit * 0.6));
$ingredientLimit = $limit - $recipeLimit;
$results = array();
$sqlRecipe = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID
FROM $tableCategory
WHERE cate_ID > 10 AND cate_ID < 1000 AND cate_ParentID != 11 $excludeClause
ORDER BY RAND()
LIMIT $recipeLimit";
$recipeResults = $zbp->db->Query($sqlRecipe);
$results = array_merge($results, $recipeResults);
$sqlIngredient = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID
FROM $tableCategory
WHERE cate_ID >= 1000 $excludeClause
ORDER BY RAND()
LIMIT $ingredientLimit";
$ingredientResults = $zbp->db->Query($sqlIngredient);
$results = array_merge($results, $ingredientResults);
if (empty($results) && !empty($excludeIds)) {
ApiCache::clearDiscoverReturnedIds($clientIP, 'category');
return getRandomCategories($limit, $clientIP);
}
$ingredientCounts = array();
$cateIds = array_column($results, 'cate_ID');
if (!empty($cateIds)) {
$cateIdsStr = implode(',', array_map('intval', $cateIds));
$sqlIngredientCount = "SELECT cate_ID, COUNT(*) as cnt FROM $tableIngredient WHERE cate_ID IN ($cateIdsStr) GROUP BY cate_ID";
$ingredientCountResults = $zbp->db->Query($sqlIngredientCount);
foreach ($ingredientCountResults as $row) {
$ingredientCounts[$row['cate_ID']] = (int) $row['cnt'];
}
}
$parentIds = array_unique(array_filter(array_column($results, 'cate_ParentID')));
$parentNames = array();
if (!empty($parentIds)) {
$parentIdsStr = implode(',', array_map('intval', $parentIds));
$sqlParent = "SELECT cate_ID, cate_Name FROM $tableCategory WHERE cate_ID IN ($parentIdsStr)";
$parentResults = $zbp->db->Query($sqlParent);
foreach ($parentResults as $row) {
$parentNames[$row['cate_ID']] = $row['cate_Name'];
}
}
$list = array();
foreach ($results as $row) {
@@ -285,36 +376,62 @@ function getRandomCategories($limit) {
$type = 'recipe_main';
}
$recipeCount = (int) ($row['cate_Count'] ?? 0);
$ingredientCount = isset($ingredientCounts[$row['cate_ID']]) ? $ingredientCounts[$row['cate_ID']] : 0;
$parentId = (int) $row['cate_ParentID'];
$parentName = isset($parentNames[$parentId]) ? $parentNames[$parentId] : '';
$list[] = array(
'id' => (int) $row['cate_ID'],
'name' => $row['cate_Name'],
'type' => $type,
'count' => (int) ($row['cate_Count'] ?? 0),
'parent_id' => (int) $row['cate_ParentID']
'recipe_count' => $recipeCount,
'ingredient_count' => $ingredientCount,
'count' => $type === 'ingredient' ? $ingredientCount : $recipeCount,
'parent_id' => $parentId,
'parent_name' => $parentName
);
}
shuffle($list);
return $list;
}
/**
* 获取随机标签
*/
function getRandomTags($limit) {
function getRandomTags($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
$tableTag = $zbp->db->dbpre . 'tag';
$excludeIds = array();
if (!empty($clientIP)) {
$excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'tag');
}
$whereClause = "tag_Alias IN ('口味', '做法')";
if (!empty($excludeIds)) {
$excludeIdsStr = implode(',', array_map('intval', $excludeIds));
$whereClause .= " AND tag_ID NOT IN ($excludeIdsStr)";
}
$sql = "SELECT tag_ID, tag_Name, tag_Alias, tag_Count
FROM $tableTag
WHERE tag_Alias IN ('口味', '做法')
WHERE $whereClause
ORDER BY RAND()
LIMIT $limit";
$results = $zbp->db->Query($sql);
if (empty($results) && !empty($excludeIds)) {
ApiCache::clearDiscoverReturnedIds($clientIP, 'tag');
return getRandomTags($limit, $clientIP);
}
$list = array();
foreach ($results as $row) {
$type = 'taste';
@@ -336,21 +453,40 @@ function getRandomTags($limit) {
/**
* 获取营养成分类型
*/
function getNutritionTypes($limit) {
function getNutritionTypes($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
$tableNutrition = $zbp->db->dbpre . 'recipe_nutrition';
$excludeNames = array();
if (!empty($clientIP)) {
$excludeNames = ApiCache::getDiscoverReturnedIds($clientIP, 'nutrition');
}
$whereClause = "name IS NOT NULL AND name != ''";
if (!empty($excludeNames)) {
$escapedNames = array_map(function($name) use ($zbp) {
return $zbp->db->EscapeString($name);
}, $excludeNames);
$excludeNamesStr = "'" . implode("','", $escapedNames) . "'";
$whereClause .= " AND name NOT IN ($excludeNamesStr)";
}
$sql = "SELECT DISTINCT name, unit
FROM $tableNutrition
WHERE name IS NOT NULL AND name != ''
WHERE $whereClause
ORDER BY RAND()
LIMIT $limit";
$results = $zbp->db->Query($sql);
if (empty($results) && !empty($excludeNames)) {
ApiCache::clearDiscoverReturnedIds($clientIP, 'nutrition');
return getNutritionTypes($limit, $clientIP);
}
$list = array();
foreach ($results as $row) {
$list[] = array(

View File

@@ -1,11 +1,27 @@
# 菜谱 API 接口文档
> **版本**: v3.2.0
> **更新日期**: 2026-04-12
> **版本**: v3.2.1
> **更新日期**: 2026-04-13
> **基础地址**: `http://eat.wktyl.com/api/`
---
## 📝 更新日志
### v3.2.1 (2026-04-13)
- **文档同步更新**:完善发现页接口字段说明
- **分类字段补充**:添加 `id` 字段到分类返回结构
### v3.2.0 (2026-04-12)
- **发现页接口优化**`api_discover.php` 多项改进
- 去重机制:同一客户端多次请求返回不重复数据
- 自动重置30分钟后自动重置
- 混合分类菜谱分类60% + 食材分类40%
- 新增字段:`recipe_count``ingredient_count``parent_name`
- 标签 `count` 字段修复
---
## 📁 接口文件说明
@@ -1588,10 +1604,17 @@ Content-Type: application/json
```
GET api_discover.php?total=30
GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3
GET api_discover.php?total=30&_refresh=1
```
**功能**: 获取随机数据用于首页/发现页瀑布流展示
**核心特性**:
-**去重机制**同一客户端多次请求返回不重复数据基于IP记录已返回ID
-**自动重置**30分钟后自动重置重新开始展示
-**混合分类**分类返回混合类型菜谱分类60% + 食材分类40%
-**完整信息**:分类包含父分类名称,标签包含实际使用次数
**请求参数**:
| 参数 | 类型 | 默认值 | 说明 |
@@ -1603,7 +1626,7 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
| tag | int | - | 标签数量 |
| nutrition | int | - | 营养成分数量 |
| meal_time | int | - | 时段数量 |
| _refresh | int | 0 | 强制刷新缓存 |
| _refresh | int | 0 | 强制刷新清除已返回ID记录 |
**数量分配比例**(使用 total 参数时):
@@ -1622,12 +1645,12 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
|------|------|
| `recipes` | 随机菜品列表 |
| `ingredients` | 随机食材列表 |
| `categories` | 随机分类列表 |
| `categories` | 随机分类列表(混合类型) |
| `tags` | 随机标签列表 |
| `nutrition_types` | 营养成分类型列表 |
| `meal_times` | 用餐时段列表 |
| `meta.request_count` | 当前IP在5秒内的请求次数 |
| `meta.cache_status` | 缓存状态fresh/cached |
| `meta.cache_status` | 缓存状态fresh |
| `meta.counts` | 各类型实际返回数量 |
**菜品字段**:
@@ -1652,12 +1675,27 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
| `allergen` | 过敏原数组 |
| `intro` | 简介截取100字 |
**缓存策略**:
**分类字段**(新增):
| 请求次数 | 返回数据 | 说明 |
|----------|----------|------|
| 第1-2次 | 新鲜数据 | 查询数据库 |
| 第3次及以后 | 缓存数据 | 返回缓存5秒TTL |
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 分类ID |
| `name` | string | 分类名称 |
| `type` | string | 类型recipe(菜谱分类) / ingredient(食材分类) / recipe_main(食谱大类) |
| `recipe_count` | int | 该分类下的菜品数量 |
| `ingredient_count` | int | 该分类下的食材数量 |
| `count` | int | 根据类型自动选择ingredient显示食材数recipe显示菜品数 |
| `parent_id` | int | 父分类ID |
| `parent_name` | string | 父分类名称 |
**标签字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 标签ID |
| `name` | string | 标签名称 |
| `type` | string | 类型taste(口味) / cooking(工艺) |
| `count` | int | 使用次数(已修复) |
**返回示例**:
```json
@@ -1669,10 +1707,10 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
{
"id": 26940,
"title": "酸菜蛇段汤",
"cover": "http://eat.wktyl.com/zb_users/upload/1605a.jpg",
"cover": "http://eat.wktyl.com/api/assets/pic/1030a.jpg",
"category": {"id": 175, "name": "汤类"},
"rating": {"score": 4.5, "nums": 128, "display": "4.5分 (128人评分)", "status": "sufficient", "level": "推荐", "star": 5},
"views": 1024
"rating": {"score": 0, "nums": 0, "display": "暂无评分", "star": 0},
"views": 0
}
],
"ingredients": [
@@ -1685,20 +1723,39 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
}
],
"categories": [
{"id": 12, "name": "中国菜", "type": "recipe_main", "count": 1234, "parent_id": 11}
{
"id": 42,
"name": "家常菜",
"type": "recipe",
"recipe_count": 4581,
"ingredient_count": 0,
"count": 4581,
"parent_id": 12,
"parent_name": "中国菜"
},
{
"id": 1150,
"name": "杭椒",
"type": "ingredient",
"recipe_count": 0,
"ingredient_count": 1,
"count": 1,
"parent_id": 1001,
"parent_name": "蔬菜类"
}
],
"tags": [
{"id": 1, "name": "麻辣", "type": "taste", "count": 567}
{"id": 1, "name": "咸鲜味", "type": "taste", "count": 12973},
{"id": 50, "name": "炒", "type": "cooking", "count": 2053}
],
"nutrition_types": [
{"name": "蛋白质", "unit": "克"}
],
"meal_times": [
{"id": 1, "name": "餐", "count": 1234}
{"id": 1, "name": "餐", "count": 481}
]
},
"meta": {
"request_count": 1,
"cache_status": "fresh",
"counts": {"recipe": 8, "ingredient": 5, "category": 4, "tag": 6, "nutrition": 4, "meal_time": 3}
},
@@ -1711,6 +1768,28 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim
- 📱 响应式布局展示
- 🎨 动态卡片生成
- 🔄 下拉刷新加载
- 📂 分类导航(显示父分类名称)
**客户端实现**:
```dart
// Flutter 示例
final response = await http.get(
Uri.parse('$baseUrl/api_discover.php?total=30')
);
final data = json.decode(response.body)['data'];
// 显示分类卡片
Widget buildCategoryCard(Map<String, dynamic> category) {
return Card(
child: Column(
children: [
Text('${category['parent_name']} > ${category['name']}'),
Text('${category['count']}${category['type'] == 'ingredient' ? '食材' : '菜谱'}'),
],
),
);
}
```
---

View File

@@ -1,81 +1,27 @@
# App 接入指南
> **版本**: v2.9.0
> **更新日期**: 2026-04-12
> **版本**: v2.10.1
> **更新日期**: 2026-04-13
> **基础地址**: `http://eat.wktyl.com/api/`
---
## 📝 更新日志
### v2.9.0 (2026-04-12)
- **新增发现页接口**`api_discover.php` 随机数据接口
- 支持返回多种类型随机数据:菜品、食材、分类、标签、营养成分、时段
- 支持分类数量参数和总数量自动分配
- 基于IP的请求频率控制5秒内前2次返回新鲜数据第3次起返回缓存
- 适用于首页/发现页瀑布流、响应式布局
### v2.10.1 (2026-04-13)
- **文档同步更新**:与 API_DOC.md 保持一致
- **分类字段完善**:补充 `id` 字段说明
### v2.8.0 (2026-04-12)
- **评分显示优化**:所有菜谱接口返回数据中新增 `rating` 字段
- 自动处理边缘情况:暂无评分、评分数量少、评分异常
- 提供格式化显示文本可直接用于UI展示
- 提供评分状态和等级,便于客户端差异化展示
- **热门排行接口更新**`sort` 参数从 `recommend` 改为 `rate`
### v2.10.0 (2026-04-12)
- **发现页接口优化**`api_discover.php` 多项改进
- 同一客户端多次请求返回不重复数据基于IP记录已返回ID
- 30分钟后自动重置重新开始展示
- 分类返回混合类型菜谱分类60% + 食材分类40%
- 分类新增字段:`recipe_count`(菜品数)、`ingredient_count`(食材数)、`parent_name`(父分类名称)
- 标签 `count` 字段已修复,显示实际使用次数
- 数据库统计字段已更新:`cate_Count``tag_Count`
### v2.7.0 (2026-04-12)
- **接口变更**`recommend` 接口改为 `rate` 评分接口
- 评分范围1-5分
- 每日限制每个IP每天最多30次
- 不可取消:评分后无法撤销
- 自动计算平均分
- 数据库字段更新:`recommend_nums`/`recommend_score` 改为 `rate_nums`/`rate_score`
- 新增本地评分日志记录IP、时间、分数到本地文件
### v2.6.0 (2026-04-12)
- 新增接口:`api_hot.php` 热门排行接口
### v2.5.0 (2026-04-12)
- 文档更新简化客户端代码示例为1-2行
- 新增接口描述:`api_action.php?act=ip_status` IP状态查询
- 新增接口描述:`api_feed.php?act=feed` 信息流接口
- 新增接口描述:`cache_manage.php` 缓存管理接口
- 修正接口描述:随机推荐使用 `filter_apply&count=1` 而非 `random`
- 修正接口描述:热门排行使用 `api_hot.php` 替代 `stats_full.php?act=hot`
- 删除不存在的接口描述
### v2.4.0 (2026-04-12)
- 筛选接口新增排除筛选参数:
- `exclude_category`/`exclude_category_name` 排除分类
- `exclude_taste`/`exclude_taste_name` 排除口味
- `exclude_cooking`/`exclude_cooking_name` 排除工艺
- `exclude_ingredient` 排除食材
- `exclude_allergen` 排除过敏原
- `exclude_author` 排除作者
- `exclude_meal_time` 排除用餐时段
### v2.3.0 (2026-04-12)
- 新增迷你版接口 (api.php?act=mini),适用于列表页快速加载
- 筛选接口新增高级筛选参数:
- `nutrition_name`/`nutrition_min`/`nutrition_max` 按营养成分筛选
- `allergen` 按过敏原筛选(排除含该过敏原的菜品)
- `ingredient` 按食材名称筛选
- `author_id` 按作者筛选
- `category_name` 按分类名称筛选
- `taste_name`/`cooking_name` 按口味/工艺名称筛选
### v2.2.0 (2026-04-12)
- 新增全局搜索接口 (api_filter.php?act=global_search)
- 支持搜索食谱、食材、口味标签、工艺标签
- 搜索结果包含所属分类、关联菜品数量等信息
- 支持模糊搜索和关键词高亮
### v2.1.0 (2026-04-12)
- 删除个性化推荐相关接口 (api_preference.php)
- 删除 api_feed.php 中的 personal 接口
- 删除 api.php 中的 use_preference 参数
- 更新推荐算法说明
---
## 一、接口文件说明
@@ -223,6 +169,11 @@ Widget buildRating(Map<String, dynamic> rating) {
用于首页/发现页瀑布流展示,返回多种类型的随机数据。
**特性**:
- 同一客户端多次请求返回不重复数据基于IP记录已返回ID
- 30分钟后自动重置重新开始展示
- 分类返回混合类型菜谱分类60% + 食材分类40%
**请求示例**:
```dart
// 获取30条随机数据自动分配
@@ -234,8 +185,74 @@ final response = await http.get(
final response = await http.get(
Uri.parse('$baseUrl/api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3')
);
// 强制刷新清除已返回ID记录
final response = await http.get(
Uri.parse('$baseUrl/api_discover.php?total=30&_refresh=1')
);
```
**返回数据结构**:
```json
{
"code": 200,
"message": "success",
"data": {
"recipes": [...],
"ingredients": [...],
"categories": [
{
"id": 42,
"name": "家常菜",
"type": "recipe",
"recipe_count": 4581,
"ingredient_count": 0,
"count": 4581,
"parent_id": 12,
"parent_name": "中国菜"
},
{
"id": 1150,
"name": "杭椒",
"type": "ingredient",
"recipe_count": 0,
"ingredient_count": 1,
"count": 1,
"parent_id": 1001,
"parent_name": "蔬菜类"
}
],
"tags": [
{
"id": 1,
"name": "咸鲜味",
"type": "taste",
"count": 12973
},
{
"id": 50,
"name": "炒",
"type": "cooking",
"count": 2053
}
],
"nutrition_types": [...],
"meal_times": [...]
}
}
```
**分类字段说明**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `type` | string | 分类类型recipe(菜谱分类) / ingredient(食材分类) / recipe_main(食谱大类) |
| `recipe_count` | int | 该分类下的菜品数量 |
| `ingredient_count` | int | 该分类下的食材数量 |
| `count` | int | 根据类型自动选择ingredient显示食材数recipe显示菜品数 |
| `parent_name` | string | 父分类名称,可直接显示为"父分类 > 子分类" |
**Flutter 瀑布流示例**:
```dart
Widget buildDiscoverPage(Map<String, dynamic> data) {
@@ -251,9 +268,13 @@ Widget buildDiscoverPage(Map<String, dynamic> data) {
items.add(IngredientCard(ingredient: ingredient));
}
// 分类卡片
// 分类卡片(显示父分类名称)
for (final category in data['categories']) {
items.add(CategoryCard(category: category));
items.add(CategoryCard(
category: category,
subtitle: '${category['parent_name']} > ${category['name']}',
count: category['count'],
));
}
// 标签卡片

322
docs/api/doc/api_discover Normal file
View File

@@ -0,0 +1,322 @@
https://eat.wktyl.com/api/api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3
{
"code": 200,
"message": "success",
"data": {
"recipes": [
{
"id": 40420,
"title": "旱蒸酸菜鸭",
"cover": "http://eat.wktyl.com/api/assets/pic/15303a.jpg",
"category": {
"id": 43,
"name": "私家菜"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 56308,
"title": "炸山鸡球",
"cover": "",
"category": {
"id": 21,
"name": "京菜"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 44289,
"title": "八宝鸡汤",
"cover": "http://eat.wktyl.com/api/assets/pic/19248a.jpg",
"category": {
"id": 78,
"name": "气血双补食谱"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 56918,
"title": "金枪鱼芸豆色拉",
"cover": "http://eat.wktyl.com/api/assets/pic/26164a.jpg",
"category": {
"id": 242,
"name": "西餐其他"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 50247,
"title": "荷兰豆饺",
"cover": "http://eat.wktyl.com/api/assets/pic/25317a.jpg",
"category": {
"id": 241,
"name": "快餐/主食"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 37723,
"title": "椒盐拌花生米",
"cover": "http://eat.wktyl.com/api/assets/pic/12514a.jpg",
"category": {
"id": 43,
"name": "私家菜"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 49117,
"title": "糟香汁卤肥鸡",
"cover": "http://eat.wktyl.com/api/assets/pic/24016a.jpg",
"category": {
"id": 238,
"name": "卤酱菜"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
},
{
"id": 48252,
"title": "琉璃山药",
"cover": "http://eat.wktyl.com/api/assets/pic/23228a.jpg",
"category": {
"id": 239,
"name": "甜品/点心"
},
"rating": {
"score": 0,
"nums": 0,
"display": "暂无评分",
"star": 0
},
"views": 0
}
],
"ingredients": [
{
"id": 56,
"name": "黄豆芽",
"category": {
"id": 1057,
"name": "黄豆芽"
},
"allergen": [
"黄豆",
"豆"
],
"intro": "豆芽是大豆经加工处理发出的嫩芽,是一种营养丰富的蔬菜。当今世界“天然”、“健康”食物引领风骚者之一,乃是源自我国的豆芽菜,特别是金灿灿的“如意菜”—黄豆芽。明人陈嶷曾有过赞美黄豆芽的诗句:“有彼物兮,..."
},
{
"id": 490,
"name": "鱼丸",
"category": {
"id": 1498,
"name": "鱼丸"
},
"allergen": [
"鱼"
],
"intro": "鱼丸是闽南、福州、广州一带经常烹制的传统食品。因为它味道鲜美,多吃不腻,可作点心配料,又可作汤,是沿海人们不可少的海味佳肴。"
},
{
"id": 1262,
"name": "酸黄瓜",
"category": {
"id": 2282,
"name": "酸黄瓜"
},
"allergen": [],
"intro": ""
},
{
"id": 214,
"name": "黑枣(无核)",
"category": {
"id": 1216,
"name": "黑枣(无核)"
},
"allergen": [],
"intro": "黑枣产各地山区,野生于山坡、谷地或栽培;分布于辽宁、河北、山东、陕西、中南及西南各地。 材质优良,可作一般用材;果实去涩生食或酿酒、制醋,含维生素丙,可提取供医用;种子入药,能消渴去热。"
},
{
"id": 172,
"name": "枣(干)",
"category": {
"id": 1174,
"name": "枣(干)"
},
"allergen": [],
"intro": "枣为鼠李科落叶灌木或小乔木植物枣树的成熟果实。我国栽培枣树范围极广,北边达到辽宁的锦州、北镇一带,以山东、河北、山西、陕西、甘肃、安徽、浙江产量最多。著名品种有金丝小枣,果实小,含糖量多,生产山东乐陵..."
}
],
"categories": [
{
"id": 1219,
"name": "椰蓉",
"type": "ingredient",
"recipe_count": 0,
"ingredient_count": 1,
"count": 1,
"parent_id": 1173,
"parent_name": "水果类及制品"
},
{
"id": 101,
"name": "婴儿食谱",
"type": "recipe",
"recipe_count": 64,
"ingredient_count": 0,
"count": 64,
"parent_id": 98,
"parent_name": "人群营养膳食"
},
{
"id": 212,
"name": "咽炎食谱",
"type": "recipe",
"recipe_count": 2,
"ingredient_count": 0,
"count": 2,
"parent_id": 172,
"parent_name": "疾病调理"
},
{
"id": 38,
"name": "滇黔菜",
"type": "recipe",
"recipe_count": 158,
"ingredient_count": 0,
"count": 158,
"parent_id": 12,
"parent_name": "中国菜"
}
],
"tags": [
{
"id": 57,
"name": "炸",
"type": "cooking",
"count": 624
},
{
"id": 110,
"name": "焗",
"type": "cooking",
"count": 70
},
{
"id": 18,
"name": "酸咸味",
"type": "taste",
"count": 212
},
{
"id": 35,
"name": "怪味",
"type": "taste",
"count": 67
},
{
"id": 133,
"name": "煎烹",
"type": "cooking",
"count": 17
},
{
"id": 144,
"name": "红外线烤",
"type": "cooking",
"count": 1
}
],
"nutrition_types": [
{
"name": "维生素B",
"unit": "微克"
},
{
"name": "锌",
"unit": "毫克"
},
{
"name": "泛酸",
"unit": "毫克"
},
{
"name": "蛋白质",
"unit": "克"
}
],
"meal_times": [
{
"id": 1,
"name": "中餐",
"count": 481
},
{
"id": 2,
"name": "晚餐",
"count": 475
},
{
"id": 3,
"name": "早餐",
"count": 325
}
]
},
"meta": {
"cache_status": "fresh",
"counts": {
"recipe": 8,
"ingredient": 5,
"category": 4,
"tag": 6,
"nutrition": 4,
"meal_time": 3
}
},
"_query_time": "2129.4ms"
}

View File

@@ -375,24 +375,23 @@
│ └───────────────────────┘ │
│ │
│ ── 🍳 烹饪助手 ── │
│ ┌──┐ ┌──┐ ┌──┐
│ │⏱️│ │📝│ │🧮│
│ │计时│ │笔记│ │换算│
│ │ ↓│ │ ↓│ │ ↓│
└──┘ └──┘ └──┘ │
│ ┌──┐ ┌──┐ ← GridView 2列布局
│ │⏱️│ │📝│
│ │计时│ │笔记│
│ │ ↓│ │ ↓│
│使用│ │使用│ ← 新增"使用工具"按钮
│ └──┘ └──┘ │
│ │
│ ── 📋 规划管理 ── │
│ ┌──┐ ┌──┐ ┌──┐
│ │📅│ │🛒│ │⚖️│
│ │菜单│ │购物│ │缩放│
│ ↓│ │ ↓│ │ ↓│
│ └──┘ └──┘ └──┘ │
│ ┌──┐ ┌──┐
│ │📅│ │🛒│
│ │菜单│ │购物│
└──┘ └──┘
│ │
│ ── 🏥 健康工具 ── │
│ ┌──┐ ┌──┐ │
│ │BMI│ │🌙│ │
│ │计算│ │就寝│ │
│ │ ↓│ │ ↓│ │
│ └──┘ └──┘ │
│ │
│ ── 🏷️ 热门标签 ── │
@@ -419,6 +418,7 @@
| BMI计算 | `Get.toNamed` | BMI计算器 | `/bmi-calculator` | - |
| 🌙 就寝提醒 | `Get.toNamed` | 就寝提醒页 | `/profile/bedtime-reminder` | - |
| 热门标签 | `Get.toNamed` | 标签菜谱列表 | `/tag-recipe-list` | tagName, tagId, tagType |
| 🎮 使用工具按钮 | `Get.toNamed` | 工具功能页 | 工具对应路由 | - |
### 🎨 美观问题
| # | 问题 | 严重度 | 建议 |
@@ -515,6 +515,112 @@
---
## ❤️ 收藏页 (FavoritesPage)
```
┌─────────────────────────────┐
│ ← [↑返回] 我的收藏 │
├─────────────────────────────┤
│ │
│ ┌──┐ ┌──┐ │ ← GridView 2列布局
│ │🖼️│ │🖼️│ │
│ │菜│ │菜│ │
│ │品│ │品│ │
│ │1│ │2│ │
│ └──┘ └──┘ │
│ ┌──┐ ┌──┐ │
│ │🖼️│ │🖼️│ │
│ │菜│ │菜│ │
│ │品│ │品│ │
│ │3│ │4│ │
│ └──┘ └──┘ │
│ │
└─────────────────────────────┘
```
### 📄 页面视图文件
- `lib/src/pages/profile/social/favorites_page.dart`(主页面)
### 🔗 跳转关系
| 触发元素 | 跳转方式 | 目标页面 | 路由 | 传参 |
|---------|---------|---------|------|------|
| ← 返回 | `Get.back` | 上一个页面 | - | - |
| 菜品卡片 | `Get.toNamed` | 菜品详情页 | `/recipe-detail` | id |
### 🎨 美观问题
| # | 问题 | 严重度 | 建议 |
|---|------|--------|------|
| 1 | 空收藏状态缺少引导 | 🟡中 | 添加"去发现美食"按钮引导用户 |
### ⚡ 功能缺失
| # | 功能 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | 收藏分组 | P3 | 按分类/标签分组管理收藏 |
| 2 | 收藏排序 | P3 | 按收藏时间/评分排序 |
---
## 📋 关于页面 (AboutPage)
```
┌─────────────────────────────┐
│ ← [↑返回] 关于 │
├─────────────────────────────┤
│ │
│ ┌───────────────────────┐ │
│ │ 🍳 妈妈厨房 │ │
│ │ Version 0.92.4 │ │
│ └───────────────────────┘ │
│ │
│ ── 应用信息 ── │
│ ┌───────────────────────┐ │
│ │ 📱 应用版本 0.92.4 │ │
│ │ 📅 更新日期 2026-04 │ │
│ │ 🏷️ 构建版本 92 │ │
│ └───────────────────────┘ │
│ │
│ ── 联系我们 ── │
│ ┌───────────────────────┐ │
│ │ 💬 用户反馈 [→] │ │ ← [→意见反馈页]
│ │ ⭐ 评价应用 [→] │ │
│ │ 📧 联系邮箱 [→] │ │
│ └───────────────────────┘ │
│ │
│ ── 法律信息 ── │
│ ┌───────────────────────┐ │
│ │ 📜 用户协议 [→] │ │
│ │ 🔒 隐私政策 [→] │ │
│ └───────────────────────┘ │
│ │
└─────────────────────────────┘
```
### 📄 页面视图文件
- `lib/src/pages/profile/about_page.dart`(主页面)
### 🔗 跳转关系
| 触发元素 | 跳转方式 | 目标页面 | 路由 | 传参 |
|---------|---------|---------|------|------|
| ← 返回 | `Get.back` | 上一个页面 | - | - |
| 💬 用户反馈 | `Get.toNamed` | 意见反馈页 | `/chat` | - |
| ⭐ 评价应用 | 打开应用商店 | App Store | - | - |
| 📧 联系邮箱 | 打开邮件客户端 | Mail App | - | - |
| 📜 用户协议 | `Get.toNamed` | 用户协议页 | `/user-agreement` | - |
| 🔒 隐私政策 | `Get.toNamed` | 隐私政策页 | `/privacy-policy` | - |
### 🎨 美观问题
| # | 问题 | 严重度 | 建议 |
|---|------|--------|------|
| 1 | 应用图标区域可增加动画效果 | 🟢低 | 添加呼吸动画或渐变效果 |
### ⚡ 功能缺失
| # | 功能 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | 检查更新 | P2 | 检测新版本并提示更新 |
| 2 | 更新日志 | P3 | 展示版本更新内容 |
---
## 🥬 食材详情页 (IngredientDetailPage)
```
@@ -737,10 +843,10 @@
| P1 | 下拉刷新 | 首页/发现 | 缺少下拉手势刷新 | 🔴待开发 |
| P2 | 搜索历史 | 搜索页 | 无本地搜索记录 | 🔴待开发 |
| P2 | 分页加载 | 多个列表页 | 分类浏览/标签列表无分页 | 🔴待开发 |
| P2 | 相关推荐 | 详情页 | 缺少相关菜谱推荐 | 🔴待开发 |
| P2 | 相关推荐 | 详情页 | 缺少相关菜谱推荐 | ✅v0.92.0 |
| P2 | 烹饪模式 | 详情页 | 全屏步骤+计时器 | 🔴待开发 |
| P2 | 过敏原警示 | 详情页 | 食材含过敏原时警告 | 🔴待开发 |
| P2 | 营养可视化 | 详情页 | 环形图/进度条展示营养占比 | 🔴待开发 |
| P2 | 过敏原警示 | 详情页 | 食材含过敏原时警告 | ✅v0.92.0 |
| P2 | 营养可视化 | 详情页 | 环形图/进度条展示营养占比 | ✅v0.92.0 |
| P3 | 排序筛选 | 列表页 | 按评分/浏览量/最新排序 | 🔴待开发 |
| P3 | 评论系统 | 详情页 | 需后端支持 | 🔴需后端 |
| P3 | 用户等级 | 个人中心 | 经验值+等级+徽章 | 🔴需后端 |
@@ -755,72 +861,62 @@
| 接口文件 | Repository | 已用act | 状态 |
|---------|-----------|---------|------|
| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query | ✅ |
| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query/mini | ✅ |
| `api_action.php` | ActionRepository | like/rate/view/ip_status | ✅ |
| `api_feed.php` | FeedRepository | recommend/latest/hot/prefetch | ✅ |
| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search | ✅ |
| `api_hot.php` | HotRepository(→stats_full.php?act=hot) | hot | ✅ |
| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail | ✅ |
| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search/meal_times/recipe_sub_categories/ingredient_main_categories/ingredient_sub_categories/category_tags/filter_ingredients/ingredient_recipes | ✅ |
| `api_hot.php` | HotRepository(→stats_full.php?act=hot) | hot(sort=view/like/rate) | ✅ |
| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail/filter_steps | ✅ |
| `api_discover.php` | DiscoverRepository | 随机数据 | ✅ |
| `stats_full.php` | StatsRepository | online/request/hot | ✅ |
| 静态数据 | 未使用 | eating_times.json/nutrition_types.json/gmy.json | ❌未使用 |
| `api_check_duplicate.php` | RecipeRepository | check_title/check_ingredient/check_step/check_content/check_all | ✅v0.92.0 |
| `stats_full.php` | StatsRepository | online/request/hot/stats | ✅ |
| 静态数据 | 部分使用 | eating_times.json(✅)/nutrition_types.json(✅)/gmy.json(✅) | ✅v0.92.0 |
### 📊 未使用API接口一览
> 以下接口在v0.92.0版本已大部分实现,仅保留少量待开发接口
| 接口 | act/参数 | 文档描述 | 当前状态 |
|------|---------|---------|---------|
| `api.php?act=mini` | mini | 迷你版菜谱信息(~1KB),适用于列表页快速加载 | ❌未调用 |
| `api_filter.php?act=meal_times` | meal_times | 用餐时段列表(早餐/中餐/晚餐等) | ❌未调用 |
| `api_filter.php?act=recipe_sub_categories` | recipe_sub_categories&parent_id= | 食谱子分类列表 | ❌未调用 |
| `api_filter.php?act=ingredient_main_categories` | ingredient_main_categories | 食材大类列表 | ❌未调用 |
| `api_filter.php?act=ingredient_sub_categories` | ingredient_sub_categories&parent_id= | 食材子分类列表 | ❌未调用 |
| `api_filter.php?act=category_tags` | category_tags&category_id= | 指定分类下的口味+工艺标签 | ❌未调用 |
| `api_filter.php` exclude_*参数 | exclude_category/taste/cooking/allergen等 | 排除筛选(排除分类/口味/工艺/过敏原) | ❌未调用 |
| `api_filter.php` 高级筛选 | nutrition_name/min/max, allergen, ingredient, author_id | 营养范围/过敏原/食材/作者筛选 | ❌未调用 |
| `api_what_to_eat.php?act=filter_steps` | filter_steps&category= | 获取筛选步骤和菜谱数量 | ❌未调用 |
| `api_what_to_eat.php?act=detail&code=` | detail&code=CP032892 | 编码查询菜谱详情 | ❌未调用 |
| `api_what_to_eat.php?act=detail&title=&fuzzy=1` | detail&title=&fuzzy=1 | 模糊标题搜索 | ❌未调用 |
| `api_hot.php?sort=rate` | sort=rate | 按评分排序的热门排行 | ❌未调用 |
| `api.php?act=unified_list&type=ingredient` | unified_list&type=ingredient | 统一格式食材列表 | ❌未调用 |
| `api.php?act=unified_detail&type=ingredient` | unified_detail&type=ingredient | 统一格式食材详情 | ❌未调用 |
| `api.php?act=unified_hot&type=ingredient` | unified_hot&type=ingredient | 统一格式食材热门 | ❌未调用 |
| `eating_times.json` | 静态资源 | 34种用餐时段数据(标准/组合/频率/方法) | ❌未使用 |
| `nutrition_types.json` | 静态资源 | 31种营养成分数据(含单位) | ❌未使用 |
| `gmy.json` | 静态资源 | 585种过敏原数据(21大类) | ❌未使用 |
| `api.php?act=unified_list&type=ingredient` | unified_list&type=ingredient | 统一格式食材列表 | 🔴待开发 |
| `api.php?act=unified_detail&type=ingredient` | unified_detail&type=ingredient | 统一格式食材详情 | 🔴待开发 |
| `api.php?act=unified_hot&type=ingredient` | unified_hot&type=ingredient | 统一格式食材热门 | 🔴待开发 |
---
### 🟢 已有API可直接开发P2优先级
| # | 功能 | API接口 | 数据源 | 页面位置 | 开发复杂度 | 说明 |
> 以下功能在v0.92.0版本已全部实现
| # | 功能 | API接口 | 数据源 | 页面位置 | 开发复杂度 | 状态 |
|---|------|---------|--------|---------|-----------|------|
| 1 | 🕐 用餐时段推荐 | `api_filter.php?act=meal_times` | eating_times.json(34种) | 首页/工具中心 | ⭐⭐ | 根据当前时间智能推荐早/午/晚餐,首页瀑布流已有时段卡片但跳转搜索页,应改为时段专属推荐页 |
| 2 | ⚠️ 过敏原警示 | `api.php?act=full` allergens字段 | gmy.json(585种) | 菜品详情页 | ⭐⭐ | 详情页显示过敏原警告结合用户过敏原设置自动过滤已有allergen字段但未展示 |
| 3 | 📊 营养可视化 | `api.php?act=full` nutrition字段 | nutrition_types.json(31种) | 菜品详情页/营养中心 | ⭐⭐⭐ | 环形图/进度条展示营养占比nutrition字段已有数据需前端可视化组件 |
| 4 | 🏆 评分排行榜 | `api_hot.php?type=recipe&sort=rate` | api_hot.php | 热门排行页 | ⭐ | 热门页已有Tab增加"评分榜"排序选项API已支持sort=rate |
| 5 | 📱 迷你信息加载 | `api.php?act=mini&id=` | api.php | 列表页/卡片 | ⭐⭐ | 列表页使用mini接口(~1KB)替代detail(~10KB)10倍性能提升需改造列表加载逻辑 |
| 6 | 🔍 排除筛选 | `api_filter.php?act=filter_recipes` exclude_*参数 | api_filter.php | 高级搜索页 | ⭐ | 高级搜索页增加"排除"选项,如排除辣味/油炸等API已支持7个exclude参数 |
| 7 | 🌐 IP状态显示 | `api_action.php?act=ip_status` | api_action.php | 菜品详情页 | ⭐ | 评分前显示今日剩余评分次数ActionRepository已封装fetchIpStatus()但UI未使用 |
| 8 | 🏷️ 分类标签联动 | `api_filter.php?act=category_tags&category_id=` | api_filter.php | 分类浏览页/高级搜索 | ⭐⭐ | 选择分类后自动加载该分类下的口味+工艺标签,提升筛选体验 |
| 9 | 🥗 食材分类浏览 | `api_filter.php?act=ingredient_main_categories/sub_categories` | api_filter.php | 发现页/工具中心 | ⭐⭐ | 食材大类→子类→食材列表三级浏览,当前发现页食材分类跳转分类浏览页但无食材专用分类 |
| 10 | 📋 食谱子分类 | `api_filter.php?act=recipe_sub_categories&parent_id=` | api_filter.php | 分类浏览页 | ⭐ | 当前分类浏览使用api.php?act=categories改用filter接口可获取更丰富的子分类数据 |
| 11 | 🎲 筛选步骤引导 | `api_what_to_eat.php?act=filter_steps` | api_what_to_eat.php | 今天吃什么 | ⭐⭐ | 逐步筛选先选分类→再选标签→显示匹配数量比当前直接filter_apply体验更好 |
| 12 | 🔢 编码/模糊查询 | `api_what_to_eat.php?act=detail&code=/title=` | api_what_to_eat.php | 搜索页 | ⭐ | 支持菜谱编码(CP032892)查询和标题模糊搜索,扩展搜索能力 |
| 1 | 🕐 用餐时段推荐 | `api_filter.php?act=meal_times` | eating_times.json(34种) | 首页/工具中心 | ⭐⭐ | ✅v0.92.0 |
| 2 | ⚠️ 过敏原警示 | `api.php?act=full` allergens字段 | gmy.json(585种) | 菜品详情页 | ⭐⭐ | ✅v0.92.0 |
| 3 | 📊 营养可视化 | `api.php?act=full` nutrition字段 | nutrition_types.json(31种) | 菜品详情页/营养中心 | ⭐⭐⭐ | ✅v0.92.0 |
| 4 | 🏆 评分排行榜 | `api_hot.php?type=recipe&sort=rate` | api_hot.php | 热门排行页 | ⭐ | ✅v0.92.0 |
| 5 | 📱 迷你信息加载 | `api.php?act=mini&id=` | api.php | 列表页/卡片 | ⭐⭐ | ✅v0.92.0 |
| 6 | 🔍 排除筛选 | `api_filter.php?act=filter_recipes` exclude_*参数 | api_filter.php | 高级搜索页 | ⭐ | ✅v0.92.0 |
| 7 | 🌐 IP状态显示 | `api_action.php?act=ip_status` | api_action.php | 菜品详情页 | ⭐ | ✅v0.92.0 |
| 8 | 🏷️ 分类标签联动 | `api_filter.php?act=category_tags&category_id=` | api_filter.php | 分类浏览页/高级搜索 | ⭐⭐ | ✅v0.92.0 |
| 9 | 🥗 食材分类浏览 | `api_filter.php?act=ingredient_main_categories/sub_categories` | api_filter.php | 发现页/工具中心 | ⭐⭐ | ✅v0.92.0 |
| 10 | 📋 食谱子分类 | `api_filter.php?act=recipe_sub_categories&parent_id=` | api_filter.php | 分类浏览页 | ⭐ | ✅v0.92.0 |
| 11 | 🎲 筛选步骤引导 | `api_what_to_eat.php?act=filter_steps` | api_what_to_eat.php | 今天吃什么 | ⭐⭐ | ✅v0.92.0 |
| 12 | 🔢 编码/模糊查询 | `api_what_to_eat.php?act=detail&code=/title=` | api_what_to_eat.php | 搜索页 | ⭐ | ✅v0.92.0 |
### 🟡 需组合API开发P3优先级
| # | 功能 | 所需API | 页面位置 | 开发复杂度 | 说明 |
| # | 功能 | 所需API | 页面位置 | 开发复杂度 | 状态 |
|---|------|---------|---------|-----------|------|
| 1 | 🧠 智能推荐 | `api_feed.php?act=recommend` + `api_filter.php?act=filter_recipes` + `gmy.json` + 用户偏好设置 | 首页 | ⭐⭐⭐⭐ | 结合时段+营养+过敏原+用户偏好的智能推荐,需设计推荐算法权重 |
| 2 | 📅 每日菜单规划 | `api_what_to_eat.php?act=filter_apply` × 3次 + `eating_times.json` | 工具中心 | ⭐⭐⭐ | 一次性生成早中晚餐完整菜单,需新建菜单规划页面 |
| 3 | 📱 二维码海报 | `api.php?act=detail` code字段 + qr_flutter库 | 菜品详情页 | ⭐⭐ | 生成菜谱二维码分享图code字段已有(如CP032892) |
| 4 | 🔗 社交分享增强 | `api.php?act=detail` code字段 + `api_hot.php` statistics | 菜品详情页 | ⭐⭐ | 分享链接含菜谱编码+热度标签,当前分享功能已有但内容简单 |
| 5 | 🏋️ 健身餐推荐 | `api_filter.php?act=filter_recipes` nutrition_min/max + nutrition_types.json | 工具中心/发现页 | ⭐⭐⭐ | 高蛋白(>30g)/低脂(<10g)/低碳水菜谱筛选需营养目标设置UI |
| 6 | 📋 过敏原报告 | `api.php?act=full` allergens + `gmy.json` | 菜品详情页 | ⭐⭐⭐ | 菜谱过敏原完整分析报告交叉比对585种过敏原数据 |
| 7 | 🥗 食材营养详情 | `api.php?act=ingredient_detail` + `nutrition_types.json` | 食材详情页 | ⭐⭐ | 食材详情页增加营养成分表格+单位匹配nutrition_types.json提供31种营养单位 |
| 8 | 🔄 食材替代建议 | `api_filter.php?act=filter_recipes` ingredient参数 + `gmy.json` | 食材详情页 | ⭐⭐⭐ | 缺少某食材时推荐替代品,需建立食材替代关系映射 |
| 9 | 📈 营养目标追踪 | `api.php?act=full` nutrition × 多菜谱 + `nutrition_types.json` | 营养中心 | ⭐⭐⭐⭐ | 每日营养摄入统计+目标追踪,需记录用户每日饮食 |
| 10 | 🏷️ 统一格式输出 | `api.php?act=unified_list/detail/search/hot` type=ingredient | 食材相关页面 | ⭐⭐ | 统一格式简化食材数据处理,当前食材页面使用不同接口格式 |
| 1 | 🧠 智能推荐 | `api_feed.php?act=recommend` + `api_filter.php?act=filter_recipes` + `gmy.json` + 用户偏好设置 | 首页 | ⭐⭐⭐⭐ | 🔴待开发 |
| 2 | 📅 每日菜单规划 | `api_what_to_eat.php?act=filter_apply` × 3次 + `eating_times.json` | 工具中心 | ⭐⭐⭐ | 🔴待开发 |
| 3 | 📱 二维码海报 | `api.php?act=detail` code字段 + qr_flutter库 | 菜品详情页 | ⭐⭐ | 🔴待开发 |
| 4 | 🔗 社交分享增强 | `api.php?act=detail` code字段 + `api_hot.php` statistics | 菜品详情页 | ⭐⭐ | 🔴待开发 |
| 5 | 🏋️ 健身餐推荐 | `api_filter.php?act=filter_recipes` nutrition_min/max + nutrition_types.json | 工具中心/发现页 | ⭐⭐⭐ | ✅v0.92.0 |
| 6 | 📋 过敏原报告 | `api.php?act=full` allergens + `gmy.json` | 菜品详情页 | ⭐⭐⭐ | ✅v0.92.0 |
| 7 | 🥗 食材营养详情 | `api.php?act=ingredient_detail` + `nutrition_types.json` | 食材详情页 | ⭐⭐ | ✅v0.92.0 |
| 8 | 🔄 食材替代建议 | `api_filter.php?act=filter_recipes` ingredient参数 + `gmy.json` | 食材详情页 | ⭐⭐⭐ | ✅v0.92.0 |
| 9 | 📈 营养目标追踪 | `api.php?act=full` nutrition × 多菜谱 + `nutrition_types.json` | 营养中心 | ⭐⭐⭐⭐ | ✅v0.92.0 |
| 10 | 🏷️ 统一格式输出 | `api.php?act=unified_list/detail/search/hot` type=ingredient | 食材相关页面 | ⭐⭐ | 🔴待开发 |
### 🔴 需后端新开发API

View File

@@ -1,6 +1,6 @@
# 📋 未完成功能清单
> 创建: 2026-04-09 | 更新: 2026-04-13 v0.92.0 | 优先级: P1=核心 P2=重要 P3=增强 | 优先级值1-5(5=最高)
> 创建: 2026-04-09 | 更新: 2026-04-13 v0.92.4 | 优先级: P1=核心 P2=重要 P3=增强 | 优先级值1-5(5=最高)
---
@@ -33,7 +33,9 @@
| 三十四 | 食材详情本地缓存+缓存管理 | 2 | 2 | 100% | ✅ |
| 三十五 | 食材详情页闪退修复 | 1 | 1 | 100% | ✅ |
| 三十六 | 21项功能批量实现 | 21 | 21 | 100% | ✅ |
| **合计** | **220** | **210** | **95%** | |
| 三十七 | 目录结构整理+导入路径修复 | 8 | 8 | 100% | ✅ |
| 三十八 | UI布局优化+缓存修复 | 4 | 4 | 100% | ✅ |
| **合计** | **232** | **222** | **96%** | |
---
@@ -217,6 +219,22 @@
- 📈 数据管理中心增强DataCenterPage新增运营大屏入口
- 🔧 代码质量flutter analyze零错误
### 阶段三十七:目录结构整理+导入路径修复 ✅
- 📁 lib/src目录重组每个文件夹≤8文件按功能分子目录
- 🔧 导入路径更新批量更新import路径适配新目录结构
- 🧹 BOM字符清理72个Dart文件移除UTF-8 BOM (U+FEFF)
- 🐛 类型错误修复ShoppingItemModel/MealRecordModel类型匹配
- 🧹 未使用代码清理:移除未使用的导入和字段
- 📊 flutter analyze解决所有critical错误
- 🔄 路由参数修复CategoryModel类型转换问题修复
- 📝 文档更新:同步目录结构变更
### 阶段三十八UI布局优化+缓存修复 ✅
- 📱 收藏页面网格布局GridView 2列卡片展示
- 🛠️ 工具中心增强:新增"使用工具"按钮直接打开工具功能
- 💾 食材缓存修复CacheService键名匹配问题修复
- 🔗 缓存管理页面跳转修复:正确跳转到食材详情页
### 阶段三十:发现页口味/工艺筛选 ✅
- ✅ 口味标签筛选 / ✅ 工艺标签筛选
- ✅ 相关菜谱推荐(详情页底部)— v0.92.0实现
@@ -245,46 +263,25 @@
## 📊 API接口使用状态一览
> 基于 API_DOC.md v3.2.0 + APP_GUIDE.md v2.9.0
> 基于 API_DOC.md v3.2.0 + APP_GUIDE.md v2.9.0,更新于 v0.92.0
### ✅ 已使用接口
| 接口文件 | Repository | 已用act |
|---------|-----------|---------|
| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query |
| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query/mini |
| `api_action.php` | ActionRepository | like/rate/view/ip_status |
| `api_feed.php` | FeedRepository | recommend/latest/hot/prefetch |
| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search |
| `api_hot.php` | HotRepository | hot(today/month/total) |
| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail |
| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search/meal_times/recipe_sub_categories/ingredient_main_categories/ingredient_sub_categories/category_tags/filter_ingredients/ingredient_recipes |
| `api_hot.php` | HotRepository | hot(today/month/total) sort=view/like/rate |
| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail/filter_steps |
| `api_discover.php` | DiscoverRepository | 随机数据 |
| `stats_full.php` | StatsRepository+OnlineRepository | online/request/hot/heartbeat |
| `api_check_duplicate.php` | RecipeRepository | check_title/check_ingredient/check_step/check_content/check_all |
| `stats_full.php` | StatsRepository+OnlineRepository | online/request/hot/stats/heartbeat |
| 静态数据 | 各页面 | eating_times.json(✅)/nutrition_types.json(✅)/gmy.json(✅) |
### 未使用接口(可直接调用
### 🔴 未使用接口(待开发
| 接口 | act | 功能 | 可开发功能 |
|------|-----|------|-----------|
| `api.php` | mini | 迷你版菜谱(~1KB) | 列表页性能优化 |
| `api_filter.php` | meal_times | 用餐时段列表 | 时段推荐 |
| `api_filter.php` | recipe_sub_categories | 食谱子分类 | 分类浏览增强 |
| `api_filter.php` | ingredient_main_categories | 食材大类 | 食材分类浏览 |
| `api_filter.php` | ingredient_sub_categories | 食材子分类 | 食材三级浏览 |
| `api_filter.php` | category_tags | 分类下标签 | 标签联动筛选 |
| `api_filter.php` | filter_ingredients | 食材筛选 | 高级搜索食材 |
| `api_filter.php` | ingredient_recipes | 食材对应菜品 | 食材详情页菜品列表 |
| `api_filter.php` | index | 接口索引 | 调试/接口文档 |
| `api_what_to_eat.php` | filter_steps | 筛选步骤引导 | "吃什么"步骤UI |
| `api_hot.php` | sort=rate | 评分排行 | 评分排行榜 |
| `api_filter.php` | exclude_*参数(7个) | 排除筛选 | 高级搜索排除选项 |
| `api_filter.php` | nutrition_min/max | 营养范围筛选 | 健身餐推荐 |
| `api_check_duplicate.php` | 5种act | 查重检测 | 菜谱上传查重 |
| `api.php` | unified_* type=ingredient | 统一格式食材 | 食材数据标准化 |
| `stats_full.php` | stats layer=detail/full | 详细/完整统计 | 运营数据大屏 |
### ❌ 未使用静态资源
| 文件 | 数据量 | 当前使用情况 | 可开发功能 |
|------|--------|-------------|-----------|
| `eating_times.json` | 34种时段 | 仅meal_time_recommend_page直接HTTP请求 | 用餐时段推荐 |
| `nutrition_types.json` | 31种营养(含单位) | ❌完全未使用 | 营养可视化+目标追踪 |
| `gmy.json` | 585种过敏原(21大类) | 仅allergen_checker_page直接HTTP请求 | 过敏原警示+报告 |