ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于Node.js与Vue的个人健康菜谱生成系统全栈源码解析

基于Node.js与Vue的个人健康菜谱生成系统全栈源码解析 1. 项目概述个人健康菜谱生成系统到底在解决什么问题做这个个人健康菜谱生成系统其实一开始只是想解决我自己每天吃饭的纠结。上班族做饭最烦的不是不会做而是打开冰箱不知道今天能做什么查菜谱网站又一堆反人类的广告和“适量盐”描述。所以我干脆用 Node.js 和 Vue 写了这个全栈项目前端负责点菜、收藏、看详情后端负责按用户热量目标、口味偏好和忌口条件去生成每日菜谱整套源码放在一个仓库里clone 下来就能跑。项目本身不复杂但麻雀虽小五脏俱全涉及 Vue 组件通信、动态路由、Node 接口设计、数据库建模、推荐逻辑和常见部署问题。想练全栈的初学者、需要做课程设计的同学甚至只是想把家里食材利用起来的朋友都能从这套源码里找到能直接用的东西。这个项目我一直放在本地方便自己改后来整理干净之后把源码拎了出来。标题里写“项目源码”说明重心不只是讲概念而是给你一套能跑通的结构。你拿到手之后前端是标准的 Vue 3 Vite 工程后端是 Express SQLite没有复杂的中间件和云服务依赖Windows、macOS、Ubuntu 都试过能跑。我会把从环境配置、目录结构、核心接口到前端交互的完整链路拆开讲重点讲那些文档里不会写、但实际开发一定会踩的坑。必须强调一点系统里的热量和营养建议只是根据通用食物成分表估算的用来做日常参考没问题但不要当作医疗建议。尤其是有慢性病或者孕期饮食需求的朋友拿这套系统当工具可以重要决策请咨询专业人士。1.1 这个项目适合谁能学到什么我先说句实话这个项目没有上微服务也不搞高并发。它就是一个典型的中小型全栈项目适合一两个人维护。选这个规模是有意的太复杂了劝退太简单了没干货。如果你正在学 Vue背了路由、插槽、组件通信的面试题但没实际项目经验这套源码能让你看到这些概念是怎么串起来的如果你刚接触 Node 后端能学到如何用 Express 把接口拆得清晰怎么连 SQLite怎么做登录鉴权怎么处理菜谱封面图上传如果你纯粹想解决每天吃什么把示例食材换掉导入自己常买的菜这套系统一样能服务你。从学习角度看这个项目最大的价值在于“完整”。市面上的教程代码大多是片段级一个登录页讲三小时但没有人告诉你登录之后怎么跳转、数据存在哪里、刷新页面后 token 怎么恢复、前端调接口跨域怎么处理。这些恰恰是源码项目最值钱的部分。我在整理代码时特意保留了合理的 TODO 注释和单元测试目录就是为了让后来者能顺着思路继续扩展。1.2 技术选型为什么是 Node.js Vue 的全栈组合选择 Node.js 和 Vue不是因为它俩最先进而是因为它们最适合这类项目的开发状态。后端用 Node.js意味着前后端都是 JavaScript/TypeScript一个开发者不用频繁切换语言心智。尤其是做菜谱推荐这种逻辑数据结构是典型的对象数组操作JS 处理起来比 Java 短得多。前端用 Vue是因为 Vue 的响应式系统和单文件组件设计对中小型项目非常友好模板写法接近原生 HTML新手拿起来不会像看某些框架那样一头雾水。有人会问为什么不直接 Spring Boot Vue不是说不行如果你要交一个“基于 Spring Boot 的商品管理系统”那一套也完全能跑。但在我这个场景里Node.js 的启动速度、轻量级内存占用和 npm 生态让我改代码更爽。Express 路由写起来几乎没有仪式感SQLite 则是零配置的文件数据库整个后端部署起来就是node server.js一条命令。等你把这套逻辑吃透了再用 Spring Boot 重写也只是换个壳核心推荐和建模思路完全一样。2. 功能拆解与核心模块设计菜谱不是瞎生成的2.1 用户场景与功能清单这个系统的核心使用场景是这样的晚上七点到家冰箱里有鸡胸肉、西兰花、豆腐、鸡蛋你今天晚餐目标热量是 500 大卡不吃香菜但想吃点辣。系统会从菜谱库里筛选出所有不包含香菜、主要食材能匹配上的菜再按热量范围过滤最后结合你过去一周点过什么挑出三道热量合适、口味不重样的菜推给你每道菜都标注食材清单、大致做法和营养素估算。整个功能清单我拆成了两大块。用户侧包括注册登录、个人资料维护、每日热量目标设置、口味偏好与忌口管理、菜谱浏览、菜谱收藏、菜谱详情查看、菜谱生成历史。管理侧则简单一点包含食材库管理、菜谱维护、封面图上传和生成日志查看。之所以把管理端也塞进去是为了让菜谱数据不是写死在代码里而是可以从界面维护这样对做课程设计和真实使用都更方便。功能设计的取舍也值得说。我没有做社区评论、点赞、分享这些社交功能不是不会做而是它们会显著拉长开发周期。个人健康菜谱系统的重点应该放在“生成”和“筛选”上如果生成质量不行评论做出来也是空壳。所以我建议你拿到源码后先把核心链路跑通再考虑加不加社交模块。2.2 数据表设计与菜谱 JSON 结构数据库我用的是 SQLite文件就放在后端目录的data/health.db里。表结构设计的核心是食材和菜谱的多对多关系。一张菜谱由多个食材组成一个食材也可以出现在多道菜里所以不能简单在菜谱表里塞一个ingredients字段而是要拆三张表recipes、ingredients、recipe_ingredients。下面是简化后的建表 SQL你可以直接拿去用CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, target_calories INTEGER DEFAULT 1800, taste_tags TEXT DEFAULT , excluded_ingredients TEXT DEFAULT , created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE ingredients ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category TEXT, calory_per_100g REAL DEFAULT 0, unit TEXT DEFAULT g ); CREATE TABLE recipes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, steps TEXT, cover_url TEXT, meal_type TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE recipe_ingredients ( id INTEGER PRIMARY KEY AUTOINCREMENT, recipe_id INTEGER NOT NULL, ingredient_id INTEGER NOT NULL, amount_g REAL DEFAULT 100, FOREIGN KEY(recipe_id) REFERENCES recipes(id), FOREIGN KEY(ingredient_id) REFERENCES ingredients(id) ); CREATE TABLE favorites ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, recipe_id INTEGER NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP );拆表的直接好处是筛选和统计方便。比如要算出某道菜的热量只需要把这道菜关联的食材用量乘以对应食材的每百克热量再累加一个JOIN就能搞定。如果不拆表把食材名和用量塞成一个 JSON 字符串那将来做“按食材查询菜谱”和“热量估算”会痛苦到怀疑人生。菜谱里的步骤我本来想用富文本编辑后来为了降低复杂度直接存成Text。前端详情页用\n分隔展示效果完全够用。菜谱的示例数据在server/src/data/seed.js里包含了二十多道家常菜和一百个常见食材。你打开这个文件就能看到一道菜的完整 JSON 结构{ title: 香煎鸡胸肉配西兰花, description: 高蛋白低脂肪适合减脂期晚餐, mealType: dinner, steps: 1. 鸡胸肉用盐和黑胡椒腌制15分钟\n2. 平底锅少油中火每面煎4分钟\n3. 西兰花焯水后一起装盘, ingredients: [ { name: 鸡胸肉, amount: 150, unit: g }, { name: 西兰花, amount: 200, unit: g }, { name: 橄榄油, amount: 5, unit: g } ] }2.3 推荐逻辑的关键标签匹配、排除条件与热量估算菜谱推荐是这套系统的灵魂我做得很克制没有用协同过滤或深度学习。原因很简单个人系统里用户行为数据太少协同过滤在冷启动阶段基本失效而且算出来的结果用户看不懂“为什么推荐这道菜”。我选的是规则加随机权重的方案推理过程透明代码量少也容易调试。整个推荐流程分四步。第一步收集用户当前选择的食材 ID 列表没有选择食材时就默认全库食材可用。第二步根据用户资料里的excluded_ingredients排除包含忌口食材的菜谱比如用户明确写了“不吃香菜”那所有关联了香菜的菜谱直接过滤掉。第三步计算每道菜的热量估算值筛掉超出目标热量范围太多的菜。第四步对候选菜谱按口味偏好标签做加权随机再确保同一道菜一周内不重复推荐。热量估算的代码我是单独抽成函数的因为这个函数既会被推荐接口用到也会在菜谱详情页展示营养素时用到function estimateCalories(recipeId) { const rows db.prepare( SELECT i.calory_per_100g, ri.amount_g FROM recipe_ingredients ri JOIN ingredients i ON i.id ri.ingredient_id WHERE ri.recipe_id ? ).all(recipeId); const total rows.reduce((sum, row) { return sum (row.calory_per_100g * row.amount_g / 100); }, 0); return Math.round(total); }随机加权的实现不复杂重点在于不要每次都随机得完全一样。我给每道菜维护了一个“最近被推荐次数”字段推荐次数越少的菜权重越高这样用户不会连续三天看到同样的菜。另外口味标签我用的是逗号分隔的简单字符串比如“辣”“清淡”“高蛋白”匹配时只要做数组交集判断即可。这套规则一开始看着简陋但实际用下来生成结果很稳定比那些号称智能但结果随机的方案靠谱得多。3. 后端实现Node.js 接口、数据库与菜谱推荐逻辑3.1 源码目录结构先认识一下你的项目拿到源码之后别急着npm install先把目录结构看清楚。我采用的是前后端分离但放在同一个仓库里的结构避免了维护两个 Git 仓库的麻烦health-recipe-system/ ├── server/ │ ├── src/ │ │ ├── routes/ # 接口路由 │ │ │ ├── auth.js │ │ │ ├── recipes.js │ │ │ ├── ingredients.js │ │ │ └── favorites.js │ │ ├── middlewares/ │ │ │ └── authMiddleware.js │ │ ├── db/ │ │ │ ├── index.js │ │ │ └── seed.js │ │ ├── utils/ │ │ │ └── calorie.js │ │ └── app.js │ ├── data/ # SQLite 数据库文件目录 │ ├── uploads/ # 菜谱封面图上传目录 │ ├── .env # 环境变量 │ └── package.json ├── web/ │ ├── src/ │ │ ├── views/ # 页面组件 │ │ ├── components/ # 业务组件 │ │ ├── stores/ # Pinia 状态 │ │ ├── router/ # 路由配置 │ │ ├── api/ # axios 封装 │ │ └── App.vue │ └── package.json └── README.mdserver/src/app.js是后端入口web/src/main.js是前端入口。把数据和代码分离的好处是备份数据库只需要拷一个data目录。uploads目录记得要在.gitignore里忽略掉不然传几张菜谱图仓库就变得很臃肿。3.2 Node.js 环境准备与 npm 配置这一步看着简单但我见过太多人卡在这里。先说 Node.js 版本项目建议用 LTS 版本目前推荐 18 以上20 也行不要用那种还在奇数版本号的尝鲜版。Vite 在旧版 Node 上会直接报错而且报错信息很迷惑你排查半天发现是 Node 版本太老。安装完成后务必在命令行里执行两个命令验证node -v npm -v如果提示找不到命令不是没装好就是安装时没有勾选“Add to PATH”。Windows 下最稳妥的方式是重新运行安装包选择修改安装并勾选 PATH 选项。macOS 用户我建议用 Homebrew 安装Ubuntu 用户可以apt install nodejs npm但装完记得检查版本有些发行版自带版本偏旧。npm 装依赖慢是国内老生常谈的问题我习惯先配镜像源npm config set registry https://registry.npmmirror.com这行命令改的是 npm 全局配置之后所有项目的依赖下载都会走镜像。如果你在公司网络环境可能还需要额外设置代理。我另外建议不要全局安装一堆工具用npx跑脚手架命令更干净比如后面创建 Vue 项目直接npm create vuelatest就不会污染全局包。3.3 后端核心代码服务入口和健康检查接口后端入口文件app.js本身不长核心就是初始化 Express、挂载中间件、注册路由const express require(express); const cors require(cors); const path require(path); const { initDB } require(./db); const authRoutes require(./routes/auth); const recipeRoutes require(./routes/recipes); const ingredientRoutes require(./routes/ingredients); const favoriteRoutes require(./routes/favorites); const app express(); app.use(cors()); app.use(express.json()); app.use(/uploads, express.static(path.join(__dirname, ../uploads))); app.use(/api/auth, authRoutes); app.use(/api/recipes, recipeRoutes); app.use(/api/ingredients, ingredientRoutes); app.use(/api/favorites, favoriteRoutes); app.get(/api/health, (req, res) { res.json({ code: 0, data: { uptime: process.uptime(), time: new Date() } }); }); const PORT process.env.PORT || 3000; initDB().then(() { app.listen(PORT, () { console.log([server] running at http://localhost:${PORT}); }); });注意express.json()这个中间件不能少否则后端收不到前端传过来的 JSON 请求体。cors()在本地开发时必须挂否则前端在localhost:5173调localhost:3000接口会被浏览器拦截。接口返回格式我统一用{ code, data, message }前端 axios 拦截器只要统一解析一次就行。这个习惯看起来不起眼但能让前端错误处理代码减少一大半。项目里的数据库操作我用了better-sqlite3这个库它是同步 API写起来比sqlite3的异步回调舒服很多。初始化数据库时如果表不存在就自动建表如果食材表是空的就执行种子数据导入这样任何机器上 clone 下来运行都是即跑即用。3.4 菜谱生成接口的实现细节菜谱生成是核心接口路径是POST /api/recipes/generate。请求体会接收用户这次选择的食材 ID 数组、餐次类型、目标热量等参数。我简化后的核心逻辑如下router.post(/generate, authMiddleware, (req, res) { const { ingredientIds [], mealType, targetCalories } req.body; const user req.user; // 查询所有菜谱并关联出食材列表和热量 const recipes db.prepare( SELECT r.*, GROUP_CONCAT(i.name) as ingredient_names FROM recipes r LEFT JOIN recipe_ingredients ri ON ri.recipe_id r.id LEFT JOIN ingredients i ON i.id ri.ingredient_id GROUP BY r.id ).all(); const excluded parseList(user.excluded_ingredients); // 第一步排除忌口 let candidates recipes.filter(recipe { const names recipe.ingredient_names ? recipe.ingredient_names.split(,) : []; return !excluded.some(item names.includes(item)); }); // 第二步如果指定了食材要求菜谱必须包含其中至少一个 if (ingredientIds.length 0) { candidates candidates.filter(recipe { return recipe.ingredient_names ingredientIds.some(id recipe.ingredient_names.includes(id)); }); } // 第三步热量过滤 const range targetCalories || user.target_calories || 1800; candidates candidates.filter(recipe { const cal estimateCalories(recipe.id); return cal range * 0.6 cal range * 1.2; }); // 第四步加权随机选三道一周内推荐过的权重减半 const weighted candidates.map(recipe { const recentCount getRecentRecommendCount(recipe.id, user.id); return { recipe, weight: Math.max(0.2, 1 - recentCount * 0.3) }; }); // 简单加权随机 const selected weighted .sort(() Math.random() - 0.5) .slice(0, Math.min(3, weighted.length)) .map(item item.recipe); res.json({ code: 0, data: selected }); });这段代码我特意写得偏教学化实际源码里会再封装几个函数。你可能会问为什么先查出所有菜谱再在内存里过滤因为示例数据量只有几十条这样做最简单且容易理解。如果以后菜谱库上万条就改成在 SQL 里做排除和筛选按食材表JOIN后加WHERE条件。个人项目优先保证可读性性能问题等真遇到了再优化这是我一直坚持的原则。还有个细节必须返回给前端热量估算值否则前端卡片上没数字可显示。我在返回前给每道菜补充了calorie、protein、fat字段计算方式基于recipe_ingredients的用量和食材营养素表。这些营养字段即便不准确也比没有强因为它能给用户一种“被认真对待”的感觉。接口里还写了生成记录记录谁在什么时间请求了哪些菜谱方便后续做“最近推荐不重复”的判断。4. 前端实现Vue 页面、路由与交互细节4.1 用 Vite 创建 Vue 3 项目并安装依赖前端我选择 Vue 3 Vite 的组合。Vite 开发服务器启动非常快修改代码后热更新几乎是秒级比老牌的 vue-cli 体验好太多。创建项目的命令很简单但要注意npm create vuelatest运行后会交互式问你要不要 TypeScript、Router、Pinia 等我建议全部选是尤其是 Router 和 Pinia后面会用到。如果不想交互可以加--default参数。创建完成后进入前端目录安装基础依赖cd web npm install npm install axios vue-router4 pinia npm run dev这里有个容易踩的坑Vue 3 对应的是vue-router4不是老项目的vue-router3。如果安装时没写版本号npm 默认给你装最新的4一般没问题但如果你之前项目里残留老版本会出现路由组件渲染不出来的怪问题。我遇到过一次最后是清掉node_modules和package-lock.json重新安装才解决。装依赖的时候如果控制台刷出大量npm WARN先别慌只要npm run dev能跑起来就说明依赖关系没问题。4.2 页面路由与整体布局这个项目的页面不多我按业务划分了五个主要视图首页、菜谱生成页、菜谱详情页、收藏页、登录注册页。路由配置放在src/router/index.js核心代码如下import { createRouter, createWebHistory } from vue-router; const router createRouter({ history: createWebHistory(), routes: [ { path: /, name: home, component: () import(/views/HomePage.vue) }, { path: /generate, name: generate, component: () import(/views/GeneratePage.vue) }, { path: /recipes/:id, name: recipe-detail, component: () import(/views/RecipeDetailPage.vue) }, { path: /favorites, name: favorites, component: () import(/views/FavoritesPage.vue) }, { path: /login, name: login, component: () import(/views/LoginPage.vue) }, ] }); router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } }); export default router;我用了createWebHistory而不是createWebHashHistory地址栏看起来干净。但代价是部署到 Nginx 时要做 try_files 重写这个后面讲部署会提到。路由懒加载也是从这版才加上的之前用静态 import 把整个页面都打到一个包里首屏加载很慢。改成() import()之后每个页面单独分包体验提升明显。beforeEach路由守卫里做了简单的登录拦截收藏页和生成历史页需要登录才能访问这个对真实项目是刚需。整体布局我放在App.vue里顶部一个导航栏下面放router-view /。导航栏根据登录状态显示“登录/注册”还是“退出登录”用 Pinia 里的用户状态控制。这种全局布局方式简单改一个文件就能控制所有页面的公共壳子。4.3 菜谱生成页表单校验、请求状态和卡片展示菜谱生成页是这个项目里交互最复杂的一页。左半部分是筛选表单右半部分是生成结果卡片列表。页面模板骨架大致是这样template div classgenerate-page form submit.preventhandleGenerate h3告诉我你的条件/h3 label餐次类型/label select v-modelform.mealType option valuebreakfast早餐/option option valuelunch午餐/option option valuedinner晚餐/option /select label目标热量大卡/label input typenumber v-model.numberform.targetCalories min300 max3000 / label可选食材/label IngredientSelector v-modelform.ingredientIds / button typesubmit :disabledloading {{ loading ? 生成中... : 生成菜谱 }} /button /form div classresult-area RecipeCard v-forrecipe in recipes :keyrecipe.id :reciperecipe collecthandleCollect / /div /div /template这里有两个可以展开说的点。第一个是v-model.number修饰符它能把输入框里的字符串转成数字避免你在判断targetCalories 0时踩“空字符串参与比较”的坑。第二个是IngredientSelector组件用v-model双向绑定选中食材 ID 数组这涉及到子组件里如何触发更新。我在这个组件里封装了一个多选网格点击食材卡片时切换选中状态再通过emit(update:modelValue, newValue)把值传回父组件。把这套机制搞清楚Vue 的组件通信基本就入门了。请求状态处理也很重要。点击生成后按钮要置灰并显示“生成中”防止用户重复提交。请求失败要区分超时和接口报错我在 axios 拦截器里统一处理了错误提示。拿到结果后不要直接覆盖整个列表先用 loading 遮罩挡住旧内容等新结果回来再替换避免界面闪动。这个小细节对体验影响非常大我最初没做结果每次生成时页面疯狂跳动观感很差。4.4 收藏功能与组件复用技巧收藏功能我用了两个端配合后端favorites表记录用户和菜谱的关联关系前端 Pinia store 管理当前页面的收藏状态。点击收藏按钮时先判断是否登录没登录就跳转登录页登录了就调接口。收藏成功后把按钮状态切换成实心再次点击则取消收藏。这套交互在整个系统里会出现在菜谱卡片、详情页和收藏页三处所以我把收藏按钮抽成了独立组件CollectButton避免在三个页面里复制粘贴三份一样的逻辑。CollectButton组件只接收recipeId和initialCollected两个 props内部维护自己的collected状态。但真实场景里详情页收藏后返回列表页希望收藏状态同步更新这就要用到 Pinia store 来共享状态。我把收藏的菜谱 ID 集合放在 store 里任何组件提交收藏操作都会修改这个集合其他组件自然响应更新。这个方法比事件总线干净也比 localStorage 手动同步靠谱。关于插槽我在RecipeCard组件里用了一个技巧。菜谱卡片在不同页面展示的重点不一样生成页要显示热量和收藏按钮首页要显示推荐理由收藏页要显示收藏时间。如果这些差异全用 props 塞进去组件会变得很臃肿。我让RecipeCard只负责渲染标题、封面和描述然后把可替换区域开放成插槽div classrecipe-card img :srcrecipe.coverUrl / div classcard-body h4{{ recipe.title }}/h4 p{{ recipe.description }}/p slot namefooter :reciperecipe span默认底部内容/span /slot /div /div父组件使用的时候向具名插槽footer里塞不同的按钮和时间信息不用改RecipeCard本身的代码。很多初学者会觉得插槽是面试题里的抽象概念看到这里应该能明白它就是给组件留的“自定义区域占位符”让同一张卡片在不同上下文里呈现出不同细节。这套源码里插槽用得不多但RecipeCard这一个案例已经足够看懂。5. 部署运行与高频问题排查让源码在你的电脑上跑起来5.1 本地启动和打包部署流程本地跑起来只需要两个终端窗口。第一个窗口启动后端cd server npm install npm run dev第二个窗口启动前端cd web npm install npm run dev前端默认跑在5173端口后端跑在3000。我在 Vite 配置文件里加了开发代理把所有/api请求转发到http://localhost:3000这样前端页面里请求地址不用写完整的后端地址也绕开了开发环境跨域问题。配置片段如下// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } });如果你想把项目部署到服务器我比较推荐的做法是前端先npm run build打包出dist目录然后让 Express 托管静态文件。具体说在后端app.js里加几行代码当路由匹配不到/api接口时直接读取前端dist下的文件返回。这样整个应用只需要启动一个 Node 进程不太需要单独配 Nginx。对于个人项目演示场景这是最省事的方式。如果你更喜欢标准的 Nginx 部署那就让前端静态文件交给 Nginx后端接口仍然由 Node 承担。需要注意两个点一是 Nginx 里要配置将/api转发到后端端口二是前端使用的createWebHistory模式要求所有未知路径都重写到index.html否则刷新详情页会 404。我也见过有人把这个 Vue 项目打包后直接丢进 Spring Boot 的static目录思路一样只要处理好接口地址和跨域就行。5.2 高频报错速查表这部分内容是我自己踩坑和帮朋友调项目时总结出来的包含几个高频问题按现象、原因和解决方式整理成了表格错误现象原因解决方式npm : 无法加载文件 ... npm.ps1因为在此系统上禁止运行脚本PowerShell 默认执行策略限制脚本运行以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned不想改系统策略就直接用 CMD 运行 npmnode: not found或node 不是内部或外部命令Node.js 未安装成功或没有加入 PATH重新安装 Node.js 并勾选 Add to PATH安装完成后重启终端前端启动后页面能开但接口报 404开发代理没生效或后端没启动确认后端日志有输出检查vite.config.js的 proxy 配置请求路径必须带/api前缀请求提示 CORS 错误后端没开跨域或代理配置没覆盖当前请求后端挂载cors()中间件若用了代理请求地址不要写http://localhost:3000直连SQLite 报 database is locked多个进程同时写数据库文件开发时不要同时开两个后端进程生产环境可切换 PostgreSQL/MySQLCannot find module better-sqlite3原生模块编译失败或没正确安装先删除node_modules再执行npm install还是不行就检查 Node 版本是否过新退回 LTS菜谱封面图片上传后访问 404图片没有放在uploads目录或路径缺少/uploads静态服务检查app.js里express.static配置上传目录必须存在并有写入权限中文乱码数据库文件编码或返回头缺少 charsetSQLite 本身是 UTF-8乱码一般发生在 Windows 老终端建议用 VS Code 终端运行这些报错里最显眼的就是 npm.ps1 禁止运行脚本。说实话我第一次遇到也懵好不容易装好 Node运行npm -v却报错。后来查清楚这是 Windows PowerShell 为了保护系统默认不让执行.ps1脚本。我自己的处理方式是只在当前用户范围放宽执行策略不去改系统级策略Set-ExecutionPolicy -Scope CurrentUser RemoteSigned执行后重新打开终端就好。如果你在公司电脑上受组策略限制改不了就老老实实用 CMD 或 Git Bash一样能跑 npm这也是完全合规的方案。5.3 关于这套源码我的后续规划和个人建议源码我还会继续迭代但方向不是加更多炫酷功能而是把推荐质量做得更细。比如现在热量估算是基于静态食材表以后我想接入更完整的营养成分数据库把盐、油、糖的用量也纳入计算。另一个方向是记录用户对生成结果的反馈点了“喜欢”还是“换一道”这些反馈数据积累起来后规则系统能做得更智能。对于想拿这套源码做课程设计或者毕业设计的同学我强烈建议你在“生成历史”和“用户反馈”上多下功夫这两块最容易做出差异化和工作量证明。最后分享一个我实际改代码时的小技巧先把所有菜谱数据导出来把你自己日常会做的十道菜加进去然后生成一次看看推荐结果。这一步能让你迅速理解推荐规则对结果的真实影响也会发现一些数据问题比如某道菜热量算出来不合理。数据是这类系统的地基算法再花哨食材用量录入不准也没用。你把它当成一个“帮自己做饭的助手”来打磨就能真正用好这套代码。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表