初始化
36
food_nutrition-main/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
||||
# Flask
|
||||
instance/
|
||||
.webassets-cache
|
||||
BIN
food_nutrition-main/3in1.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
food_nutrition-main/Image1.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
food_nutrition-main/Image2.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
food_nutrition-main/Image3.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
food_nutrition-main/Image4.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
food_nutrition-main/Image5.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
46
food_nutrition-main/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 食物营养成分查询小册H5
|
||||
## 小册介绍
|
||||
本小册是通过AI集成开发环境工具(Trae),结合从聚合数据(JUHE.CN)免费下载的食物营养成分数据库,快速实现了一个简单的食物营养成分查询小册H5。(基本是与AI对话,Trae自动实现的😁)
|
||||
|
||||
主要功能:查询食物的每100克的营养成分信息,如能量、蛋白质、脂肪、碳水化合物、维生素、矿物质等含量。
|
||||
|
||||
- Trae IDE介绍:
|
||||
Trae 是一款字节跳动推出了一款面向海外市场的AI编程工具。旨在改变您的工作方式,通过协作帮助您提高工作效率,运行更迅速。[【访问官网】](https://www.trae.ai)
|
||||
|
||||
- 聚合数据介绍:
|
||||
一家综合性API数据流通服务商,致力于为客户提供数据处理标准化技术服务和数据处理定制化技术服务。Juhe.CN平台提供很多免费的API和数据集下载。[【访问官网】](https://www.juhe.cn/market/product/id/11087)
|
||||
|
||||
##目录结构
|
||||
```
|
||||
├── backend 后端代码目录
|
||||
│ ├── food_nutrition.db 食物营养成本SQLite3数据库文件(可以从聚合数据下载最新的替换)
|
||||
│ ├── main.py
|
||||
│ ├── food_api.py
|
||||
│ └── requirements.txt
|
||||
│ └── ......
|
||||
├── frontend 前端代码目录
|
||||
│ └── ......
|
||||
```
|
||||
|
||||
## 运行前端服务(Vue.js)
|
||||
|
||||
```shell
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 运行后段服务(Python FastAPI)
|
||||
```shell
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
python -m uvicorn main:app --reload
|
||||
```
|
||||
|
||||
## 浏览器访问
|
||||
```
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
## 最终效果图
|
||||

|
||||
261
food_nutrition-main/backend/food_api.py
Normal file
@@ -0,0 +1,261 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import sqlite3
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class Food(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
category_name: str
|
||||
category_id: int
|
||||
father_id: int
|
||||
father_category_name: str
|
||||
alias_name: str | None
|
||||
english_name: str | None
|
||||
edible_part: str | None
|
||||
water: str | None
|
||||
energy: str | None
|
||||
protein: str | None
|
||||
fat: str | None
|
||||
cholesterol: str | None
|
||||
ash: str | None
|
||||
carbohydrate: str | None
|
||||
dietary_fiber: str | None
|
||||
carotene: str | None
|
||||
vitamin_a: str | None
|
||||
vitamin_e: str | None
|
||||
thiamin: str | None
|
||||
riboflavin: str | None
|
||||
niacin: str | None
|
||||
vitamin_c: str | None
|
||||
calcium: str | None
|
||||
phosphorus: str | None
|
||||
potassium: str | None
|
||||
sodium: str | None
|
||||
magnesium: str | None
|
||||
iron: str | None
|
||||
zinc: str | None
|
||||
selenium: str | None
|
||||
copper: str | None
|
||||
manganese: str | None
|
||||
iodine: str | None
|
||||
sfa: str | None
|
||||
mufa: str | None
|
||||
pufa: str | None
|
||||
fatty_acids_total: str | None
|
||||
|
||||
|
||||
@router.get("/api/foods", response_model=List[Food])
|
||||
async def get_foods(category_id: int):
|
||||
try:
|
||||
conn = sqlite3.connect("food_nutrition.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 查询食物数据并关联分类表获取分类名称
|
||||
cursor.execute("""
|
||||
SELECT f.id, f.food_name as name, c.title as category_name, f.cate_id, c.father_id, d.title as father_category_name,
|
||||
f.alias_name, f.english_name, f.edible_part, f.water, f.energy, f.protein, f.fat, f.cholesterol, f.ash,
|
||||
f.carbohydrate, f.dietary_fiber, f.carotene, f.vitamin_a, f.vitamin_e, f.thiamin, f.riboflavin, f.niacin,
|
||||
f.vitamin_c, f.calcium, f.phosphorus, f.potassium, f.sodium, f.magnesium, f.iron, f.zinc, f.selenium,
|
||||
f.copper, f.manganese, f.iodine, f.sfa, f.mufa, f.pufa, f.fatty_acids_total
|
||||
FROM j_food_nutrition f
|
||||
LEFT JOIN j_food_categories c ON f.cate_id = c.cate_id
|
||||
LEFT JOIN j_food_categories d ON d.father_id = c.father_id and d.cate_id=0
|
||||
WHERE f.cate_id = ?
|
||||
""", (category_id,))
|
||||
|
||||
foods = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
Food(
|
||||
id=food[0],
|
||||
name=food[1],
|
||||
category_name=food[2],
|
||||
category_id=food[3],
|
||||
father_id=food[4],
|
||||
father_category_name=food[5],
|
||||
alias_name=food[6],
|
||||
english_name=food[7],
|
||||
edible_part=food[8],
|
||||
water=food[9],
|
||||
energy=food[10],
|
||||
protein=food[11],
|
||||
fat=food[12],
|
||||
cholesterol=food[13],
|
||||
ash=food[14],
|
||||
carbohydrate=food[15],
|
||||
dietary_fiber=food[16],
|
||||
carotene=food[17],
|
||||
vitamin_a=food[18],
|
||||
vitamin_e=food[19],
|
||||
thiamin=food[20],
|
||||
riboflavin=food[21],
|
||||
niacin=food[22],
|
||||
vitamin_c=food[23],
|
||||
calcium=food[24],
|
||||
phosphorus=food[25],
|
||||
potassium=food[26],
|
||||
sodium=food[27],
|
||||
magnesium=food[28],
|
||||
iron=food[29],
|
||||
zinc=food[30],
|
||||
selenium=food[31],
|
||||
copper=food[32],
|
||||
manganese=food[33],
|
||||
iodine=food[34],
|
||||
sfa=food[35],
|
||||
mufa=food[36],
|
||||
pufa=food[37],
|
||||
fatty_acids_total=food[38]
|
||||
)
|
||||
for food in foods
|
||||
]
|
||||
except sqlite3.Error as e:
|
||||
raise HTTPException(status_code=500, detail=f"数据库错误: {str(e)}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")
|
||||
|
||||
@router.get("/api/food", response_model=Food)
|
||||
async def get_food(id: int):
|
||||
try:
|
||||
conn = sqlite3.connect("food_nutrition.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 查询食物数据并关联分类表获取分类名称
|
||||
cursor.execute("""
|
||||
SELECT f.id, f.food_name as name, c.title as category_name, f.cate_id, c.father_id, d.title as father_category_name,
|
||||
f.alias_name, f.english_name, f.edible_part, f.water, f.energy, f.protein, f.fat, f.cholesterol, f.ash,
|
||||
f.carbohydrate, f.dietary_fiber, f.carotene, f.vitamin_a, f.vitamin_e, f.thiamin, f.riboflavin, f.niacin,
|
||||
f.vitamin_c, f.calcium, f.phosphorus, f.potassium, f.sodium, f.magnesium, f.iron, f.zinc, f.selenium,
|
||||
f.copper, f.manganese, f.iodine, f.sfa, f.mufa, f.pufa, f.fatty_acids_total
|
||||
FROM j_food_nutrition f
|
||||
LEFT JOIN j_food_categories c ON f.cate_id = c.cate_id
|
||||
LEFT JOIN j_food_categories d ON d.father_id = c.father_id and d.cate_id=0
|
||||
WHERE f.id = ?
|
||||
""", (id,))
|
||||
|
||||
food = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if food is None:
|
||||
raise HTTPException(status_code=404, detail="未找到该食物")
|
||||
|
||||
return Food(
|
||||
id=food[0],
|
||||
name=food[1],
|
||||
category_name=food[2],
|
||||
category_id=food[3],
|
||||
father_id=food[4],
|
||||
father_category_name=food[5],
|
||||
alias_name=food[6],
|
||||
english_name=food[7],
|
||||
edible_part=food[8],
|
||||
water=food[9],
|
||||
energy=food[10],
|
||||
protein=food[11],
|
||||
fat=food[12],
|
||||
cholesterol=food[13],
|
||||
ash=food[14],
|
||||
carbohydrate=food[15],
|
||||
dietary_fiber=food[16],
|
||||
carotene=food[17],
|
||||
vitamin_a=food[18],
|
||||
vitamin_e=food[19],
|
||||
thiamin=food[20],
|
||||
riboflavin=food[21],
|
||||
niacin=food[22],
|
||||
vitamin_c=food[23],
|
||||
calcium=food[24],
|
||||
phosphorus=food[25],
|
||||
potassium=food[26],
|
||||
sodium=food[27],
|
||||
magnesium=food[28],
|
||||
iron=food[29],
|
||||
zinc=food[30],
|
||||
selenium=food[31],
|
||||
copper=food[32],
|
||||
manganese=food[33],
|
||||
iodine=food[34],
|
||||
sfa=food[35],
|
||||
mufa=food[36],
|
||||
pufa=food[37],
|
||||
fatty_acids_total=food[38]
|
||||
)
|
||||
except sqlite3.Error as e:
|
||||
raise HTTPException(status_code=500, detail=f"数据库错误: {str(e)}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")
|
||||
|
||||
@router.get("/api/search", response_model=List[Food])
|
||||
async def get_foods(keyword: str):
|
||||
try:
|
||||
conn = sqlite3.connect("food_nutrition.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 查询食物数据并关联分类表获取分类名称
|
||||
cursor.execute("""
|
||||
SELECT f.id, f.food_name as name, c.title as category_name, f.cate_id, c.father_id, d.title as father_category_name,
|
||||
f.alias_name, f.english_name, f.edible_part, f.water, f.energy, f.protein, f.fat, f.cholesterol, f.ash,
|
||||
f.carbohydrate, f.dietary_fiber, f.carotene, f.vitamin_a, f.vitamin_e, f.thiamin, f.riboflavin, f.niacin,
|
||||
f.vitamin_c, f.calcium, f.phosphorus, f.potassium, f.sodium, f.magnesium, f.iron, f.zinc, f.selenium,
|
||||
f.copper, f.manganese, f.iodine, f.sfa, f.mufa, f.pufa, f.fatty_acids_total
|
||||
FROM j_food_nutrition f
|
||||
LEFT JOIN j_food_categories c ON f.cate_id = c.cate_id
|
||||
LEFT JOIN j_food_categories d ON d.father_id = c.father_id and d.cate_id=0
|
||||
WHERE f.food_name like ?
|
||||
""", (f'%{keyword}%',))
|
||||
|
||||
foods = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
Food(
|
||||
id=food[0],
|
||||
name=food[1],
|
||||
category_name=food[2],
|
||||
category_id=food[3],
|
||||
father_id=food[4],
|
||||
father_category_name=food[5],
|
||||
alias_name=food[6],
|
||||
english_name=food[7],
|
||||
edible_part=food[8],
|
||||
water=food[9],
|
||||
energy=food[10],
|
||||
protein=food[11],
|
||||
fat=food[12],
|
||||
cholesterol=food[13],
|
||||
ash=food[14],
|
||||
carbohydrate=food[15],
|
||||
dietary_fiber=food[16],
|
||||
carotene=food[17],
|
||||
vitamin_a=food[18],
|
||||
vitamin_e=food[19],
|
||||
thiamin=food[20],
|
||||
riboflavin=food[21],
|
||||
niacin=food[22],
|
||||
vitamin_c=food[23],
|
||||
calcium=food[24],
|
||||
phosphorus=food[25],
|
||||
potassium=food[26],
|
||||
sodium=food[27],
|
||||
magnesium=food[28],
|
||||
iron=food[29],
|
||||
zinc=food[30],
|
||||
selenium=food[31],
|
||||
copper=food[32],
|
||||
manganese=food[33],
|
||||
iodine=food[34],
|
||||
sfa=food[35],
|
||||
mufa=food[36],
|
||||
pufa=food[37],
|
||||
fatty_acids_total=food[38]
|
||||
)
|
||||
for food in foods
|
||||
]
|
||||
except sqlite3.Error as e:
|
||||
raise HTTPException(status_code=500, detail=f"数据库错误: {str(e)}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")
|
||||
BIN
food_nutrition-main/backend/food_nutrition.db
Normal file
81
food_nutrition-main/backend/main.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import sqlite3
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from food_api import router as food_router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# 配置CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 在生产环境中应该设置具体的源
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 数据模型
|
||||
class Category(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
cate_id: int
|
||||
father_id: int
|
||||
|
||||
# 注册食物API路由
|
||||
app.include_router(food_router)
|
||||
|
||||
@app.get("/api/categoryinfo", response_model=List[Category])
|
||||
async def get_categoryinfo(cate_id: int = 0):
|
||||
try:
|
||||
conn = sqlite3.connect("food_nutrition.db")
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT id, title, cate_id, father_id FROM j_food_categories WHERE cate_id = 0 and father_id = ?",
|
||||
(cate_id,)
|
||||
)
|
||||
categoryinfo = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
Category(id=cat[0], title=cat[1], cate_id=cat[2], father_id=cat[3])
|
||||
for cat in categoryinfo
|
||||
]
|
||||
except sqlite3.Error as e:
|
||||
raise HTTPException(status_code=500, detail=f"数据库错误: {str(e)}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")
|
||||
|
||||
@app.get("/api/categories", response_model=List[Category])
|
||||
async def get_categories(cate_id: int = 0, father_id: int = None):
|
||||
try:
|
||||
# 连接数据库
|
||||
conn = sqlite3.connect("food_nutrition.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 查询分类数据
|
||||
if father_id is not None:
|
||||
cursor.execute(
|
||||
"SELECT id, title, cate_id,father_id FROM j_food_categories WHERE cate_id<>0 and father_id = ?",
|
||||
(father_id,)
|
||||
)
|
||||
else:
|
||||
cursor.execute(
|
||||
"SELECT id, title, cate_id,father_id FROM j_food_categories WHERE cate_id = ?",
|
||||
(cate_id,)
|
||||
)
|
||||
categories = cursor.fetchall()
|
||||
|
||||
# 关闭数据库连接
|
||||
conn.close()
|
||||
|
||||
# 转换为响应格式
|
||||
return [
|
||||
Category(id=cat[0], title=cat[1], cate_id=cat[2], father_id=cat[3])
|
||||
for cat in categories
|
||||
]
|
||||
except sqlite3.Error as e:
|
||||
raise HTTPException(status_code=500, detail=f"数据库错误: {str(e)}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}")
|
||||
3
food_nutrition-main/backend/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.1
|
||||
24
food_nutrition-main/frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
13
food_nutrition-main/frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>食物营养成分查询小册</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1423
food_nutrition-main/frontend/package-lock.json
generated
Normal file
21
food_nutrition-main/frontend/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"element-plus": "^2.9.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
1
food_nutrition-main/frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
19
food_nutrition-main/frontend/src/App.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import BottomNav from './components/BottomNav.vue'
|
||||
import TopNav from './components/TopNav.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TopNav />
|
||||
<router-view></router-view>
|
||||
<BottomNav />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
10
food_nutrition-main/frontend/src/assets/banner.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="1920" height="200" viewBox="0 0 1920 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1920" height="200" fill="#409EFF"/>
|
||||
<path d="M0 120C320 120 480 160 800 160C1120 160 1280 120 1600 120C1920 120 1920 160 1920 160V200H0V120Z" fill="#2E8AFF" fill-opacity="0.3"/>
|
||||
<path d="M0 140C320 140 480 180 800 180C1120 180 1280 140 1600 140C1920 140 1920 180 1920 180V200H0V140Z" fill="#2E8AFF" fill-opacity="0.2"/>
|
||||
<circle cx="200" cy="60" r="4" fill="white" fill-opacity="0.3"/>
|
||||
<circle cx="600" cy="40" r="3" fill="white" fill-opacity="0.3"/>
|
||||
<circle cx="1000" cy="80" r="5" fill="white" fill-opacity="0.3"/>
|
||||
<circle cx="1400" cy="30" r="3" fill="white" fill-opacity="0.3"/>
|
||||
<circle cx="1800" cy="70" r="4" fill="white" fill-opacity="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 785 B |
5
food_nutrition-main/frontend/src/assets/carbs-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="14" fill="#FFE5D0"/>
|
||||
<path d="M10 14C10 11.7909 11.7909 10 14 10H18C20.2091 10 22 11.7909 22 14V18C22 20.2091 20.2091 22 18 22H14C11.7909 22 10 20.2091 10 18V14Z" fill="#FFA94D"/>
|
||||
<path d="M13 16C13 14.8954 13.8954 14 15 14H17C18.1046 14 19 14.8954 19 16V16C19 17.1046 18.1046 18 17 18H15C13.8954 18 13 17.1046 13 16V16Z" fill="#FF922B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
3
food_nutrition-main/frontend/src/assets/energy-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 1L3 9H8L7.5 15L13 7H8L8.5 1Z" fill="#FFB800" stroke="#FFB800" stroke-width="1" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
7
food_nutrition-main/frontend/src/assets/f1.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3C10.5 3 9 4 9 6C9 8 10.5 9 12 9C13.5 9 15 8 15 6C15 4 13.5 3 12 3Z" fill="#FFD700"/>
|
||||
<path d="M7 8C5.5 8 4 9 4 11C4 13 5.5 14 7 14C8.5 14 10 13 10 11C10 9 8.5 8 7 8Z" fill="#FFD700"/>
|
||||
<path d="M17 8C15.5 8 14 9 14 11C14 13 15.5 14 17 14C18.5 14 20 13 20 11C20 9 18.5 8 17 8Z" fill="#FFD700"/>
|
||||
<path d="M12 15L12 21" stroke="#8B4513" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 538 B |
7
food_nutrition-main/frontend/src/assets/f10.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 8C7 8 9 7 12 7C15 7 17 8 17 8C17 8 19 12 19 14C19 16 17 17 12 17C7 17 5 16 5 14C5 12 7 8 7 8Z" fill="#CD853F"/>
|
||||
<path d="M9 11C9 11 10 10.5 12 10.5C14 10.5 15 11 15 11" stroke="#8B4513" stroke-width="1" stroke-linecap="round"/>
|
||||
<circle cx="10" cy="13" r="1" fill="#8B4513"/>
|
||||
<circle cx="14" cy="13" r="1" fill="#8B4513"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
7
food_nutrition-main/frontend/src/assets/f11.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="12" cy="12" rx="4" ry="6" fill="#8B4513"/>
|
||||
<path d="M12 6C10 6 8.5 8 8.5 12C8.5 16 10 18 12 18" stroke="#CD853F" stroke-width="1"/>
|
||||
<circle cx="10.5" cy="10" r="1" fill="#CD853F"/>
|
||||
<circle cx="10.5" cy="14" r="1" fill="#CD853F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
7
food_nutrition-main/frontend/src/assets/f12.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 4C10 4 8 5 8 8C8 11 10 12 12 12C14 12 16 11 16 8C16 5 14 4 12 4Z" fill="#90EE90"/>
|
||||
<path d="M8 10C6 10 5 11 5 14C5 17 6 18 8 18C10 18 11 17 11 14C11 11 10 10 8 10Z" fill="#90EE90"/>
|
||||
<path d="M16 10C14 10 13 11 13 14C13 17 14 18 16 18C18 18 19 17 19 14C19 11 18 10 16 10Z" fill="#90EE90"/>
|
||||
<path d="M12 14L12 20" stroke="#FF6B6B" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
8
food_nutrition-main/frontend/src/assets/f13.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5C8 5 5 7 5 10C5 13 8 14 12 14C16 14 19 13 19 10C19 7 16 5 12 5Z" fill="#DEB887"/>
|
||||
<path d="M11 14L10 19" stroke="#8B4513" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M13 14L14 19" stroke="#8B4513" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="9" cy="9" r="1" fill="#FFFFFF"/>
|
||||
<circle cx="15" cy="9" r="1" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
7
food_nutrition-main/frontend/src/assets/f14.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 4C11 4 10 5 10 6C10 7 11 8 12 8C13 8 14 7 14 6C14 5 13 4 12 4Z" fill="#228B22"/>
|
||||
<path d="M12 7C8 7 5 9 5 13C5 17 8 19 12 19C16 19 19 17 19 13C19 9 16 7 12 7Z" fill="#FF0000"/>
|
||||
<path d="M12 7L12 5" stroke="#228B22" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M9 11C9 11 10 12 12 12C14 12 15 11 15 11" stroke="#8B0000" stroke-width="1" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 529 B |
8
food_nutrition-main/frontend/src/assets/f15.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5C9 5 7 6 7 8C7 10 9 11 12 11C15 11 17 10 17 8C17 6 15 5 12 5Z" fill="#D2691E"/>
|
||||
<path d="M8 10C6 10 5 11 5 14C5 17 6 18 8 18C10 18 11 17 11 14C11 11 10 10 8 10Z" fill="#8B4513"/>
|
||||
<path d="M16 10C14 10 13 11 13 14C13 17 14 18 16 18C18 18 19 17 19 14C19 11 18 10 16 10Z" fill="#8B4513"/>
|
||||
<circle cx="10" cy="8" r="1" fill="#FFFFFF"/>
|
||||
<circle cx="14" cy="8" r="1" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 543 B |
6
food_nutrition-main/frontend/src/assets/f16.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 8C6 8 8 7 12 7C16 7 18 8 18 8C18 8 20 10 20 12C20 14 18 15 12 15C6 15 4 14 4 12C4 10 6 8 6 8Z" fill="#FF6B6B"/>
|
||||
<path d="M8 10C8 10 10 11 12 11C14 11 16 10 16 10" stroke="#8B0000" stroke-width="1" stroke-linecap="round"/>
|
||||
<path d="M7 12L17 12" stroke="#8B0000" stroke-width="1" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 462 B |
8
food_nutrition-main/frontend/src/assets/f17.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5C9 5 7 7 7 10C7 13 9 15 12 15C15 15 17 13 17 10C17 7 15 5 12 5Z" fill="#FFE4B5"/>
|
||||
<path d="M10 14L8 19" stroke="#DEB887" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M14 14L16 19" stroke="#DEB887" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="10" cy="9" r="1" fill="#8B4513"/>
|
||||
<circle cx="14" cy="9" r="1" fill="#8B4513"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
7
food_nutrition-main/frontend/src/assets/f18.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4H16V7C16 7 18 8 18 10V18C18 20 16 20 12 20C8 20 6 20 6 18V10C6 8 8 7 8 7V4Z" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<path d="M8 4H16" stroke="#000000" stroke-width="1"/>
|
||||
<path d="M10 12H14" stroke="#000000" stroke-width="1"/>
|
||||
<path d="M10 15H14" stroke="#000000" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
5
food_nutrition-main/frontend/src/assets/f19.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 6C9 6 7 8 7 12C7 16 9 18 12 18C15 18 17 16 17 12C17 8 15 6 12 6Z" fill="#FFFFFF" stroke="#FFD700" stroke-width="1"/>
|
||||
<circle cx="12" cy="12" r="3" fill="#FFD700"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 322 B |
6
food_nutrition-main/frontend/src/assets/f20.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12C5 12 8 8 12 8C16 8 19 12 19 12C19 12 16 16 12 16C8 16 5 12 5 12Z" fill="#87CEEB"/>
|
||||
<circle cx="10" cy="12" r="1" fill="#000000"/>
|
||||
<path d="M19 12L21 10M19 12L21 14" stroke="#87CEEB" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 386 B |
7
food_nutrition-main/frontend/src/assets/f21.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4H14V6C14 6 16 7 16 9V18C16 20 14 20 12 20C10 20 8 20 8 18V9C8 7 10 6 10 6V4Z" fill="#FFB6C1" stroke="#FF69B4" stroke-width="1"/>
|
||||
<path d="M11 8H13" stroke="#FF69B4" stroke-width="1"/>
|
||||
<path d="M10 11H14" stroke="#FF69B4" stroke-width="1"/>
|
||||
<circle cx="12" cy="15" r="2" fill="#FF69B4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
7
food_nutrition-main/frontend/src/assets/f22.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="8" fill="#D2B48C" stroke="#8B4513" stroke-width="1"/>
|
||||
<circle cx="9" cy="10" r="1" fill="#8B4513"/>
|
||||
<circle cx="15" cy="10" r="1" fill="#8B4513"/>
|
||||
<path d="M9 14C9 14 10.5 15 12 15C13.5 15 15 14 15 14" stroke="#8B4513" stroke-width="1" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
7
food_nutrition-main/frontend/src/assets/f23.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 8H18L17 18H7L6 8Z" fill="#FF4444" stroke="#CC0000" stroke-width="1"/>
|
||||
<path d="M5 8H19V10H5V8Z" fill="#FF6666" stroke="#CC0000" stroke-width="1"/>
|
||||
<path d="M8 12H16" stroke="white" stroke-width="1"/>
|
||||
<path d="M8 14H16" stroke="white" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 414 B |
7
food_nutrition-main/frontend/src/assets/f24.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 6H16L15 18H9L8 6Z" fill="#87CEEB" stroke="#4682B4" stroke-width="1"/>
|
||||
<path d="M7 6H17V8H7V6Z" fill="#B0E0E6" stroke="#4682B4" stroke-width="1"/>
|
||||
<path d="M11 10C11 10 12 11 12 11C12 11 13 10 13 10" stroke="#4682B4" stroke-width="1"/>
|
||||
<path d="M9 18H15V20H9V18Z" fill="#87CEEB" stroke="#4682B4" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 475 B |
6
food_nutrition-main/frontend/src/assets/f25.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4H16L12 12V18H16V20H8V18H12V12L8 4Z" fill="#9370DB" stroke="#483D8B" stroke-width="1"/>
|
||||
<path d="M9 6L15 6" stroke="#483D8B" stroke-width="1"/>
|
||||
<circle cx="12" cy="8" r="1" fill="#483D8B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
7
food_nutrition-main/frontend/src/assets/f26.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 8C8 6 10 4 12 4C14 4 16 6 16 8C16 10 14 12 12 12C10 12 8 10 8 8Z" fill="#FFB6C1" stroke="#FF69B4" stroke-width="1"/>
|
||||
<path d="M8 16C8 14 10 12 12 12C14 12 16 14 16 16C16 18 14 20 12 20C10 20 8 18 8 16Z" fill="#FFB6C1" stroke="#FF69B4" stroke-width="1"/>
|
||||
<path d="M10 8H14" stroke="#FF69B4" stroke-width="1"/>
|
||||
<path d="M10 16H14" stroke="#FF69B4" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
7
food_nutrition-main/frontend/src/assets/f27.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4H14V8C14 8 16 9 16 11V18C16 20 14 20 12 20C10 20 8 20 8 18V11C8 9 10 8 10 8V4Z" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
|
||||
<path d="M11 6H13" stroke="#DAA520" stroke-width="1"/>
|
||||
<path d="M10 10H14" stroke="#DAA520" stroke-width="1"/>
|
||||
<path d="M9 14H15" stroke="#DAA520" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 460 B |
7
food_nutrition-main/frontend/src/assets/f28.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4H14V6C14 6 16 7 16 9V18C16 20 14 20 12 20C10 20 8 20 8 18V9C8 7 10 6 10 6V4Z" fill="#CD5C5C" stroke="#8B0000" stroke-width="1"/>
|
||||
<path d="M11 8H13" stroke="#8B0000" stroke-width="1"/>
|
||||
<path d="M10 11H14" stroke="#8B0000" stroke-width="1"/>
|
||||
<circle cx="12" cy="15" r="2" fill="#8B0000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
4
food_nutrition-main/frontend/src/assets/fat-icon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C4.5 2 2 6 2 9C2 12 4.5 14 8 14C11.5 14 14 12 14 9C14 6 11.5 2 8 2Z" stroke="#34C759" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 5C6.5 5 5 7 5 9C5 11 6.5 12 8 12" stroke="#34C759" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 404 B |
4
food_nutrition-main/frontend/src/assets/protein-icon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8" cy="8" r="7" stroke="#4A90E2" stroke-width="1.5"/>
|
||||
<path d="M5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11" stroke="#4A90E2" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 325 B |
1
food_nutrition-main/frontend/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
111
food_nutrition-main/frontend/src/components/BottomNav.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { House, Search, InfoFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const activeTab = ref('/')
|
||||
|
||||
const handleTabClick = (tab) => {
|
||||
router.push(tab)
|
||||
}
|
||||
|
||||
watch(() => route.path, (newPath) => {
|
||||
activeTab.value = newPath
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bottom-nav">
|
||||
<el-tabs v-model="activeTab" @tab-click="(tab) => handleTabClick(tab.props.name)" class="bottom-tabs">
|
||||
<el-tab-pane label="首页" name="/">
|
||||
<template #label>
|
||||
<div class="tab-item">
|
||||
<el-icon><House /></el-icon>
|
||||
<span>首页</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="食物搜索" name="/search">
|
||||
<template #label>
|
||||
<div class="tab-item">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span>食物搜索</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="关于小册" name="/about">
|
||||
<template #label>
|
||||
<div class="tab-item">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>关于小册</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
|
||||
z-index: 100;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bottom-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-tabs :deep(.el-tabs__nav-wrap),
|
||||
.bottom-tabs :deep(.el-tabs__nav-scroll) {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bottom-tabs :deep(.el-tabs__nav) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bottom-tabs :deep(.el-tabs__item) {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
height: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.bottom-tabs :deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.bottom-tabs :deep(.el-tabs__item.is-active) {
|
||||
color: var(--el-color-primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tab-item .el-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tab-item span {
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
43
food_nutrition-main/frontend/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
39
food_nutrition-main/frontend/src/components/TopNav.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
import { ElDivider } from 'element-plus'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-nav">
|
||||
<div class="nav-content">
|
||||
<h1 class="app-title">食物营养成分查询手册</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.top-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background-color: #409EFF;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0.8rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
11
food_nutrition-main/frontend/src/main.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
41
food_nutrition-main/frontend/src/router/index.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('../views/Home.vue')
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
name: 'Search',
|
||||
component: () => import('../views/Search.vue')
|
||||
},
|
||||
{
|
||||
path: '/category/:id',
|
||||
name: 'Category',
|
||||
component: () => import('../views/Category.vue')
|
||||
},
|
||||
{
|
||||
path: '/foods/:id',
|
||||
name: 'FoodList',
|
||||
component: () => import('../views/FoodList.vue')
|
||||
},
|
||||
{
|
||||
path: '/food/:id',
|
||||
name: 'Food',
|
||||
component: () => import('../views/Food.vue')
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: () => import('../views/About.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
79
food_nutrition-main/frontend/src/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #f9f9f9;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
124
food_nutrition-main/frontend/src/views/About.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import { ElCard, ElDivider } from 'element-plus'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="about-container">
|
||||
|
||||
|
||||
<el-card class="info-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>数据来源</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
聚合数据(<a href="https://www.juhe.cn/market/product/id/11087" target="_blank">JUHE.CN</a>)
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="info-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>AI开发工具</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
TRAE(<a href="https://www.trae.ai/" target="_blank">TRAE.AI</a>)
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="info-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>技术栈</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card-content tech-stack">
|
||||
<div class="tech-item">
|
||||
<span class="tech-name">Vue 3</span>
|
||||
<span class="tech-desc">前端框架</span>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div class="tech-item">
|
||||
<span class="tech-name">Element Plus</span>
|
||||
<span class="tech-desc">UI组件库</span>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div class="tech-item">
|
||||
<span class="tech-name">Vite</span>
|
||||
<span class="tech-desc">构建工具</span>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div class="tech-item">
|
||||
<span class="tech-name">FastAPI</span>
|
||||
<span class="tech-desc">后端框架</span>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div class="tech-item">
|
||||
<span class="tech-name">SQLite</span>
|
||||
<span class="tech-desc">数据库</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.about-container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
min-width: 300px;
|
||||
padding: 8px;
|
||||
margin-bottom: 50px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 30px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.tech-stack {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.tech-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.tech-name {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.tech-desc {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
151
food_nutrition-main/frontend/src/views/Category.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElCard, ElRow, ElCol, ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const categories = ref([])
|
||||
const loading = ref(false)
|
||||
const parentCategory = ref('')
|
||||
|
||||
const fetchParentCategory = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/categoryinfo?cate_id=${route.params.id}`)
|
||||
if (!response.ok) throw new Error('获取父级分类数据失败')
|
||||
const data = await response.json()
|
||||
if (data && data.length > 0) {
|
||||
parentCategory.value = data[0].title
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('获取父级分类数据错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCategories = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/categories?father_id=${route.params.id}`)
|
||||
if (!response.ok) throw new Error('获取分类数据失败')
|
||||
const data = await response.json()
|
||||
categories.value = data.map(category => ({
|
||||
id: category.id,
|
||||
name: category.title,
|
||||
cate_id: category.cate_id,
|
||||
father_id: category.father_id,
|
||||
icon: `/src/assets/f${category.father_id}.svg`
|
||||
}))
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('获取分类数据错误:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories()
|
||||
fetchParentCategory()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<!--
|
||||
<div class="banner">
|
||||
<img src="../assets/banner.svg" alt="Banner" class="banner-image" />
|
||||
<h1 class="title">{{ parentCategory }}</h1>
|
||||
</div>
|
||||
-->
|
||||
<el-row :gutter="16" justify="start">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" v-for="category in categories" :key="category.id">
|
||||
<el-card class="category-card" shadow="hover" @click="router.push(`/foods/${category.cate_id}`)">
|
||||
<div class="category-content">
|
||||
<img :src="category.icon" class="category-icon" alt="分类图标" />
|
||||
<span class="category-name">{{ category.name }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
padding: 8px;
|
||||
max-width: 480px;
|
||||
margin: 50px auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.el-row {
|
||||
width: 100%;
|
||||
}
|
||||
.banner {
|
||||
position: relative;
|
||||
margin: -8px -8px 16px -8px;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 24px;
|
||||
font-size: clamp(20px, 5vw, 32px);
|
||||
}
|
||||
|
||||
.category-card {
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
height: clamp(48px, 12vw, 64px);
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: clamp(12px, 3vw, 14px);
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
399
food_nutrition-main/frontend/src/views/Food.vue
Normal file
@@ -0,0 +1,399 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElCard, ElDivider, ElSkeleton, ElMessage } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const food = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const fetchFoodDetail = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/food?id=${route.params.id}`)
|
||||
if (!response.ok) throw new Error('获取食物详情失败')
|
||||
const data = await response.json()
|
||||
food.value = {
|
||||
...data,
|
||||
icon: `/src/assets/f${data.father_id}.svg`
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('获取食物详情错误:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFoodDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="food-detail-page">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<template #template>
|
||||
<div class="skeleton-content">
|
||||
<el-skeleton-item variant="image" style="width: 240px; height: 240px" />
|
||||
<el-skeleton-item variant="h1" style="width: 50%" />
|
||||
<el-skeleton-item variant="text" style="width: 80%" />
|
||||
<el-skeleton-item variant="text" style="width: 60%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<el-card v-if="food" class="food-detail-card">
|
||||
<div class="food-header">
|
||||
<img :src="food.icon" class="food-icon" alt="食物图标" />
|
||||
<div class="food-title">
|
||||
<h1>{{ food.name }}</h1>
|
||||
<p class="category">{{ food.father_category_name }}、{{ food.category_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">基本营养成分</span><br/><span style="color:#666; font-size:12px;">(每100g)</span>
|
||||
</el-divider>
|
||||
|
||||
<!-- 能量与相关成分 -->
|
||||
<div class="nutrition-grid">
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/energy-icon.svg" alt="能量" />
|
||||
<div class="nutrition-info">
|
||||
<span class="label">能量 / Energy</span>
|
||||
<span class="value">{{ food.energy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/protein-icon.svg" alt="蛋白质" />
|
||||
<div class="nutrition-info">
|
||||
<span class="label">蛋白质 / Protein</span>
|
||||
<span class="value">{{ food.protein }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/fat-icon.svg" alt="脂肪" />
|
||||
<div class="nutrition-info">
|
||||
<span class="label">脂肪 / Fat</span>
|
||||
<span class="value">{{ food.fat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/carbs-icon.svg" alt="碳水化合物" />
|
||||
<div class="nutrition-info">
|
||||
<span class="label">碳水化合物 / CHO</span>
|
||||
<span class="value">{{ food.carbohydrate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">维生素</span>
|
||||
</el-divider>
|
||||
<div class="detailed-nutrition">
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素A / Vitamin A</span>
|
||||
<span class="value">{{ food.vitamin_a }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素C / Vitamin C</span>
|
||||
<span class="value">{{ food.vitamin_c }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素E / Vitamin E</span>
|
||||
<span class="value">{{ food.vitamin_e }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">硫胺素 / Thiamin</span>
|
||||
<span class="value">{{ food.thiamin }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">核黄素 / Riboflavin</span>
|
||||
<span class="value">{{ food.riboflavin }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">烟酸 / Niacin</span>
|
||||
<span class="value">{{ food.niacin }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">矿物质</span>
|
||||
</el-divider>
|
||||
<div class="detailed-nutrition">
|
||||
<div class="nutrition-row">
|
||||
<span class="label">钙 / Calcium</span>
|
||||
<span class="value">{{ food.calcium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">铁 / Iron</span>
|
||||
<span class="value">{{ food.iron }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">锌 / Zinc</span>
|
||||
<span class="value">{{ food.zinc }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">磷 / Phosphorus</span>
|
||||
<span class="value">{{ food.phosphorus }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">钾 / Potassium</span>
|
||||
<span class="value">{{ food.potassium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">钠 / Sodium</span>
|
||||
<span class="value">{{ food.sodium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">镁 / Magnesium</span>
|
||||
<span class="value">{{ food.magnesium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">硒 / Selenium</span>
|
||||
<span class="value">{{ food.selenium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">铜 / Copper</span>
|
||||
<span class="value">{{ food.copper }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">锰 / Manganese</span>
|
||||
<span class="value">{{ food.manganese }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">碘 / Iodine</span>
|
||||
<span class="value">{{ food.iodine }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">脂肪酸</span>
|
||||
</el-divider>
|
||||
<div class="detailed-nutrition">
|
||||
<div class="nutrition-row">
|
||||
<span class="label">饱和脂肪酸 / SFA</span>
|
||||
<span class="value">{{ food.sfa }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">单不饱和脂肪酸 / MUFA</span>
|
||||
<span class="value">{{ food.mufa }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">多不饱和脂肪酸 / PUFA</span>
|
||||
<span class="value">{{ food.pufa }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">脂肪酸合计 / Total</span>
|
||||
<span class="value">{{ food.fatty_acids_total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">详细营养信息</span>
|
||||
</el-divider>
|
||||
|
||||
<div class="detailed-nutrition">
|
||||
<div class="nutrition-row">
|
||||
<span class="label">膳食纤维 / Dietary Fiber</span>
|
||||
<span class="value">{{ food.fiber }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">胆固醇 / Cholesterol</span>
|
||||
<span class="value">{{ food.cholesterol }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">钙 / Calcium</span>
|
||||
<span class="value">{{ food.calcium }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">铁 / Iron</span>
|
||||
<span class="value">{{ food.iron }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">锌 / Zinc</span>
|
||||
<span class="value">{{ food.zinc }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素A / Vitamin A</span>
|
||||
<span class="value">{{ food.vitamin_a }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素B1 / Vitamin B1</span>
|
||||
<span class="value">{{ food.vitamin_b1 }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素B2 / Vitamin B2</span>
|
||||
<span class="value">{{ food.vitamin_b2 }}</span>
|
||||
</div>
|
||||
<div class="nutrition-row">
|
||||
<span class="label">维生素C / Vitamin C</span>
|
||||
<span class="value">{{ food.vitamin_c }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider>
|
||||
<span class="divider-title">备注说明</span>
|
||||
</el-divider>
|
||||
|
||||
<div class="notes-section">
|
||||
<div class="notes-content">
|
||||
<div class="notes-item">"—":表示未检测</div>
|
||||
<div class="notes-item">"Tr":表示未检出</div>
|
||||
<div class="notes-item">"un":仅对维生素E和能量在计算时,表示不能得出结果</div>
|
||||
<div class="notes-item">"0":表示测定后修约的0值,计算后的0值,理论上估计的0值</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.food-detail-page {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 60px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 0px;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.food-detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
padding: 0 8px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.food-info {
|
||||
padding: 16px;
|
||||
margin: 0 -12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.food-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.food-title {
|
||||
font-size: clamp(24px, 6vw, 32px);
|
||||
margin: 0 0 8px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.food-title h1 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.food-title p.category {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
.food-category {
|
||||
color: #666;
|
||||
font-size: clamp(14px, 3.5vw, 16px);
|
||||
}
|
||||
|
||||
.nutrition-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.nutrition-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.nutrition-item img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.nutrition-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
.nutrition-info .label {
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
}
|
||||
.nutrition-info .value {
|
||||
text-align: right;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.detailed-nutrition .nutrition-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.detailed-nutrition .value {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.notes-section {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.notes-title {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.notes-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.notes-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
195
food_nutrition-main/frontend/src/views/FoodList.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElCard, ElRow, ElCol, ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const foods = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const fetchFoods = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/foods?category_id=${route.params.id}`)
|
||||
if (!response.ok) throw new Error('获取食物数据失败')
|
||||
const data = await response.json()
|
||||
foods.value = data.map(food => ({
|
||||
id: food.id,
|
||||
name: food.name,
|
||||
category_name: food.category_name,
|
||||
father_category_name: food.father_category_name,
|
||||
icon: `/src/assets/f${food.father_id}.svg`,
|
||||
energy: food.energy,
|
||||
protein: food.protein,
|
||||
fat: food.fat
|
||||
}))
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('获取食物数据错误:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goToFoodDetail = (foodId) => {
|
||||
router.push(`/food/${foodId}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFoods()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="food-list-page">
|
||||
<!--
|
||||
<div class="banner">
|
||||
<img src="../assets/banner.svg" alt="Banner" class="banner-image" />
|
||||
<h1 class="title">食物清单</h1>
|
||||
</div>
|
||||
-->
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" v-for="food in foods" :key="food.id">
|
||||
<el-card class="food-card" shadow="hover" @click="goToFoodDetail(food.id)">
|
||||
<div class="food-content">
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
<img :src="food.icon" class="food-icon" alt="食物图标" />
|
||||
<div class="unit-text">每100克</div>
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h3 class="food-name">{{ food.name }}</h3>
|
||||
<p class="category-name">{{ food.father_category_name }}、{{ food.category_name }}</p>
|
||||
<div class="nutrition-info">
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/energy-icon.svg" alt="能量" class="nutrition-icon" />
|
||||
<span>{{ food.energy }}</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/protein-icon.svg" alt="蛋白质" class="nutrition-icon" />
|
||||
<span>{{ food.protein }}</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/fat-icon.svg" alt="脂肪" class="nutrition-icon" />
|
||||
<span>{{ food.fat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.food-list-page {
|
||||
padding: 8px;
|
||||
max-width: 480px; /* 修改这行,设置为移动端的典型宽度 */
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 50px;
|
||||
margin-top:50px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
margin: -8px -8px 16px -8px;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
font-size: clamp(20px, 5vw, 32px);
|
||||
}
|
||||
|
||||
.food-card {
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.food-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.food-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.food-icon {
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
height: clamp(48px, 12vw, 64px);
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unit-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 4px 16px 0 0;
|
||||
flex-shrink: 0;
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
}
|
||||
|
||||
.text-content {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.food-name {
|
||||
margin: 0 0 4px;
|
||||
font-size: clamp(16px, 4vw, 18px);
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width:200px
|
||||
}
|
||||
|
||||
.category-name {
|
||||
color: #666;
|
||||
font-size: clamp(14px, 3.5vw, 16px);
|
||||
margin: 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nutrition-info {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nutrition-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background-color: #f5f7fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.nutrition-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
132
food_nutrition-main/frontend/src/views/Home.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElCard, ElRow, ElCol, ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const categories = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const fetchCategories = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch('/api/categories?cate_id=0')
|
||||
if (!response.ok) throw new Error('获取分类数据失败')
|
||||
const data = await response.json()
|
||||
categories.value = data.map(category => ({
|
||||
id: category.id,
|
||||
name: category.title,
|
||||
cate_id: category.cate_id,
|
||||
father_id: category.father_id,
|
||||
icon: `/src/assets/f${category.father_id}.svg`
|
||||
}))
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('获取分类数据错误:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<!--<div class="banner">
|
||||
<img src="../assets/banner.svg" alt="Banner" class="banner-image" />
|
||||
<h1 class="title">食物营养成分查询手册</h1>
|
||||
</div>-->
|
||||
<el-row :gutter="8">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" v-for="category in categories" :key="category.id">
|
||||
<el-card class="category-card" shadow="hover" @click="router.push(`/category/${category.father_id}`)">
|
||||
<div class="category-content">
|
||||
<img :src="category.icon" class="category-icon" alt="分类图标" />
|
||||
<span class="category-name">{{ category.name }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
padding: 8px;
|
||||
max-width: 480px;
|
||||
overflow-x: hidden;
|
||||
margin-bottom:50px;
|
||||
margin-top:50px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
margin: -8px -8px 16px -8px;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 24px;
|
||||
font-size: clamp(20px, 5vw, 32px);
|
||||
}
|
||||
|
||||
.category-card {
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
height: clamp(48px, 12vw, 64px);
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: clamp(12px, 3vw, 14px);
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
223
food_nutrition-main/frontend/src/views/Search.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ElInput, ElCard, ElRow, ElCol, ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const searchQuery = ref('')
|
||||
const searchResults = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchQuery.value.trim()) return
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/search?keyword=${encodeURIComponent(searchQuery.value)}`)
|
||||
if (!response.ok) throw new Error('搜索失败')
|
||||
const data = await response.json()
|
||||
searchResults.value = data.map(food => ({
|
||||
id: food.id,
|
||||
name: food.name,
|
||||
category_name: food.category_name,
|
||||
father_category_name: food.father_category_name,
|
||||
icon: `/src/assets/f${food.father_id}.svg`,
|
||||
energy: food.energy,
|
||||
protein: food.protein,
|
||||
fat: food.fat
|
||||
}))
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message)
|
||||
console.error('搜索错误:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goToFoodDetail = (foodId) => {
|
||||
router.push(`/food/${foodId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-page">
|
||||
<!--
|
||||
<div class="banner">
|
||||
<img src="../assets/banner.svg" alt="Banner" class="banner-image" />
|
||||
<h1 class="title">搜索食物</h1>
|
||||
</div>
|
||||
-->
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="食物名称关键词"
|
||||
clearable
|
||||
size="large"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch" :loading="loading">搜索</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" v-if="searchResults.length > 0">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" v-for="food in searchResults" :key="food.id">
|
||||
<el-card class="food-card" shadow="hover" @click="goToFoodDetail(food.id)">
|
||||
<div class="food-content">
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
<img :src="food.icon" class="food-icon" alt="食物图标" />
|
||||
<div class="unit-text">每100克</div>
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h3 class="food-name">{{ food.name }}</h3>
|
||||
<p class="category-name">{{ food.father_category_name }}、{{ food.category_name }}</p>
|
||||
<div class="nutrition-info">
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/energy-icon.svg" alt="能量" class="nutrition-icon" />
|
||||
<span>{{ food.energy }}</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/protein-icon.svg" alt="蛋白质" class="nutrition-icon" />
|
||||
<span>{{ food.protein }}</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<img src="../assets/fat-icon.svg" alt="脂肪" class="nutrition-icon" />
|
||||
<span>{{ food.fat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-page {
|
||||
padding: 8px;
|
||||
max-width: 480px;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
margin: -8px -8px 16px -8px;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
font-size: clamp(20px, 5vw, 32px);
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin: 24px auto;
|
||||
max-width: 800px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.food-card {
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.food-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.food-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.food-icon {
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
height: clamp(48px, 12vw, 64px);
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unit-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 4px 16px 0 0;
|
||||
flex-shrink: 0;
|
||||
width: clamp(48px, 12vw, 64px);
|
||||
}
|
||||
|
||||
.text-content {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
max-width: 198px;
|
||||
}
|
||||
|
||||
.food-name {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nutrition-info {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.nutrition-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.nutrition-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-input) {
|
||||
--el-input-height: 56px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 24px 0 0 24px;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
border-radius: 0 24px 24px 0;
|
||||
padding: 0 32px;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
19
food_nutrition-main/frontend/vite.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: [
|
||||
'frpc.binghuai.xyz'
|
||||
],
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
6
food_nutrition-main/package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "food_nutrition-main",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||