在线教育项目之前后端整合讲师业务模块

举报
tea_year 发表于 2025/06/30 21:02:28 2025/06/30
【摘要】 课程目标1)使用Nginx配置后台服务器2)后台管理-讲师CRUD【重点】3)集成阿里云OSS【重点】4)前端上传组件整合1、 前台项目的基础配置1. 项目的创建和基本配置1.1. 创建项目将vue-admin-template-master重命名为yxzx-admin1.2. 修改项目信息package.json { "name": "yxzx-admin", ...... "descri...

课程目标

1)使用Nginx配置后台服务器

2)后台管理-讲师CRUD【重点】

3)集成阿里云OSS【重点】

4)前端上传组件整合

1、 前台项目的基础配置

1. 项目的创建和基本配置

1.1. 创建项目

将vue-admin-template-master重命名为yxzx-admin

1.2. 修改项目信息

package.json

 

{

"name": "yxzx-admin",

......

"description": "优学在线教育后台管理系统",

"author": "yz <327591159@qq.com>",

......

}

1.3. 如果需要修改端口号

config/index.js中修改

 port: 9528

1.4. 项目的目录结构

 ├── build // 构建脚本

├── config // 全局配置

├── node_modules // 项目依赖模块

├── src //项目源代码

├── static // 静态资源

└── package.jspon // 项目信息和依赖配置

 src

├── api // 各种接口

├── assets // 图片等资源

├── components // 各种公共组件,非公共组件在各自view下维护

├── icons //svg icon

├── router // 路由表

├── store // 存储

├── styles // 各种样式

├── utils // 公共工具,非公共工具,在各自view下维护

├── views // 各种layout

├── App.vue //***项目顶层组件***

├── main.js //***项目入口文件***

└── permission.js //认证入口

2. 页面零星修改

2.1. 标题

index.html(项目的html入口) 

<title>优学在线后台管理系统</title>

修改后热部署功能,浏览器自动刷新

2.2. 国际化设置

打开 src/main.js(项目的js入口),第7行,修改语言为 zh-CN,使用中文语言环境

 import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n


2.3. 导航栏文字

src/views/layout/components(当前项目的布局组件)

src/views/layout/components/Navbar.vue

13行 

<el-dropdown-item>

首页

</el-dropdown-item>

17行

 <span style="display:block;" @click="logout">退出登录</span>

2.4. 面包屑文字

src/components(可以在很多项目中复用的通用组件)

src/components/Breadcrumb/index.vue

38行

 meta: { title: '首页' }

2.5. 运行项目

 npm run dev

3. 登录页修改

src/views/login/index.vue(登录组件)

4行

 <h3 class="title">优学在线教育后台管理系统</h3>

28行

 <el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">

登录

</el-button>

4. Eslint语法规范型检查

4.1. ESLint简介

JavaScript 是一个动态的弱类型语言,在开发中比较容易出错。因为没有编译程序,为了寻找 JavaScript 代码错误通常需要在执行过程中不断调适。

ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。让程序员在编码的过程中发现问题而不是在执行的过程中。

4.2. 语法规则

ESLint 内置了一些规则,也可以在使用过程中自定义规则。

本项目的语法规则包括:两个字符缩进,必须使用单引号,不能使用双引号,语句后不可以写分号,代码段之间必须有一个空行等。

4.3. 确认开启语法检查

打开 config/index.js,配    检查

 

useEslint: true,

可以关闭语法检查,建议开启,养成良好的编程习惯。

4.4. ESLint插件安装

vs code的ESLint插件,能帮助我们自动整理代码格式 

4.5. 插件的扩展设置

选择vs code左下角的“设置”, 打开 VSCode 配置文件,添加如下配置

"files.autoSave": "off",

"eslint.validate": [

"javascript",

"javascriptreact",

"vue-html",

{

"language": "vue",

"autoFix": true

}

],

"eslint.run": "onSave",

"eslint.autoFixOnSave": true


2、 配置Nginx

为什么配置Nginx?

反向代理:因为我们后台很多服务器(IP),怎么访问:http://192.168.1.1:8080 登陆, 8081:讲师;此时我们前端访问的IP地址就很复杂,有很多个

1、 在访问每一个链接的时候,我们都让他们访问http://****:8000 等此链接,

a) 缺点:每一次请求都要写这个链接,太繁琐;

2、 在后台配置Nginx,监听着一个IP端口号,当访问的不同的链接的时候,反向代理到不同的服务器;

负载均衡:因为我们每一个项目模块都需要搭建集群,此时请求过来的时候,需要nginx帮我们在集群中负载均衡(平均分配到每一个服务器上)!


1. 项目中的Easy Mock

config/dev.env.js 中BASE_API 为项目的easymock地址,目前具有模拟登录、登出、获取用户信息的功能

 

BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',


登录:/user/login

获取用户信息:/user/info?token=admin

登出:/user/logout

1.1. 手写登陆接口

从浏览器根据F12查看

login请求需要返回的数据:

{

    "code" : 20000,

    "data" : {

        "token" : "admin"

    }

}


写一个Controller返回20000成功、并返回token:admin数据

Info请求需要返回的数据:

{

    "code" : 20000,

    "data" : {

        "roles" : [ "admin" ],

        "name" : "admin",

        "avatar" : "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"

    }

}


登陆成功后返回如图所示数据;

写Controller接口:

@RestController

@CrossOrigin
@RequestMapping("user")
public class UserController {

@PostMapping("login")
public Result login(){
return Result.ok().data("token","admin");
}

@GetMapping("info")
public Result info(){
//"data":{"roles":["admin"],"name":"admin",
// "avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"}}
return Result.ok().data("roles","[\"admin\"]")
.data("name","admin")
.data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
}
}


config/dev.env.js,只有一个api地址的配置位置,而我们实际的后端有很多微服务,所以接口地址有很多,

我们可以使用nginx反向代理让不同的api路径分发到不同的api服务器中

1.2. 出现的问题

跨域问题:

什么是跨域?

    在不同的服务器中有前端执行ajax异步请求到不同的后台服务器,这个过程中叫跨域;

1、 协议变化

2、 域名变化

3、 端口号变化

为什么能出现?

    http://www.taobao.com      ——> http://item.taobao.com 跨域

    http://www.taobao.com :8080    ——> http://www.taobao.com:8081 跨域

    http://www.taobao.com         ——>https://www.taobao.com 跨域

    请求不一样的时候也出现问题!


怎么解决?

    市场上有两种形式来解决:

1、 jsonp: 在传统行业内使用的比较多;

基本原理:

jsonp就是利用了script标签的src属性是没有跨域的限制的,从而达到跨域访问的目的。因此它的最基本原理就是:动态添加一个<script>标签来实现。

2、 注解形式:在跨域的类上 @CrossOrigin

2. 配置nginx反向代理

2.1. 安装window版的nginx

将nginx-1.12.0.zip解压到开发目录中

如:G:\yxzx_nginx\nginx-1.12.0

双击nginx.exe运行nginx

访问:localhost

2.2. 配置nginx代理

在Nginx中配置对应的微服务服务器地址即可

 

http {

server {

listen 80;

......

},

......

server {

listen 9000;

server_name localhost;


location ~ /ebs/ {

        proxy_pass http://localhost:8001;

        }


    location ~ /user/ {

        proxy_pass http://localhost:8001;

    }

}

}

2.3. 重启nginx

 

nginx -s reload

2.4. 测试

访问讲师列表接口:http://localhost:9000 /ebs /teacher

访问获取用户信息接口:http://localhost:9000/ebs/user/info?token=admin

3. 配置开发环境

3.1. 修改config/dev.env.js

 

BASE_API: '"http://127.0.0.1:9000"'


3.2. 重启前端程序

修改配置文件后,需要手动重启前端程序




3、 项目中路由配置

1. 后台系统路由实现分析

1.1. 入口文件中调用路由

src/main.js

......

import router from './router' //引入路由模块

......

new Vue({

el: '#app',

router, //挂载路由

store,

render: h => h(App)

})



1.2. 路由模块中定义路由

src/router/index.js

......

export const constantRouterMap = [

......

]

export default new Router({

......

routes: constantRouterMap

})

 


2. 优学在线教育路由定义

2.1. 复制icon图标

将vue-element-admin/src/icons/svg 中的图标复制到 yxzx-admin项目中

2.2. 修改路由

修改 src/router/index.js 文件,重新定义constantRouterMap

注意:每个路由的name不能相同

export const constantRouterMap = [

{ path: '/login', component: () => import('@/views/login/index'), hidden: true },

{ path: '/404', component: () => import('@/views/404'), hidden: true },

// 首页

{

path: '/',

component: Layout,

redirect: '/dashboard',

name: 'Dashboard',

children: [{

path: 'dashboard',

component: () => import('@/views/dashboard/index'),

meta: { title: '优学在线后台首页', icon: 'dashboard' }

}]

},

// 讲师管理

{

path: '/ebs/teacher',

component: Layout,

redirect: '/ebs/teacher/list',

name: 'Teacher',

meta: { title: '讲师管理', icon: 'peoples' },

children: [

{

path: 'list',

name: 'EbsTeacherList',

component: () => import('@/views/ebs/teacher/list'),

meta: { title: '讲师列表' }

},

{

path: 'create',

name: 'EbsTeacherCreate',

component: () => import('@/views/ebs/teacher/form'),

meta: { title: '添加讲师' }

},

{

path: 'edit/:id',

name: 'EbsTeacherEdit',

component: () => import('@/views/ebs/teacher/form'),

meta: { title: '编辑讲师', noCache: true },

hidden: true

}

]

},

{ path: '*', redirect: '/404', hidden: true }

]


 

2.3. 创建vue组件

在src/views文件夹下创建以下文件夹和文件

2.4. form.vue

     

<template>

<div class="app-container">

讲师表单

</div>

</template>



2.5. list.vue 

<template>

<div class="app-container">

讲师列表

</div>

</template>


4、 讲师模块-业务操作

1. 讲师分页列表

1.1. 定义api

创建文件 src/api/ebs/teacher.js

 

import request from '@/utils/request'

const api_name = '/teacher'

export default {

getPageList(page, limit, searchObj) {

return request({

url: `${api_name}/${page}/${limit}`,

method: 'get',

params: searchObj

})

}

}



1.2. 初始化vue组件

src/views/ebs/teacher/list.vue 

<template>

<div class="app-container">

讲师列表

</div>

</template>

<script>

import teacher from '@/api/edu/teacher'

export default {

data() {// 定义数据

return {}

},


created() { // 当页面加载时获取数据

this.fetchData()

},


methods: {

fetchData() { // 调用api层获取数据库中的数据

console.log('加载列表')

}

}

}

</script>



1.3. 定义data

 

data() {

return {

listLoading: true, // 是否显示loading信息

list: null, // 数据列表

total: 0, // 总记录数

page: 1, // 页码

limit: 10, // 每页记录数

searchObj: {}// 查询条件

}

},



1.4. methods

methods: {

fetchData(page = 1) { // 调用api层获取数据库中的数据

console.log('加载列表')

this.page = page

this.listLoading = true

teacher.getPageList(this.page, this.limit, this.searchObj).then(response => {

// debugger 设置断点调试

if (response.success === true) {

this.list = response.data.rows

this.total = response.data.total

}

this.listLoading = false

})

}

}


1.5. 表格渲染

<!-- 表格 -->

<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>



1.6. 分页组件 


<!-- 分页 -->

<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="fetchData"

/>



1.7. 顶部查询表单

注意:

element-ui的 date-picker组件默认绑定的时间值是默认世界标准时间,和中国时间差8小时

设置 value-format="yyyy-MM-dd HH:mm:ss" 改变绑定的值

 


<!--查询表单-->

<el-form :inline="true" class="demo-form-inline">

<el-form-item>

<el-input v-model="searchObj.name" placeholder="讲师名"/>

</el-form-item>

<el-form-item>

<el-select v-model="searchObj.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="searchObj.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="searchObj.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="resetData()">清空</el-button>

</el-form>



清空方法

 

resetData() {

this.searchObj = {}

this.fetchData()

}


1.8. 测试


2. 讲师删除功能

2.1. 定义api

src/api/ebs/teacher.js

 

removeById(teacherId) {

return request({

url: `${api_name}/${teacherId}`,

method: 'delete'

})

}


2.2. 定义methods

src/views/edu/teacher/list.vue

使用MessageBox 弹框组件

 

removeDataById(id) {

// debugger

// console.log(memberId)

this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {

confirmButtonText: '确定',

cancelButtonText: '取消',

type: 'warning'

}).then(() => {

return teacher.removeById(id)

}).then(() => {

this.fetchData()

this.$message({

type: 'success',

message: '删除成功!'

})

}).catch((response) => { // 失败

if (response === 'cancel') {

this.$message({

type: 'info',

message: '已取消删除'

})

} else {

this.$message({

type: 'error',

message: '删除失败'

})

}

})

}



3. 讲师新增功能

3.1. 定义api

 src/api/ebs/teacher.js

save(teacher) {

return request({

url: api_name,

method: 'post',

data: teacher

})

}


数据提交的说明:

在api中如果提交的是普通数据对象使用:

params:对象

在api中如果提交的是json格式数据对象使用:

data:对象

3.2. 初始化组件

src/views/edu/teacher/form.vue

html

<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>




3.3. 实现新增功能

引入teacher api模块

 

import teacher from '@/api/ebs/teacher'


完善save方法

// 保存

saveData() {

teacher.save(this.teacher).then(response => {

return this.$message({

type: 'success',

message: '保存成功!'

})

}).then(resposne => {

this.$router.push({ path: '/ebs/teacher' })

}).catch((response) => {

// console.log(response)

this.$message({

type: 'error',

message: '保存失败'

})

})

}


 

4. 讲师修改回显

4.1. 定义api

 src/api/ebs/teacher.js

getById(id) {

return request({

url: `${api_name}/${id}`,

method: 'get'

})

}



4.2. 组件中调用api

methods中定义fetchDataById

// 根据id查询记录

fetchDataById(id) {

teacher.getById(id).then(response => {

this.teacher = response.data.item

}).catch((response) => {

this.$message({

type: 'error',

message: '获取数据失败'

})

})

}


 

4.3. 页面渲染前调用fetchDataById

 


created() {

console.log('created')

if (this.$route.params && this.$route.params.id) {

const id = this.$route.params.id

this.fetchDataById(id)

}

}



5. 三、更新

5.1. 定义api

 

updateById(teacher) {

return request({

url: `${api_name}/${teacher.id}`,

method: 'put',

data: teacher

})

}



5.2. 组件中调用api

methods中定义updateData

// 根据id更新记录

updateData() {

this.saveBtnDisabled = true

teacher.updateById(this.teacher).then(response => {

return this.$message({

type: 'success',

message: '修改成功!'

})

}).then(resposne => {

this.$router.push({ path: '/edu/teacher' })

}).catch((response) => {

// console.log(response)

this.$message({

type: 'error',

message: '保存失败'

})

})

}


 

5.3. 完善saveOrUpdate方法

 saveOrUpdate() {

this.saveBtnDisabled = true

if (!this.teacher.id) {

this.saveData()

} else {

this.updateData()

}

}


6. 存在问题

vue-router导航切换时,如果两个路由都渲染同个组件,组件会重(chong)用,

组件的生命周期钩子(created)不会再被调用, 使得组件的一些数据无法根据 path的改变得到更新

因此:

1、我们可以在watch中监听路由的变化,当路由变化时,重新调用created中的内容

2、在init方法中我们判断路由的变化,如果是修改路由,则从api获取表单数据,

      如果是新增路由,则重新初始化表单数据

 

<script>

import teacher from '@/api/ebs/teacher'

const defaultForm = {

name: '',

sort: 0,

level: '',

career: '',

intro: '',

avatar: ''

}

export default {

data() {

return {

teacher: defaultForm,

saveBtnDisabled: false // 保存按钮是否禁用,

}

},

watch: {

$route(to, from) {

console.log('watch $route')

this.init()

}

},

created() {

console.log('created')

this.init()

},

methods: {

init() {

if (this.$route.params && this.$route.params.id) {

const id = this.$route.params.id

this.fetchDataById(id)

} else {

// 使用对象拓展运算符,拷贝对象,而不是引用,

// 否则新增一条记录后,defaultForm就变成了之前新增的teacher的值

this.teacher = { ...defaultForm }

}

},

......

}

}

</script>




【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。