教育平台项目前端:项目前后端接口联调,项目上线部署发布

时间:2022-07-24
本文章向大家介绍教育平台项目前端:项目前后端接口联调,项目上线部署发布,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

项目前后端接口联调

联调准备

运行后台项目

clean 清空项目的编译文件

compile 重新编译项目

将项目部署到 Tomcat:项目名为 lagou_edu_home,端口号: 8080,使用 war 方式部署

部署图片上传路径为 Tomcat 的 webapps 目录下的 upload 目录

最后运行前端项目

首先导入前端项目到 VS Code

运行项目

课程管理首页

Courses.vue 的视图部分代码

<template>
  <section class="courses">
    <!-- 表单部分 -->
    <el-form class="actions" :inline="true" :model="filter">
      <el-form-item class="input-title" label="课程名称">
        <el-input v-model="filter.course_name" type="search" placeholder="课程名称" />
      </el-form-item>

      <el-form-item label="状态">
        <el-select v-model="filter.status" placeholder="课程状态">
          <el-option label="全部" value></el-option>
          <el-option label="已发布" value="1"></el-option>
          <el-option label="草稿" value="0"></el-option>
        </el-select>
      </el-form-item>

      <el-form-item>
        <el-button @click="filterQuery">查询</el-button>
      </el-form-item>

      <el-form-item class="btn-add">
        <el-button type="primary" icon="el-icon-plus" @click="addCourse">新建课程</el-button>
      </el-form-item>
    </el-form>

    <!-- 表格部分 -->
    <el-table :data="courses" v-loading="loading" element-loading-text="数据加载中...">
      <el-table-column prop="id" label="ID" width="100"></el-table-column>
      <el-table-column prop="course_name" label="课程名称" width="200"></el-table-column>
      <el-table-column
        prop="price"
        label="价格"
        align="center"
        width="120"
        :formatter="priceFormatter"
      ></el-table-column>
      <el-table-column prop="sort_num" label="排序" align="center" width="120"></el-table-column>

      <!-- 状态展示 -->
      <el-table-column prop="status" label="状态" align="center" width="120">
        <template slot-scope="scope">
          <i
            class="status status-success"
            title="已发布"
            v-if="scope.row.status == '1'"
            @click="updateStatus(scope.row)"
          ></i>
          <i class="status status-warning" title="草稿" v-else-if="scope.row.status == '0'"></i>
        </template>
      </el-table-column>

      <!-- 操作部分 -->
      <el-table-column label="操作" align="center">
        <template slot-scope="scope">
          <!-- 状态按钮 -->
          <el-button
            size="mini"
            :type="scope.row.status == '1' ? 'danger' : 'success'"
            @click="updateStatus(scope.row)"
          >{{ scope.row.status == "1" ? "下架" : "发布" }}</el-button>

          <!-- 营销信息按钮 -->
          <el-button size="mini" @click="handleNavigate('CourseItem', scope.row.id)">营销信息</el-button>

          <!-- 内容管理按钮 -->
          <el-button size="mini" @click="handleNavigate('CourseTasks', scope.row.id)">内容管理</el-button>
        </template>
      </el-table-column>
    </el-table>
  </section>
</template>
获取课程列表

Courses.vue JS 部分代码

export default {
    name: "Courses",
    title: "课程管理",
    // 定义数据部分
    data() {
        return {
            filter: { course_name: "", status: "" }, // 查询对象
            courses: [], // 课程信息集合
            loading: false // 是否弹出加载
        };
    },
    // 钩子函数
    created() {
        this.loadCourses();
    },
    methods: {
        // 方法 1: 获取课程列表
        loadCourses() {
            this.loading = true;
            // 请求后台查询课程列表接口
            return axios
                .get("/course", {
                params: {
                    methodName: "findCourseList"
                }
            })
                .then(resp => {
                console.log(resp);
                this.loading = false; // 关闭加载
                this.courses = resp.data; // 取出数据
            })
                .catch(error => {
                this.$message.error("数据获取失败! ! !");
            });
        },
    }
};
条件查询课程信息

Courses.vue JS 代码的 methods

//方法 2: 条件查询课程信息
filterQuery() {
    this.loading = true;
    // 定义查询条件对象
    const search = {};
    // 保存用户输入的条件
    if (this.filter.course_name) search.course_name = this.filter.course_name;
    if (this.filter.status) search.status = this.filter.status;
    // 请求后台条件查询接口
    return axios
        .get("/course", {
        // 准备参数
        params: {
            methodName: "findByCourseNameAndStatus",
            course_name: search.course_name,
            status: search.status
        }
    })
        .then(resp => {
        console.log(resp);
        this.loading = false;
        // 将响应数据保存到 courses
        this.courses = resp.data;
    })
        .catch(error => {
        this.$message.error("数据获取失败! ! !");
    });
},
跳转到新建课程页面

Courses.vue JS 代码的 methods

// 方法 3: 添加课程跳转方法 
addCourse() {
    // 路由跳转到 CourseItem.vue 组件
    this.$router.push({ name: "CourseItem", params: { courseId: "new" } });
},
修改课程状态

Courses.vue JS 代码的 methods

// 方法 4: 修改课程状态
updateStatus(item) {
    // item 表示选中的数据 = Course 对象
    axios
        .get("/course", {
        params: {
            methodName: "updateCourseStatus",
            id: item.id
        }
    })
        .then(res => {
        console.log(res);
        // 将返回的状态字段,封装到对象中
        Object.assign(item, res.data);
        // 重新加载页面
        window.location.reload;
    });
},
跳转课程营销或内容管理

Courses.vue JS 代码的 methods

// 方法 5: 根据路由名称,导航到对应组件  
handleNavigate(name, id) {
    this.$router.push({ name, params: { courseId: id } });
},

新建 & 修改课程

Course 组件中的跳转方法
<!-- 营销信息按钮 -->
<el-button
           size="mini"
           @click="handleNavigate('CourseItem', scope.row.id)"
           >营销信息</el-button>
// 方法 3: 添加课程跳转方法
addCourse() {
    // 路由跳转到 CourseItem.vue 组件
    this.$router.push({ name: "CourseItem", params: { courseId: "new" } });
},
`router.js` 路由

找到 name 为: CourseItem 的路由

// 添加课程的路由
{
    path: "/courses/:courseId", // 路径,携带参数: 课程 ID
    name: "CourseItem",
    // 路由导航到的组件
    component: () =>
    import(/* webpackChunkName: 'courses' */ "../views/CourseItem.vue")
},
`CourseItem` 组件

CourseItem.vue 的视图部分代码

<template>
  <section class="course-item">
    <!-- 头部 -->
    <div class="header">
      <!-- 返回上一页 -->
      <el-page-header
        @back="() => this.$router.back()"
        :content="course.title"
      />
      <el-button type="primary" @click="handleSave">保存</el-button>
    </div>

    <!-- 表单开始 -->
    <el-form ref="form" :model="course" :rules="rules" label-width="120px">
      <el-card
        shadow="never"
        v-loading="loading"
        element-loading-text="数据加载中..."
      >
        <header slot="header">基本信息</header>
        <el-form-item label="名称" prop="course_name">
          <el-input
            v-model="course.course_name"
            type="text"
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="简介" prop="brief">
          <el-input
            v-model="course.brief"
            type="text"
            maxlength="100"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="讲师姓名" prop="teacher_name">
          <el-input
            v-model="course.teacher_name"
            type="text"
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="讲师简介" prop="teacher_title">
          <el-input
            v-model="course.teacher_info"
            type="text"
            maxlength="100"
            show-word-limit
          />
        </el-form-item>
        <el-form-item
          label="课程概述"
          prop="preview_first_field"
          class="form-control-summary"
        >
          <el-input
            v-model="course.preview_first_field"
            type="text"
            maxlength="20"
            show-word-limit
          />
          <el-input
            v-model="course.preview_second_field"
            type="text"
            maxlength="20"
            show-word-limit
          />
        </el-form-item>
      </el-card>

      <el-card
        shadow="never"
        v-loading="loading"
        element-loading-text="数据加载中..."
      >
        <header slot="header">销售信息</header>
        <el-form-item label="售卖价格" prop="discounts">
          <el-input v-model="course.discounts" type="number">
            <template slot="append">元</template>
          </el-input>
        </el-form-item>
        <el-form-item label="商品原价">
          <el-input v-model="course.price" type="number">
            <template slot="append">元</template>
          </el-input>
        </el-form-item>
        <el-form-item label="活动标签">
          <el-input
            v-model="course.price_tag"
            type="text"
            maxlength="4"
            show-word-limit
          />
        </el-form-item>
      </el-card>

      <el-card
        shadow="never"
        v-loading="loading"
        element-loading-text="数据加载中..."
      >
        <header slot="header">分享信息</header>
        <!-- 上传图片部分 -->
        <el-form-item label="分享小图" prop="share_image_title">
          <el-input v-model="course.share_image_title" type="text">
            <!-- :auto-upload="false",取消自动上传, :on-change="onchange" 调用 onchange 进行处理 -->
            <el-upload
              slot="prepend"
              :auto-upload="false"
              :on-change="onchange"
              action
              :limit="1"
            >
              <el-button size="small" type="primary">点击上传</el-button>
            </el-upload>
          </el-input>
        </el-form-item>
        <el-form-item label="分享标题" prop="share_title">
          <el-input
            v-model="course.share_title"
            type="text"
            maxlength="40"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="分享简介" prop="share_description">
          <el-input
            v-model="course.share_description"
            type="text"
            maxlength="60"
            show-word-limit
          />
        </el-form-item>
      </el-card>

      <el-card
        shadow="never"
        v-loading="loading"
        element-loading-text="数据加载中..."
      >
        <header slot="course_description">课程详情</header>
        <editor v-model="course.course_description" />
      </el-card>

      <div class="actions">
        <el-button type="primary" @click="handleSave">保存</el-button>
      </div>
    </el-form>

    <!-- 表单结束 -->
  </section>
</template>

CourseItem.vue 的 JS 部分代码

data() {
    // 数据
    return {
        rules, //规则
        course: {}, //课程
        loading: false,
        params: {} //参数对象
    };
},
// 钩子函数
created() {
    // 1.显示当前页面在网站中的位置
    this.$breadcrumbs = [
      { name: "Courses", text: "课程管理" },
      { text: "营销信息" }
    ];

    // 2.从路由中获取传递的参数, 课程 id
    const id = this.$route.params.courseId;

    // 3.判断 id 是否有值
    if (!id) return this.redirectToError();

    // 4.判断是 new 还是具体的id
    if (id === "new") {
      // new 代表新增
      this.course.title = "新增课程";
    } else {
      // 否则就是修改
      this.loadCourse(id);
    }
},
图片上传分析

页面部分

<!-- 上传图片部分 -->
<el-form-item label="分享小图" prop="share_image_title">
    <el-input v-model="course.share_image_title" type="text">
        <!-- :auto-upload="false",取消自动上传, :on-change="onchange" 调用onchange
进行处理 -->
        <el-upload
                   slot="prepend"
                   :auto-upload="false"
                   :on-change="onchange"
                   action
                   :limit="1"
                   >
            <el-button size="small" type="primary">点击上传</el-button>
        </el-upload>
    </el-input>
</el-form-item>

FormData 使用演示

// 通过 FormData 构造函数创建一个空对象
var formdata = new FormData();
// 可以通过 append() 方法来追加数据
formdata.append("name","laotie");
// 通过 get 方法对值进行读取
console.log(formdata.get("name")); // laotie
// 通过 set 方法对值进行设置
formdata.set("name","laoliu");
console.log(formdata.get("name")); // laoliu
  1. 将 form 表单元素的 name 与 value 进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率。
  2. 异步上传文件
  3. 创建 FormData 对象

CourseItem.vue 的 JS 的 methodscreated 部分的代码

//5.创建FormData对象,将图片与表单一同上传
this.params = new FormData();

methods 添加方法

// 文件上传
onchange(file) {
    // 判断文件不为空
    if (file != null) {
        // 将文件信息保存到 params 中
        this.params.append("file", file.raw, file.name);
    }
},
新建课程信息

CourseItem.vue 的 JS 的 methods 的代码

// 方法 1: 保存和修改课程信息
handleSave() {
    // 检查是否拿到了正确的需要验证的 form
    this.$refs.form.validate(valid => {
        if (!valid) return false;

        // 1.设置 Content-Type 为多部件上传
        let config = {
            headers: {
                "Content-Type": "multipart/form-data"
            }
        };

        // 2.获取表单中的数据,保存到 params (params 就是 FromData对象)
        for (let key in this.course) {
            //debugger
            console.log(key + "---" + this.course[key]);
            this.params.append(key, this.course[key]);
        }

        // 3.保存课程信息
        axios
            .post("/courseSalesInfo", this.params, config)
            .then(res => {
            //debugger
            if (res.data.status == ) {
                // 保存成功,跳转到首页
                this.$router.back();
            } else if (res.data.status == ) {
                this.$message({
                    type: "error",
                    message: res.data.msg
                });
            }
        })
            .catch(err => {
            this.$message.error("保存失败! ! !");
        });
    });
},
修改课程信息

CourseItem.vue 的 JS 的 methods 的代码

// 方法 2: 根据 ID 回显课程信
loadCourse(id) {
    this.loading = true;
    axios
        .get("/course", {
        params: {
            methodName: "findCourseById",
            id: id
        }
    })
        .then(res => {
        this.loading = false;
        this.course = res.data;
    })
        .catch(() => {
        this.$message.error("回显数据失败! ! !");
    });
},

内容管理

Course 组件中的跳转方法
<!-- 内容管理按钮 -->
<el-button
           size="mini"
           @click="handleNavigate('CourseTasks', scope.row.id)">内容管理</el-button>
// 方法 5: 根据路由名称, 导航到对应组件
handleNavigate(name, id) {
    this.$router.push({ name, params: { courseId: id } });
},
`router.js` 路由
// 内容管理的路由
{
    path: "/courses/:courseId/tasks",
    name: "CourseTasks",
    meta: { requireAuth: true },
    component: () =>
    import(/* webpackChunkName: 'courses' */ "../views/CourseTasks.vue")
}
`CourseTasks` 组件
树形控件测试
  1. 打开之前编写 Element UI 的项目
  2. 在 component 目录下添加一个组件 TestTree.vue
<template>
</template>
<script>
    export default {

    }
</script>
<style scoped>
</style>
  1. Index.vue 组件中的导航菜单位置添加一个树形控件导航;注意要设置 index 的路径为 “/tree”
<el-submenu index="1">
  <template slot="title">
    <i class="el-icon-location"></i>
    <span>导航菜单</span>
  </template>
  <el-menu-item-group>
    <el-menu-item index="/course">
      <i class="el-icon-menu"></i>
      <span>课程管理</span>
    </el-menu-item>
    <el-menu-item index="/tree">
      <i class="el-icon-menu"></i>
      <span>树形控件</span>
    </el-menu-item>
  </el-menu-item-group>
</el-submenu>
  1. router/index.js 路由文件中进行配置,在布vue局路由中再添加一个子路由
// 导入树形控件组件
import Tree from "@/components/TestTree.vue"

...
  {
    path: "/index",
    name: "index",
    component: Index,
    children: [
      {
        path: "/course",
        name: "course",
        component: Course
      },
       // Tree控件测试路由
      {
        path: "/tree",
        name: "tree",
        component: Tree
      }
    ]
  }
...
  1. ElementUI 官网中查找树形控件
  2. 先来查看基础用法,赋值代码到 TestTree.vue
<template>
    <el-tree :data="data" :props="defaultProps"></el-tree>
</template>

<script>
export default {
    data() {
      return {
        data: [{
          label: '一级 1',
          children: [{
            label: '二级 1-1',
            children: [{
              label: '三级 1-1-1'
            }]
          }]
        }, {
          label: '一级 2',
          children: [{
            label: '二级 2-1',
            children: [{
              label: '三级 2-1-1'
            }]
          }, {
            label: '二级 2-2',
            children: [{
              label: '三级 2-2-1'
            }]
          }]
        }, {
          label: '一级 3',
          children: [{
            label: '二级 3-1',
            children: [{
              label: '三级 3-1-1'
            }]
          }, {
            label: '二级 3-2',
            children: [{
              label: '三级 3-2-1'
            }]
          }]
        }],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    methods: {
    }
  };
</script>

<style scoped>
</style>
  1. Tree 组件属性分析

data:展示数据

props:配置树形结构;label - 设置节点名称,children - 指定生成子树的属性名称

  1. 自定义树节点内容:data - 数据对象,node - 节点对象
<el-tree :data="data" :props="defaultProps">
    <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ data.label }}</span>
        <span> 级别:{{node.level}}</span>
    </span>
</el-tree>
  1. 展示树形结构章节与课时
<template>
  <el-tree :data="data" :props="defaultProps">
    <span class="custom-tree-node" slot-scope="{ node, data }">
      <span>{{ data.section_name || data.theme }}</span>
      <span> 级别:{{node.level}}</span>
    </span>
  </el-tree>
</template>

<script>
export default {
  data() {
    return {
      data: [
        {
          id: 5,
          course_id: 10,
          section_name: "麻式太极",
          description: "麻式太极拳,你动我试试",
          orderNum: 0,
          status: 2,
          create_time: "2019-07-11 10:55:10.0",
          update_time: "2019-10-09 12:43:01.0",
          isDel: 0,
          lessonList: [
            {
              id: 32,
              course_id: 10,
              section_id: 5,
              theme: "第一讲:如何给自己洗脑",
              duration: 10,
              is_free: 1,
              order_num: 1,
              status: 2,
              create_time: "2019-01-23 20:37:02.0",
              update_time: "2020-02-24 18:37:34.0",
              isDel: 0
            },
            {
              id: 33,
              course_id: 10,
              section_id: 5,
              theme: "第二讲:如何给别人洗脑",
              duration: 10,
              is_free: 1,
              order_num: 1,
              status: 2,
              create_time: "2019-01-23 20:37:02.0",
              update_time: "2020-02-24 18:37:34.0",
              isDel: 0
            }
          ]
        }
      ],
      defaultProps: {
        children: "lessonList",
        label: item => {
            return item.section_name || item.theme;
        },
      },
    };
  }
};
</script>
显示当前课程的名称
<el-page-header
        @back="() => this.$router.back()"
        :content="addSectionForm.course_name"
      />

data 数据

data() {
    // 定义章节信息
    const addSectionForm = {
        course_id: undefined,
        course_name: "",
        section_name: "",
        description: "",
        order_num: 
    };

    // 章节与课时信息,树形结构
    const treeProps = {
        label: item => {
            return item.section_name || item.theme;
        },
        children: "lessonList"
    };

    // 定义章节状态信息
    const statusMapping = {
        : "已隐藏",
        : "待更新",
        : "已更新"
    };

    const statusForm = {
        status: 
    };

    return {
        addSectionForm,
        treeProps,
        sections: [],
        statusForm, // 状态表单
        statusMapping,

        loading: false, // 树形控件
        showAddSection: false, // 添加或修改章节
        showStatusForm: false // 状态修改
    };
},

加载课程信息

created() {
    // 1.显示当前页面在网站中的位置
    this.$breadcrumbs = [
        { name: "Courses", text: "课程管理" },
        { text: "课程结构" }
    ];

    // 2.从路由中获取传递的参数, 课程 id
    const id = this.$route.params.courseId;
    if (!id) return this.redirectToError();

    // 3.加载课程信息
    this.loadCourse(id);

    // 4.加载课程对应的章节与课时
    this.loadChildren(id);
},
methods: {
    // 方法 1: 加载课程信息
    loadCourse(id) {
        axios
            .get("/courseContent", {
            params: {
                methodName: "findCourseById",
                course_id: id
            }
        })
            .then(res => {
            // 将数据保存到表单对象中
            this.addSectionForm.course_id = res.data.id;
            this.addSectionForm.course_name = res.data.course_name;
        })
            .catch(error => {
            this.loading = false;
            this.$message.error("数据获取失败! ! !");
        });
    },
},
加载章节与课时信息
// 方法 2: 加载树 (章节与课程)
loadChildren(id) {
    this.loading = true;
    axios
        .get("/courseContent", {
        params: {
            methodName: "findSectionAndLessonByCourseId",
            course_id: id
        }
    })
        .then(resp => {
        // 获取章节数据, 保存到 sections
        this.sections = resp.data;
        this.loading = false;
    })
        .catch(error => {
        this.loading = false;
        this.$message.error("数据获取失败! ! !");
    });
},

Element UI 树形控件 el-tree:data 列表数据;:props 配置选项。

<!-- 树形控件, 展示课程对应的章节信息 -->
<el-tree
         :data="sections"
         :props="treeProps"
         v-loading="loading"
         element-loading-text="数据加载中..."
         >
    <!-- slot-scope: 代表当前树节点内容,有两个参数 data 表示当前树节点, node 表示当前节点状态 -->
    <div class="inner" slot-scope="{ data, node }">
        <span>{{ data.section_name || data.theme }}</span>
        ...
    </div>
    ...
</el-tree>

:props 配置选项:label - 指定节点标签为节点对象的某个属性值;children - 指定子树为节点对象的某个属性值。

// 章节与课时信息, 树形结构
const treeProps = {
    label: item => {
        return item.section_name || item.theme;
    },
    children: "lessonList"
}; 

操作按钮显示:node.level 获取当前节点的级别;@click.stop 事件冒泡,点击哪个元素就执行哪个元素绑定的事件

<span class="actions">
    <!-- 编辑章节按钮  @click.stop 阻止事件冒泡 -->
    <el-button
               v-if="node.level == 1"
               size="small"
               @click.stop="handleEditSection(data)"
               >编辑</el-button>
    <!-- 修改章节状态按钮 -->
    <el-button
               v-if="node.level == 1"
               size="small"
               @click.stop="handleShowToggleStatus(data)"
               >{{ statusMapping[data.status] }}</el-button>
</span>
回显信息
<el-button type="primary" icon="el-icon-plus" @click="handleShowAddSection">添加章节</el-button>

JS 代码

// 方法 3: 显示添加章节表单, 回显课程信息
handleShowAddSection() {
    // 显示表单
    this.showAddSection = true;
},
添加章节
<el-button type="primary" @click="handleAddSection">确 定</el-button>

JS 代码

// 方法 4: 添加&修改章节操作
handleAddSection() {
    axios
        .post("/courseContent", {
        methodName: "saveOrUpdateSection",
        section: this.addSectionForm
    })
        .then(resp => {
        this.showAddSection = false;
        // 重新加载列表
        return this.loadChildren(this.addSectionForm.course_id);
    })
        .catch(error => {
        this.loading = false;
        this.$message.error("操作执行失败! ! !");
    });
},
后台接口问题解决

BaseServlet 中代码修改

if("application/json;charset=utf-8".equals(contentType)) {
    ...
}

// 修改为:

if("application/json;charset=utf-8".equalsIgnoreCase(contentType)) {
    ...
}

CourseContentServlet 中的 saveOrUpdateSection 方法修改

// 使用 BeanUtils 的 copyProperties 方法将 map 中的数据封装到 section 对象里
BeanUtils.copyProperties(section,map.get("section"));
修改章节
<el-button v-if="node.level == 1" size="small" 
           @click.stop="handleEditSection(data)">编辑</el-button>

JS 回显示方法

// 方法 5: 修改章节回显方法
handleEditSection(section) {
    // 对象拷贝
    Object.assign(this.addSectionForm, section);
    this.showAddSection = true;
},

事件冒泡:当点击子元素的事件。如果父元素也有同样的事件的话。他就会一并的触发。

解决冒泡事件的方法:@click.stop

<body>
    <!-- 事件冒泡: 解决方案 @click.stop -->
    <div id="app" @click="log('div点击事件')">
        <button @click.stop="log('button点击事件')">事件冒泡</button>
    </div>
</body>
<script src="./js/vue.min.js"></script>
<script>
    var VM = new Vue({
        el: "#app",
        methods: {
            log(t) {
                alert(t);
            },
        },
    });
</script>
章节状态回显
<!-- 章节状态按钮 -->
<el-button
           v-if="node.level == 1"
           size="small"
           @click.stop="showStatus(data)"
           >{{ statusMapping[data.status] }}</el-button>

JS 代码

// data 函数中定义的章节状态
const statusMapping = {
    : "已隐藏",
    : "待更新",
    : "已更新"
};
// 方法 6: 显示章节状态
showStatus(data) {
    this.statusForm.id = data.id;
    this.statusForm.status = data.status.toString();
    this.statusForm.data = data;
    this.showStatusForm = true;// 显示状态表单
},
Select 选择器
  1. 打开之前编写 Element UI 的项目
  2. 在 component 目录下添加一个组件 TestSelect.vue
<template></template>
<script>
    export default {};
</script>
<style scoped>
</style>
  1. Index.vue 组件中的导航菜单位置添加一个 Select 选择器导航;注意要设置 index 的路径为 /select
<el-menu-item index="/select">
    <i class="el-icon-menu"></i>Select选择器
</el-menu-item>
  1. index.js 路由文件中进行配置,在布局路由中再添加一个子路由
import Select from "@/components/TestSelect.vue"
{
    path: "/select",
    name: "select",
    component: Select,
},
  1. 在 Element UI 官网中查找 Select 选择器
  2. 查看基础用法,将代码复制到 TestSelect.vue
<template>
  <el-select v-model="value" placeholder="请选择">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
  </el-select>
</template>

<script>
  export default {
    data() {
      return {
        options: [{
          value: '选项1',
          label: '黄金糕'
        }, {
          value: '选项2',
          label: '双皮奶'
        }, {
          value: '选项3',
          label: '蚵仔煎'
        }, {
          value: '选项4',
          label: '龙须面'
        }, {
          value: '选项5',
          label: '北京烤鸭'
        }],
        value: ''
      }
    }
  }
</script>

<style scoped>
</style>
  1. select 选择器属性分析

v-model 的值为当前被选中的 el-optionvalue 属性值

el-option 选项:label 选项的标签名;value 选择的值

  1. 使用 Select 选择器展示状态信息
<template>
  <el-select v-model="status" placeholder="请选择">
    <el-option
      v-for="index in Object.keys(statusMappings)"
      :key="index"
      :label="statusMappings[index]"
      :value="index"
    ></el-option>
  </el-select>
</template>

<script>
export default {
  data() {
    return {
      statusMappings: {
        0: "已隐藏",
        1: "待更新",
        2: "已更新",
      },
      status: "",
    };
  },
};
</script>

<style scoped>
</style>

Object.keys()

var obj = { : 'a', : 'b', : 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
章节状态修改
<el-button type="primary" @click="updateStatus">确 定</el-button>

JS 部分

//方法7: 修改章节状态
updateStatus(statusForm) {
    axios
        .get("/courseContent", {
        params: {
            methodName: "updateSectionStatus",
            status: this.statusForm.status,
            id: this.statusForm.id
        }
    })
        .then(resp => {
        this.statusForm.data.status = this.statusForm.status;
        this.statusForm = {};
        this.showStatusForm = false;
    })
        .catch(error => {
        this.showStatusForm = false;
        this.$message.error("修改状态失败! ! !");
    });
},

v-for 里面数据层次太多, 修改过数据变了,页面没有重新渲染,需手动强制刷新。

<!-- 强制刷新 @change="$forceUpdate()" -->
<el-select @change="$forceUpdate()" v-model="statusForm.status" style="width: 100%">
    <el-option
               v-for="(item,index) in Object.keys(statusMapping)"
               :key="index"
               :label="statusMapping[item]"
               :value="item"
               ></el-option>
</el-select>

项目上线部署发布

简介

服务器与操作系统

服务器是计算机的一种,它比普通计算机运行更快、负载更高、价格更贵。

服务器从硬件上等同于电脑 PC。而服务器跟 PC 都是由 CPU、内存、主板、硬盘、电源等组成;但服务器的性能要远远超过 PC,因为它要保证全年无休。

操作系统是作为应用程序与计算机硬件之间的一个接口。

没有安装操作系统的计算机,被称为裸机, 如果想在裸机上运行自己的程序,就需要使用机器语言。

安装操作系统之后,就可以配置一些高级语言的环境,进行高级语言的开发。

Linux 系统是最具稳定性的系统。

Linux 比 Windows 更具安全性。

Linux 服务器在应用开发上更能节约成本。

项目的发布部署

项目的开发流程大致要经过一下几个步骤:

  • 项目立项
  • 需求分析阶段
  • 原型图设计阶段
  • 开发阶段
  • 测试阶段
  • 系统上线

后台项目部署

安装虚拟机

在 Linux 阶段已经安装过了虚拟机,使用的是 Linux 操作系统 CentOS 7 版本。

安装软件环境

以下软件在 Linux 阶段都已安装完成:

  • JDK 11
  • Tomcat 8.5
  • MySQL 5.7

查看 Java 版本

java -version

查看 tomcat 是否能够正常启动

# 进入到 tomcat 目录
cd /usr/tomcat/

# 启动 tomcat
./bin/startup.sh 

# 关闭 tomcat
./bin/shutdown.sh 

关闭防火墙

#查看已经开放的端口:
firewall-cmd --list-ports

#开启端口
    firewall-cmd --zone=public --add-port=8080/tcp --permanent

#命令含义:
    –zone #作用域
    –add-port=8080/tcp #添加端口,格式为:端口/通讯协议
    –permanent #永久生效,没有此参数重启后失效

#重启防火墙
firewall-cmd --reload

# 关闭防火墙
# 停止 firewall
systemctl stop firewalld.service
# 禁止 firewall 开机启动
systemctl disable firewalld.service
# 查看默认防火墙状态(关闭后显示 notrunning,开启后显示 running)
firewall-cmd --state

登录 MySQL 检查数库连接是否正常

-- 创建用户
CREATE USER 'JiuYuan'@'%' IDENTIFIED BY 'JiuYuan@123';

-- 授予所有权限
GRANT ALL PRIVILEGES ON *.* TO 'JiuYuan'@'%' IDENTIFIED BY 'JiuYuan@123';

-- 刷新
FLUSH PRIVILEGES;

使用 SQLYog 连接 Linux 上的 MySQL,导入 SQL 脚本创建项目所需的数据库

项目打包发布
  1. 修改项目的数据库配置文件:数据库的 IP、用户名、密码。
  2. 修改 Constants 常量类中的项目 URL
// 生产环境地址
public static final String LOCAL_URL = "http://192.168.91.128:8080";
  1. 修改后启动项目,测试一下 保证数据库连接没有问题
  2. 检查 POM 文件打包方式必须是 war 编译版本为 JDK 11
<packaging>war</packaging>
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>
  1. 执行打包命令
// 清除 target 文件夹
clean 
// 打包跳过测试
package
  1. 复制出 target 目录下的 war 包
  2. 修改一下 war 包名称为 lagou_edu_home.war
  3. 上传到 tomcat 的 webapps 目录中启动测试,访问接口:
http://192.168.91.128:8080/lagou_edu_home/course?methodName=findCourseList

前端项目部署

修改配置文件

前端项目的配置文件有两个:一个是开发环境的配置文件,一个是生产环境的配置文件。

先修改一下开发环境文件 .env.development 的后端服务器访问地址,然后进行一下测试:

VUE_APP_API_BASE =  http://192.168.91.128:/lagou_edu_home

修改生产环境的配置文件 .env.production

VUE_APP_API_BASE = http://192.168.91.128:/lagou_edu_home
前端项目打包

修改 vue.config.js 配置文件

module.exports = {
  // relative path for dev
  publicPath: process.env.NODE_ENV === "production" ? "/edu-boss/" : "./",
  // for gh-pages
  indexPath: "index.html",
  assetsDir: "static",
  lintOnSave: process.env.NODE_ENV !== "production",
  productionSourceMap: false,
  css: {
    // sourceMap: process.env.NODE_ENV !== 'production'
  },
  devServer: {
    open: true,
    port: 
  }
};

执行下面的打包命令:

npm run build

在项目下会生成一个 dist 目录

在本地 Tomcat 的 webapps 目录下创建一个 edu-boss 文件夹将 dist 目录中的文件拷贝到里面

启动本地 Tomcat 访问前端项目路径为

http://localhost:8081/edu-boss/
前端项目发布
  1. 验证没有问题后将 edu-boss 项目压缩上传到 Tomcat 服务器
# 复制一份 Tomcat
cp -r /usr/tomcat/ /usr/tomcat2
# 上传 edu-boss.zip 并解压
unzip edu-boss.zip 
# 删除 edu-boss.zip
rm -rf edu-boss.zip
  1. 修改 tomcat2 的 server.xml 配置文件的 3 个端口避免与 tomcat 冲突,修改结果如下:
...
<Server port="8006" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
...
  <Service name="Catalina">
...
    <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8444" />
...
  </Service>
</Server>
  1. 在部署后端项目的 tomcat1 的 webapps 目录下创建一个 upload 文件夹保存图片
  2. 运行前端项目:
# 进入 tomcat2,启动项目
./bin/startup.sh
# 动态查看日志
tail -f logs/catalina.out
  1. 运行后端项目
# 进入 tomcat1,启动项目
./bin/startup.sh 
# 动态查看日志
tail -f logs/catalina.out 
  1. 前后端都启动后进行测试