环境及项目初始化

环境介绍

开发环境版本:

Python3.10.11、Node.js16.9.0、Vue3、MySQL5.7.31

官方文档

Node.js:https://nodejs.org/en

Vue:https://cn.vuejs.org/

Vite:https://cn.vitejs.dev/guide/

Element-Plus:https://element-plus.org/zh-CN/

FastAPI:https://fastapi.tiangolo.com/zh/

SQLAlchemy:https://docs.sqlalchemy.org/en/20/dialects/mysql.html#module-sqlalchemy.dialects.mysql.mysqldb

图片素材库:https://iconscout.com/

前端 Vue

安装 Node.js

进入官网 https://nodejs.org/download/release/v16.19.0/安装好 node.js

安装 vue-cli

然后使用npm install -g @vue/cli命令安装 vue-cli

创建 vue 项目

使用 vite 进行构建

通过npm create vite@latest studentfontend -- --template vue构建前端项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  ~ cd Documents/PythonCode
➜ PythonCode mkdir studentProject
➜ PythonCode cd studentProject
➜ studentProject npm create vite@latest studentfontend -- --template vue

Scaffolding project in /Users/zachary/Documents/PythonCode/studentProject/studentfontend...

Done. Now run:

cd studentfontend
npm install
npm run dev

➜ studentProject cd studentfontend
➜ studentfontend ls
README.md package.json src
index.html public vite.config.js

npm 源

如果 npm 命令运行的时候很卡的话,尝试换源

npm config set registry [https://registry.npmmirror.com](https://registry.npmmirror.com)

打开 vue 项目

使用 WebStorm 打开创建好的前端项目

先什么都不做,尝试把项目运行起来

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
➜  studentfontend npm install
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: '@vitejs/plugin-vue@5.0.4',
npm WARN EBADENGINE required: { node: '^18.0.0 || >=20.0.0' },
npm WARN EBADENGINE current: { node: 'v16.19.0', npm: '8.19.3' }
npm WARN EBADENGINE }
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'rollup@4.12.1',
npm WARN EBADENGINE required: { node: '>=18.0.0', npm: '>=8.0.0' },
npm WARN EBADENGINE current: { node: 'v16.19.0', npm: '8.19.3' }
npm WARN EBADENGINE }
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'vite@5.1.5',
npm WARN EBADENGINE required: { node: '^18.0.0 || >=20.0.0' },
npm WARN EBADENGINE current: { node: 'v16.19.0', npm: '8.19.3' }
npm WARN EBADENGINE }

up to date in 649ms
➜ studentfontend npm run dev

> studentfontend@0.0.0 dev
> vite


VITE v5.1.5 ready in 814 ms

➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

项目整改

现在我们把这个项目做好看些,为了学生信息管理系统做准备的,

需要把路由写好,同时修改主题色,还有 request http 的请求封装,选择一个自己喜欢的页面标题及 icon

index.html

修改项目下的 index.html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<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>

css&imgs

删除/assets下的vue.svg

/src/assets路径下创建一个cssimgs目录

在创建好的/css目录下分别创建global.cssindex.scss

全局 css 样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 0;
color: #252424;
}

a {
text-decoration: none;
}

.card {
background-color: rgb(255, 255, 255);
border-radius: 5px;
padding: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

主题色配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
"base": #0a7fce
),
"success": (
"base": #08b41f
),
"warning": (
"base": #e8af56
),
"danger": (
"base": #ef3030
),
"info": (
"base": #5d66ea
)
)
);

路由设置

/src路径下创建一个router目录,在其中创建一个index.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Manager",
component: () => import("@/views/Manager.vue"),
redirect: "/home",
children: [
{
path: "home",
name: "Home",
component: () => import("@/views/manager/Home.vue"),
},
],
},
],
});

export default router;

request 请求

/src路径下创建一个utils目录,在其中创建一个request.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { ElMessage } from "element-plus";
import router from "../router";
import axios from "axios";

const request = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 30000, // 后台接口超时时间设置
});

// request 拦截器
// 可以自请求发送前对请求做一些处理
request.interceptors.request.use(
(config) => {
config.headers["Content-Type"] = "application/json;charset=utf-8";
return config;
},
(error) => {
return Promise.reject(error);
}
);

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
(response) => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === "blob") {
return res;
}
// 兼容服务端返回的字符串数据
if (typeof res === "string") {
res = res ? JSON.parse(res) : res;
}
// 当权限验证不通过的时候给出提示
if (res.code === "401") {
ElMessage.error(res.msg);
router.push("/login");
}
return res;
},
(error) => {
console.log("err" + error);
return Promise.reject(error);
}
);

export default request;

views

/src路径下的/components修改为/views目录,将其中的HelloWorld.vue修改成Manager.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<template>
<div>
<div
style="height: 60px; background-color: #eae8e8; display: flex; align-items: center; border-bottom: 1px solid #c4c2c2"
>
<div style="flex: 1">
<div style="padding-left: 20px; display: flex; align-items: center">
<img src="@/assets/imgs/logo.png" alt="" style="width: 40px" />
<div style="font-weight: bold; font-size: 24px; margin-left: 5px">
学生信息管理系统
</div>
</div>
</div>
<div
style="width: fit-content; padding-right: 10px; display: flex; align-items: center;"
>
<img
src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
alt=""
style="width: 40px; height: 40px"
/>
<span style="margin-left: 5px">管理员</span>
</div>
</div>

<div style="display: flex">
<div
style="width: 200px; border-right: 1px solid #f3eeee; min-height: calc(100vh - 60px)"
>
<el-menu
router
style="border: none"
:default-active="$route.path"
:default-openeds="['/home']"
>
<el-menu-item index="/home">
<el-icon><HomeFilled /></el-icon>
<span>系统首页</span>
</el-menu-item>
<el-menu-item index="/person">
<el-icon><User /></el-icon>
<span>个人资料</span>
</el-menu-item>
<el-menu-item index="login" @click="logout">
<el-icon><SwitchButton /></el-icon>
<span>退出系统</span>
</el-menu-item>
</el-menu>
</div>

<div style="flex: 1; width: 0; background-color: #eaeaee; padding: 10px">
<router-view />
</div>
</div>
</div>
</template>

<script setup>
import { useRoute } from "vue-router";
const $route = useRoute();
console.log($route.path);

const logout = () => {
localStorage.removeItem("student-user");
};
</script>

<style scoped>
.el-menu-item.is-active {
background-color: #c3d7d3 !important;
}
.el-menu-item:hover {
color: #0c98d5;
}
:deep(th) {
color: #333;
}
</style>

接着在/src/views路径下创建一个manager目录,在其中创建一个Home.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
<template>
<div class="welcome-container">
<div class="custom-card bg-white shadow rounded p-4 mb-5">
<h2 class="text-center text-primary">欢迎来到本系统</h2>
<p class="text-center mt-3">
您好 <span :style="{ color: '#116ca9' }">{{ user.username }}</span
>,祝您使用愉快!
</p>
</div>
</div>
</template>

<script setup>
import request from "@/utils/request";

const user = JSON.parse(localStorage.getItem("student-user") || "{}");
</script>

<style scoped>
.welcome-container {
display: flex;
justify-content: center;
align-items: center;
min-height: calc(100vh - 100px); /* 根据实际项目需要调整高度 */
}

.custom-card {
max-width: 400px;
border: none;
}
</style>

App.vue

修改项目中的/src/App.vue

1
2
3
<template>
<RouterView />
</template>

main.js

修改项目中的/src/main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import ElementPlus from "element-plus";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";

import "@/assets/css/global.css";

const app = createApp(App);

app.use(router);
app.use(ElementPlus, {
locale: zhCn,
});
app.mount("#app");

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}

.env

在项目路径下创建两个文件.env.development.env.production

1
VITE_BASE_URL='http://localhost:9090'
1
VITE_BASE_URL='http://:9090'

json 配置

修改项目路径下的文件:package.json

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
{
"name": "studentfontend",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.6.2",
"element-plus": "^2.4.2",
"sass": "^1.69.5",
"unplugin-element-plus": "^0.8.0",
"vue": "^3.3.4",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.4.0",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.11"
}
}

vite.config.js

修改项目路径下的文件:vite.config.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";

import ElementPlus from "unplugin-element-plus/vite";

export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
}),
Components({
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
}),

ElementPlus({
useSource: true,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `
@use "@/assets/css/index.scss" as *;
`,
},
},
},
});

修改一下 logo 和标题的 icon

分别放在/public/favicon.ico/src/assets/imgs/logo.png路径下

删除/public/vite.svg

可以去这个网站下载https://iconscout.com/icons

https://iconscout.com/free-icon/library-2642818,将该图片保存为/src/assets/imgs/logo.png

或者https://iconscout.com/free-icon/student-79

https://iconscout.com/free-icon/student-reading-2909468,保存为/public/favicon.ico

运行

命令运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  studentfontend npm install

added 93 packages, removed 1 package, and changed 5 packages in 16s
➜ studentfontend npm run dev

> studentfontend@0.0.0 dev
> vite


VITE v4.5.2 ready in 1169 ms

➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help

一键运行

通过配置一个启动项,便捷运行项目


后端 FastAPI

创建空项目

新建一个项目进入 FastAPI 的世界

先创建项目文件夹

1
2
3
4
5
6
7
8
➜  studentProject ll
total 0
drwxr-xr-x@ 16 zachary staff 512B 3 10 20:25 studentfontend
➜ studentProject mkdir studentbackend
➜ studentProject ll
total 0
drwxr-xr-x@ 2 zachary staff 64B 3 10 20:41 studentbackend
drwxr-xr-x@ 16 zachary staff 512B 3 10 20:25 studentfontend

然后使用 Pycharm 创建一个空项目

依赖包安装

需要安装一个 FastAPI 的依赖包

命令安装

pip install fastapi[all]

requirements.txt 安装

1
fastapi[all]

实现一个 api

创建

项目目录下创建一个main.py

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
# Author: Zachary

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def hello():
return {"message": "Hello World"}

运行

需要使用 fastapi 提供的一个uvicornASGI 网关服务器来启动 api 服务

命令运行

uvicorn main:app --reload

一键运行
  • 配置 main 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
# Author: Zachary
import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def hello():
return {"message": "Hello World"}


if __name__ == "__main__":
uvicorn.run("main:app", reload=True)

  • 启动配置项

以上两种方式均可以实现,具体看个人习惯

项目整改

.env

这个文件用于存放环境变量,包括项目的运行 ip、端口号等,后面的数据库环境变量也在这里存放

1
2
HOST = "localhost"
PORT = "9090"

common

在项目路径下创建一个/common的 package,用于实现公共类或者公共方法

环境变量配置文件

/common包下创建一个config.py文件,实现对环境变量的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding:utf-8 -*-
# Author: Zachary
import os
from pathlib import Path
from dotenv import load_dotenv


class Config:
def __init__(self):
dotenv_path = Path(__file__).parent.parent / ".env"
load_dotenv(dotenv_path=dotenv_path)
self._env = dict(os.environ)

@property
def env(self):
return self._env


config = Config()

/common包下创建一个constant.py文件,用于配置常量

1
2
3
4
5
6
7
# -*- coding:utf-8 -*-
# Author: Zachary

from common.config import config

HOST = config.env.get("HOST")
PORT = config.env.get("PORT")
返回类 Result

实现一下 api 的通用返回类,在/common包下创建一个result.py

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
33
# -*- coding:utf-8 -*-
# Author: Zachary
from pydantic import BaseModel


class ResultBase:
code: str
msg: str
data: dict


class ResultModel(BaseModel, ResultBase):
pass


class Result(ResultBase):

def __init__(self, code, msg, data):
self.code = code
self.msg = msg
self.data = data

@classmethod
def success(cls, data: object = None, code: str = "200", msg: str = "success"):
if not data:
data = {}
return cls(code, msg, data)

@classmethod
def error(cls, data: object = None, code: str = "500", msg: str = "error"):
if not data:
data = {}
return cls(code, msg, data)

exception

创建一个/exception的 package 用于自定义异常

api

创建一个/api的 package,用于创建后端的 API,实际就是 Controller 层

init.py

用于创建 FastAPI 应用初始化

1
2
3
4
5
6
# -*- coding:utf-8 -*-
# Author: Zachary

from fastapi import FastAPI

app = FastAPI()
adminApi.py

创建一个 hello fastapi 接口测试一下项目

1
2
3
4
5
6
7
8
9
10
# -*- coding:utf-8 -*-
# Author: Zachary

from api import app
from common.result import Result


@app.get("/")
async def hello():
return Result.success()

然后新增了一个 api 文件之后需要给 api 的init.py 说明一下新增了一个 api 文件

1
2
3
4
5
6
7
8
# -*- coding:utf-8 -*-
# Author: Zachary

from fastapi import FastAPI

app = FastAPI()

from api import adminApi

service

创建一个/service的 package 用于实现 Service 层的业务代码

model

创建一个/model的 package 用于实现数据库的映射类

main

用于启动 FastAPI 的主入口程序

1
2
3
4
5
6
7
8
9
10
# -*- coding:utf-8 -*-
# Author: Zachary

from api import app
import uvicorn

from common.constant import HOST, PORT

if __name__ == '__main__':
uvicorn.run("main:app", host=HOST, port=int(PORT), reload=True)

运行

直接运行 main.py 文件即可

出现这个就是访问成功啦

至此,我们的前端后端项目就都初始化好了


更新: 2024-05-03 22:13:04
原文: https://www.yuque.com/zacharyblock/iacda/sengqtgphw3hc0hv