本文将一个Vue框架打包的资源挂载到Fastapi上,解决了assets文件404导致Vue页面白屏问题。
前言
学校一个大作业要求开发一个系统,拥有web界面以及对用户上传文件进行数据处理。
web前端打算选择使用Vue框架,后端框架使用简单易上手的Fastapi。
现在把搭建的流程走一遍。
搭建前端框架
在目录里面使用Vue
官方脚手架创建项目。(请确保电脑上已安装Node.js,如未安装则无法使用npm
)
1 2 3 4 5 6 7 8 9 10 11 12 npm create vue@latest Vue.js - The Progressive JavaScript Framework √ 请输入项目名称: ... frontend √ 是否使用 TypeScript 语法? ... 否 / 是 √ 是否启用 JSX 支持? ... 否 / 是 √ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是 √ 是否引入 Pinia 用于状态管理? ... 否 / 是 √ 是否引入 Vitest 用于单元测试? ... 否 / 是 √ 是否要引入一款端到端(End to End)测试工具? » 不需要 √ 是否引入 ESLint 用于代码质量检测? ... 否 / 是 √ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是
这里项目命名为frontend
,除了使用Vue Router
和Pinia
之外其他都选否。
(当然也可酌情选择,其他选项可让代码更规范,不过由于独立开发以效率优先就没选)
接下来按照说明执行一下这三条命令。
如卡在npm install
这步可在网上了解一下如何设置npm镜像源(注意内容时效性,例如淘宝源修改域名,某些源由公开转为私有)
1 2 3 cd frontendnpm install npm run dev
输入完npm run dev
后可以打开http://localhost:5173/
,此时即可看到效果。
搭建后端框架
在目录下新建文件夹backend
用于存放后端文件,进入backend
文件夹。
为了防止Python环境污染,通过venv
创建一个虚拟环境并进入。
1 2 python -m venv .venv .\.venv\Scripts\activate
注意venv环境无法指定Python版本,venv环境会使用创建它的python版本。
进入虚拟环境后可输入python -V
查看版本。
我的Python版本为3.10.12。
安装fastapi
框架。
稍等片刻后安装完成,在backend
目录下新建main.py
文件作为fastapi
的入口。
1 2 3 4 5 6 7 8 from fastapi import FastAPIapp = FastAPI() @app.get("/" ) def read_root (): return {"Hello" : "World" }
随后在终端输入uvicorn main:app --reload --port 8001
启动应用。
在浏览器打开http://127.0.0.1:8001
即可看到效果。
http://127.0.0.1:8001/docs
可查看fastapi
默认生成的文档
此时项目目录如下图:
前端获取后端数据
前端需要加个包axios
用来向后端发送数据。
后端需要设置CORS
来允许跨域访问。
前端部分
回到.\frontend
,输入npm install axios
来安装包。
安装后修改.\frontend\src\main.js
来导入这个包。
1 2 3 4 5 6 7 8 9 10 11 12 ... import App from './App.vue' import router from './router' import axios from 'axios' axios.defaults .baseURL = 'http://localhost:8001/api/' axios.defaults .withCredentials = true const app = createApp (App )...
修改.\frontend\src\app.vue
来向后端发送请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script setup> import { RouterLink , RouterView } from 'vue-router' ;import HelloWorld from './components/HelloWorld.vue' ;import { ref } from 'vue' ;import axios from 'axios' ;const message = ref ('Empty message' );const handleClick = ( ) => { axios.get ('/button' ).then ((res ) => { message.value = res.data ; }); } </script> <template > <header > <img alt ="Vue logo" class ="logo" src ="@/assets/logo.svg" width ="125" height ="125" /> <div class ="wrapper" > <HelloWorld :msg ="message" /> <button @click ="handleClick" > click me!</button > <nav > <RouterLink to ="/" > Home</RouterLink > <RouterLink to ="/about" > About</RouterLink > </nav > </div > </header > <RouterView /> </template > ...
此时点击按钮并不会发生什么反应,我们在console
里可以发现出现了什么问题。
上述错误信息提示跨域访问不成功,这需要在服务器后端来配置。
后端部分
修改main.py
添加CORS即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewareapp = FastAPI() app.add_middleware( CORSMiddleware, allow_origins= "http://localhost:5174/" , allow_credentials=True , allow_methods=["*" ], allow_headers=["*" ], ) @app.get("/" ) def read_root (): return {"Hello" : "World" } @app.get("/api/button" ) def read_button (): return "You clicked the button!"
此时点击按钮即可获取到消息。
打包Vue文件
对于目前全栈来说每次都要分别启动前端服务和后端服务未免有些繁琐,因此我们选择将Vue文件打包成html
和若干个js
与css
文件,通过静态文件挂载到fastapi
上,这样只需要启动后端就可以了。
修改build路径
来到frontend\vite.config.js
里修改一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig ({ plugins : [ vue (), ], resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )) } }, build : { outDir : '../backend/static' , } })
接着npm run build
即可打包。
挂载静态文件
再回到后端main.js
挂载一下。
将对应内容修改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewarefrom fastapi.responses import FileResponsefrom fastapi.staticfiles import StaticFilesapp = FastAPI() app.add_middleware( CORSMiddleware, allow_origins= "http://127.0.0.1:8001/" , allow_credentials=True , allow_methods=["*" ], allow_headers=["*" ], ) app.mount("/static" , StaticFiles(directory="static" ), name="static" ) app.mount('/assets' , StaticFiles(directory='static/assets' ), name='assets' ) @app.get("/" ) def root (): return FileResponse('static/index.html' ) @app.get("/api/button" ) def read_button (): return "You clicked the button!"
许多教程并没有mount assets文件夹,导致页面白屏
最后这一句话导致找了几天的bug,不过总算把这个框架搭建完了。
后续开发也许还要注意一下资源的路径问题。