在线教育后端

讲师管理模块(后端)

  • 前后端分离

    • 前端

      • html,css,js、jq
      • 主要作用:数据显示
      • ajax操作
    • 后端

      • controller service mapper
    • 主要作用,返回数据或者操作数据

      • 接口,不是inferface
    • 开发controller service mapper 过程

建表

  • 数据库设计

创建项目结构

  • 创建父工程
    • pom类型
      • 管理依赖版本和放公共依赖
    • 子模块
      • 子子模块1
      • 子子模块2
  • 修改了maven仓库[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTbOiUa6-1685015617226)(在线教育.assets/image-20230108201017261.png)]

  • 父工程控制版本

  • 子工程使用

  • 建立子模块方法,在父工程中点击新建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9n3GX9oC-1685015617227)(在线教育.assets/image-20230108210249027.png)]

子子模块同理

  • 创建applicatio.properties文件

    # 服务端口
    server.port=8001
    # 服务名
    spring.application.name=service-edu
    
    # 环境设置:dev、test、prod
    spring.profiles.active=dev
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=root
    
    #mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    

开发讲师管理模块

  • 编写controller service mapper代码内容
  • mp提供的代码生成器,生成相关代码-网站上面有模板
    • 注意数据库中的主键策略,如果是long用ID_WORKER,如果是字符串类型用ID_WORKER_STR
    • 里面的数据库配置是单独的,需要自己修改,密码,时区,以及看版本是否要加cj
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43e3o5A0-1685015617228)(在线教育.assets/image-20230109210853417.png)]

接口部分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6AtiG5id-1685015617229)(在线教育.assets/image-20230109211154403.png)]

  • 返回接口数据 ResponseBody json数据

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsCohej2-1685015617230)(在线教育.assets/image-20230109211443212.png)]

注入service,service,mapper都已经被封装好了

  • 创建controller

    @RestController
    @RequestMapping("/eduservice/teacher")
    public class EduTeacherController {
    
        //把service注入
        @Autowired
        private EduTeacherService TeacherService;
    
    
        //1 查询讲师表的所有数据
        //rest风格
        @GetMapping("findAll")
        public List<EduTeacher> findAllTeacher(){
            //调用service的方法实现查询所有的操作
            List<EduTeacher> list = TeacherService.list(null);
            return list;
    
        }
    
  • 创建启动类

    @SpringBootApplication
    public class EduApplication {
    
        public static void main(String[] args){
            SpringApplication.run(EduApplication.class,args);
        }
    }
    
    
  • 创建配置类,配置mapper扫描和其他

    @Configuration
    @MapperScan("com.atguigu.eduservice.mapper")
    public class EduConfig {
    }
    
    
  • 最终测试

    • 启动起来,使用端口8001

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvDIJBEM-1685015617231)(在线教育.assets/image-20230109213350996.png)]

通过那个注入,将数据转换为json

  • 统一返回的json时间格式

    默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时,在application.properties中设置

    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    

讲师逻辑删除功能

  • 配置一个逻辑删除的插件

    在EduConfig中添加

    /**
          * 逻辑删除插件
          */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }
    
  • 实体类中加个注解TableLogic

  • Controller编写方法

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XklmiuT-1685015617232)(在线教育.assets/image-20230109214527515.png)]

       //2逻辑删除讲师的方法
        @DeleteMapping("{id}")
        public boolean removeTeacher(@PathVariable String id){
    
            boolean flag = teacherService.removeById(id);
            return flag;
        }
    //@PathVariable String id 得到路径中的id值
    
  • 如何测试 因为delete提交

    借助一些工具进行测试

swagger测试(重点)

  • 创建一个公共模块,整合swagger,为了所有模块都能进行使用

  • 前后端分离开发模式中,api文档是最好的沟通方式。

  • Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful 风格的 Web 服务。

    • 及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
    • 规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
    • 一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
    • 可测性 (直接在接口文档上进行测试,以方便理解业务)
  • <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <scope>provided </scope>
            </dependency>
    
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>
    
        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided </scope>
        </dependency>
    
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <scope>provided </scope>
        </dependency>
    
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    
        <!-- spring2.X集成redis所需common-pool2
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>-->
    </dependencies>
    
  • 创建common模块,在guli-parent下创建模块common在common中引入相关依赖

  • 在common下面创建子模块service-base

  • 在模块service-base中创建swagger的配置类

  • @Configuration//配置类
    @EnableSwagger2
    public class SwaggerConfig {
        @Bean
        public Docket webApiConfig(){
    
            return new Docket(DocumentationType.SWAGGER_2)
                    .groupName("webApi")
                    .apiInfo(webApiInfo())
                    .select()
                    .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                    .paths(Predicates.not(PathSelectors.regex("/error.*")))
                    .build();
    
        }
    
        private ApiInfo webApiInfo(){
    
            return new ApiInfoBuilder()
                    .title("网站-课程中心API文档")
                    .description("本文档描述了课程中心微服务接口定义")
                    .version("1.0")
                    .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com"))
                    .build();
        }
    
    • 配置类是固定的,只要会改就行
  • 怎么使用

    • 需要引入

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0w2eBmq-1685015617233)(在线教育.assets/image-20230120233210248.png)]

    • 在加入注解,设置包扫描规则

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhDba0Ho-1685015617234)(在线教育.assets/image-20230120233400626.png)]

    • 配置完记得maven重新加载一下,不然容易无限弹窗

  • 定义接口说明和参数说明

  • 定义在类上:@Api

    定义在方法上:@ApiOperation

    定义在参数上:@ApiParam

  • postman

讲师管理模块接口开发

  • json数据格式两种

    • 对象数组 两种模式混合使用

    • 定义统一结果
      {
        "success": 布尔, //响应是否成功
        "code": 数字, //响应码
        "message": 字符串, //返回消息
        "data": HashMap //返回数据,放在键值对中
      }
      
      • 在common创建子模块common-utils

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-frIbQJN8-1685015617235)(在线教育.assets/image-20230121170503803.png)]

      • 创建interface定义数据返回状态码

        成功 20000 失败20001

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsKKn2zL-1685015617236)(在线教育.assets/image-20230121171237945.png)]

      • 创建结果类

        把构造方法私有

        成功静态方法

        失败静态方法

        //统一返回结果的类
        @Data
        public class R {
            @ApiModelProperty(value="是否成功")
            private Boolean success;
        
            @ApiModelProperty(value = "返回码")
            private Integer code;
        
            @ApiModelProperty(value ="返回消息")
            private String message;
        
            @ApiModelProperty(value ="返回数据")
            private Map<String,Object> data = new HashMap<String,Object>();
        
            //把构造方法私有
            private R(){
            }
            //链式编程
            
            //成功静态方法
            public static R ok(){
                R r = new R();
                r.setSuccess(true);
                r.setCode(ResultCode.SUCCESS);
                r.setMessage("成功");
                return r;
            }
        
            //失败静态方法
            public static R error(){
                R r = new R();
                r.setSuccess(false);
                r.setCode(ResultCode.ERROR);
                r.setMessage("失败");
                return r;
            }
        
            public R success(Boolean success){
                this.setSuccess(success);
                return this;
            }
        
            public R message(String message){
                this.setMessage(message);
                return this;
            }
        
            public R code(Integer code){
                this.setCode(code);
                return this;
            }
        
            public R data(String key, Object value){
                this.data.put(key, value);
                return this;
            }
        
            public R data(Map<String, Object> map){
                this.setData(map);
                return this;
            }
        }
        

        this 返回,可以链式编程

        • 使用统一结果

          [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxBmEZlS-1685015617237)(在线教育.assets/image-20230122215300593.png)]

        引入返回对象都是R(用到链式编程)

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Iz2CmUg-1685015617237)(在线教育.assets/image-20230122215626517.png)]

讲师分页功能

  • 配置mp分页插件

    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    
  • 编写分页接口讲师的方法

    • 分页Controller方法

       //3分页查询讲师的方法
          //current 当前页
          //limit 每页记录数
          @GetMapping("pageTeacher/{current}/{limit}")
          public R pageListTeacher(@PathVariable long current,
                                   @PathVariable long limit){
      
              //创建page对象
              Page<EduTeacher> pageTeacher = new Page<>(current,limit);
              //调用方法实现分页
              //调用方法时候,底层封装,把分页所有数据封装到pageTeacher对象里面
              teacherService.page(pageTeacher,null);
      
              long total = pageTeacher.getTotal();//总记录数
              List<EduTeacher> records = pageTeacher.getRecords(); //数据list集合
      
      //        Map map = new HashMap();
      //        map.put("total",total);
      //        map.put("rows",records);
      //        return R.ok().data(map);
      
              return R.ok().data("total",total).data("rows",records);
          }
      

条件查询

  • 把条件值传递到接口里面

    把条件值封装到对象里面,把对象传递到接口里面(vo)

    创建TeacherQuery class

      @ApiModelProperty(value = "教师名称,模糊查询")
        private String name;
    
        @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
        private Integer level;
    
        @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
        private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
    
        @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
        private String end;
    
  • 根据条件值进行判断,拼接条件

    RequestBody 需要使用 post 提交方式

    使用json传递数据,把json,把json数据封装到对应对象里面

    @RequestBody(required = false) TeacherQuery teacherQuery
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGPNea4h-1685015617239)(在线教育.assets/image-20230130150820621.png)]

    传递的值可以为空

    ResponseBody

    返回数据,返回json数据

    新增和修改讲师接口开发

    • 在service-base模块中添加,创建handler,创建自动填充类@TableField(fill = FieldFill.INSERT)

      MyMetaObjectHandler

      @Component
      public class MyMetaObjectHandler implements MetaObjectHandler {
          @Override
          public void insertFill(MetaObject metaObject) {
              this.setFieldValByName("gmtCreate", new Date(), metaObject);
              this.setFieldValByName("gmtModified", new Date(), metaObject);
          }
      
          @Override
          public void updateFill(MetaObject metaObject) {
              this.setFieldValByName("gmtModified", new Date(), metaObject);
      
          }
      }
      
       //添加讲师接口的方法
          @PostMapping("addTeacher")
          public R addTeacher(@RequestBody EduTeacher eduTeacher){
              boolean save = teacherService.save(eduTeacher);
              if (save){
                  return R.ok();
              }
              else{
                  return  R.error();
              }
          }
      

      讲师修改功能

    • 根据讲师id进行查询

       @GetMapping("getTeacher/{id}")
          public R getTeacher(@PathVariable String id){
              EduTeacher eduTeacher = teacherService.getById(id);
              return R.ok().data("teacher",eduTeacher);
          }
      
    • 讲师修改

        //讲师修改操作
          @PostMapping("updateTeacher")
          public R updateTeacher(@RequestBody EduTeacher eduTeacher){
              boolean flag = teacherService.updateById(eduTeacher);
              if (flag){
                  return R.ok();
              }
              else{
                  return  R.error();
              }
          }
      

统一异常处理

  • 创建统一异常处理器

    再service-base中创建统一异常处理类GlobalExceptionHandler.java

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzJdpLpC-1685015617240)(在线教育.assets/image-20230203100428482.png)]

    要在service-base中添加依赖

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GqirfMWL-1685015617241)(在线教育.assets/image-20230201114104314.png)]

  • 依赖传递,在service-base包含common-utils

    service-edu包含service-base,依赖传递所以service-edu也会包含common-utils

  • 特定异常处理

    //特定异常
        @ExceptionHandler(ArithmeticException.class)
        @ResponseBody//为了放回数据
        public R error(ArithmeticException e){
            e.printStackTrace();
            return R.error().message("执行了ArithmeticException异常处理");
        }
    
  • 自定义异常处理

    创建自定义异常类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9klLOyn6-1685015617242)(在线教育.assets/image-20230203123712659.png)]

    在统一的异常类里面添加规则

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKlbSqkV-1685015617243)(在线教育.assets/image-20230203124453202.png)]

    最终执行自定义异常

统一日志处理

  • 配置日志级别

    日志记录器(Logger)的行为是分等级的。如下表所示:

    分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

    # 设置日志级别
    logging.level.root=WARN
    
  • logback

    把日志不仅输出到控制台也可以输出到文件中,使用日志工具logback日志工具

    • 删除之前配置的日志信息

    • resource中船舰logback-spring.xml

    • <?xml version="1.0" encoding="UTF-8"?>
      <configuration  scan="true" scanPeriod="10 seconds">
          <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
          <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
          <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
          <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
      
          <contextName>logback</contextName>
          <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
          <property name="log.path" value="D:/guli_log/edu" />
      
          <!-- 彩色日志 -->
          <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
          <!-- magenta:洋红 -->
          <!-- boldMagenta:粗红-->
          <!-- cyan:青色 -->
          <!-- white:白色 -->
          <!-- magenta:洋红 -->
          <property name="CONSOLE_LOG_PATTERN"
                    value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
      
      
          <!--输出到控制台-->
          <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
              <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
              <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
              <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                  <level>INFO</level>
              </filter>
              <encoder>
                  <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
                  <!-- 设置字符集 -->
                  <charset>UTF-8</charset>
              </encoder>
          </appender>
      
      
          <!--输出到文件-->
      
          <!-- 时间滚动输出 level为 INFO 日志 -->
          <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <!-- 正在记录的日志文件的路径及文件名 -->
              <file>${log.path}/log_info.log</file>
              <!--日志文件输出格式-->
              <encoder>
                  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                  <charset>UTF-8</charset>
              </encoder>
              <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
              <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                  <!-- 每天日志归档路径以及格式 -->
                  <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                  <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                      <maxFileSize>100MB</maxFileSize>
                  </timeBasedFileNamingAndTriggeringPolicy>
                  <!--日志文件保留天数-->
                  <maxHistory>15</maxHistory>
              </rollingPolicy>
              <!-- 此日志文件只记录info级别的 -->
              <filter class="ch.qos.logback.classic.filter.LevelFilter">
                  <level>INFO</level>
                  <onMatch>ACCEPT</onMatch>
                  <onMismatch>DENY</onMismatch>
              </filter>
          </appender>
      
          <!-- 时间滚动输出 level为 WARN 日志 -->
          <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <!-- 正在记录的日志文件的路径及文件名 -->
              <file>${log.path}/log_warn.log</file>
              <!--日志文件输出格式-->
              <encoder>
                  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                  <charset>UTF-8</charset> <!-- 此处设置字符集 -->
              </encoder>
              <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
              <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                  <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                  <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                      <maxFileSize>100MB</maxFileSize>
                  </timeBasedFileNamingAndTriggeringPolicy>
                  <!--日志文件保留天数-->
                  <maxHistory>15</maxHistory>
              </rollingPolicy>
              <!-- 此日志文件只记录warn级别的 -->
              <filter class="ch.qos.logback.classic.filter.LevelFilter">
                  <level>warn</level>
                  <onMatch>ACCEPT</onMatch>
                  <onMismatch>DENY</onMismatch>
              </filter>
          </appender>
      
      
          <!-- 时间滚动输出 level为 ERROR 日志 -->
          <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <!-- 正在记录的日志文件的路径及文件名 -->
              <file>${log.path}/log_error.log</file>
              <!--日志文件输出格式-->
              <encoder>
                  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                  <charset>UTF-8</charset> <!-- 此处设置字符集 -->
              </encoder>
              <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
              <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                  <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                  <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                      <maxFileSize>100MB</maxFileSize>
                  </timeBasedFileNamingAndTriggeringPolicy>
                  <!--日志文件保留天数-->
                  <maxHistory>15</maxHistory>
              </rollingPolicy>
              <!-- 此日志文件只记录ERROR级别的 -->
              <filter class="ch.qos.logback.classic.filter.LevelFilter">
                  <level>ERROR</level>
                  <onMatch>ACCEPT</onMatch>
                  <onMismatch>DENY</onMismatch>
              </filter>
          </appender>
      
          <!--
              <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
              <logger>仅有一个name属性,
              一个可选的level和一个可选的addtivity属性。
              name:用来指定受此logger约束的某一个包或者具体的某一个类。
              level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
                    如果未设置此属性,那么当前logger将会继承上级的级别。
          -->
          <!--
              使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
              第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
              第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
           -->
          <!--开发环境:打印控制台-->
          <springProfile name="dev">
              <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
              <logger name="com.guli" level="INFO" />
      
              <!--
                  root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
                  level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
                  可以包含零个或多个appender元素。
              -->
              <root level="INFO">
                  <appender-ref ref="CONSOLE" />
                  <appender-ref ref="INFO_FILE" />
                  <appender-ref ref="WARN_FILE" />
                  <appender-ref ref="ERROR_FILE" />
              </root>
          </springProfile>
      
      
          <!--生产环境:输出到文件-->
          <springProfile name="pro">
      
              <root level="INFO">
                  <appender-ref ref="CONSOLE" />
                  <appender-ref ref="DEBUG_FILE" />
                  <appender-ref ref="INFO_FILE" />
                  <appender-ref ref="ERROR_FILE" />
                  <appender-ref ref="WARN_FILE" />
              </root>
          </springProfile>
      
      </configuration>
      

      模板不用记

    • 将错误日志输出到文件

      在GlobalExceptionHandler.java中

      类上添加注解 @Slf4j

      异常输出语句

       log.error(e.getMessage());
      
    • 将日志堆栈信息输出到文件

      定义工具类

      guli-framework-common下创建util包,创建ExceptionUtil.java工具类

      package com.guli.common.util;
      
      public class ExceptionUtil {
      
      	public static String getMessage(Exception e) {
      		StringWriter sw = null;
      		PrintWriter pw = null;
      		try {
      			sw = new StringWriter();
      			pw = new PrintWriter(sw);
      			// 将出错的栈信息输出到printWriter中
      			e.printStackTrace(pw);
      			pw.flush();
      			sw.flush();
      		} finally {
      			if (sw != null) {
      				try {
      					sw.close();
      				} catch (IOException e1) {
      					e1.printStackTrace();
      				}
      			}
      			if (pw != null) {
      				pw.close();
      			}
      		}
      		return sw.toString();
      	}
      }
      
    • 调用

      log.error(ExceptionUtil.getMessage(e));
      
    • GuliException中创建toString方法

      @Override
      public String toString() {
          return "GuliException{" +
              "message=" + this.getMessage() +
              ", code=" + code +
              '}';
      }
      

前端

  • 下载VsCode

  • 安装插件

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kd9dzgWx-1685015617244)(在线教育.assets/image-20230203165527820.png)]

  • 创建工作区

    前端代码都写到工作区里面

    • 存在本地创建空文件夹
    • 使用vscode打开创建空文件
    • 把文件夹保存为工作区
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5LbmELbq-1685015617245)(在线教育.assets/image-20230203170117541.png)]

ES6入门

ES6是一套标准一套规范

JavaScript语言的下一代标准

满足ECMAScript标准

最少必要知识

  • let作用的范围

    <script>
        //es6如何定义变量,定义变量特点
        //js 定义var a = 1;
        //es6写法定义变量,使用变量let a = 10;
        //let定义变量有作用范围
        //1 创建代码块,定义变量
        {
            var a =10
            let b=20    
        }
        // 2在代码块,外面输出
        console.log(a)
        console.log(b) //Uncaught ReferenceError: b is not defined
        
    
    </script>
    
  • const声明常量(只读变量)

    // 1、声明之后不允许改变    
    const PI = "3.1415926"
    PI = 3  // TypeError: Assignment to constant variable.
    
  • // 2、一但声明必须初始化,否则会报错
    const MY_AGE  // SyntaxError: Missing initializer in const declaration
    
  • 解构赋值

    //1、数组解构
    // 传统
    let a = 1, b = 2, c = 3
    console.log(a, b, c)
    // ES6
    let [x, y, z] = [1, 2, 3]
    console.log(x, y, z)
    
  • 对象解构

    //2、对象解构
    let user = {name: 'Helen', age: 18}
    // 传统
    let name1 = user.name
    let age1 = user.age
    console.log(name1, age1)
    // ES6
    let { name, age } =  user//注意:结构的变量必须是user中的属性
    console.log(name, age)
    
  • 模板字符串

    模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。

    // 1、多行字符串
    let string1 =  `Hey,
    can you stop angry now?`
    console.log(string1)
    // Hey,
    // can you stop angry now?
    
    // 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
    let name = "Mike"
    let age = 27
    let info = `My Name is ${name},I am ${age+1} years old next year.`
    console.log(info)
    // My Name is Mike,I am 28 years old next year.
    
    // 3、字符串中调用函数
    function f(){
        return "have fun!"
    }
    let string2 = `Game start,${f()}`
    console.log(string2);  // Game start,have fun!
    
  • 声明对象简写

    创建 声明对象简写.html

    const age = 12
    const name = "Amy"
    // 传统
    const person1 = {age: age, name: name}
    console.log(person1)
    // ES6
    const person2 = {age, name}
    console.log(person2) //{age: 12, name: "Amy"}
    

    定义方法简写

    创建 定义方法简写.html

    // 传统
    const person1 = {
        sayHi:function(){
            console.log("Hi")
        }
    }
    person1.sayHi();//"Hi"
    // ES6
    const person2 = {
        sayHi(){
            console.log("Hi")
        }
    }
    person2.sayHi()  //"Hi"
    

    对象拓展运算符

    创建 对象拓展运算符.html

    拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。

    // 1、拷贝对象
    let person1 = {name: "Amy", age: 15}
    let someone = { ...person1 }
    console.log(someone)  //{name: "Amy", age: 15}
    
    // 2、合并对象
    let age = {age: 15}
    let name = {name: "Amy"}
    let person2 = {...age, ...name}
    console.log(person2)  //{age: 15, name: "Amy"}
    

    箭头函数

    创建 箭头函数.html

    箭头函数提供了一种更加简洁的函数书写方式。基本语法是:参数 => 函数体

    // 传统
    var f1 = function(a){
        return a
    }
    console.log(f1(1))
    // ES6
    var f2 = a => a
    console.log(f2(1))
    
    // 当箭头函数没有参数或者有多个参数,要用 () 括起来。
    // 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
    // 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
    var f3 = (a,b) => {
        let result = a+b
        return result
    }
    console.log(f3(6,2))  // 8
    // 前面代码相当于:
    var f4 = (a,b) => a+b
    

    箭头函数多用于匿名函数的定义

Vue入门

  • Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。

    Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

  • 官方网站:https://cn.vuejs.org

  • 入门案例

    • 创建html页面,使用vscode快捷键生成html代码!

    • 引入vue的js文件,类似jquery

      <script src="vue.min.js"></script>
      
    • 在html页面创建div标签,div添加id属性

      <div id="app"></div>
      
    • 创建一个vue对象,固定模板

       <script>
              //创建一个vue对象
              new Vue({
                  el:'#app',//绑定vue作用的方位
                  data:{//定义页面中显示的模型数据
                      message:'hello Vue'
                  }
      
              })
      
          </script>
      
    • 使用 插值表达式,获取data里面定义值

      <!-- {{}} 插值表达式,绑定vue中的data数据 -->
              {{message}}
      
  • 抽取vue代码片段

    • 文件 => 首选项 => 用户代码片段 => 新建全局代码片段/或文件夹代码片段:vue-html.code-snippets

      注意:制作代码片段的时候,字符串中如果包含文件中复制过来的“Tab”键的空格,要换成“空格键”的空格

{
	"vue htm": {
		"scope": "html",
		"prefix": "vuehtml",
		"body": [
			"<!DOCTYPE html>",
			"<html lang=\"en\">",
			"",
			"<head>",
			"    <meta charset=\"UTF-8\">",
			"    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
			"    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
			"    <title>Document</title>",
			"</head>",
			"",
			"<body>",
			"    <div id=\"app\">",
			"",
			"    </div>",
			"    <script src=\"vue.min.js\"></script>",
			"    <script>",
			"        new Vue({",
			"            el: '#app',",
			"            data: {",
			"                $1",
			"            }",
			"        })",
			"    </script>",
			"</body>",
			"",
			"</html>",
		],
		"description": "my vue template in html"
	}
}
  • 基本语法

    基本数据渲染和指令

    创建 01-基本数据渲染和指令.html

    你看到的 v-bind 特性被称为指令。指令带有前缀 v-

    除了使用插值表达式{{}}进行数据渲染,也可以使用 v-bind指令,它的简写的形式就是一个冒号(:)

    data: {
        content: '我是标题',
        message: '页面加载于 ' + new Date().toLocaleString()
    }
    
    <!-- 如果要将模型数据绑定在html属性中,则使用 v-bind 指令
         此时title中显示的是模型数据
    -->
    <h1 v-bind:title="message">
        {{content}}
    </h1>
    <!-- v-bind 指令的简写形式: 冒号(:) -->
    <h1 :title="message">
        {{content}}
    </h1>
    
    • 双向数据绑定

    创建 02-双向数据绑定.html

    双向数据绑定和单向数据绑定:使用 v-model 进行双向数据绑定

    data: {
        searchMap:{
            keyWord: '尚硅谷'
        }
    }
    
    <!-- v-bind:value只能进行单向的数据渲染 -->
    <input type="text" v-bind:value="searchMap.keyWord">
    <!-- v-model 可以进行双向的数据绑定  -->
    <input type="text" v-model="searchMap.keyWord">
    <p>您要查询的是:{{searchMap.keyWord}}</p>
    
    • 事件

    创建 03-事件.html

    **需求:**点击查询按钮,按照输入框中输入的内容查找公司相关信息

    在前面的例子基础上,data节点中增加 result,增加 methods节点 并定义 search方法

    data: {
         searchMap:{
             keyWord: '尚硅谷'
         },
         //查询结果
         result: {}
    },
    methods:{
        search(){
            console.log('search')
            //TODO
        }
    }
    

    html中增加 button 和 p

    使用 v-on 进行数件处理,v-on:click 表示处理鼠标点击事件,事件调用的方法定义在 vue 对象声明的 methods 节点中

    <!-- v-on 指令绑定事件,click指定绑定的事件类型,事件发生时调用vue中methods节点中定义的方法 -->
    <button v-on:click="search()">查询</button>
    <p>您要查询的是:{{searchMap.keyWord}}</p>
    <p><a v-bind:href="result.site" target="_blank">{{result.title}}</a></p>
    

    完善search方法

    search(){
        console.log('search');
        this.result = {
            "title":"尚硅谷",
            "site":"http://www.atguigu.com"
        }
    }
    

    简写

    <!-- v-on 指令的简写形式 @ -->
    <button @click="search()">查询</button>
    
    • 修饰符

    创建 04-修饰符.html

    修饰符 (Modifiers) 是以半角句号(.)指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

    例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():

    即阻止事件原本的默认行为

    data: {
        user: {}
    }
    
    <!-- 修饰符用于指出一个指令应该以特殊方式绑定。
         这里的 .prevent 修饰符告诉 v-on 指令对于触发的事件调用js的 event.preventDefault():
         即阻止表单提交的默认行为 -->
    <form action="save" v-on:submit.prevent="onSubmit">
        <label for="username">
            <input type="text" id="username" v-model="user.username">
            <button type="submit">保存</button>
        </label>
    </form>
    
    methods: {
        onSubmit() {
            if (this.user.username) {
                console.log('提交表单')
            } else {
                alert('请输入用户名')
            }
        }
    }
    
    • 条件渲染

    创建 05-条件渲染.html

    v-if:条件指令

    data: {
        ok: false
    }
    

    注意:单个复选框绑定到布尔值

    <input type="checkbox" v-model="ok">同意许可协议
    <!-- v:if条件指令:还有v-else、v-else-if 切换开销大 -->
    <h1 v-if="ok">if:Lorem ipsum dolor sit amet.</h1>
    <h1 v-else>no</h1>
    

    v-show:条件指令

    使用v-show完成和上面相同的功能

    <!-- v:show 条件指令 初始渲染开销大 -->
    <h1 v-show="ok">show:Lorem ipsum dolor sit amet.</h1>
    <h1 v-show="!ok">no</h1>
    
    • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

    • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

    • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

    • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

    • 列表渲染

    创建 06-列表渲染.html

    v-for:列表循环指令

    例1:简单的列表渲染

    <!-- 1、简单的列表渲染 -->
    <ul>
        <li v-for="n in 10">{{ n }} </li>
    </ul>
    <ul>
        <!-- 如果想获取索引,则使用index关键字,注意,圆括号中的index必须放在后面 -->
        <li v-for="(n, index) in 5">{{ n }} - {{ index }} </li>
    </ul>
    

    例2:遍历数据列表

    data: {
        userList: [
            { id: 1, username: 'helen', age: 18 },
            { id: 2, username: 'peter', age: 28 },
            { id: 3, username: 'andy', age: 38 }
        ]
    }
    
    <!-- 2、遍历数据列表 -->
    <table border="1">
        <!-- <tr v-for="item in userList"></tr> -->
        <tr v-for="(item, index) in userList">
            <td>{{index}}</td>
            <td>{{item.id}}</td>
            <td>{{item.username}}</td>
            <td>{{item.age}}</td>
        </tr>
    </table>
    

vue进阶

  • 1、局部组件

    创建 01-1-局部组件.html

    定义组件

    var app = new Vue({
        el: '#app',
        // 定义局部组件,这里可以定义多个局部组件
        components: {
            //组件的名字
            'Navbar': {
                //组件的内容
                template: '<ul><li>首页</li><li>学员管理</li></ul>'
            }
        }
    })
    

    使用组件

    <div id="app">
        <Navbar></Navbar>
    </div>
    

    2、全局组件

    创建 01-2-全局组件.html

    定义全局组件:components/Navbar.js

    // 定义全局组件
    Vue.component('Navbar', {
        template: '<ul><li>首页</li><li>学员管理</li><li>讲师管理</li></ul>'
    })
    
    <div id="app">
        <Navbar></Navbar>
    </div>
    <script src="vue.min.js"></script>
    <script src="components/Navbar.js"></script>
    <script>
        var app = new Vue({
            el: '#app'
        })
    </script>
    

    二、实例生命周期

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tAGlwZBk-1685015617247)(在线教育.assets/0.9177152660737906.png)]

    创建 03-vue实例的生命周期.html

    data: {
        message: '床前明月光'
    },
    methods: {
        show() {
            console.log('执行show方法')
        },
        update() {
            this.message = '玻璃好上霜'
        }
    },
    
    <button @click="update">update</button>
    <h3 id="h3">{{ message }}</h3>
    

    分析生命周期相关方法的执行时机

    //===创建时的四个事件
    beforeCreate() { // 第一个被执行的钩子方法:实例被创建出来之前执行
        console.log(this.message) //undefined
        this.show() //TypeError: this.show is not a function
        // beforeCreate执行时,data 和 methods 中的 数据都还没有没初始化
    },
    created() { // 第二个被执行的钩子方法
        console.log(this.message) //床前明月光
        this.show() //执行show方法
        // created执行时,data 和 methods 都已经被初始化好了!
        // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
    },
    beforeMount() { // 第三个被执行的钩子方法
        console.log(document.getElementById('h3').innerText) //{{ message }}
        // beforeMount执行时,模板已经在内存中编辑完成了,尚未被渲染到页面中
    },
    mounted() { // 第四个被执行的钩子方法
        console.log(document.getElementById('h3').innerText) //床前明月光
        // 内存中的模板已经渲染到页面,用户已经可以看见内容
    },
    //===运行中的两个事件
    beforeUpdate() { // 数据更新的前一刻
        console.log('界面显示的内容:' + document.getElementById('h3').innerText)
        console.log('data 中的 message 数据是:' + this.message)
        // beforeUpdate执行时,内存中的数据已更新,但是页面尚未被渲染
    },
    updated() {
        console.log('界面显示的内容:' + document.getElementById('h3').innerText)
        console.log('data 中的 message 数据是:' + this.message)
        // updated执行时,内存中的数据已更新,并且页面已经被渲染
    }
    

    四、路由

    Vue.js 路由允许我们通过不同的 URL 访问不同的内容。

    通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。

    Vue.js 路由需要载入 vue-router 库

    创建 04-路由.html

    1、引入js

    <script src="vue.min.js"></script>
    <script src="vue-router.min.js"></script>
    

    2、编写html

    <div id="app">
        <h1>Hello App!</h1>
        <p>
            <!-- 使用 router-link 组件来导航. -->
            <!-- 通过传入 `to` 属性指定链接. -->
            <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
            <router-link to="/">首页</router-link>
            <router-link to="/student">会员管理</router-link>
            <router-link to="/teacher">讲师管理</router-link>
        </p>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
    </div>
    

    3、编写js

    <script>
        // 1. 定义(路由)组件。
        // 可以从其他文件 import 进来
        const Welcome = { template: '<div>欢迎</div>' }
        const Student = { template: '<div>student list</div>' }
        const Teacher = { template: '<div>teacher list</div>' }
        // 2. 定义路由
        // 每个路由应该映射一个组件。
        const routes = [
            { path: '/', redirect: '/welcome' }, //设置默认指向的路径
            { path: '/welcome', component: Welcome },
            { path: '/student', component: Student },
            { path: '/teacher', component: Teacher }
        ]
        // 3. 创建 router 实例,然后传 `routes` 配置
        const router = new VueRouter({
            routes // (缩写)相当于 routes: routes
        })
        // 4. 创建和挂载根实例。
        // 从而让整个应用都有路由功能
        const app = new Vue({
            el: '#app',
            router
        })
        // 现在,应用已经启动了!
    </script>
    

    五、axios

    axios是独立于vue的一个项目,基于promise用于浏览器和node.js的http客户端

    • 在浏览器中可以帮助我们完成 ajax请求的发送
    • 在node.js中可以向远程接口发送请求

    获取数据

    <script src="vue.min.js"></script>
    <script src="axios.min.js"></script>
    

    注意:测试时需要开启后端服务器,并且后端开启跨域访问权限

    //固定的结构
                data: {//在data定义变量和初始值
                    //定义变量,值空数组
                    userList:[]
                },
                created(){//在页面渲染之前执行
                    //调用定义的方法
                    this.getUserList()
                },
                methods:{//编写具体的方法
                    //创建方法 查询所有用户的数据
                    getUserList(){
                        //使用axios发送ajax请求
                        //axios.提交方式("请求路径接口").then(箭头函数).catch(箭头函数)
                        axios.get("data.json")
                            .then(response =>{
                                //respons就是请求之后返回数据
                                console.log("****"+response)
                            }) // 请求成功执行then方法
                            .catch(error =>{
    
                            }) // 请求失败执行catch方法
                    }
                }
    

    控制台查看输出

    2、显示数据

    <div id="app">
        <table border="1">
            <tr>
                <td>id</td>
                <td>姓名</td>
            </tr>
            <tr v-for="item in memberList">
                <td>{{item.memberId}}</td>
                <td>{{item.nickname}}</td>
            </td>
        </tr>
    </table>
    </div>
    

    六、element-ui:

    element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建

    官网: Element - 网站快速成型工具

    创建 06-element-ui.html

    将element-ui引入到项目

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ea6VHNoz-1685015617248)(在线教育.assets/67c16425-795a-48bb-ad7d-e0b8fa1c8ea5.png)]

    1、引入css

    <!-- import CSS -->
    <link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">
    

    2、引入js

    <!-- import Vue before Element -->
    <script src="vue.min.js"></script>
    <!-- import JavaScript -->
    <script src="element-ui/lib/index.js"></script>
    

    3、编写html

    <div id="app">
        <el-button @click="visible = true">Button</el-button>
        <el-dialog :visible.sync="visible" title="Hello world">
            <p>Try Element</p>
        </el-dialog>
    </div>
    

    关于.sync的扩展阅读

    https://www.jianshu.com/p/d42c508ea9de

    4、编写js

    <script>
        new Vue({
          el: '#app',
          data: function () {//定义Vue中data的另一种方式
            return { visible: false }
          }
        })
    </script>
    

    测试

axios 入门

  • 1、axios,在vue中发送ajax请求

    • axios是独立的项目,不是vue里面的一部分,使用axios经常和vue一起使用,实现ajax操作

    • 使用axios应用场景

      • 前端发送ajaxi请求,后端接口,返回数据

      • axios的使用

        • 创建html页面,引入js文件,包含两个js文件,vue和ajax

        • 引入文件

              <script src="vue.min.js"></script>
              <script src="axios.min.js"></script>
          
        • 编写axios代码

           //固定的结构
                      data: {//在data定义变量和初始值
                          //定义变量,值空数组
                          userList:[]
                      },
                      created(){//在页面渲染之前执行
                          //调用定义的方法
                          this.getUserList()
                      },
                      methods:{//编写具体的方法
                          //创建方法 查询所有用户的数据
                          getUserList(){
                              //使用axios发送ajax请求
                              //axios.提交方式("请求路径接口").then(箭头函数).catch(箭头函数)
                              axios.get("data.json")
                                  .then(response =>{
                                      //respons就是请求之后返回数据
                                      console.log("****"+response)
                                  }) // 请求成功执行then方法
                                  .catch(error =>{
          
                                  }) // 请求失败执行catch方法
                          }
                      }
          
          {
              "sucess":true,
              "code":20000,
              "message":"成功",
              "data":{
                  "items":[
                      {"name":"lucy","age":20},
                      {"name":"lucy2","age":25},
                      {"name":"lucy3","age":30}
                  ]
              }
          }
          
  • 2、element-ui

  • 3、node.js

    • 什么是nodejs
      • 之前学过java 运行java 需要安装jdk环境
      • 学习的这个nodejs ,是JavaScript的运行环境,用于执行javaScript代码环境,不需要浏览器,直接使用nodejs运行JavaScript代码
      • 模拟服务器效果,比如tomcat
    • 安装nodejs
    • 查看版本
    • 使用nodejs执行javaScript代码
    • 在vscode工具打开cmd窗口,进行js代码执行
  • 4、npm

    • npm是什么

      • Node Package Manager ,是Node.jsNode.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven
    • 安装npm

      • 装nodejs也会安装

      • 查看是否安装

      • 具体操作

        • npm项目初始化操作

          • 使用命令

            npm init -y
            

            默认值

          • 生成package.jsonl类似pom文件

        • npm下载js依赖

          • 命令 npm install 依赖名称

          • 修改镜像

            NPM官方的管理的包都是从 http://npmjs.com下载的,但是这个网站在国内速度很慢。

            这里推荐使用淘宝 NPM 镜像 http://npm.taobao.org/ ,淘宝 NPM 镜像是一个完整 npmjs.com 镜像,同步频率目前为 10分钟一次,以保证尽量与官方服务同步。

            #经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
            npm config set registry https://registry.npm.taobao.org 
            
            #查看npm配置信息
            npm config list
            
            #使用 npm install 安装依赖包的最新版,
            #模块安装的位置:项目目录\node_modules
            #安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本
            #同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 <dependencies>
            npm install jquery
            
            
            #npm管理的项目在备份和传输的时候一般不携带node_modules文件夹
            npm install #根据package.json中的配置下载依赖,初始化项目
            
            
            #如果安装时想指定特定的版本
            npm install jquery@2.1.x
            
            
            #devDependencies节点:开发时的依赖包,项目打包到生产环境的时候不包含的依赖
            #使用 -D参数将依赖添加到devDependencies节点
            npm install --save-dev eslint
            #或
            npm install -D eslint
            
            
            #全局安装
            #Node.js全局安装的npm包和工具的位置:用户目录\AppData\Roaming\npm\node_modules
            #一些命令行工具常使用全局安装的方式
            npm install -g webpack
            
        • #更新包(更新到最新版本)
          npm update 包名
          #全局更新
          npm update -g 包名
          
          #卸载包
          npm uninstall 包名
          #全局卸载
          npm uninstall -g 包名
          
        • 根据package.json文件下载依赖

          • 使用命令,npm install
  • 5、babel

    • 转码器,把es6代码转换成es5代码,es5代码浏览器兼容性很好

    • 安装baael

      • 项目初始化

      • npm install --global babel-cli
        
        #查看是否安装成功
        babel --version
        
    • babel的使用

      • 创建js文件,编写es6代码

        // 转码前
        // 定义数据
        let input = [1, 2, 3]
        // 将数组的每个元素 +1
        input = input.map(item => item + 1)
        console.log(input)
        
      • 配置.babelrc

        {
            "presets": ["es2015"],
            "plugins": []
        }
        
      • 安装es2015转码器

        npm install --save-dev babel-preset-es2015
        
      • 使用命令进行转码

        # 转码结果写入一个文件
        mkdir dist1
        # --out-file 或 -o 参数指定输出文件
        babel src/example.js --out-file dist1/compiled.js
        # 或者
        babel src/example.js -o dist1/compiled.js
        
        # 整个目录转码
        mkdir dist2
        # --out-dir 或 -d 参数指定输出目录
        babel src --out-dir dist2
        # 或者
        babel src -d dist2
        
  • 6、模块化

    • 是什么

      • 开发后端接口时,开发controller service mapper,controller注入service,service注入mapper在后端中,类与类之间的调用成为模块化调用
      • 前端模块化,在前端中,js与js之间成为前端模块化操作
    • CommonJS模块规范

      • 导出模块

        创建common-js模块化/四则运算.js

        // 定义成员:
        const sum = function(a,b){
            return parseInt(a) + parseInt(b)
        }
        const subtract = function(a,b){
            return parseInt(a) - parseInt(b)
        }
        const multiply = function(a,b){
            return parseInt(a) * parseInt(b)
        }
        const divide = function(a,b){
            return parseInt(a) / parseInt(b)
        }
        
      • 写出模块中的成员

        // 导出成员:
        module.exports = {
            sum: sum,
            subtract: subtract,
            multiply: multiply,
            divide: divide
        }
        
      • 简写

        //简写
        module.exports = {
            sum,
            subtract,
            multiply,
            divide
        }
        
      • 导入模块

        创建common-js模块化/引入模块.js

        //引入模块,注意:当前路径必须写 ./
        const m = require('./四则运算.js')
        console.log(m)
        
        const result1 = m.sum(1, 2)
        const result2 = m.subtract(1, 2)
        console.log(result1, result2)
        
    • ES6模块化规范

      • ES6使用 export 和 import 来导出、导入模块。

        导出模块 ES6使用export和import来导出,导入模块。

        export function getList() {
            console.log('获取数据列表')
        }
        
        export function save() {
            console.log('保存数据')
        }
        
      • 导入模块

        //只取需要的方法即可,多个方法用逗号分隔
        import { getList, save } from "./userApi.js"
        getList()
        save()
        
      • 注意:这时的程序无法运行的,因为ES6的模块化无法在Node.js中执行,需要用Babel编辑成ES5后再执行。

        
        
    • ES6模块化的另一种写法

      • 导出模块

        export default {
            getList() {
                console.log('获取数据列表2')
            },
        
            save() {
                console.log('保存数据2')
            }
        }
        
      • 另一种写法

        export default {
            getList() {
                console.log('获取数据列表2')
            },
        
            save() {
                console.log('保存数据2')
            }
        }
        
        import user from "./userApi2.js"
        user.getList()
        user.save()
        
  • 7、webpack

    • 什么是Webpack

      • Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

        从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。

    • 安装webpack工具的插件

      全局安装
      npm install -g webpack webpack-cli
      查看版本号
      webpack -v
      
    • 打包js文件

      • 创建src文件夹

      • src下创建common.js

        exports.info = function (str) {
            document.write(str);
        }
        
      • src下创建utils.js

        exports.add = function (a, b) {
            return a + b;
        }
        
      • src下创建main.js

        const common = require('./common');
        const utils = require('./utils');
        
        common.info('Hello world!' + utils.add(100, 200));
        
      • webpack目录下创建配置文件webpack.config.js

        const path = require("path"); //Node.js内置模块
        module.exports = {
            entry: './src/main.js', //配置入口文件
            output: {
                path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径
                filename: 'bundle.js' //输出文件
            }
        }
        
      • 命令行执行编译命令

        webpack #有黄色警告
        webpack --mode=development #没有警告
        #执行后查看bundle.js 里面包含了上面两个js文件的内容并惊醒了代码压缩
        
      • 也可以配置项目的npm运行命令,修改package.json文件

        "scripts": {
            //...,
            "dev": "webpack --mode=development"
         }
        
      • 运行npm命令执行打包

        npm run dev
        
      • webpack目录下创建index.html

        引用bundle.js

        <body>
            <script src="dist/bundle.js"></script>
        </body>
        
      • 浏览器查看index.html

    • CSS打包

      • 安装style-loader和css-loader

      • Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

        Loader 可以理解为是模块和资源的转换器。

        首先我们需要安装相关Loader插件,css-loader 是将 css 装载到 javascript;style-loader 是让 javascript 认识css

        npm install --save-dev style-loader css-loader 
        
      • 在src下创建css文件

      • 引入css文件

        //css文件引入
        require('./style.css')
        
      • 修改webpack.config.js配置文件

         //在原有output{},加上
         module: {
                rules: [  
                    {  
                        test: /\.css$/,    //打包规则应用到以css结尾的文件上
                        use: ['style-loader', 'css-loader']
                    }  
                ]  
            }
        
      • 在main.js中引入style.css

        require('./style.css');
        

搭建前端环境

  • 选取一个模板(框架)进行环境搭建

    vue-admin-template

    • 资料里面找到模板压缩文件

    • 解压到工作区中

    • 通过vscode的终端打开解压文件,安装依赖

      npm install
      
    • 启动下载好依赖项目

      npm run dev
      

      遇到的问题

      npm报错Failed at the node-sass@4.14.1 postinstall script
      

      解决方法

      卸载node-sass
      npm uninstall node-sass
      再重新安装node-sass
      npm i node-sass@4.14.1 --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ --unsafe-perm
      
    • 前端页面的入口一个是index.html,另一个是main.js

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CirPE1lq-1685015617249)(在线教育.assets/image-20230205232445369.png)]

    • 前端页面环境使用框架(模板),主要基于两种技术实现出来

      vue-admin-template模板 =vue + element-ui

    • 框架build目录

      放项目构建的脚本文件

    • config目录

      index.js 修改 useEslint: false 会检查代码,十分严格

      dev.env.js 修改访问地址

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bzuKBfI-1685015617249)(在线教育.assets/image-20230205233205056.png)]

      • api 定义方法
      • assets 静态文件
      • components 组件/插件部分
      • icons 图标
      • router 路由
      • store 脚本文件
      • styles 样式文件
      • utils 工具类
      • views 具体的页面

讲师管理前端开发

  • 讲师列表(分页条件查询)

    • 先临时把后台管理系统改造本地(临时)后面把登录添加权限框架 spring secutiry

    • 把系统的登录功能改到本地

      https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin/user/login
      
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCFmKiBl-1685015617252)(在线教育.assets/image-20230206145120039.png)]

    • 在config文件下有个dev.env.js

      module.exports = merge(prodEnv, {
        NODE_ENV: '"development"',
        //BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
        BASE_API:'http://localhost:8001'
      }
      
    • 进行登录调用两个方法,login登录操作方法,和info登录之后获取用户信息的方法,所以创建接口两个方法实现登录

      • 根据前端js文件stroe/modules/user.js

      • login返回token值

      • info返回roles name avatar

    • 开发接口

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EEVsSnF1-1685015617252)(在线教育.assets/image-20230206150002234.png)]

      •   //login
            @PostMapping("login")
            public R login(){
                return R.ok().data("token","admin");
            }
          
            //info
            @GetMapping("info")
            public R info(){
                return R.ok().data("roles","[admin]").data("name","admin").data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
            }
        
    • 修改路径

      • /eduservice/user
        

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izv98ITT-1685015617254)(在线教育.assets/image-20230206151156549.png)]

      • 测试出现问题

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92C17fyx-1685015617255)(在线教育.assets/image-20230206151750877.png)]

        :9528/#/login?redirect=%2Fdashboard:1 
                
               Access to XMLHttpRequest at 'http://localhost:8001/eduservice/user/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
        
      • 跨域问题,通过一个地址去访问另外一个地址,这个过程中如果有三个地方任何一个不一样 访问协议,ip地址,端口号9528 跟8001 不一样

      • 跨域解决方式

        (1)在后端接口controller添加注解(常用)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mXoncLkZ-1685015617255)(在线教育.assets/image-20230206152306849.png)]

        (2)使用网关解决(后面讲到)

    • 框架使用过程

      • 添加路由

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsAFRIk3-1685015617256)(在线教育.assets/image-20230206161252261.png)]

        {
            path: '/example',
            component: Layout,
            redirect: '/example/table',
            name: 'Example',
            meta: { title: 'Example', icon: 'example' },
            children: [
              {
                path: 'table',
                name: 'Table',
                component: () => import('@/views/table/index'),
                meta: { title: 'Table', icon: 'table' }
              },
              {
                path: 'tree',
                name: 'Tree',
                component: () => import('@/views/tree/index'),
                meta: { title: 'Tree', icon: 'tree' }
              }
            ]
          }
        
      • 点击某个路由,显示对应页面内容

          {
            path: '/example',
            component: Layout,
            redirect: '/example/table',
            name: 'Example',
            meta: { title: 'Example', icon: 'example' },
            children: [
              {
                path: 'table',
                name: 'Table',
                component: () => import('@/views/table/index'),
                meta: { title: 'Table', icon: 'table' }
              },
              {
                path: 'tree',
                name: 'Tree',
                component: () => import('@/views/tree/index'),
                meta: { title: 'Tree', icon: 'tree' }
              }
            ]
          }
        
      • 在api文件夹创建js文件,定义接口地址和参数

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d643ioQe-1685015617257)(在线教育.assets/image-20230206162712733.png)]

        • 在创建vue文件中引入js文件,调用方法实现功能

          引入 import user from‘…’

          data:{
          
          },
          created(){
          
          },
          methods:{
          
          }
          

          最后element-ui显示数据内容

    • 讲师列表添加

      • 添加路由,修改example

      • 创建路由对应的页面

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gCEuDbN-1685015617258)(在线教育.assets/image-20230206164118211.png)]

      • 在api文件夹红创建teacher.js文件

        import request from '@/utils/request'
        
        export default{
        
            //1 讲师列表(条件查询分页)
            //current当前页 limit每页记录数 teacherQuery条件对象
            getTeacherListPage(current,limit,teacherQuery){
                return request({
                    //url: '/table/list/'+current+"/"+limit,
                    url:`/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
                    method: 'post',
                    //teacherQuery条件对象,后端使用RequestBody获取数据
                    //data表示把对象转换json进行传递到接口里面
                    data:teacherQuery
                  })
            }
        }
        
      • 在讲师列表页面vue调用定义的接口方法,得到接口放回数据

      • 把请求接口获取数据,在页面进行显示

        使用组件 element-ui实现

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYkZQ6KK-1685015617259)(在线教育.assets/image-20230206202826941.png)]

      • <!-- 表格 -->
            <el-table
              v-loading="listLoading"
              :data="list"
              element-loading-text="数据加载中"
              border
              fit
              highlight-current-row>
        
              <el-table-column
                label="序号"
                width="70"
                align="center">
                <template slot-scope="scope">
                  {{ (page - 1) * limit + scope.$index + 1 }}
                </template>
              </el-table-column>
        
              <el-table-column prop="name" label="名称" width="80" />
        
              <el-table-column label="头衔" width="80">
                <template slot-scope="scope">
                  {{ scope.row.level===1?'高级讲师':'首席讲师' }}
                </template>
              </el-table-column>
        
              <el-table-column prop="intro" label="资历" />
        
              <el-table-column prop="gmtCreate" label="添加时间" width="160"/>
        
              <el-table-column prop="sort" label="排序" width="60" />
        
              <el-table-column label="操作" width="200" align="center">
                <template slot-scope="scope">
                  <router-link :to="'/edu/teacher/edit/'+scope.row.id">
                    <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
                  </router-link>
                  <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
        
    • 分页条

      • <!-- 分页 :单向绑定 @绑定事件=v-on -->
            <el-pagination
              :current-page="page" //当前页
              :page-size="limit"	//每页记录数
              :total="total"		//总记录数
              style="padding: 30px 0; text-align: center;"
              layout="total, prev, pager, next, jumper"
              @current-change="getList" //分页的方法 修改添加页码页数
            />
            //需要修改getList方法
            getList(page=1){
            this.page=page
            
        
    • 条件查询功能

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIKjdMQR-1685015617260)(在线教育.assets/image-20230206212822886.png)]

      • 使用element-ui组件实现出来

            <!--查询表单-->
            <el-form :inline="true" class="demo-form-inline">
              <el-form-item>
                <el-input v-model="teacherQuery.name" placeholder="讲师名"/>
              </el-form-item>
        
              <el-form-item>
                <el-select v-model="teacherQuery.level" clearable placeholder="讲师头衔">
                  <el-option :value="1" label="高级讲师"/>
                  <el-option :value="2" label="首席讲师"/>
                </el-select>
              </el-form-item>
        
              <el-form-item label="添加时间">
                <el-date-picker
                  v-model="teacherQuery.begin"
                  type="datetime"
                  placeholder="选择开始时间"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  default-time="00:00:00"
                />
              </el-form-item>
              <el-form-item>
                <el-date-picker
                  v-model="teacherQuery.end"
                  type="datetime"
                  placeholder="选择截止时间"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  default-time="00:00:00"
                />
              </el-form-item>
        
              <el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
              <el-button type="default" @click="getList()">清空</el-button>
            </el-form>
        
      • 清空的功能

      • 清空表单输入数据,查询所有数据

             resetData(){//表示清空的方法
                    //表单输入项目数据清空
                    this.teacherQuery={}
                    //查询所有讲师数据
                    this.getList()
        
                    }
        
  • 讲师添加

    • 点击添加讲师按钮,进入表单页面,输入表单信息

      save.vue
      <template>
        <div class="app-container">
          <el-form label-width="120px">
            <el-form-item label="讲师名称">
              <el-input v-model="teacher.name"/>
            </el-form-item>
            <el-form-item label="讲师排序">
              <el-input-number v-model="teacher.sort" controls-position="right" min="0"/>
            </el-form-item>
            <el-form-item label="讲师头衔">
              <el-select v-model="teacher.level" clearable placeholder="请选择">
                <!--
                  数据类型一定要和取出的json中的一致,否则没法回填
                  因此,这里value使用动态绑定的值,保证其数据类型是number
                -->
                <el-option :value="1" label="高级讲师"/>
                <el-option :value="2" label="首席讲师"/>
              </el-select>
            </el-form-item>
            <el-form-item label="讲师资历">
              <el-input v-model="teacher.career"/>
            </el-form-item>
            <el-form-item label="讲师简介">
              <el-input v-model="teacher.intro" :rows="10" type="textarea"/>
            </el-form-item>
      
            <!-- 讲师头像:TODO -->
      
            <el-form-item>
              <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
            </el-form-item>
          </el-form>
        </div>
      </template>
      
      js
      <script>
      export default {
        data() {
          return {
            teacher: {
              name: '',
              sort: 0,
              level: 1,
              career: '',
              intro: '',
              avatar: ''
            },
            saveBtnDisabled: false // 保存按钮是否禁用,
          }
        },
      
        methods: {
      
          saveOrUpdate() {
            this.saveBtnDisabled = true
            this.saveData()
          },
      
          // 保存
          saveData() {
      
          }
      
        }
      }
      </script>
      
    • 在表单页面点击 保存,提交接口,添加数据库

       //api中定义接口地址
       //添加讲师
          addTeacher(teacher){
              return request({
              url:`/eduservice/teacher/addTeacher`,
              method: 'post',
              data: teacher
          })    
          }
      
       //添加讲师方法
              saveTeacher(){
                teacherApi.addTeacher(this.teacher)
                .then(response =>{//添加成功
                  //提示信息
                    //提示消息
                    this.$message({
                          type: 'success',
                          message: '添加成功!'
                    })
                    //回到列表中页面 路由跳转
                    this.$router.push({path:'/teacher/table'})
                })
      

      遇到了无法导入的问题,设置里面搜索vture 把script 勾选取消,重启就行

  • 讲师删除功能

    • 在每条记录后面添加删除按钮

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhj6C4Ry-1685015617261)(在线教育.assets/image-20230206214503092.png)]

    • 在绑定事件的方法传递id值

      看实体enity
      scope.row.id传递id值
      
    • 在api文件夹中teacher.js中定义删除方法

       //删除讲师
          deleteTeacherId(id){
              return request({
                  //url: '/table/list/'+current+"/"+limit,
                  url:`/eduservice/teacher/${id}/`,
                  method: 'delete'
                })
          }
      
    • 页面中调用方法,实现删除

        //删除讲师的方法
                  removeDataById(id){
                      teacher.deleteTeacherId(id)
                      .then(response=>{
                          //提示消息
                          console.log(response)
      
                          //回到列表页面
                          this.getList()
                      })
                      .catch(error =>{
      
                      })
                  }
      
    • 用户体验不佳

      • 做个弹出框提示

             removeDataById(id){
                        this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                            type: 'warning'
                        }).then(() => { //点击确定,执行then方法
                        teacher.deleteTeacherId(id)
                        .then(response=>{
                            //提示消息
                            this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                            //回到列表页面
                            this.getList()
                        })
                        }).catch(() => {//点击取消,执行then方法
                        this.$message({ 
                            type: 'info',
                            message: '已取消删除'
                        });          
                        });
                    }
        
  • 讲师修改功能

    • 每条记录后面添加 修改按钮

    • 点击修改按钮,进入表单页面,进行数据回显

      • 根据讲师id查询数据显示
    • 通过路由跳转进入数据回显页面,在路由index页面添加路由

      • {
                path: 'edit/:id', //:相当于占位符
                name: 'EduTeacherEdit',
                component: () => import('@/views/edu/teacher/form'),
                meta: { title: '编辑讲师', noCache: true },
                hidden: true //隐藏路由
              }
              //添加到index.js下面
        

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9miZY0W-1685015617262)(在线教育.assets/image-20230207101615565.png)]

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UX1wKOO9-1685015617263)(在线教育.assets/image-20230207101823009.png)]

    • 在表单页面中,实现数据回显

      • 在teacher.js定义根据id查询接口

         //根据id查询
            getTeacherInfo(id){
                return request({
                    url:`/eduservice/teacher/getTeacher/${id}`,
                    method: 'get',
            })
            }
        
      • 在页面调用接口实现数据回显 save.vue

          //根据讲师id查询的方法
                getInfo(id){
                  teacherApi.getTeacherInfo(id)
                  .then(response=>{
                    this.teacher=response.data.teacher
                  })
                },
        
      • 调用 根据id查询的方法

        因为添加修改使用sava页面,区别添加还是修改

        判断路径里面是否有讲师id值,如果有id值,没有id值,直接做添加

         created(){//页面渲染之前执行
              //判断路径是否有id值
              if (this.$route.params && this.$route.params.id) {
                  //从路径获取id值
                  const id = this.$route.params.id
                  //调用根据id查询方法
                  this.getInfo(id)
            }
        
    • 最终修改实现

      • 在api的teacher.js 定义修改接口

        //修改讲师
            updateTeacherInfo(teacher){
                return request({
                    url:`/eduservice/teacher/updateTeacher`,
                    method: 'post',
                    data:teacher
            })
            }
        
      • 在页面中调用修改的方法save.vue

        //修改讲师的方法
                updateTeacher(){
                  teacherApi.updateTeacherInfo(this.teacher)
                  .then(response=>{
                      //提示消息
                      this.$message({
                            type: 'success',
                            message: '修改成功!'
                      })
                      //回到列表中页面 路由跳转
                      this.$router.push({path:'/teacher/table'})
                  })
                },
        
      • 要判断按钮是修改还是添加,根据是否传来id值进行判断

        //保存or修改方法
                saveOrUpdate(){
                  //判断修改还是添加
                  //根据teacher是否有id值
                  if(this.teacher.id){
                    //添加
                    this.saveTeacher()
                  }else{
                    //修改
                    this.updateTeacher()
                  }
                },
        
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PecM3FC9-1685015617263)(在线教育.assets/image-20230207110734882.png)]

        出现以上问题,后端方法测试没问题,就是前端的问题

        最后发现if判断语句错误(!this.teacher.id)

      • 遇到问题

        • 第一次点击修改,数据回显,第二次再去点击,添加讲师,进入表单页面,但是问题:表单页面还是显示修改回显数据,正确效果应该是表单数据清空

        • 解决方法,做添加讲师的时候,表单数据清空就可以了

          [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRwPpBVj-1685015617264)(在线教育.assets/image-20230207111529745.png)]

        • 但以上代码并没解决问题,why

          多次路由跳转都到同一个页面,但created()只会执行一次

        • 最终解决方式,使用vue监听

          watch: {
              $route(to, from) {
                console.log('watch $route')
                this.init()
              }
            },
          
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p4HkqZXT-1685015617265)(在线教育.assets/image-20230207112240756.png)]

  • 添加讲师实现头像上传功能

    • 阿里云oss存储服务

      • 打开阿里云网站 https://www.aliyun.com/

      • 注册阿里云账户,最好使用支付宝,需要实名认证

      • 登录阿里云

      • 使用存储oss

        • 首先创建bucket[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2xAMM5p-1685015617267)(在线教育.assets/image-20230207114633016.png)]

        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TaJYFsVs-1685015617268)(在线教育.assets/image-20230207115152850.png)]

        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VgB2qD1G-1685015617269)(在线教育.assets/image-20230207115453239.png)]

        • java代码操作阿里云oss上传文件到阿里云oss操作

          • 创建操作阿里云oss许可证(阿里云颁发id和密钥)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0mlx4To-1685015617269)(在线教育.assets/image-20230207143054474.png)]

          • LTAI5tH7AWEE49HMPDZFZ9z4
            XCE6NFdwIte1N67W658jx83Hn3yE0J[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFEpj8O4-1685015617270)(在线教育.assets/image-20230207145015314.png)]

          • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lv3HRMLE-1685015617271)(在线教育.assets/image-20230207145228695.png)]

          • idea service 新建一个service module maven service_oss

          • 配置pom文件

              <dependencies>
                        <!-- 阿里云oss依赖 -->
                        <dependency>
                            <groupId>com.aliyun.oss</groupId>
                            <artifactId>aliyun-sdk-oss</artifactId>
                        </dependency>
            
                        <!-- 日期工具栏依赖 -->
                        <dependency>
                            <groupId>joda-time</groupId>
                            <artifactId>joda-time</artifactId>
                        </dependency>
                </dependencies>
            
          • 配置文件

            #服务端口
            server.port=8002
            #服务名
            spring.application.name=service-oss
            
            #环境设置:dev、test、prod
            spring.profiles.active=dev
            
            #阿里云 OSS
            #不同的服务器,地址不同
            aliyun.oss.file.endpoint=oss-cn-hangzhou.aliyuncs.com
            aliyun.oss.file.keyid=LTAI5tH7AWEE49HMPDZFZ9z4
            aliyun.oss.file.keysecret=XCE6NFdwIte1N67W658jx83Hn3yE0J
            #bucket可以在控制台创建,也可以使用java代码创建
            aliyun.oss.file.bucketname=edu-10086110
            
          • spring boot 会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration这个类,

            而DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了dataSource bean,又因为项目(oss模块)中并没有关于dataSource相关的配置信息,所以当spring创建dataSource bean时因缺少相关的信息就会报错。

            exclude = DataSourceAutoConfiguration.class
            
          • 上传代码

            • 创建启动类

              package com.atguigu.oss;
              
              import org.springframework.boot.SpringApplication;
              import org.springframework.boot.autoconfigure.SpringBootApplication;
              import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
              import org.springframework.context.annotation.ComponentScan;
              
              @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
              @ComponentScan(basePackages = {"com.atguigu"})
              public class OssApplication {
                  public static void main(String[] args) {
                      SpringApplication.run(OssApplication.class, args);
                  }
              }
              
              
            • 创建常量类,读取配置文件内容

              
              //当项目已启动,spring接口,spring加载之后,执行接口一个方法
              @Component
              public class ConstantPropertiesUtils implements InitializingBean {
              
                  //读取配置文件内容
                  @Value("${aliyun.oss.file.endpoint}")
                  private String endpoint;
              
                  @Value("${aliyun.oss.file.keyid}")
                  private String keyId;
              
                  @Value("${aliyun.oss.file.keysecret}")
                  private String keySecret;
              
                  @Value("${aliyun.oss.file.bucketname}")
                  private String bucketName;
              
                  //定义公开静态常量
                  public static String END_POINT;
                  public static String ACCESS_KEY_ID;
                  public static String ACCESS_KEY_SECRET;
                  public static String BUCKET_NAME;
              
                  @Override
                  public void afterPropertiesSet() throws Exception{
                      END_POINT=endpoint;
                      ACCESS_KEY_ID = keyId;
                      ACCESS_KEY_SECRET = keySecret;
                      BUCKET_NAME = bucketName;
                      
                  }
              }
              
            • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTvoBdQ2-1685015617272)(在线教育.assets/image-20230207153125117.png)]

            • 创建controller

              @RestController
              @RequestMapping("/eduoss/fileoss")
              public class OssController {
              
                  @Autowired
                  private OssService ossService;
                  //上传头像的方法
                  @PostMapping
                  public R uploadOssFile(MultipartFile file){
                      //获取上传的文件
                      String url = ossService.uploadFileAvatar(file);
                      return R.ok().data("url",url);
                  }
              }
              
            • 在service实现上传文件到oss过程

              @Service
              public class OssServiceImpl implements OssService {
              
                  //上传头像到oss
                  @Override
                  public String uploadFileAvatar(MultipartFile file) {
              
                      String endpoint = ConstantPropertiesUtils.END_POINT;
                      String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
                      String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
                      String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
              
                      try {
                          // 创建OSSClient实例。
                          OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
              
                          //上传文件流
                          InputStream inputStream =file.getInputStream();
              
                          //获取一下文件的名称
                          String fileName =file.getOriginalFilename();
                          //调用oss方法实现上传
                          //第一个参数 Bucket名称
                          //第二个参数 上传到oss文件路径,和文件名称
              
              
                          ossClient.putObject(bucketName,fileName,inputStream);
              
                          //关闭OSSClient
                          ossClient.shutdown();
              
                          //把上传之后文件路径放回
                          //需要把上传到阿里云oss路径手动拼接出来
                          //
                          String url="https://"+bucketName+"."+endpoint+"/"+fileName;
                          return url;
                      }catch (Exception e){
                          e.printStackTrace();
                          return null;
                      }
                  }
              }
              
            • 遇到的问题

              • 多次上传相同名称文件,造成最后一次上传把之前上传文件覆盖

                • 在文件名称添加随机唯一值,让每个文件名称不同

                • 把文件进行分类管理

                  • 根据日期进行分类,实现年月日分类

                  • //获取一下文件的名称
                                String fileName =file.getOriginalFilename();
                    
                                //1、在文件名称里面添加随机唯一的值
                                String uuid = UUID.randomUUID().toString().replaceAll("-","");
                                //yuy76t5rew01.jpg
                                fileName =uuid +fileName;
                    
                                // 2、把文件安装日期进行分类
                                //2023/2/7/02.jpg
                                //获取当前日期
                                String datePath= new DateTime().toString("yyyy/MM/dd");
                                
                                //拼接
                                fileName = datePath+"/"+fileName;
                    
  • nginx 反向代理服务器

    • 请求转发

      • 什么是请求转发[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g99wj9Km-1685015617273)(在线教育.assets/image-20230207174023507.png)]
    • 负载均衡 把请求分布到不同服务器中去

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cYO5jrC-1685015617273)(在线教育.assets/image-20230207174252536.png)]
    • 动静分离

      • java和普通的页面分开来
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yFxwDPo-1685015617274)(在线教育.assets/image-20230207175202385.png)]

    • 手动停止再启动

    • 配置nginx实现请求转发的功能

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0WUkEDr-1685015617275)(在线教育.assets/image-20230207175314637.png)]

      • 修改nginx默认端口,把80修改成81[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QwKJUdb-1685015617276)(在线教育.assets/image-20230207175505401.png)]

      • 配置nginx转发规则

        • 在http{}里面创建配置

          server {
          
          		listen 9001; //监听端口
          		server_name localhost; //主机
          						//匹配路径
          		location ~ /eduservice/ {           
          			 proxy_pass http://localhost:8001;
          			 				//转发服务器地址
          		}
          		
          		location ~ /eduoss/ {           
          			 proxy_pass http://localhost:8002;
          		}
          		
          		
          	}
          
        • 修改前端页面地址[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VHZJ8qtI-1685015617277)(在线教育.assets/image-20230207180324006-1675764205218-1.png)]

    • 使用element-ui组件实现

      • 到源码中道道组件复制到前端项目 src components里面[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zW1jxH1L-1685015617278)(在线教育.assets/image-20230207194247446.png)]

      • 添加讲师页面使用这个复制上传组件

        <el-form-item label="讲师头像">
        
            <!-- 头衔缩略图 -->
            <pan-thumb :image="teacher.avatar"/>
            <!-- 文件上传按钮 -->
            <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
            </el-button>
        
            <!--
        v-show:是否显示上传组件
        :key:类似于id,如果一个页面多个图片上传控件,可以做区分
        :url:后台上传的url地址
        @close:关闭上传组件
        @crop-upload-success:上传成功后的回调 -->
            <image-cropper
                           v-show="imagecropperShow"
                           :width="300"
                           :height="300"
                           :key="imagecropperKey"
                           :url="BASE_API+'/admin/oss/file/upload'"
                           field="file"
                           @close="close"
                           @crop-upload-success="cropSuccess"/>
        
        </el-form-item>
        
        

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZTzhhks-1685015617278)(在线教育.assets/image-20230207195810264.png)]

      • 引入组件模块

        import ImageCropper from '@/components/ImageCropper'
        import PanThumb from '@/components/PanThumb'
        
      • js脚本实现上传和图片回显

        export default {
          components: { ImageCropper, PanThumb },
          data() {
            return {
              //其它数据模型
              ......,
                
              BASE_API: process.env.BASE_API, // 接口API地址
              imagecropperShow: false, // 是否显示上传组件
              imagecropperKey: 0 // 上传组件id
            }
          },
            
          ......,
            
          methods: {
            //其他函数
            ......,
        
            // 上传成功后的回调函数
            cropSuccess(data) {
              console.log(data)
              this.imagecropperShow = false
              this.teacher.avatar = data.url
              // 上传成功后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
              this.imagecropperKey = this.imagecropperKey + 1
            },
        
            // 关闭上传组件
            close() {
              this.imagecropperShow = false
              // 上传失败后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
              this.imagecropperKey = this.imagecropperKey + 1
            }
          }
        }
        
  • 添加课程分类功能

    • 使用EasyExcel读取excel内容添加数据

      • 操作excel进行读和写操作

          • 引入easyexcel依赖

            <dependencies>
                <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>easyexcel</artifactId>
                    <version>2.1.1</version>
                </dependency>
            </dependencies>
            

            还需要poi依赖,但在父工程引用过了

          • 建立与excel对应的实体类

            @Data
            public class DemoData {
                
                //设置excel表头名字
                @ExcelProperty("学生编号")
                private Integer sno;
            
                @ExcelProperty("学生姓名")
                private String sname;
            }
            
          • public class TestEasyExcel {
                public static void main(String[] args) {
                    //实现excel写操作
                    //1、设置写入文件夹地址和excel文件名称
                    String filename ="F:\\write.xlsx";
            
                    //2、调用easyexcel里面的方法实现写操作
                    EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData());
                }
            
                //创建方法放回list集合
                private static List<DemoData> getData(){
                    List<DemoData> list = new ArrayList<>();
                    for (int i =0;i<10;i++){
                        DemoData data = new DemoData();
                        data.setSno(i);
                        data.setSname("lucy"+i);
                        list.add(data);
                    }
                    return list;
                }
            }
            
          • 创建和excel对应实体类,标记对应列关系

              //设置excel表头名字
                @ExcelProperty(value = "学生编号",index = 0)
                private Integer sno;
            
                @ExcelProperty(value = "学生姓名",index = 1)
                private String sname;
            
          • 创建监听进行excel文件读取

            //一行一行的读取excel内容
                @Override
                public void invoke(DemoData data, AnalysisContext analysisContext) {
                    System.out.println("****"+data);
                }
            
                //读取表头内容
                @Override
                public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
                    System.out.println("表头:"+headMap);
                }
            
                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            
                }
            
          • 最终方法的调用

             public static void main(String[] args) {
                    //实现excel写操作
                    //1、设置写入文件夹地址和excel文件名称
                    //String filename ="F:\\write.xlsx";
            
                    //2、调用easyexcel里面的方法实现写操作
                    //EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData());
            
                    //实现excel读操作
                    String filename = "F:\\write.xlsx";
                    EasyExcel.read(filename,DemoData.class,new ExcelListener()).sheet().doRead();
                }
                
            
        • 代码生成器生成代码

        • 创建实体类,和excel对应

          @Data
          @EqualsAndHashCode(callSuper = false)
          @Accessors(chain = true)
          @ApiModel(value="EduSubject对象", description="课程科目")
          public class EduSubject implements Serializable {
          
              private static final long serialVersionUID = 1L;
          
              @ApiModelProperty(value = "课程类别ID")
              @TableId(value = "id", type = IdType.ID_WORKER_STR)
              private String id;
          
              @ApiModelProperty(value = "类别名称")
              private String title;
          
              @ApiModelProperty(value = "父ID")
              private String parentId;
          
              @ApiModelProperty(value = "排序字段")
              private Integer sort;
          
              @ApiModelProperty(value = "创建时间")
              @TableField(fill = FieldFill.INSERT)
              private Date gmtCreate;
          
              @ApiModelProperty(value = "更新时间")
              @TableField(fill = FieldFill.INSERT_UPDATE)
              private Date gmtModified;
          
          
          }
          
        • 创建监听进行excel读取操作

          public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
          
              //因为SubjectExcelListener不能交给spring进行管理,需要自己new,不能注入其他对象
              //不能实现数据库操作
              public EduSubjectService subjectService;
          
              public SubjectExcelListener() {}
              public SubjectExcelListener(EduSubjectService subjectService) {
                  this.subjectService = subjectService;
              }
          
          
              //读取excel内容,一行一行进行读取
              @Override
              public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
                  if(subjectData ==null){
                      throw new GuliException(20001,"文件数据为空");
                  }
          
                  //一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类
                  //判断一级分类是否重复
                  EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
                  if (existOneSubject == null) {//表示没有相同的一级分类,进行添加
                      existOneSubject =new EduSubject();
                      existOneSubject.setParentId("0");
                      existOneSubject.setTitle(subjectData.getOneSubjectName()); //一级分类名称
                      subjectService.save(existOneSubject);
          
                  }
                  //获取一级分类id值
                  String pid =existOneSubject.getId();
                  //添加二级分类
                  //判断二级分类是否重复
                  EduSubject existtwoSubject = this.existtwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
                  if (existtwoSubject == null) {
                      existtwoSubject =new EduSubject();
                      existtwoSubject.setParentId(pid);
                      existtwoSubject.setTitle(subjectData.getTwoSubjectName()); //二级分类名称
                      subjectService.save(existtwoSubject);
          
          
                  }
          
              }
          
              //判断一级分类不能重复添加
              private EduSubject existOneSubject(EduSubjectService subjectService,String name){
                  QueryWrapper<EduSubject>wrapper = new QueryWrapper<>();
                  wrapper.eq("title",name);
                  wrapper.eq("parent_id","0");
                  EduSubject oneSubject=subjectService.getOne(wrapper);
                  return oneSubject;
              }
          
          
              //判断耳机分类不能重复添加
              private EduSubject existtwoSubject(EduSubjectService subjectService,String name,String pid){
                  QueryWrapper<EduSubject>wrapper = new QueryWrapper<>();
                  wrapper.eq("title",name);
                  wrapper.eq("parent_id",pid);
                  EduSubject twoSubject=subjectService.getOne(wrapper);
                  return twoSubject;
              }
          
          
              @Override
              public void doAfterAllAnalysed(AnalysisContext analysisContext) {
          
              }
          }
          
        • 最终调用方法,写入数据库

          @RestController
          @RequestMapping("/eduservice/subject")
          @CrossOrigin
          public class EduSubjectController {
          
              @Autowired
              private EduSubjectService subjectService;
          
              //添加课程分类
              //获取上传过来文件,把文件内容读取出来
              @PostMapping("addSubject")
              public R addSubject(MultipartFile file){
                  //上传过来excel文件
          
                  subjectService.saveSubject(file,subjectService);
                  return R.ok();
              }
          
          }
          
  • 课程分类列表

    • 课程分类列表显示功能

      • 添加课程分类路由,并创建修改路由对应页面路径[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtJAmqAY-1685015617279)(在线教育.assets/image-20230208102930822.png)]

         {
            path: '/subject',
            component: Layout,
            redirect: '/subject/table',
            name: '课程分类管理',
            meta: { title: '课程分类管理', icon: 'example' },
            children: [
              {
                path: 'list',
                name: '课程分类列表',
                component: () => import('@/views/edu/subject/list'),
                meta: { title: '课程分类列表', icon: 'table' }
              },
              {
                path: 'add',
                name: '添加课程分类',
                component: () => import('@/views/edu/subject/save'),
                meta: { title: '添加课程分类', icon: 'tree' }
              },
            ]
          },
        
        
      • 添加课程分类页面,实现效果

        • 添加上传组件,并修改相关参数

          <template>
            <div class="app-container">
              <el-form label-width="120px">
                <el-form-item label="信息描述">
                  <el-tag type="info">excel模版说明</el-tag>
                  <el-tag>
                    <i class="el-icon-download"/>
                    <a :href="OSS_PATH + '/static/01.xls'">点击下载模版</a>
                  </el-tag>
          
                </el-form-item>
          
                <el-form-item label="选择Excel">
                  <el-upload
                    ref="upload"
                    :auto-upload="false"
                    :on-success="fileUploadSuccess"
                    :on-error="fileUploadError"
                    :disabled="importBtnDisabled"
                    :limit="1"
                    :action="BASE_API+'/eduservice/subject/subject/addSubject'"
                    name="file"
                    accept="application/vnd.ms-excel">
                    <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
                    <el-button
                      :loading="loading"
                      style="margin-left: 10px;"
                      size="small"
                      type="success"
                      @click="submitUpload">上传到服务器</el-button>
                  </el-upload>
                </el-form-item>
              </el-form>
            </div>
          </template>
          
        • 点击按钮方法,做个表单提交

          <script>
          export default {
              data(){
                  return {
                  BASE_API: process.env.BASE_API, // 接口API地址
                  importBtnDisabled: false, // 按钮是否禁用,
                  loading: false
                  }
              },
              created(){
          
              },
              methods(){
                  //点击按钮上传文件到接口里面
                  submitUpload(){
                       this.importBtnDisabled = true
                       this.loading = true
                       //js: document.getElementById("upload").submit()
                       this.$refs.upload.submit()
                  },
                  //上传成功
                  fileUploadSuccess(){
                      //提示信息
                      this.loading =false
                      this.$message({
                          type: 'success',
                          message: '添加课程分类成功'
                      })
                      //跳转课程分类列表
          
                  },
                  //上传失败
                  fileUploadError(){
                      this.loading =false
                      this.$message({
                          type: 'error',
                          message: '添加课程分类失败'
                      })
          
                  }
              }
          }
          </script>
          
    • 树形结构显示

      • 参考tree模块把前端显示出来

        <template>
          <div class="app-container">
            <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
        
            <el-tree
              ref="tree2"
              :data="data2"
              :props="defaultProps"
              :filter-node-method="filterNode"
              class="filter-tree"
              default-expand-all
            />
        
          </div>
        </template>
        
        <script>
        export default {
        
          data() {
            return {
              filterText: '',
              data2: [{
                id: 1,
                label: 'Level one 1',
                children: [{
                  id: 4,
                  label: 'Level two 1-1',
                  children: [{
                    id: 9,
                    label: 'Level three 1-1-1'
                  }, {
                    id: 10,
                    label: 'Level three 1-1-2'
                  }]
                }]
              }, {
                id: 2,
                label: 'Level one 2',
                children: [{
                  id: 5,
                  label: 'Level two 2-1'
                }, {
                  id: 6,
                  label: 'Level two 2-2'
                }]
              }, {
                id: 3,
                label: 'Level one 3',
                children: [{
                  id: 7,
                  label: 'Level two 3-1'
                }, {
                  id: 8,
                  label: 'Level two 3-2'
                }]
              }],
              defaultProps: {
                children: 'children',
                label: 'label'
              }
            }
          },
          watch: {
            filterText(val) {
              this.$refs.tree2.filter(val)
            }
          },
        
          methods: {
            filterNode(value, data) {
              if (!value) return true
              return data.label.indexOf(value) !== -1
            }
          }
        }
        </script>
        
        
        
      • 创建接口,把分类按照要求的分类传递

      • 针对放回数据船舰对应的实体类

        • 一级和二级分类

          @Data
          public class TwoSubject {
              private String id;
              private String title;
          }
          
          
        • 一个实体分类里面有多个二级分类[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B5ihnXmo-1685015617280)(在线教育.assets/image-20230208114841912.png)]

        • 封装一级分类

          List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne); =>变成
          List<Object> finalSubjectList = new ArrayList<>();
          
        •   //课程分类列表(树形)
              @Override
              public List<OneSubject> getAllOneTwoSubject() {
                  //1查询所有一级分类 parentid=0
                  QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
                  wrapperOne.eq("parent_id","0");
            
                  List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
          
          
                  //2查询所有二级分类 parentid!=0
                  QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
                  wrapperTwo.ne("parent_id","0");
              
                  List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
              
                  //创建list集合,用于存储最终封装数据
                  List<OneSubject> finalSubjectList = new ArrayList<>();
              
                  //3封装一级分类
                  //查询出来所有的一级分类list集合遍历,得到每个一级分类对象,获取每个一级分类对象值,
                  //封装到要求的list集合里面,list<OneSubject> finalSubjectList
                  for (int i = 0; i < oneSubjectList.size(); i++) { //遍历oneSubjectList集合
                      //得到oneSubjectList每个eduSubject对象
                      EduSubject eduSubject = oneSubjectList.get(i);
                      //把eduSubject中的值取出来,放到OneSubject对象里面
                      OneSubject oneSubject =new OneSubject();
          //            oneSubject.setId(eduSubject.getId());
          //            oneSubject.setTitle(eduSubject.getTitle());
                      //eduSubject值复制到对应的onesubject对象里面
                      BeanUtils.copyProperties(eduSubject,oneSubject);
                      //把多个OneSubject放到finalSubjectList里面
                      finalSubjectList.add(oneSubject);
          
          
          

              }
    

              //4封装二级分类
              return finalSubjectList;
          }
      ```
    

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdgBgSJj-1685015617281)(在线教育.assets/image-20230208113923116.png)]

        //在一级分类循环遍历所有的二级分类
                  //创建list集合封装每个一级分类的二级分类
                  List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
                  //遍历二级分类list集合
                  for (int m = 0; m < twoSubjectList.size(); m++) {
                      //获取每个二级分类
                      EduSubject tSubject = twoSubjectList.get(m);
                      //判断二级分类parentid和一级分类id是否一样
                      if(tSubject.getParentId().equals(eduSubject.getId())){
                          //如果一样把tSubject值复制到TwoSubject里面,放到twoFinalSubjecyList里面
                          TwoSubject twoSubject = new TwoSubject();
                          BeanUtils.copyProperties(tSubject,twoSubject);
                          twoFinalSubjectList.add(twoSubject);
                      }
                  }
                  //把一级分类下面的所有额日记分类放到一级分类里面
                  oneSubject.setChildren(twoFinalSubjectList);
              }
      
    • 前端实现

      • list.vue,sava.vue进行修改

      • 创建api subject.js

        import request from '@/utils/request'
        
        export default{
        
            //1 课程分类列表
            getAllSubjectList(){
                return request({
                    url:`/eduservice/subject/getAllSubject`,
                    method: 'get'
                  })
            }
        }
        
        
      • 修改list.vue

        <template>
          <div class="app-container">
            <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
        
            <el-tree
              ref="tree2"
              :data="data2"
              :props="defaultProps"
              :filter-node-method="filterNode"
              class="filter-tree"
              default-expand-all
            />
        
          </div>
        </template>
        
        <script>
        import subject from '@/api/edu/subject';
        export default {
        
          data() {
            return {
              filterText: '',
              data2: [], //返回所有分类数据
              defaultProps: {
                children: 'children',
                label: 'title'
              }
            }
          },
          watch: {
            filterText(val) {
              this.$refs.tree2.filter(val)
            }
          },
          created(){
              this.getAllSubjectList()
          },
          methods: {
            getAllSubjectList(){
                subject. getAllSubjectList()
                .then(response =>{
                    this.data2 = response.data.list
                })
            },
            filterNode(value, data) {
              if (!value) return true
              return data.title.toLowerCase().indexOf(value) !== -1
            }
          }
        }
        </script>
        
        
        
    • 课程管理模块需求

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KkDIrjRV-1685015617282)(在线教育.assets/image-20230208155855420.png)]
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xfrU4XHM-1685015617284)(在线教育.assets/image-20230208160141656.png)]
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CX0DM1BX-1685015617284)(在线教育.assets/image-20230208160435134.png)]
    • 添加课程的基本信息功能

      • 使用代码生成器,生成课程相关的代码

      • 创建vo实体类用于表单数据封装

        • @ApiModelProperty(value = "课程ID")
          	private String id;
          
          	@ApiModelProperty(value = "课程讲师ID")
          	private String teacherId;
          
          	@ApiModelProperty(value = "课程专业ID")
          	private String subjectId;
          
          	@ApiModelProperty(value = "课程标题")
          	private String title;
          
          	@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
          	private BigDecimal price;
          
          	@ApiModelProperty(value = "总课时")
          	private Integer lessonNum;
          
          	@ApiModelProperty(value = "课程封面图片路径")
          	private String cover;
          
          	@ApiModelProperty(value = "课程简介")
          	private String description;
          
        • 编写controllerl部分

          @RestController
          @RequestMapping("/eduservice/edu-course")
          @CrossOrigin
          public class EduCourseController {
              @Autowired
              private EduCourseService courseService;
          
              //添加课程基本信息的方法
              @PostMapping("addCourseInfo")
              public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
                  courseService.saveCourseInfo(courseInfoVo);
                  return R.ok();
              }
          
          }
          
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7iHFzCFi-1685015617285)(在线教育.assets/image-20230208181018956.png)]

        • 编写EduCourseServicempl文件

          package com.atguigu.eduservice.service.impl;
          
          import com.atguigu.eduservice.entity.EduCourse;
          import com.atguigu.eduservice.entity.EduCourseDescription;
          import com.atguigu.eduservice.entity.vo.CourseInfoVo;
          import com.atguigu.eduservice.mapper.EduCourseMapper;
          import com.atguigu.eduservice.service.EduCourseDescriptionService;
          import com.atguigu.eduservice.service.EduCourseService;
          import com.atguigu.servicebase.exceptionhandler.GuliException;
          import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
          import org.springframework.beans.BeanUtils;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Service;
          
          /**
           * <p>
           * 课程 服务实现类
           * </p>
           *
           * @author testjava
           * @since 2023-02-08
           */
          @Service
          public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
          
              //课程描述注入
              @Autowired
              private EduCourseDescriptionService courseDescriptionService;
          
              //添加课程基本信息的方法
              @Override
              public void saveCourseInfo(CourseInfoVo courseInfoVo) {
          
                  //1 向课程表添加课程基本信息
                  //CourseInfoVo对象转换eduCourse对象
                  EduCourse eduCourse = new EduCourse();
                  BeanUtils.copyProperties(courseInfoVo,eduCourse);
                  int insert = baseMapper.insert(eduCourse);
          
                  if(insert == 0){
                      //添加失败
                      throw new GuliException(20001,"添加课程信息失败");
          
                  }
          
                  //获取添加之后课程id
                  String cid =eduCourse.getId();
          
                  //2 向课程简介表添加课程简介
                  //edu_course_description
                  EduCourseDescription courseDescription = new EduCourseDescription();
                  courseDescription.setDescription(courseInfoVo.getDescription());
                  //设置描述id就是课程id
                  courseDescription.setId(cid);
                  courseDescriptionService.save(courseDescription);
          
              }
          }
          
          
        • 测试方法

          遇到parent_id not default 方法,在vo实体类加上parent_id 属性
          
      • 前端生成

        • route添加路由

        • 创建页面

        • {
              path: '/course',
              component: Layout,
              redirect: '/course/table',
              name: '课程管理',
              meta: { title: '课程管理', icon: 'example' },
              children: [
                {
                  path: 'list',
                  name: '课程列表',
                  component: () => import('@/views/edu/course/list'),
                  meta: { title: '课程列表', icon: 'table' }
                },
                {
                  path: 'info',
                  name: '添加课程',
                  component: () => import('@/views/edu/course/info'),
                  meta: { title: '添加课程', icon: 'tree' }
                },
                {
                  path: 'info/:id',
                  name: 'EduCourseInfoEdit',
                  component: () => import('@/views/edu/course/info'),
                  meta: { title: '编辑课程基本信息', noCache: true },
                  hidden: true
                },
                {
                  path: 'chapter/:id',
                  name: 'EduCourseChapterEdit',
                  component: () => import('@/views/edu/course/chapter'),
                  meta: { title: '编辑课程大纲', noCache: true },
                  hidden: true
                },
                {
                  path: 'publish/:id',
                  name: 'EduCoursePublishEdit',
                  component: () => import('@/views/edu/course/publish'),
                  meta: { title: '发布课程', noCache: true },
                  hidden: true
                }
              ]
            }
          
        • 添加vue组件

          <template>
          
            <div class="app-container">
          
              <h2 style="text-align: center;">发布新课程</h2>
          
              <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
              </el-steps>
          
              <el-form label-width="120px">
          
                <el-form-item>
                  <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
                </el-form-item>
              </el-form>
            </div>
          </template>
          

          chapter.vue

          <template>
          
            <div class="app-container">
          
              <h2 style="text-align: center;">发布新课程</h2>
          
              <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
              </el-steps>
          
              <el-form label-width="120px">
          
                <el-form-item>
                  <el-button @click="previous">上一步</el-button>
                  <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
                </el-form-item>
              </el-form>
            </div>
          </template>
          
          <script>
          
          export default {
            data() {
              return {
                saveBtnDisabled: false // 保存按钮是否禁用
              }
            },
          
            created() {
              console.log('chapter created')
            },
          
            methods: {
              previous() {
                console.log('previous')
                this.$router.push({ path: '/edu/course/info/1' })
              },
          
              next() {
                console.log('next')
                this.$router.push({ path: '/edu/course/publish/1' })
              }
            }
          }
          </script>
          

          publish.vue

          <template>
          
            <div class="app-container">
          
              <h2 style="text-align: center;">发布新课程</h2>
          
              <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
              </el-steps>
          
              <el-form label-width="120px">
          
                <el-form-item>
                  <el-button @click="previous">返回修改</el-button>
                  <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
                </el-form-item>
              </el-form>
            </div>
          </template>
          
          <script>
          
          export default {
            data() {
              return {
                saveBtnDisabled: false // 保存按钮是否禁用
              }
            },
          
            created() {
              console.log('publish created')
            },
          
            methods: {
              previous() {
                console.log('previous')
                this.$router.push({ path: '/edu/course/chapter/1' })
              },
          
              publish() {
                console.log('publish')
                this.$router.push({ path: '/edu/course/list' })
              }
            }
          }
          </script>
          
        • 表单

          • 添加课程管理路由,隐藏路由,做页面跳转

            info.vue

            <template>
            
              <div class="app-container">
            
                <h2 style="text-align: center;">发布新课程</h2>
            
                <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
                  <el-step title="填写课程基本信息"/>
                  <el-step title="创建课程大纲"/>
                  <el-step title="最终发布"/>
                </el-steps>
            
                <el-form label-width="120px">
            
                <el-form-item label="课程标题">
                <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
                </el-form-item>
            
                <!-- 所属分类 TODO -->
            
            
                <!-- 课程讲师 TODO -->
            
                <el-form-item label="总课时">
                <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right"/>
                </el-form-item>
            
                <!-- 课程简介 TODO -->
                <el-form-item label="课程标题">
                <el-input v-model="courseInfo.description" placeholder=""/>
                </el-form-item>
            
                <!-- 课程封面 TODO -->
            
                <el-form-item label="课程价格">
                <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
                </el-form-item>
            
                <el-form-item>
                <el-button :disabled="saveBtnDisabled" type="primary" @click="saveorUpdate">保存并下一步</el-button>
                </el-form-item>
                </el-form>
              </div>
            </template>
            <script>
            import course from '@/api/edu/course'
            export default {
                data(){
                    return{
                        saveBtnDisabled:false,
                        courseInfo:{
                            title: '',
                            subjectId: '',
                            subjectParentId:'',
                            teacherId: '',
                            lessonNum: 0,
                            description: '',
                            cover: '',
                            price: 0
                        }
                    }
                },
                created(){
            
                },
                methods:{
                    saveorUpdate(){
                        course.addCourseInfo(this.courseInfo)
                            .then(response =>{
                                //提示
                                this.$message({
                                type: 'success',
                                message: '添加课程信息成功!'
                            })
                                 //跳转到第二步
                                this.$router.push({path:'/course/chapter/1'})
                            })
                       
                    }
                }
            }
            </script>
            
          • chapter.vue

            <template>
            
              <div class="app-container">
            
                <h2 style="text-align: center;">发布新课程</h2>
            
                <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
                  <el-step title="填写课程基本信息"/>
                  <el-step title="创建课程大纲"/>
                  <el-step title="最终发布"/>
                </el-steps>
            
                <el-form label-width="120px">
            
                  <el-form-item>
                    <el-button @click="previous">上一步</el-button>
                    <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
                  </el-form-item>
                </el-form>
              </div>
            </template>
            <script>
            export default {
                data(){
                    return{
                        saveBtnDisabled:false
                    }
                },
                created(){
            
                },
                methods:{
                    previous(){
                        this.$router.push({path:'/course/info/1'})
                    },
                    next(){
                        //跳转到第二步
                        this.$router.push({path:'/course/publish/1'})
                    }
                }
            }
            </script>
            
          • publish.vue

            <template>
            
              <div class="app-container">
            
                <h2 style="text-align: center;">发布新课程</h2>
            
                <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
                  <el-step title="填写课程基本信息"/>
                  <el-step title="创建课程大纲"/>
                  <el-step title="最终发布"/>
                </el-steps>
            
                <el-form label-width="120px">
            
                  <el-form-item>
                    <el-button @click="previous">返回修改</el-button>
                    <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
                  </el-form-item>
                </el-form>
              </div>
            </template>
            <script>
            
            export default {
              data() {
                return {
                  saveBtnDisabled: false // 保存按钮是否禁用
                }
              },
            
              created() {
                console.log('publish created')
              },
            
              methods: {
                previous() {
                  console.log('previous')
                  this.$router.push({ path: '/course/chapter/1' })
                },
            
                publish() {
                  console.log('publish')
                  this.$router.push({ path: '/course/list' })
                }
              }
            }
            </script>
            
          • 编写表单页面,实现接口调用

          • 添加之后,返回课程id[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMkkZoNP-1685015617286)(在线教育.assets/image-20230208205028051.png)]

          • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qTCKngc-1685015617287)(在线教育.assets/image-20230208205146140.png)]

      • 把表单提交的数据添加到数据库中

        • 向两张表添加数据:课程表和课程描述表
      • 把讲师和分类使用下拉列表显示

        • 课程分类做成二级联动效果

          • vue

                <el-form-item label="课程讲师">
                <el-select
                    v-model="courseInfo.teacherId"
                    placeholder="请选择">
                    <el-option
                    v-for="teacher in teacherList"
                    :key="teacher.id"
                    :label="teacher.name"
                    :value="teacher.id"/>
                </el-select>
                </el-form-item>
            
          • course.js

                //2查询所有讲师
                getListTeacher(){
                    return request({
                        url:`/eduservice/teacher/findAll`,
                        method: 'get'
                      })
                }
            
          • methods

            //查询所有的讲师
                    getListTeacher(){
                        course.getListTeacher()
                        .then(response=>{
                            this.teacherList=response.data.items
                        })
                    },
            

            [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDGL6nIk-1685015617288)(在线教育.assets/image-20230208214227210.png)]

          • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5g62rr2P-1685015617288)(在线教育.assets/image-20230208220827957.png)]

          • subject.js

            import request from '@/utils/request'
            
            export default{
            
                //1 课程分类列表
                getSubjectList(){
                    return request({
                        url:`/eduservice/subject/getAllSubject`,
                        method: 'get'
                      })
                }
            }
            
            
          • 引入subject,表单组件,引入方法

            <template>
            
              <div class="app-container">
            
                <h2 style="text-align: center;">发布新课程</h2>
            
                <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
                  <el-step title="填写课程基本信息"/>
                  <el-step title="创建课程大纲"/>
                  <el-step title="最终发布"/>
                </el-steps>
            
                <el-form label-width="120px">
            
                <el-form-item label="课程标题">
                <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
                </el-form-item>
            
                <!-- 所属分类 TODO -->
                  <el-form-item label="课程分类">
                    <el-select
                        v-model="courseInfo.subjectParentId"
                        placeholder="请选择">
                        <el-option
                        v-for="subject in subjectOneList"
                        :key="subject.id"
                        :label="subject.title"
                        :value="subject.id"/>
                    </el-select>
                </el-form-item>
            
                <!-- 课程讲师 TODO -->
                <el-form-item label="课程讲师">
                <el-select
                    v-model="courseInfo.teacherId"
                    placeholder="请选择">
                    <el-option
                    v-for="teacher in teacherList"
                    :key="teacher.id"
                    :label="teacher.name"
                    :value="teacher.id"/>
                </el-select>
                </el-form-item>
            
                <el-form-item label="总课时">
                <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right"/>
                </el-form-item>
            
                <!-- 课程简介 TODO -->
                <el-form-item label="课程标题">
                <el-input v-model="courseInfo.description" placeholder=""/>
                </el-form-item>
            
                <!-- 课程封面 TODO -->
            
                <el-form-item label="课程价格">
                <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
                </el-form-item>
            
                <el-form-item>
                <el-button :disabled="saveBtnDisabled" type="primary" @click="saveorUpdate">保存并下一步</el-button>
                </el-form-item>
                </el-form>
              </div>
            </template>
            <script>
            import course from '@/api/edu/course'
            import subject from '@/api/edu/subject'
            export default {
                data(){
                    return{
                        saveBtnDisabled:false,
                        courseInfo:{
                            title: '',
                            subjectId: '', //二级分类id
                            subjectParentId:'', // 一级分类id
                            teacherId: '',
                            lessonNum: 0,
                            description: '',
                            cover: '',
                            price: 0
                        },
            
                        teacherList:[], //封装所有的讲师
                        subjectOneList:[], //一级分类
                        subjectTwoList:[] //二级分类
                    }
                },
                created(){
                    //初始化所有讲师
                    this.getListTeacher()
                    //初始化一级分类
                    this.getOneSubject()
                },
                methods:{
                    //查询所有的一级分类
                    getOneSubject(){
                        subject.getSubjectList()
                        .then(response =>{
                           this.subjectOneList = response.data.list
                        })
                    },
                    //查询所有的讲师
                    getListTeacher(){
                        course.getListTeacher()
                        .then(response=>{
                            this.teacherList=response.data.items
                        })
                    },
                    saveorUpdate(){
                        course.addCourseInfo(this.courseInfo)
                            .then(response =>{
                                //提示
                                this.$message({
                                type: 'success',
                                message: '添加课程信息成功!'
                            })
                                 //跳转到第二步
                                this.$router.push({path:'/course/chapter/'+response.data.courseId})
                            })
                       
                    }
                }
            }
            </script>
            
          • 二级分类

              <!-- 二级分类 -->
                <el-select v-model="courseInfo.subjectId" placeholder="二级分类">
                <el-option
                    v-for="subject in subjectTwoList"
                    :key="subject.id"
                    :label="subject.title"
                    :value="subject.id"/>
                </el-select>
            
          • 绑定事件

            [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PfnDGn1-1685015617289)(在线教育.assets/image-20230208223838435.png)]

          • methods

             subjectLevelOneChanged(value){
                        //value就是一级分类id值
                        //先遍历所有分类,包含一级和二级
                        for(var i =0;i<this.subjectOneList.length;i++){
                            //每个一级分类
                            var oneSubject = this.subjectOneList[i]
                            //判断:所有一级分类和点击一级分类id是否相同
                            if(value === oneSubject.id){
                               //从一级分类获取里面所有的二级分类
                               this.subjectTwoList = oneSubject.children 
                               //把二级分类的id值清空
                               this.courseInfo.subjectId=" "
            
                            }
            
                        }
            
                    },
            
          • 上传封面

            <!-- 课程封面-->
            <el-form-item label="课程封面">
            
              <el-upload
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeAvatarUpload"
                :action="BASE_API+'/admin/oss/file/upload?host=cover'"
                class="avatar-uploader">
                <img :src="courseInfo.cover">
              </el-upload>
            
            </el-form-item>
            
          •  //上传封面成功调用的方法
                    handleAvatarSuccess(res, file){
                        this.courseInfo.cover=res.data.url
              
                    },
                    //上传之前调用的方法
                    beforeAvatarUpload(file){
                        const isJPG = file.type === 'image/jpeg'
                        const isLt2M = file.size / 1024 / 1024 < 2
              
                        if (!isJPG) {
                            this.$message.error('上传头像图片只能是 JPG 格式!')
                        }
                        if (!isLt2M) {
                            this.$message.error('上传头像图片大小不能超过 2MB!')
                        }
                        return isJPG && isLt2M
              
                    },
            
        • 整合文本编辑器

          一、Tinymce可视化编辑器

          参考

          https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce

          https://panjiachen.gitee.io/vue-element-admin/#/example/create

          二、组件初始化

          Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤

          1、复制脚本库

          将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)

          2、配置html变量

          在 guli-admin/build/webpack.dev.conf.js 中添加配置

          使在html页面中可是使用这里定义的BASE_URL变量

          new HtmlWebpackPlugin({
              ......,
              templateParameters: {
                  BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
              }
          })
          

          3、引入js脚本

          在guli-admin/index.html 中引入js脚本

          <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
          <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
          

          三、组件引入

          为了让Tinymce能用于Vue.js项目,vue-element-admin-master对Tinymce进行了封装,下面我们将它引入到我们的课程信息页面

          1、复制组件

          src/components/Tinymce

          2、引入组件

          课程信息组件中引入 Tinymce

          import Tinymce from '@/components/Tinymce'
          export default {
            components: { Tinymce },
            ......
          }
          

          3、组件模板

          <!-- 课程简介-->
          <el-form-item label="课程简介">
              <tinymce :height="300" v-model="courseInfo.description"/>
          </el-form-item>
          

          4、组件样式

          在info.vue文件的最后添加如下代码,调整上传图片按钮的高度

          <style scoped>
          .tinymce-container {
            line-height: 29px;
          }
          </style>
          

          5、图片的base64编码

          Tinymce中的图片上传功能直接存储的是图片的base64编码,因此无需图片服务器

  • 课程大纲列表功能

    • 参考昨天课程分类列表
      • 创建实体类,章节和小姐,在章节实体类,使用list表示小节
    • 修改数据回显
      • 根据课程id查询课程基本信息接口
      • 修改课程信息的接口
  • 内连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x0U6JYgV-1685015617290)(在线教育.assets/image-20230210200245800.png)]

  • 左外连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6zrctA6-1685015617290)(在线教育.assets/image-20230210201711366.png)]

  • 右外连接

    ```

      - 二级分类
    
        ```
          <!-- 二级分类 -->
            <el-select v-model="courseInfo.subjectId" placeholder="二级分类">
            <el-option
                v-for="subject in subjectTwoList"
                :key="subject.id"
                :label="subject.title"
                :value="subject.id"/>
            </el-select>
        ```
    
      - 绑定事件
    
        [外链图片转存中...(img-1PfnDGn1-1685015617289)]
    
      - methods
    
        ```
         subjectLevelOneChanged(value){
                    //value就是一级分类id值
                    //先遍历所有分类,包含一级和二级
                    for(var i =0;i<this.subjectOneList.length;i++){
                        //每个一级分类
                        var oneSubject = this.subjectOneList[i]
                        //判断:所有一级分类和点击一级分类id是否相同
                        if(value === oneSubject.id){
                           //从一级分类获取里面所有的二级分类
                           this.subjectTwoList = oneSubject.children 
                           //把二级分类的id值清空
                           this.courseInfo.subjectId=" "
        
                        }
        
                    }
        
                },
        ```
    
      - 上传封面
    
        ```
        <!-- 课程封面-->
        <el-form-item label="课程封面">
        
          <el-upload
            :show-file-list="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
            :action="BASE_API+'/admin/oss/file/upload?host=cover'"
            class="avatar-uploader">
            <img :src="courseInfo.cover">
          </el-upload>
        
        </el-form-item>
        ```
    
      - ```
         //上传封面成功调用的方法
                handleAvatarSuccess(res, file){
                    this.courseInfo.cover=res.data.url
          
                },
                //上传之前调用的方法
                beforeAvatarUpload(file){
                    const isJPG = file.type === 'image/jpeg'
                    const isLt2M = file.size / 1024 / 1024 < 2
          
                    if (!isJPG) {
                        this.$message.error('上传头像图片只能是 JPG 格式!')
                    }
                    if (!isLt2M) {
                        this.$message.error('上传头像图片大小不能超过 2MB!')
                    }
                    return isJPG && isLt2M
          
                },
        ```
    
    - 整合文本编辑器
    
      # 一、Tinymce可视化编辑器
    
      参考
    
      https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce
    
      https://panjiachen.gitee.io/vue-element-admin/#/example/create 
    
      # 二、组件初始化
    
      Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤
    
      ## 1、复制脚本库
    
      将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)
    
      ## 2、配置html变量
    
      在 guli-admin/build/webpack.dev.conf.js 中添加配置
    
      使在html页面中可是使用这里定义的BASE_URL变量
    
       
    
      ```
      new HtmlWebpackPlugin({
          ......,
          templateParameters: {
              BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
          }
      })
      ```
    
      ## 3、引入js脚本
    
      在guli-admin/index.html 中引入js脚本
    
       
    
      ```
      <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
      <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
      ```
    
      # 三、组件引入
    
      为了让Tinymce能用于Vue.js项目,vue-element-admin-master对Tinymce进行了封装,下面我们将它引入到我们的课程信息页面
    
      ## 1、复制组件
    
      src/components/Tinymce
    
      ## 2、引入组件
    
      课程信息组件中引入 Tinymce
    
       
    
      ```
      import Tinymce from '@/components/Tinymce'
      export default {
        components: { Tinymce },
        ......
      }
      ```
    
      ## 3、组件模板
    
       
    
      ```
      <!-- 课程简介-->
      <el-form-item label="课程简介">
          <tinymce :height="300" v-model="courseInfo.description"/>
      </el-form-item>
      ```
    
      ## 4、组件样式
    
      在info.vue文件的最后添加如下代码,调整上传图片按钮的高度
    
       
    
      ```
      <style scoped>
      .tinymce-container {
        line-height: 29px;
      }
      </style>
      ```
    
      ## 5、图片的base64编码
    
      Tinymce中的图片上传功能直接存储的是图片的base64编码,因此无需图片服务器
    
  • 课程大纲列表功能

    • 参考昨天课程分类列表
      • 创建实体类,章节和小姐,在章节实体类,使用list表示小节
    • 修改数据回显
      • 根据课程id查询课程基本信息接口
      • 修改课程信息的接口
  • 内连接

    [外链图片转存中…(img-x0U6JYgV-1685015617290)]

  • 左外连接

    [外链图片转存中…(img-S6zrctA6-1685015617290)]

  • 右外连接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值