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 } }); },找到 name 为: CourseItem 的路由
// 添加课程的路由 { path: "/courses/:courseId", // 路径,携带参数: 课程 ID name: "CourseItem", // 路由导航到的组件 component: () => import(/* webpackChunkName: 'courses' */ "../views/CourseItem.vue") },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 将 form 表单元素的 name 与 value 进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率。异步上传文件创建 FormData 对象CourseItem.vue 的 JS 的 methods的 created 部分的代码
//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 == 0) { // 保存成功,跳转到首页 this.$router.back(); } else if (res.data.status == 1) { 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("回显数据失败! ! !"); }); },data:展示数据
props:配置树形结构;label - 设置节点名称,children - 指定生成子树的属性名称
自定义树节点内容: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> 展示树形结构章节与课时 <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>data 数据
data() { // 定义章节信息 const addSectionForm = { course_id: undefined, course_name: "", section_name: "", description: "", order_num: 0 }; // 章节与课时信息,树形结构 const treeProps = { label: item => { return item.section_name || item.theme; }, children: "lessonList" }; // 定义章节状态信息 const statusMapping = { 0: "已隐藏", 1: "待更新", 2: "已更新" }; const statusForm = { status: 0 }; 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("数据获取失败! ! !"); }); }, },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>JS 代码
// 方法 3: 显示添加章节表单, 回显课程信息 handleShowAddSection() { // 显示表单 this.showAddSection = true; },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"));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>JS 代码
// data 函数中定义的章节状态 const statusMapping = { 0: "已隐藏", 1: "待更新", 2: "已更新" }; // 方法 6: 显示章节状态 showStatus(data) { this.statusForm.id = data.id; this.statusForm.status = data.status.toString(); this.statusForm.data = data; this.showStatusForm = true;// 显示状态表单 },v-model 的值为当前被选中的 el-option 的 value 属性值
el-option 选项:label 选项的标签名;value 选择的值
使用 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 = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.keys(obj)); // console: ['0', '1', '2']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 11Tomcat 8.5MySQL 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 脚本创建项目所需的数据库
修改后启动项目,测试一下 保证数据库连接没有问题
检查 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> 执行打包命令 // 清除 target 文件夹 clean // 打包跳过测试 package 复制出 target 目录下的 war 包修改一下 war 包名称为 lagou_edu_home.war上传到 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:8080/lagou_edu_home修改生产环境的配置文件 .env.production:
VUE_APP_API_BASE = http://192.168.91.128:8080/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: 8081 } };执行下面的打包命令:
npm run build在项目下会生成一个 dist 目录
在本地 Tomcat 的 webapps 目录下创建一个 edu-boss 文件夹将 dist 目录中的文件拷贝到里面
启动本地 Tomcat 访问前端项目路径为
http://localhost:8081/edu-boss/在部署后端项目的 tomcat1 的 webapps 目录下创建一个 upload 文件夹保存图片
运行前端项目:
# 进入 tomcat2,启动项目 ./bin/startup.sh # 动态查看日志 tail -f logs/catalina.out 运行后端项目 # 进入 tomcat1,启动项目 ./bin/startup.sh # 动态查看日志 tail -f logs/catalina.out 前后端都启动后进行测试想了解更多,欢迎关注我的微信公众号:Renda_Zhang