项目部署
项目开发过程
初始源码
Egg
数据库sql
clazz表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for clazz
-- ----------------------------
DROP TABLE IF EXISTS `clazz`;
CREATE TABLE `clazz` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of clazz
-- ----------------------------
INSERT INTO `clazz` VALUES (1, '1班');
INSERT INTO `clazz` VALUES (2, '2班');
INSERT INTO `clazz` VALUES (3, '3班');
INSERT INTO `clazz` VALUES (4, '4班');
INSERT INTO `clazz` VALUES (5, '5班');
SET FOREIGN_KEY_CHECKS = 1;
student表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`score` double NULL DEFAULT NULL,
`clazz_id` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `clazz_id`(`clazz_id`) USING BTREE,
CONSTRAINT `student_ibfk_1` FOREIGN KEY (`clazz_id`) REFERENCES `clazz` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 666 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, 'Benson', 55, 3);
INSERT INTO `student` VALUES (5, '谷歌l', 26, 5);
INSERT INTO `student` VALUES (7, '试试', 77, 2);
INSERT INTO `student` VALUES (8, '撒', 46, 1);
INSERT INTO `student` VALUES (9, '是撒无', 45, 3);
INSERT INTO `student` VALUES (10, '来了', 24, 4);
INSERT INTO `student` VALUES (11, '咯', 99, 5);
INSERT INTO `student` VALUES (17, 'jk', 24, 1);
INSERT INTO `student` VALUES (24, '的', 22, 3);
INSERT INTO `student` VALUES (31, 'grr', 24, 3);
SET FOREIGN_KEY_CHECKS = 1;
app/controller
clazz.js
const Controller = require("egg").Controller
class ClazzController extends Controller {
async index(){
let clazzList = await this.ctx.service.clazz.getClazzList();
this.ctx.body = clazzList;
console.log(clazzList)
}
}
module.exports = ClazzController
home.js
'use strict';
const { Controller } = require('egg');
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = 'hi, egg';
}
}
module.exports = HomeController;
login.js
const Controller = require("egg").Controller
class LoginController extends Controller {
async index(){
this.ctx.body = "hi,egg";
}
async login(){//处理用户登录,验证成功就发个token
let user = this.ctx.request.body.user;//前端传过来的user,包含username和password属性
console.log(user)
let result = await this.ctx.service.user.checkUser(user);
if(result){
let user_jwt = {userjwt:user.username};//用user.username来签名生成token
let token = this.app.jwt.sign(user_jwt,this.app.config.jwt.secret);
//let token = this.app.jwt.sign({username},this.app.config.jwt.secret);
//这是种简写法,展开来是这样的{username:username},属性username的值是username
this.ctx.body = {
code:20000,
token:token
}//验证成功就给个响应,code和token是自己定义的,像msg那样
}else{//登录失败的响应
this.ctx.body = {
code:40000,
msg:"用户名或密码错误"
}
}
}
}
module.exports = LoginController
student.js
const { Controller } = require('egg');
class StudentController extends Controller {
async index() {
//调用service的student的getStudentList方法
let list = await this.ctx.service.student.getStudentList();
this.ctx.body = list;
}
async create(){
let student = this.ctx.request.body.student;
let createStu = await this.ctx.service.student.createStudent(student);
if(createStu){
this.ctx.body = {
code:20000,
msg:"添加成功"
}
}else{
this.ctx.body = {
code:40000,
msg:"添加失败"
}
}
}
async destroy(){
let id = this.ctx.params.id;//接收的是学号
let deleteStu = await this.ctx.service.student.destroyStudent(id);
if(deleteStu){
this.ctx.body = "删除成功";//要设置响应,不然前端接收不到就会认为连接失败
}
}
async update(){
let student = this.ctx.request.body.student;
let id = this.ctx.params.id;//接收的是学号
let updateStu = await this.ctx.service.student.updateStudent(student,id);
if(updateStu){
this.ctx.body = {
code:20000,
msg:"修改成功"
}
}else{
this.ctx.body = {
code:40000,
msg:"修改失败"
}
}
}
}
module.exports = StudentController;
app/middleware
checktoken.js
function checktoken(){
return async function(ctx,next){//ctx上下文对象,next路由放行
try{
let token = ctx.request.header.token;//获取请求头里的token属性的值,不能加this
//用户再次请求数据的时候都要带着生成的token给服务器校验
let decode = ctx.app.jwt.verify(token,ctx.app.config.jwt.secret);//ctx.app可以指向应用实例app
console.log(decode)
if(decode.userjwt){
/*如果jwt.js的doLogin中用user生成的token,
那么decode:{ username: 'admin', password: '123456', iat: 1675928007 }
有username和password两个属性,这种情况是decode.username
如果用user_jwt生成的token,由于user_jwt是{userjwt:user.username}定义的
所以它只有一个属性,userjwt,值为user对象的username
decode:{ userjwt: 'admin', iat: 1675928083 }
jwt.js采用user_jwt生成token,所以是decode.userjwt
两种生成token的方式都可以,user容易理解,user_jwt更贴合实际
*/
await next();//放行
}
}catch(e){
ctx.body = {
code:40000,
msg:"用户校验失败"
}
}
}
}
module.exports = checktoken;
app/model
clazz.js
module.exports = app => {
const {STRING} = app.Sequelize;//表中定义的数据类型
const Clazz = app.model.define('clazz',{//通过app.model.define方法创建数据表
//会自动生成id字段
name:STRING,
},
{
freezeTableName: true,//如果不设置此项,在数据库中表名会多个s,如clazzs
createdAt: false,//不默认加创建时间字段
updatedAt: false,//不默认加修改时间字段
}
)
return Clazz;
}
student.js
module.exports = app => {
const {STRING,DOUBLE} = app.Sequelize;//表中定义的数据类型
const Student = app.model.define('student',{//通过app.model.define方法创建数据表
//会自动生成id字段
name:STRING,
score:DOUBLE,
},
{
freezeTableName: true,//如果不设置此项,在数据库中表名会多个s,如clazzs
createdAt: false,//不默认加创建时间字段
updatedAt: false,//不默认加修改时间字段
}
);
Student.associate = function(){
app.model.Student.belongsTo(app.model.Clazz,{//与Clazz关联
foreignKey:'clazz_id',//student表的外键,字段名为clazz_id
as:'clazz'
})
}
return Student;
}
user.js
module.exports = app => {
const {STRING,INTEGER} = app.Sequelize;//表中定义的数据类型
const User = app.model.define('user',{//通过app.model.define方法创建数据表
//默认会自动生成id主键字段,如果手动设置主键就不会自动生成id字段
//name字段设置了主键,所以需要手写id字段
id: {
type: INTEGER,
primaryKey: true,//主键
},
name: {
type: STRING,
primaryKey: true,//主键
},
upwd: STRING,
},
{
freezeTableName: true,//如果不设置此项,在数据库中表名会多个s,如users
createdAt: false,//不默认加创建时间字段
updatedAt: false,//不默认加修改时间字段
}
)
return User;
}
app/service
clazz.js
const Service = require('egg').Service;
class ClazzService extends Service {
async getClazzList(){
try{
let clazzList = await this.app.model.Clazz.findAll();
return clazzList;
}catch(e){
return null;
}
}
}
module.exports = ClazzService;
student.js
const Service = require('egg').Service;
class StudentService extends Service {
async getStudentList(){
try{
let studentList = await this.app.model.Student.findAll();
return studentList;
}catch(e){
return null;
}
}
async createStudent(student){//数据是前端通过controller传过来的
try{
await this.app.model.Student.create({
id:student.id,
name:student.name,
score:student.score,
clazz_id:student.clazz_id
});
return true;//告诉controller已经成功了
}catch(e){
return false;
}
}
async destroyStudent(id){
try{
await this.app.model.Student.destroy({//返回的是被删除的索引
//如果查不到后台显示[],空的集合,但不是null。[]和{}都会判断为true
where: {
id: id
}
});
return true;
}catch(e){
return false;
}
}
async updateStudent(student,id){
try{
await this.app.model.Student.update({
id:student.id,
name:student.name,
score:student.score,
clazz_id:student.clazz_id
},{
where:{
id:id
}});
return true;//告诉controller已经成功了
}catch(e){
return false;
}
}
}
module.exports = StudentService;
user.js
const Service = require('egg').Service;
class UserService extends Service {
async getUserList(){
try{
let userList = await this.app.model.User.findAll();
return userList;
}catch(e){
return null;
}
}
async checkUser(user){//数据是前端通过controller传过来的
//条件查询就行了,不需要遍历
try{
let userResult = await this.app.model.User.findAll({
//如果查不到后台显示[],空的集合,但不是null。[]和{}都会判断为true
where: {
name: user.username,
upwd: user.password
}
})
if(userResult.length == 0){//通过集合的长度判断是空集合,没有查到结果
return false;
}else {
return true;
}
}catch(e){
return false;
}
}
}
module.exports = UserService;
app/
router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.post('/login', controller.login.login);//处理前端/login提交的表单
router.get('/clazz', controller.clazz.index);
router.resources("student","/student",app.middleware.checktoken(),controller.student);
// router.get('/student', app.middleware.checktoken(), controller.student.index);
//每次get请求访问/student时都会执行中间件checktoken()方法来校验token
};
config/
plugin.js
'use strict';
/** @type Egg.EggPlugin */
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
cors: {
enable: true,
package: 'egg-cors',
},
jwt: {
enable: true,
package: 'egg-jwt'
},
sequelize: {
enable: true,
package: 'egg-sequelize'
},
};
config.default.js
/* eslint valid-jsdoc: "off" */
'use strict';
/**
* @param {Egg.EggAppInfo} appInfo app info
*/
module.exports = appInfo => {
/**
* built-in config
* @type {Egg.EggAppConfig}
**/
const config = exports = {};
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1676096564513_404';
// add your middleware config here
config.middleware = [];
// add your user config here
const userConfig = {
// myAppName: 'egg',
};
config.security = {
csrf: {
enable:false,
},
};
config.cors = {
origin: "*",//允许所有地址跨域请求
allowMethods:'GET,HEAD,PUT,POST,DELETE,PATCH'//允许的请求
};
config.jwt = {
secret:"benson"
}
config.sequelize = {
dialect:'mysql',//使用mysql数据库系统
database:'test',//使用test这个数据库
host:'localhost',//数据库地址
port:3306,
username:'root',//用root用户的登录数据库系统
password:'123456'
};
return {
...config,
...userConfig,
};
};
/
app.js
module.exports = app => {
app.beforeStart(async function(){//在项目启动的时候执行
await app.model.sync({});//app.model拿到数据模型,它的sync方法能根据模型创建表
//await app.model.sync({force:true});//开发时使用,再次启动项目时会清空表格中的数据
});
};
Vue
src/
components/Login.vue
<template>
<div>
<h1 style="text-align: center;">登录</h1>
<el-form :model="form" label-width="auto" style="max-width: 300px" @submit.native.prevent="login">
<el-form-item label="用户名" style="margin-top:20px">
<el-input v-model="user.username" />
</el-form-item>
<el-form-item label="密码" style="margin-top:20px">
<el-input type="password" v-model="user.password" />
</el-form-item>
<el-button type="primary"
style='position: absolute;right:535px;margin-top:10px'
native-type="submit"
>
登录
</el-button><!--普通的elbutton的@click也行,也不需要设置form的@submit-->
</el-form>
<el-dialog
v-model="dialogVisible"
title="登录失败"
width="30%"
>
<span>用户名或密码错误</span>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">
确认
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import axios from "axios";
export default {
data(){
return {
user:{
username:"",
password:""
},
dialogVisible:false,
}
},
methods:{
login(){
axios.post("http://127.0.0.1:7001/login",{user:this.user})//跨域发送user对象给后台负责登录验证的地址/login
.then((res)=>{
if(res.data.code === 20000){//后台响应的数据里有个code字段,值为20000则成功
localStorage.setItem("token",res.data.token);//把响应的token值存在本地储存的token字段中
console.log(res.data.token);
this.$router.push("/");
}else {
this.dialogVisible = true;
}
})
}
}
}
</script>
<style scoped>/* 不写scoped会对其他组件造成影响如Student */
*{
margin:0 auto;
}
</style>
components/Student.vue
<template><!--element-plus的table组件找的-->
<div>
<el-button text @click="dialogVisible = true" type="primary">
添加学生
</el-button>
<el-button text @click="logout" type="primary" style="margin-left:455px">
退出
</el-button>
<el-dialog
v-model="dialogVisible"
title="添加学生信息"
width="30%"
>
<el-form :model="form" ref="addForm">
<el-form-item label="学号" prop="id">
<el-input v-model="form.id"/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name"/>
</el-form-item>
<el-form-item label="成绩" prop="score">
<el-input v-model="form.score"/>
</el-form-item>
<el-form-item label="班级" prop="clazz_id">
<el-select v-model="form.clazz_id" placeholder=" ">
<el-option
v-for="item in clazzList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option><!--班级从clazz列表中选取-->
</el-select>
</el-form-item>
<!--form.id form.name form.score form.clazz_id通过input的赋值,此时data()中的form也就有了值
所以addsubmit里的stuent:this.form的this.form才有值传给后台-->
<!--要想提交表单后清空表单因v-model存储的内容,先给form配上:model、ref和form-item配上对应的prop(绑定属性名)
然后在addsubmit那this.$refs["addForm"].resetFields();
-->
</el-form><!--把对话框的内容换成表单-->
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="addSubmit">
确认
</el-button>
</span>
</template>
</el-dialog><!--dialog对话框-->
<!--.slice((currentPage-1)*pagesize,currentPage*pagesize)是切割数组来分页-->
<el-table :data="studentList.slice((currentPage-1)*pagesize,currentPage*pagesize)" border style="width:600px;"
:cell-style="{ textAlign: 'center' }"
:header-cell-style="{ 'text-align': 'center' }"
><!--:cell-style="{ textAlign: 'center' }"是所有列居中
:header-cell-style="{ 'text-align': 'center' }"是表头居中
如果是让单纯一两列el-table-column居中就在它的标签加个align="center"-->
<el-table-column prop="id" label="学号" width="120" />
<el-table-column prop="name" label="姓名" width="120" />
<el-table-column prop="score" label="成绩" width="120" />
<el-table-column prop="clazz_id" label="班级" width="120" />
<el-table-column label="操作" width="120">
<template #default="scope"><!--想并排显示就跳转table的宽度620px "操作"的width=140-->
<el-button type="primary" size="small" @click="del(scope.row.id)">删除</el-button>
<el-button type="primary" size="small" @click="editClick(scope.$index)" style="margin-left:0">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog
v-model="editVisible"
title="修改学生信息"
width="30%"
>
<el-form>
<el-form-item label="学号">
<el-input v-model="editObj.id" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="editObj.name" />
</el-form-item>
<el-form-item label="成绩">
<el-input v-model="editObj.score" />
</el-form-item>
<el-form-item label="班级">
<el-select v-model="editObj.clazz_id">
<el-option
v-for="item in clazzList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option><!--班级从clazz列表中选取-->
</el-select>
</el-form-item>
</el-form><!--把对话框的内容换成表单-->
<template #footer>
<span class="dialog-footer">
<el-button @click="editVisible = false">取消</el-button>
<el-button type="primary" @click="editSubmit">
确认
</el-button>
</span>
</template>
</el-dialog>
<el-pagination
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="pagesize"
:total="studentList.length"
background
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="margin-top: 20px; text-align: right;"
/><!--分页-->
<!--:page-sizes="[5, 10, 15, 20]"在下拉框中用来选择一页显示几条数据
:total显示总共有几条数据
background当前页背景设为蓝色
layout中total显示:total的值 sizes是下拉框 jumper是跳转页工具
prev和next是当页数很多时当前页的两边有...,prev是往前5页,next是往后5页
-->
</div>
</template>
<script>
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus'//引入才能用messagebox
export default {
data(){
return {
studentList:[],
studentList2:[],
dialogVisible:false,
editVisible:false,
form:{
id:null,
name:"",
score:null,
clazz_id:null
},
editObj:{
id:null,
name:"",
score:null,
clazz_id:null
},
id_edit:null,//创建一个容器存放修改前的学号
clazzList:[],//班级列表
currentPage: 1,
pagesize: 5,//一页有几条数据
totalPages: 0,//默认最大的页数
}
},
methods:{
//查看
getData(){
let token = localStorage.getItem("token");//从本地存储中拿到token
axios.get("http://127.0.0.1:7001/student",{headers:{token:token}}).then((res)=>{
this.studentList = res.data;
})
/*axios的get请求,它的第二个参数能设置请求头响应头之类的信息。
headers代表的是请求头,headers:{token:token}的意思是
在请求头中设置一个字段为token(:左边)的属性,字段的值为token(:右边)
如果是post请求,第二个参数是数据,第三个才算请求头*/
},
//删除
del(id){
ElMessageBox.confirm(//messgebox组件
'此操作将永久删除该数据,是否继续?',
'提示',
{
confirmButtonText: '确定',//执行then方法
cancelButtonText: '取消',//执行catch方法
type: 'warning',//弹窗的标题
}
)
.then(() => {
let token = localStorage.getItem("token");//从本地存储中拿到token
axios.delete(`http://127.0.0.1:7001/student/${id}`,{headers:{token:token}})
.then(()=>{
ElMessage({
type: 'success',
message: '删除成功',
})
this.getData();
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除',
})
})
},
//添加
addSubmit(){
let token = localStorage.getItem("token");//从本地存储中拿到token
axios.post("http://127.0.0.1:7001/student",{student:this.form},{headers:{token:token}})//提交的是添加学生信息form的数据
.then((res)=>{
if(res.data.code === 20000){//后台响应的数据里有个code字段,值为20000则成功)
this.dialogVisible = false;
this.getData();
this.$refs["addForm"].resetFields();//清空表单数据
ElMessage({
type: 'success',
message: '添加成功'
});
}else{
ElMessage({message: '添加失败'});
}
})
},
//修改
editClick(index){
/*scope.row获取当前行的数据,而scope.$index获取的是当前行的数据在数组中的索引
(tabel表格绑定的数据是一个数组,数组中每一个对象就相当于一行的数据)*/
this.editVisible = true;
const studentList2 = JSON.parse(JSON.stringify(this.studentList));/*深拷贝*/
/*深拷贝是完完全全拷贝一个数据,连带着数据结构和值。修改一个对象的属性,也不会影响另一个。
浅拷贝相当于赋值,如果是对象或者数组,就会只拷贝对象和数组的引用,无论对新旧数组的哪一个进行了修改,两者都会发生变化
*/
// this.editObj = row;
/*当前行的数据赋值给editObj,而修改按钮的表单绑定的是editObj的各属性,所以会同步显示。
但是input输入中也会更新页面数据,所以采用深拷贝的方式用索引绑定当前数据*/
this.editObj = studentList2[index+(this.currentPage-1)*this.pagesize];
/*分页后索引还是index,比如第1页的首条数据索引是0,点击第二页的首条数据的修改按钮
发现表单的数据跟第1页首条数据一样,说明索引没变。所以需要人为修改索引
规律是无论是哪一页的第i行的数据都能对应第一页的第i行数据的索引,那么相差的就是页数*每页的数据
如每页有5条数据的前提下,第1页首条数据索引为0,第2页首条数据(第6条)真正索引为5
两者相差1页的数据量,即(2-1)*5
*/
this.id_edit = this.editObj.id;//把修改前的学号放到data()中
},
editSubmit(){
let token = localStorage.getItem("token");//从本地存储中拿到token
let id = this.id_edit; //通过data()的id_edit实现传参!!!想了半天竟然没想到这个
axios.put(`http://127.0.0.1:7001/student/${id}`,{student:this.editObj},{headers:{token:token}})
.then((res)=>{
if(res.data.code ===20000){
this.editVisible = false;
this.getData();
ElMessage({
type: 'success',
message: '修改成功'
});
}else{
ElMessage({message: '修改失败'});
}
})
},
getClazz(){
axios.get("http://127.0.0.1:7001/clazz").then((res)=>{
this.clazzList = res.data
})
},
handleSizeChange(size) {
this.pagesize = size;
},
handleCurrentChange(currentPage) {
this.currentPage = currentPage;
},
logout(){
localStorage.setItem("token","");
location.reload();//js的方法,能自动刷新页面
}
},
created(){
this.getData();
this.getClazz();
}
}
</script>
App.vue
<template>
<router-view></router-view><!--用router-view占位,换页后显示的组件放在这里-->
</template>
<script>
export default {
name: 'App',
}
</script>
main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import './index.css'
import router from './router.js'//引入router
const app = createApp(App)
app.use(router);
app.use(ElementPlus)
app.mount('#app')
router.js
import { createRouter,createWebHashHistory } from "vue-router";//引入创建路由的方法和哈希模式
import Login from "./components/Login.vue"//要加上.vue不让显示不出来
import Student from "./components/Student.vue"
const router = createRouter({
history:createWebHashHistory(),//路由模式
routes:[//配置路由
{
path:"/",//首页
component:Student//跳转到Student组件
},
{
path:"/login",
component:Login
},
]
});//创建路由
router.beforeEach((to,from,next) => {
//只有存在token时才能跳转到内容页
//localStorage的getItem方法能根据数据名获取它的值
let token = localStorage.getItem("token");
if(token){
if(to.path === "/login"){//有token时访问/login
next("/");//有token时不准去登录页
}else {//有token时只要不去登录页去哪都行
next();
}
}else if(to.path === "/login"){//没有token或token为空
next();
}else {
//如果没有上面的else if,直接else{next("/login")路由会报错,无限重定向
next("/login")
}
})
export default router;
配置开发&生产环境
项目开发时发出请求用的是7001端口,而部署阶段(生产时)前端和后台在同一服务器中,前端请求时就不需要跨域了,
请求的地址(本地请求)也就不需要端口了(真实地址还是有的)
我们不可能部署前把开发时的请求地址都改成生产阶段的地址,那样太麻烦了
所以可以配置开发和生产环境,两者有同名的变量x存放请求地址,程序会识别是开发环境还是生产环境,从而让x的值也与之对应
在vue的项目根目录下创建开发环境文件.env.development和生产环境文件.env.production
.env.development
VITE_APP_BASE_API = "http://127.0.0.1:7001"
.env.production
VITE_APP_BASE_API = ""
变量名一定要以VITE_开头
然后修改Login.vue和Student.vue的请求地址
Login.vue
login()
axios.post(`${import.meta.env.VITE_APP_BASE_API}/login`,{user:this.user})
Student.vue
getData()
axios.get(`${import.meta.env.VITE_APP_BASE_API}/student`,{headers:{token:token}})
del(id)
axios.delete(`${import.meta.env.VITE_APP_BASE_API}/student/${id}`,{headers:{token:token}})
addSubmit()
axios.post(`${import.meta.env.VITE_APP_BASE_API}/student`,{student:this.form},{headers:{token:token}})
editSubmit()
axios.put(`${import.meta.env.VITE_APP_BASE_API}/student/${id}`,{student:this.editObj},{headers:{token:token}})
getClazz()
axios.get(`${import.meta.env.VITE_APP_BASE_API}/clazz`)
封装axios
觉得请求地址的代码还是太长了,可以在src下创建一个utils目录,新建request.js来封装axios
request.js
import axios from "axios";
/*axios的create方法可以创建一个axios对象,即创建一个自己
但能进行一些配置*/
const request = axios.create({
baseURL:import.meta.env.VITE_APP_BASE_API
//baseURL是axios定义好的基础rul,把它设置成环境变量
}
)
export default request;//用法跟axios是一样的
然后去把各个用axios发送的请求改成用request发送
以Login.vue为例
// import axios from "axios";
import request from '../utils/request';//封装axios后就无需导入axios了
//axios.post(`${import.meta.env.VITE_APP_BASE_API}/login`,{user:this.user})
request.post(`/login`,{user:this.user})//把axios改成request,并删掉环境变量
拦截器interceptors
源码中给每个请求都带了token请求头,有些繁琐,可以在上面的request.js中用拦截器来封装往请求头塞token的代码
request.js
import axios from "axios";
/*axios的create方法可以创建一个axios对象,即创建一个自己
但能进行一些配置*/
const request = axios.create({
baseURL:import.meta.env.VITE_APP_BASE_API
//baseURL是axios定义好的基础rul,把它设置成环境变量
}
)
/*interceptors能拦截请求request也能拦截响应
.request.use表明拦截的是请求*/
request.interceptors.request.use((req) => {//req是请求
let token = localStorage.getItem("token");//从本地存储中拿到token
if(token){//有token就往请求头里加token字段,值为从本地拿到的token
req.headers.token = token;
}
});
export default request;//用法跟axios是一样的
以Student.vue的getData为例
getData(){
//let token = localStorage.getItem("token");//从本地存储中拿到token
//request.get(`/student`,{headers:{token:token}}).then((res)=>{
request.get(`/student`).then((res)=>{
this.studentList = res.data;
})
项目部署
打包项目
vue的cmd
npm run build
然后把dist目录中的文件复制到egg的app/public目录下
然后访问egg的端口127.0.0.1:7001/public/index.html,/public是egg的默认静态文件目录
发现有个打包的文件报错
原因是vue生成的asset里文件的访问路径默认是根目录下的asset,而egg默认是/public,两者冲突了
可以在egg的config.default.js里配置
config.static = {
prefix: '/',//访问路径,访问index.html就不用再输/public了
dir: path.join(appInfo.baseDir, 'app/public'),//设置静态文件目录,需要导入path
};
访问http://127.0.0.1:7001/index.html就能得到图中的效果
真正部署后egg的cmd输入的是
npm strat
如果要关闭服务器,Ctrl+C是没用的
npm stop
npm run dev是开发时用的,方便在cmd里看到一些细节