项目部署

项目开发过程

初始源码

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里看到一些细节

posted @ 2023-02-11 02:43  ben10044  阅读(22)  评论(0编辑  收藏  举报