谷粒学苑二
谷粒学苑二
一、第四天
1、axios
Axios API | Axios 中文文档 | Axios 中文网 (axios-http.cn)
{ "success": true, "code": 20000, "data": { "items": [ {"name": "lucy","age": 20}, {"name": "mart","age": 21}, {"name": "jack","age": 22} ] } }
<!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"> <!-- v-for循环遍历 --> <div v-for="user in userList"> {{user.name}} - {{user.age}} </div> </div> <script src="vue.min.js"></script> <!-- 引入axios --> <script src="axios.min.js"></script> <script> new Vue({ el: '#app', //固定结构 data: { //在data定义变量和初始值 //定义变量,值空数组 userList: [] }, created() { //页面渲染之前执行 //调用定义的方法 this.getUserList() }, methods: { //编写具体方法 getUserList(id) { //使用axios发送ajax请求 //axios.提交方式("请求接口地址").then(箭头函数).catch(箭头函数) //vm = this axios.get('data.json') //请求成功 .then(response => { //response就是请求之后返回的数据 console.log(response) this.userList = response.data.data.items }) //请求失败 .catch(error => { console.log(error) }) } }, }) </script> </body> </html>
2、element-ui
element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建
3、nodejs
3.1、简介
1、什么是Node.js
简单的说 Node.js 就是运行在服务端的 JavaScript。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
2、Node.js有什么用
如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择。
Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。
当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。
3.2、安装
1、下载
官网:https://nodejs.org/en/
中文网:http://nodejs.cn/
LTS:长期支持版本
Current:最新版
2、安装
3、查看版本
node -v
已卸载16.18.0
新安装18.16.0
3.3、快速入门
1、简单测试
test.js
console.log('Hello Node.js')
打开命令行终端:Ctrl + Shift + y
进入到程序所在的目录,输入
node test.js
浏览器的内核包括两部分核心:
- DOM渲染引擎;
- js解析器(js引擎)
- js运行在浏览器中的内核中的js引擎内部
Node.js是脱离浏览器环境运行的JavaScript程序,基于V8 引擎(Chrome 的 JavaScript的引擎)
2、服务端应用开发
server.js
const http = require('http'); http.createServer(function (request, response) { // 发送 HTTP 头部 // HTTP 状态值: 200 : OK // 内容类型: text/plain response.writeHead(200, { 'Content-Type': 'text/plain' }); // 发送响应数据 "Hello World" response.end('Hello Server'); }).listen(8888); // 终端打印如下信息 console.log('Server running at http://127.0.0.1:8888/');
node server.js
服务器启动成功后,在浏览器中输入:http://localhost:8888/ 查看webserver成功运行,并输出html页面
停止服务:ctrl + c
3、vscode中打开集成cmd窗口,进行js代码执行
注意:如果不能运行就退出选择使用管理员身份打开vscode
4、npm包管理器
4.1、简介
1、什么是NPM
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven 。
2、NPM工具的安装位置
我们通过npm 可以很方便地下载js库,管理前端工程。
Node.js默认安装的npm包和工具的位置:Node.js目录\node_modules
在这个目录下你可以看见 npm目录,npm本身就是被NPM包管理器管理的一个工具,说明
Node.js已经集成了npm工具
#在命令提示符输入 npm -v 可查看当前npm版本 npm -v
4.2、使用npm管理项目
1、创建文件夹npmdemo
2、项目初始化
#建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化 npm init #按照提示输入相关信息,如果是用默认值则直接回车即可。 #name: 项目名称 #version: 项目版本号 #description: 项目描述 #keywords: {Array}关键词,便于用户搜索到我们的项目 #最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml #我们之后也可以根据需要进行修改。 #如果想直接生成 package.json 文件,那么可以使用命令 npm init -y
3、修改npm镜像
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的依赖包,会出现下载失败的情况
4、安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
以后下载可以使用cnpm install
5、npm install 命令的使用
#使用 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
6、其他命令
#更新包(更新到最新版本) npm update 包名 #全局更新 npm update -g 包名 #卸载包 npm uninstall 包名 #全局卸载 npm uninstall -g 包名
5、babel
5.1、简介
Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行执行。
这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持。
5.2、安装
1、安装命令行转码工具
Babel提供babel-cli工具,用于命令行转码。它的安装命令如下:
npm install --global babel-cli #查看是否安装成功 babel --version
5.3、Babel的使用
1、初始化项目
npm init -y
2、创建文件
ES6代码:
// 转码前 // 定义数据 let input = [1, 2, 3] // 将数组的每个元素 +1 input = input.map(item => item + 1) console.log(input)
3、配置.babelrc
Babel的配置文件是.babelrc,存放在项目的根目录下,该文件用来设置转码规则和插件,基本格式如下。
{ "presets": [], "plugins": [] }
presets字段设定转码规则,将es2015规则加入 .babelrc:
{ "presets": ["es2015"], "plugins": [] }
4、安装转码器
在项目中安装
npm install --save-dev babel-preset-es2015
5、转码
# 转码结果写入一个文件 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、模块化
6.1、简介
1、模块化产生的背景
随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂。
Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
但是,Javascript不是一种模块化编程语言,它不支持"类"(class),包(package)等概念,更遑论"模块"(module)了。
2、什么是模块化开发
传统非模块化开发有如下的缺点:
- 命名冲突
- 文件依赖
模块化规范:
- CommonJS模块化规范
- ES6模块化规范
6.2、CommonJS模块规范
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
1、创建“module”文件夹
2、导出模块
//创建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 }
3、导入模块
//调用01.js里面的方法 //1 引入模块 const math = require('./01.js') //2 使用模块 console.log(math.sum(1, 2)) console.log(math.subtract(1, 2)) console.log(math.multiply(1, 2)) console.log(math.divide(1, 2))
4、运行程序
node xx.js
5、总结
CommonJS使用 exports 和require 来导出、导入模块。
6.3、ES6模块化规范
ES6使用 export 和 import 来导出、导入模块
1、导出模块
//定义方法,设置哪些方法可以被外部调用 export function getList() { console.log('获取数据列表') } export function save() { console.log('保存数据') }
2、导入模块
//调用01.js import { getList, save } from "./01"; //只取需要的方法即可,多个方法用逗号分隔 getList(); save();
注意:这时的程序无法运行的,因为ES6的模块化无法在Node.js中执行,需要用Babel编辑成ES5后再执行。
新版13.2.0以上已经支持了,不用进行转换了
但需要进行一些配置
- 需要将您的模块文件的扩展名更改为
.mjs
,或者在您的package.json
文件中将"type"
字段设置为"module"
,以告诉Node.js使用ES6模块化。 - 需要使用
import
关键字来导入模块,而不是使用require
函数
3、运行程序
node 02.js
6.4、ES6模块化的另一种写法
1、导出模块
export default { getList() { console.log('获取数据列表2') }, save() { console.log('保存数据2') } }
2、导入模块
import user from "./userApi2.js" user.getList() user.save()
7、webpack
7.1、什么是webpack
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
7.2、Webpack安装
1、全局安装
npm install -g webpack webpack-cli
可能存在兼容性问题
npm install -g webpack webpack-cli@4.9.2
2、安装后查看版本号
webpack -v
7.3、初始化项目
1、创建webpack文件夹
进入webpack目录,执行命令
npm init -y
2、创建src文件夹
3、src下创建common.js
exports.info = function (str) { document.write(str); }
4、src下创建utils.js
exports.add = function (a, b) { return a + b; }
5、src下创建mian.js
const common = require('./common'); const utils = require('./utils'); common.info('Hello world!' + utils.add(100, 200));
7.4、js打包
1、webpack目录下创建配置webpack.config.js
以下配置的意思是:读取当前项目目录下src文件夹中的main.js(入口文件)内容,分析资源依赖,把相关的js文件打包,打包后的文件放入当前目录的dist文件夹下,打包后的js文件名为bundle.js
const path = require("path"); //Node.js内置模块 module.exports = { entry: './src/main.js', //配置入口文件 output: { path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径 filename: 'bundle.js' //输出文件 } }
2、命令行执行编译命令
webpack #有黄色警告 webpack --mode=development #没有警告 #执行后查看bundle.js 里面包含了上面两个js文件的内容并惊醒了代码压缩
也可以配置项目的npm运行命令,修package.json文件
"scripts": { //..., "dev": "webpack --mode=development" }
运行npm命令执行打包
npm run dev
3、webpack目录下创建index.html
引用bundle.js
<body> <script src="dist/bundle.js"></script> </body>
4、浏览器中查看index.html
7.5、css打包
1、安装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
2、修改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' //输出文件 }, module: { rules: [ { test: /\.css$/, //打包规则应用到以css结尾的文件上 use: ['style-loader', 'css-loader'] } ] } }
3、在src文件夹创建style.css
body{ background-color:pink; }
4、修改main.js
第一行引入style.css
require('./style.css');
5、浏览器查看index.html
8、搭建项目前端环境
8.1、vue-element-admin
1、简介
而vue-element-admin是基于element-ui 的一套后台管理系统集成方案。
功能:https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能
GitHub地址:https://github.com/PanJiaChen/vue-element-admin
项目在线预览:https://panjiachen.gitee.io/vue-element-admin
2、安装
# 解压压缩包 # 进入目录 cd vue-element-admin-master # 安装依赖 npm install # 启动。执行后,浏览器自动弹出并访问http://localhost:9527/ npm run dev
8.2、vue-admin-template
1、简介
vueAdmin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模
板进行二次开发。
GitHub地址:https://github.com/PanJiaChen/vue-admin-template
建议:你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来。
2、安装
# 解压压缩包 # 进入目录 cd vue-admin-template-master # 安装依赖 npm install # 启动。执行后,浏览器自动弹出并访问http://localhost:9528/ npm run dev
nodejs新版本引起的:digital envelope routines::unsupported
8.3、前端页面环境说明
1、前端框架入口
index.html
main.js
2、前端页面环境使用框架(模板)
vue-admin-template = vue + elementui
3、目录结构
├── build # 构建相关 ├── mock # 项目mock 模拟数据 ├── plop-templates # 基本模板 ├── public # 静态资源 │ │── favicon.ico # favicon图标 │ └── index.html # html模板 ├── src # 源代码 │ ├── api # 所有请求 │ ├── assets # 主题 字体等静态资源 │ ├── components # 全局公用组件 │ ├── directive # 全局指令 │ ├── filters # 全局 filter │ ├── icons # 项目所有 svg icons │ ├── lang # 国际化 language │ ├── layout # 全局 layout │ ├── router # 路由 │ ├── store # 全局 store管理 │ ├── styles # 全局样式 │ ├── utils # 全局公用方法 │ ├── vendor # 公用vendor │ ├── views # views 所有页面 │ ├── App.vue # 入口页面 │ ├── main.js # 入口文件 加载组件 初始化等 │ └── permission.js # 权限管理 ├── tests # 测试 ├── .env.xxx # 环境变量配置 ├── .eslintrc.js # eslint 配置项 ├── .babelrc # babel-loader 配置 ├── .travis.yml # 自动化CI配置 ├── vue.config.js # vue-cli 配置 ├── postcss.config.js # postcss 配置 └── package.json # package.json
4、修改项目信息
package.json
{ "name": "guli-admin", ...... "description": "谷粒学院后台管理系统", "author": "Xiaolin <184569695@qq.com>", ...... }
5、修改端口号
8.4、登录页修改
src/views/login/index.vue(登录组件)
6行
<h3 class="title">谷粒学院后台管理系统</h3>
44行
<el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin"> 登录 </el-button>
8.5、页面零星修改
1、标题
index.html(项目的html入口)
<title>谷粒学院后台管理系统</title>
修改后热部署功能,浏览器自动刷新
2、国际化设置
打开 src/main.js(项目的js入口)
// set ElementUI lang to EN // Vue.use(ElementUI, { locale }) // 如果想要中文版 element-ui,按如下方式声明 Vue.use(ElementUI)
3、icon
复制 favicon.ico 到根目录
4、导航栏文字
src/layout/components(当前项目的布局组件)
src/layout/components/Navbar.vue
16行
<el-dropdown-item> 首页 </el-dropdown-item>
26行
<title>谷粒学院后台管理系统</title>
5、面包屑文字
src/components(可以在很多项目中复用的通用组件)
src/components/Breadcrumb/index.vue
36行
meta: { title: '首页' }
8.6、Eslint语法规范型检查
1、Eslint简介
JavaScript 是一个动态的弱类型语言,在开发中比较容易出错。因为没有编译程序,为了寻找
JavaScript 代码错误通常需要在执行过程中不断调适。
ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。让程序
员在编码的过程中发现问题而不是在执行的过程中。
2、语法规则
ESLint 内置了一些规则,也可以在使用过程中自定义规则。
本项目的语法规则包括:两个字符缩进,必须使用单引号,不能使用双引号,语句后不可以写分号,代
码段之间必须有一个空行等。
3、确认开启语法检查
打开 config/index.js,配置是否开启语法检查
useEslint: true,
各种关闭eslint方法总结_去掉eslint_CloudJay_喵喵喵的博客-CSDN博客
4、ESlint插件安装
求解vscode安装的eslint和prettier插件和npm安装的有什么联系呀? - 知乎 (zhihu.com)
VSCode 插件之 ESLint - 掘金 (juejin.cn)
vs code的ESLint插件,能帮助我们自动整理代码格式
5、插件的扩展设置
选择vs code左下角的“设置”, 打开 VSCode 配置文件,添加如下配置
"files.autoSave": "off", "eslint.validate": [ "javascript", "javascriptreact", "vue-html", { "language": "vue", "autoFix": true } ], "eslint.run": "onSave", "eslint.autoFixOnSave": true
二、第五天
1、后台系统登录功能改造
1.1、把系统登录功能改造为本地
新版系统默认使用这个地址
# VUE_APP_BASE_API = '/dev-api'
把请求地址改为本地
VUE_APP_BASE_API ='http://localhost:8001'
打开.env.development修改即可
1.2、编写模拟登录的接口
package com.lxg.eduservice.controller; import com.lxg.commonutils.R; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @auther xiaolin * @creatr 2023/5/11 10:14 */ @RestController @RequestMapping("/eduservice/user") public class EduLoginController { //login @PostMapping("login") public R login(){ return R.ok().data("token","admin"); } //info @PostMapping("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"); } }
1.3、修改api/user.js的接口地址
1.4、访问出现错误
1.5、跨域解决
1、什么是跨域
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域 。前后端
分离开发中,需要考虑ajax跨域的问题。
这里我们可以从服务端解决这个问题
2、跨域解决方式
- 在后端接口controller添加注解(@CrossOrigin)
- 使用网关解决(后面解释)
3、成功登录
2、添加路由流程
2.1、路由文件中添加路由规则
2.2、路由对应组件位置添加对应页面组件
import('@/views/table/index')
2.3、在api文件夹创建js文件,定义接口地址和参数(组件需要使用的方法)
2.4、在vue组件引入js文件,调用方法
3、讲师列表前端实现
3.1、添加讲师列表路由
{ path: '/teacher', component: Layout, redirect: '/teacher/table', name: '讲师管理', meta: { title: '讲师管理', icon: 'el-icon-s-help' }, children: [ { path: 'list', name: '讲师列表', component: () => import('@/views/table/index'), meta: { title: '讲师列表', icon: 'table' } }, { path: 'save', name: '添加讲师', component: () => import('@/views/tree/index'), meta: { title: '添加讲师', icon: 'tree' } } ] },
3.2、创建vue组件
1、list.vue
<template> <div class="app-container">讲师列表</div> </template>
2、save.vue
<template> <div class="app-container">讲师添加</div> </template>
3.3、在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 }) } }
3.4、在讲师列表里面list.vue页面调用定义的接口方法,得到接口返回数据
list.vue
<script> //引入调用teacher.js文件 import teacher from "@/api/edu/teacher"; export default { //写核心代码位置 data() { //定义变量和初始值 return { page: 1, //当前页码 limit: 10, //每页显示的条数 teacherQuery: {}, //查询条件 total: 0, //总记录数 teacherList: [], //讲师列表 }; }, created() { //页面渲染之前执行,一般调用methods定义的方法 this.getTeacherList(); }, methods: { //创建具体的方法,调用teacher.js定义的方法 getTeacherList() { teacher .getTeacherListPage(this.page, this.limit, this.teacherQuery) .then((response) => { //response接口返回的数据 console.log(response); this.teacherList = response.data.rows; //获取讲师列表 this.total = response.data.total; //获取总记录数 console.log(this.teacherList); console.log(this.total); }) //请求成功 .catch((error) => { console.log(error); }); //请求失败 }, }, }; </script>
3.5、把请求接口获取的数据在页面进行显示
使用elementui
<template> <div class="app-container"> 讲师列表 <el-table :data="teacherList" 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="180" /> <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> </div> </template>
3.6、分页
添加在table下面即可
<!-- 分页 --> <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="getTeacherList" />
注意修改以下:
3.7、讲师条件查询
在列表上面添加条件输入表单,使用v-model数据绑定
<!--查询表单--> <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="getTeacherList()" >查 询</el-button > <el-button type="default" @click="resetData()">清空</el-button> </el-form>
3.8、条件清空功能
resetData() { //清空的方法 //清空查询条件 this.teacherQuery = {}; //重新查询 this.getTeacherList(); },
3.9、删除功能
1、在teacher.js文件中定义接口方法
//删除讲师 deleteTeacherId(id) { return request({ url: `/eduservice/teacher/${id}`, method: 'delete' }) }
2、页面调用方法
//删除讲师 removeDataById(id) { //询问是否删除 this.$confirm("是否删除此条讲师记录?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(() => { //点击确定执行 //调用删除的方法 teacher .deleteTeacherId(id) .then((response) => { //删除成功 //提示信息 this.$message({ type: "success", message: "删除成功", }); //重新查询 this.getTeacherList(this.page); }) .catch((error) => { //删除失败 console.log(error); //提示信息 this.$message({ type: "error", message: "删除失败", }); }); }) .catch(() => { //点击取消执行 //提示信息 this.$message({ type: "info", message: "已取消删除", }); }); },
3.10、添加功能
1、页面
<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>
2、teacher.js新增接口请求方法
//添加讲师 addTeacher(teacher) { return request({ url: `/eduservice/teacher/addTeacher/`, method: 'post', data: teacher }) }
3、页面方法调用
saveOrUpdate() { this.saveBtnDisabled = true; this.saveTeacher(); }, // 保存 saveTeacher() { teacherApi .addTeacher(this.teacher) .then((response) => { //添加成功 //提示信息 this.$message({ type: "success", message: "添加成功!", }); //回到列表页面 路由跳转 this.$router.push({ path: "/teacher/list" }); }) .catch((error) => { //提示信息 this.$message({ type: "error", message: "添加失败!", }); }); },
4、后端对数据进行排序
3.11、修改功能
1、list.vue页面
2、新增路由规则
{ path: 'edit/:id', name: '修改讲师', component: () => import('@/views/edu/teacher/save'), meta: { title: '修改讲师', noCache: true }, hidden: true },
3、表单页面实现数据回显
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 }) .catch(error=>{ console.log(error) }) }
因为添加和修改都使用save页面
要区分搜索添加还是修改,只在修改时进行数据回显
判断路径是否包含讲师id,有就回显
created() { if (this.$route.params && this.$route.params.id) { const id = this.$route.params.id; this.getInfo(id); } },
4、进行修改
teacher.js中定义api
//修改讲师 updateTeacher(teacher) { return request({ url: `/eduservice/teacher/updateTeacher`, method: 'post', data: teacher }) },
在页面进行方法调用
//修改讲师的方法 updateTeacherInfo() { teacherApi .updateTeacherInfo(this.teacher) .then((response) => { this.$message({ type: "success", message: "修改成功!", }); //回到列表页面 this.$router.push({ path: "/teacher/list" }); }) .catch((error) => { this.$message({ type: "error", message: "修改失败!", }); }); },
判断修改还是添加
saveOrUpdate() { this.saveBtnDisabled = true; //判断修改还是添加 if (!this.teacher.id) { this.saveTeacher(); } else { this.updateTeacherInfo(); } },
5、出现的问题
视图通过以下解决但失败
最终解决方式:
新增方法
init() { if (this.$route.params && this.$route.params.id) { const id = this.$route.params.id; this.getInfo(id); } else { //清空表单 this.teacher = {}; } },
新版没有这个问题:
三、第六天
1、添加讲师实现头像上传功能
一般可以存储在服务器上
本次使用阿里云OSS存储服务
1.1、阿里云存储OSS
为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案- 阿里云OSS。
1、开通“对象存储OSS”服务
2、创建Bucket
3、上传默认头像
4、创建RAM子用户
LTAI5tDDc7oFqhiZFejDVZtp***UWBM3EChOz3WNt4NiiJ4N8E0dTZH00
1.2、java SDK整合OOS
使用文档:阿里云对象OOS之Java快速入门
1、service模块下创建子模块service_oss
2、在service_oss引入依赖
<dependencies> <!--aliyunOSS--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency> </dependencies>
3、配置application.properties
#服务端口 server.port=8002 #服务名 spring.application.name=service-oss #环境设置:dev、test、prod spring.profiles.active=dev #阿里云 OSS #不同的服务器,地址不同 aliyun.oss.file.endpoint=oss-cn-guangzhou.aliyuncs.com aliyun.oss.file.keyid=LTAI5tDDc7oFqhiZFejDVZtp aliyun.oss.file.keysecret=UWBM3EChOz3WNt4NiiJ4N8E0dTZH00 #bucket可以在控制台创建,也可以使用java代码创建 aliyun.oss.file.bucketname=edu20230513
4、启动类
package com.lxg.oss; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; /** * @auther xiaolin * @creatr 2023/5/13 20:16 */ @SpringBootApplication @ComponentScan(basePackages = {"com.lxg"}) public class OssApplication { public static void main(String[] args) { SpringApplication.run(OssApplication.class,args); } }
5、启动时报错
是因为父模块导入数据库相关依赖了,会自动配置
但是现在模块不需要操作数据库,只是做上传到oss功能,没有配置数据库
解决方式:两种
- 添加数据库配置
- 在启动类添加属性,默认不去加载数据库配置
采用第二种
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
6、创建常量类,读取配置文件中的内容
package com.lxg.oss.utils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * @auther xiaolin * @creatr 2023/5/13 22:00 */ //这样做的原因是,静态变量不能自动注入 //当项目一启动,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; } }
7、创建Osscontroller
package com.lxg.oss.controller; import com.lxg.commonutils.R; import com.lxg.oss.service.OssService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /** * @auther xiaolin * @creatr 2023/5/13 22:24 */ @RestController @RequestMapping("eduoss/fileoss") @CrossOrigin public class OssController { @Autowired private OssService ossService; @PostMapping public R uploadOssFile(MultipartFile file){ //获取上传的文件 MultipartFile //返回上传oss的路径 String url = ossService.uploadFileAvatar(file); return R.ok().data("url",url); } }
8、编写OssService及其实现类
OssServiceImpl
package com.lxg.oss.service.impl; import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.PutObjectRequest; import com.aliyun.oss.model.PutObjectResult; import com.lxg.oss.service.OssService; import com.lxg.oss.utils.ConstantPropertiesUtils; import org.springframework.web.multipart.MultipartFile; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * @auther xiaolin * @creatr 2023/5/13 22:25 */ public class OssServiceImpl implements OssService { @Override public String uploadFileAvatar(MultipartFile file) { // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = ConstantPropertiesUtils.END_POINT; // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID; String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET; // 填写Bucket名称,例如examplebucket。 String bucketName = ConstantPropertiesUtils.BUCKET_NAME; // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 String objectName = file.getOriginalFilename(); // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。 // String filePath= "D:\\localpath\\examplefile.txt"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // InputStream inputStream = new FileInputStream(filePath); InputStream inputStream = file.getInputStream(); // 创建PutObjectRequest对象。 /** * 第一个参数 Bucket名称 * 第二个参数 上传到oss文件路径和文件名称 * 第三个参数 文件输入流 */ PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream); // 设置该属性可以返回response。如果不设置,则返回的response为空。 putObjectRequest.setProcess("true"); // 创建PutObject请求。 PutObjectResult result = ossClient.putObject(putObjectRequest); // 如果上传成功,则返回200。 System.out.println(result.getResponse().getStatusCode()); //把上传之后文件路径返回 //需要把上传到阿里云oss路径手动拼接出来 //https://edu20230513. // oss-cn-guangzhou.aliyuncs.com // /favicon.png String url = "https://"+bucketName+"."+endpoint+"/"+objectName; return url; } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } catch (Exception e) { e.printStackTrace(); } finally { if (ossClient != null) { ossClient.shutdown(); } } return null; } }
OssService
package com.lxg.oss.service; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; /** * @auther xiaolin * @creatr 2023/5/13 22:24 */ @Service public interface OssService { //上传头像到oss String uploadFileAvatar(MultipartFile file); }
9、Swagger测试
1.3、解决文件重复名称问题
在文件名称拼接随机唯一值
//在文件名称里面添加随机唯一的值 String substring = objectName.substring(objectName.lastIndexOf(".")); String uuid = UUID.randomUUID().toString().replaceAll("-",""); objectName = uuid+substring;
1.4、文件分类管理
根据日期分类
//2、将文件按照日期进行分类 //获取当前日期 /*Date currentDate = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); String formattedDate = dateFormat.format(currentDate);*/ String datePath = new DateTime().toString("yyyy/MM/dd"); //拼接 objectName = datePath + "/"+objectName;
效果如下:
1.5、配置nginx反向代理服务器
1、概念
- 请求转发
- 负载均衡
- 动静分离
-
请求转发
客户端->nginx(9001)->服务器(8001)
-
负载均衡
nginx(9001)根据规则负载到对应服务器->服务器(8001/8002)
-
动静分离
将静态资源存储在nginx服务器,页面存储在其他服务器,访问时,直接访问nginx服务器的资源
2、安装
安装:nginx:下载
关闭窗口,nginx不会自动停止的
nginx.exe -s stop
需要如下操作
3、配置
1、修改默认端口为81
2、配置nginx转发规则
server { listen 9001; server_name localhost; location ~ /eduservice/ { proxy_pass http://localhost:8001; } location ~ /eduuser/ { proxy_pass http://localhost:8001; } location ~ /eduoss/ { proxy_pass http://localhost:8002; } location ~ /eduvod/ { proxy_pass http://localhost:8003; } location ~ /cmsservice/ { proxy_pass http://localhost:8004; } location ~ /ucenterservice/ { proxy_pass http://localhost:8006; } }
3、修改前端访问地址
4、启动 前后端服务,重启nginx
1.6、添加讲师实现上传头像前端整合
1、在添加讲师页面添加上传组件
复制vue-element-admin项目src/components下的两个组件到相应位置
2、修改save页面
<!-- 讲师头像 --> <el-form-item label="讲师头像"> <!-- 头衔缩略图 --> <pan-thumb :image="String(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 + '/eduoss/fileoss'" field="file" @close="close" @crop-upload-success="cropSuccess" /> </el-form-item>
3、data添加数据
imagecropperShow: false, //是否显示图片上传组件 imagecropperKey: 0,//上传组件key值 BASE_API: process.env.VUE_APP_BASE_API,//获取.env.development里面的值 saveBtnDisabled: false, // 保存按钮是否禁用,
4、引入组件
import ImageCropper from "@/components/ImageCropper"; import PanThumb from "@/components/PanThumb";
components: { ImageCropper, PanThumb, },
5、新增方法
close() { //关闭上传弹框的方法 this.imagecropperShow = false; }, //上传成功后调用的方法 cropSuccess(data) { this.imagecropperShow = false; //上传之后接口返回地址 this.teacher.avatar = data.url; },
6、上传成功后的小问题解决
修改方法
close() { //关闭上传弹框的方法 this.imagecropperShow = false; //上传组件初始化 this.imagecropperKey = this.imagecropperKey + 1; }, //上传成功后调用的方法 cropSuccess(data) { //关闭上传弹框的方法 this.imagecropperShow = false; //上传之后接口返回地址 this.teacher.avatar = data.url; this.imagecropperKey = this.imagecropperKey + 1; },
7、设置默认头像
修改init方法
init() { if (this.$route.params && this.$route.params.id) { const id = this.$route.params.id; this.getInfo(id); } else { //清空表单 this.teacher = { avatar: process.env.VUE_APP_OSS_PATH + "/default/Default.jpg", }; } },
定义变量
2、添加课程分类功能
使用EasyExcel读取excel内容添加数据
2.1、创建edu_subject表
/* Navicat Premium Data Transfer Source Server : localhost_3306 Source Server Type : MySQL Source Server Version : 50729 Source Host : localhost:3306 Source Schema : guli Target Server Type : MySQL Target Server Version : 50729 File Encoding : 65001 Date: 14/05/2023 13:55:42 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for edu_subject -- ---------------------------- DROP TABLE IF EXISTS `edu_subject`; CREATE TABLE `edu_subject` ( `id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '课程类别ID', `title` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类别名称', `parent_id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '父ID', `sort` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序字段', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_parent_id`(`parent_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '课程科目' ROW_FORMAT = COMPACT; -- ---------------------------- -- Records of edu_subject -- ---------------------------- INSERT INTO `edu_subject` VALUES ('1178214681118568449', '后端开发', '0', 1, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681139539969', 'Java', '1178214681118568449', 1, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681181483010', '前端开发', '0', 3, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681210843137', 'JavaScript', '1178214681181483010', 4, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681231814658', '云计算', '0', 5, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681252786178', 'Docker', '1178214681231814658', 5, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681294729217', 'Linux', '1178214681231814658', 6, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681324089345', '系统/运维', '0', 7, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681353449473', 'Linux', '1178214681324089345', 7, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681382809602', 'Windows', '1178214681324089345', 8, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681399586817', '数据库', '0', 9, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681428946945', 'MySQL', '1178214681399586817', 9, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681454112770', 'MongoDB', '1178214681399586817', 10, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681483472898', '大数据', '0', 11, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681504444418', 'Hadoop', '1178214681483472898', 11, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681529610242', 'Spark', '1178214681483472898', 12, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681554776066', '人工智能', '0', 13, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681584136193', 'Python', '1178214681554776066', 13, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681613496321', '编程语言', '0', 14, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178214681626079234', 'Java', '1178214681613496321', 14, '2019-09-29 15:47:25', '2019-09-29 15:47:25'); INSERT INTO `edu_subject` VALUES ('1178585108407984130', 'Python', '1178214681118568449', 2, '2019-09-30 16:19:22', '2019-09-30 16:19:22'); INSERT INTO `edu_subject` VALUES ('1178585108454121473', 'HTML/CSS', '1178214681181483010', 3, '2019-09-30 16:19:22', '2019-09-30 16:19:22'); SET FOREIGN_KEY_CHECKS = 1;
2.2、表如何存储二级分类
2.3、Excel导入导出的应用场景
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
2.4、EasyExcel简介
1、特点
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
2.5、整合EasyExcel实现Excel写操作
EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)
1、引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.1</version> <exclusions> <exclusion> <artifactId>poi-ooxml-schemas</artifactId> <groupId>org.apache.poi</groupId> </exclusion> </exclusions> </dependency>
修改父工程的poi版本号为5.2.2
2、创建实体类,和excel数据对应
package com.lxg.demo.excel; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * @auther xiaolin * @creatr 2023/5/14 15:19 */ @Data public class DemoData { //设置excel表头名称 @ExcelProperty("学生编号") private Integer sno; @ExcelProperty("学生姓名") private String sname; }
3、测试写
package com.lxg.demo.excel; import com.alibaba.excel.EasyExcel; import lombok.Data; import java.util.ArrayList; import java.util.List; /** * @auther xiaolin * @creatr 2023/5/14 15:22 */ public class TestEasyExcel { public static void main(String[] args) { //实现excel写的操作 //1.设置写入文件夹地址和excel文件名称 String filename = "D:\\write.xlsx"; //2.调用easyexcel里面的方法实现写操作 //write方法两个参数:第一个参数文件路径名称,第二个参数实体类class 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; } }
方法二
public static void main(String[] args) throws Exception { // 写法2,方法二需要手动关闭流 String fileName = "F:\\112.xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("写入方法二").build(); excelWriter.write(data(), writeSheet); /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); }
2.6、整合EasyExcel实现Excel读操作
1、创建实体类,对应Excel数据
package com.lxg.demo.excel; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * @auther xiaolin * @creatr 2023/5/14 15:19 */ @Data public class DemoData { //设置excel表头名称 @ExcelProperty("学生编号") private Integer sno; @ExcelProperty("学生姓名") private String sname; }
2、创建监听器进行监听
package com.lxg.demo.excel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.processor.AnalysisEventProcessor; import com.alibaba.excel.util.ListUtils; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Map; // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 @Slf4j public class DemoDataListener extends AnalysisEventListener<DemoData> { //一行一行读取excel内容 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("****"+demoData); } //读取表头内容 @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { System.out.println("表头:"+headMap); } //读取完成之后 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } }
3、测试读操作
//实现excel读的操作 String fileName = "D:\\write.xlsx"; EasyExcel.read(fileName, DemoData.class,new DemoDataListener()) .sheet() .doRead(); // 写法2: /*fileName = "demo.xlsx"; ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); ReadSheet readSheet = EasyExcel.readSheet(0).build(); excelReader.read(readSheet); // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish();*/
2.7、课程分类添加功能
1、引入依赖
2、使用代码生成器把课程分类相关代码生成
3、编写controller
package com.lxg.eduservice.controller; import com.lxg.commonutils.R; import com.lxg.eduservice.service.SubjectService; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /** * @auther xiaolin * @creatr 2023/5/14 16:07 */ @RestController @RequestMapping("/eduservice/subject") @CrossOrigin public class SubjectController { @Autowired private SubjectService subjectService; // 添加课程分类 // 获取上传过来的文件,把文件内容读取出来 @ApiOperation(value = "Excel批量导入") @PostMapping("/addSubject") public R addSubject(MultipartFile file){ //上传过来的excel文件 subjectService.saveSubject(file,subjectService); return R.ok(); } }
4、创建实体类与excel对应关系
package com.lxg.eduservice.entity.excel; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * @auther xiaolin * @creatr 2023/5/14 16:14 */ @Data public class SubjectData { @ExcelProperty(index = 0) private String oneSubjectName; @ExcelProperty(index = 1) private String twoSubjectName; }
5、编写service
SubjectService
package com.lxg.eduservice.service; import com.lxg.eduservice.entity.Subject; import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.web.multipart.MultipartFile; /** * @author xiaolin * @description 针对表【edu_subject(课程科目)】的数据库操作Service * @createDate 2023-05-14 16:06:22 */ public interface SubjectService extends IService<Subject> { //添加课程分类 void saveSubject(MultipartFile file,SubjectService subjectService); }
SubjectServiceImpl
package com.lxg.eduservice.service.impl; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.lxg.eduservice.entity.Subject; import com.lxg.eduservice.entity.excel.SubjectData; import com.lxg.eduservice.listener.SubjectExcelListener; import com.lxg.eduservice.service.SubjectService; import com.lxg.eduservice.mapper.SubjectMapper; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; /** * @author xiaolin * @description 针对表【edu_subject(课程科目)】的数据库操作Service实现 * @createDate 2023-05-14 16:06:22 */ @Service public class SubjectServiceImpl extends ServiceImpl<SubjectMapper, Subject> implements SubjectService{ //添加课程分类 @Override public void saveSubject(MultipartFile file,SubjectService subjectService) { try { InputStream inputStream = file.getInputStream(); EasyExcel.read(inputStream, SubjectData.class,new SubjectExcelListener(subjectService)) .sheet() .doRead(); } catch (Exception e) { e.printStackTrace(); } } }
6、监听器
package com.lxg.eduservice.listener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lxg.eduservice.entity.Subject; import com.lxg.eduservice.entity.excel.SubjectData; import com.lxg.eduservice.service.SubjectService; import com.lxg.servicebase.exceptionhandler.GuliException; /** * @auther xiaolin * @creatr 2023/5/14 16:19 */ public class SubjectExcelListener extends AnalysisEventListener<SubjectData> { //因为监听器不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 //不能用mp实现数据库操作 public SubjectService subjectService; public SubjectExcelListener(){} public SubjectExcelListener(SubjectService subjectService) { this.subjectService = subjectService; } //读取excel内容,一行一行进行读取 @Override public void invoke(SubjectData subjectData, AnalysisContext analysisContext) { if (subjectData==null){ throw new GuliException(20001,"文件数据为空"); } //一行一行读取,每次读取有两个值,第一个值是一级分类,第二个值是二级分类 //判断一级分类是否重复 Subject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName()); if (existOneSubject==null) {//没有相同的一级分类,可以进行添加 existOneSubject = new Subject(); existOneSubject.setParentId("0"); existOneSubject.setTitle(subjectData.getOneSubjectName());; subjectService.save(existOneSubject); } String pid = existOneSubject.getId(); //添加二级分类 //判断二级分类是否重复 Subject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid); if (existTwoSubject==null) {//没有相同的一级分类,可以进行添加 existTwoSubject = new Subject(); existTwoSubject.setParentId(pid); existTwoSubject.setTitle(subjectData.getTwoSubjectName());; subjectService.save(existTwoSubject); } } //判断一级分类不能重复添加 private Subject existOneSubject(SubjectService subjectService,String name){ QueryWrapper<Subject> wrapper = new QueryWrapper<>(); wrapper.eq("title",name); wrapper.eq("parent_id",0); Subject oneSubject = subjectService.getOne(wrapper); return oneSubject; } //判断二级分类不能重复添加 private Subject existTwoSubject(SubjectService subjectService,String name,String pid){ QueryWrapper<Subject> wrapper = new QueryWrapper<>(); wrapper.eq("title",name); wrapper.eq("parent_id",pid); Subject twoSubject = subjectService.getOne(wrapper); return twoSubject; } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } }
7、实体类
package com.lxg.eduservice.entity; import com.baomidou.mybatisplus.annotation.*; import java.io.Serializable; import java.util.Date; import lombok.Data; /** * 课程科目 * @TableName edu_subject */ @TableName(value ="edu_subject") @Data public class Subject implements Serializable { /** * 课程类别ID */ @TableId private String id; /** * 类别名称 */ private String title; /** * 父ID */ private String parentId; /** * 排序字段 */ private Integer sort; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private Date gmtCreate; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified; @TableField(exist = false) private static final long serialVersionUID = 1L; }
四、第七天
1、添加课程分类前端实现
1.1、添加课程分类路由
{ path: '/subject', component: Layout, redirect: '/subject/list', name: '课程分类管理', meta: { title: '课程分类管理', icon: 'nested' }, children: [ { path: 'list', name: '课程分类列表', component: () => import('@/views/edu/subject/list'), meta: { title: '课程分类列表', icon: 'table' } }, { path: 'save', name: '导入课程类别', component: () => import('@/views/edu/subject/save'), meta: { title: '导入课程类别', icon: 'tree' } }, ] },
1.2、导入课程分类页面
<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 + '/default/template.xlsx'">点击下载模版</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/addSubject'" name="file" accept=".xls,.xlsx" > <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>
1.3、导入课程分类方法和数据
export default { data() { return { BASE_API: process.env.VUE_APP_BASE_API, // 接口API地址 OSS_PATH: process.env.VUE_APP_OSS_PATH, // 阿里云OSS地址 importBtnDisabled: false, // 按钮是否禁用, loading: false, // 是否显示上传中 }; }, created() {}, methods: { //点击按钮上传文件到接口里面 submitUpload() { //判断是否有文件 if (this.$refs.upload.uploadFiles.length != 0) { this.importBtnDisabled = true; this.loading = true; this.$refs.upload.submit(); } else { this.$message({ type: "error", message: "您还未选择文件哦!", }); } }, //上传成功的回调 fileUploadSuccess() { this.loading = false; this.$message({ type: "success", message: "添加课程分类成功!", }); // 上传成功之后清除历史记录;否则只能上传一次 this.$refs.upload.clearFiles(); }, //上传失败的回调 fileUploadError() { this.loading = false; this.$message({ type: "error", message: "添加课程分类失败", }); // 上传成功之后清除历史记录;否则只能上传一次 this.$refs.upload.clearFiles(); }, }, };
2、课程分类列表显示功能(树形)
2.1、参考tree模块把前端整合出来
2.2、按照tree模块的数据格式返回数据
1、OneSubject
package com.lxg.eduservice.entity.subject; import lombok.Data; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import java.util.ArrayList; import java.util.List; /** * @auther xiaolin * @creatr 2023/5/14 22:14 */ //一级分类 @Data public class OneSubject { private String id; private String title; //一个一级分类有多个二级分类 private List<TwoSubject> children = new ArrayList<>(); }
2、TwoSubject
package com.lxg.eduservice.entity.subject; import lombok.Data; /** * @auther xiaolin * @creatr 2023/5/14 22:14 */ //二级分类 @Data public class TwoSubject { private String id; private String title; }
3、SubjectService
List<OneSubject> getAllOneTwoSubject();
4、SubjectServiceImpl
@Override public List<OneSubject> getAllOneTwoSubject() { //1.查询所有一级分类 parent_id=0 QueryWrapper<Subject> wrapperOne = new QueryWrapper<>(); wrapperOne.eq("parent_id","0"); List<Subject> oneSubjectList = baseMapper.selectList(wrapperOne); //2.查询所有二级分类 parent_id!=0 QueryWrapper<Subject> wrapperTwo = new QueryWrapper<>(); wrapperTwo.ne("parent_id","0"); List<Subject> 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每个Subject对象 Subject subject = oneSubjectList.get(i); //把Subject的值取出来,放到OneSubject对象里面 OneSubject oneSubject = new OneSubject(); // oneSubject.setId(subject.getId()); // oneSubject.setTitle(subject.getTitle()); //subject的值复制到oneSubject(属性名要相同) BeanUtils.copyProperties(subject,oneSubject); //在一级分类循环遍历查询所有二级分类 //创建一个list集合封装每个一级分类的二级分类 List<TwoSubject> twoFinalSubjectList = new ArrayList<>(); //遍历二级分类list集合 for (int m = 0; m < twoSubjectList.size(); m++) { //获取每个二级分类 Subject tSubject = twoSubjectList.get(m); //判断二级分类pid和一级分类的id是否一样 if (tSubject.getParentId().equals(subject.getId())){ //把TSubject值复制到TwoSubject里面,放到twoFinalSubjectList里面 TwoSubject twoSubject = new TwoSubject(); BeanUtils.copyProperties(tSubject,twoSubject); twoFinalSubjectList.add(twoSubject); } } //把一级下面所有二级分类放到一级分类里面 oneSubject.setChildren(twoFinalSubjectList); finalSubjectList.add(oneSubject); } return finalSubjectList; }
5、controller
//课程分类列表(树形) @GetMapping("getAllSubject") public R getAllSubject(){ //list集合泛型是一级分类 List<OneSubject> list = subjectService.getAllOneTwoSubject(); return R.ok().data("list",list); }
2.3、前端展示
1、创建api
subject.js
import request from '@/utils/request' export default { //1、课程分类列表 getSubjectList() { return request({ url: '/eduservice/subject/getAllSubject', method: 'get' }) } }
2、list.vue
<template> <div class="app-container"> <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom: 30px" /> <el-tree ref="tree2" :data="treeData" :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: "", treeData: [], //返回所有分类的数据 defaultProps: { children: "children", label: "title", }, }; }, watch: { filterText(val) { this.$refs.tree2.filter(val); }, }, created() { this.getAllSubjectList(); }, methods: { getAllSubjectList() { subject .getSubjectList() .then((response) => { console.log(response); this.treeData = response.data.list; console.log(this.treeData); }) .catch((error) => { console.log(error); }); }, filterNode(value, data) { if (!value) return true; return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1; }, }, }; </script>
3、修改save.vue
this.$router.push("/subject/list");
3、课程管理模块需求
3.1、课程发布流程
3.2、课程管理相关表之间的关系
1、需要用到的表
https://gitee.com/lin-xugeng/guli_parent.git里面拿
2、关系
4、添加课程基本信息功能
4.1、使用代码生成器生成表对应相关代码
4.2、细节问题
4.3、创建vo类封装表单提交信息
package com.lxg.eduservice.entity.vo; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.math.BigDecimal; /** * @auther xiaolin * @creatr 2023/5/15 23:19 */ @Data public class CourseInfoVo { @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; }
4.4、接口编写
1、controller
package com.lxg.eduservice.controller; import com.lxg.commonutils.R; import com.lxg.eduservice.entity.vo.CourseInfoVo; import com.lxg.eduservice.service.CourseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.stereotype.Controller; /** * <p> * 课程 前端控制器 * </p> * * @author xiaolin * @since 2023-05-15 10:57:53 */ @RestController @RequestMapping("/eduservice/course") @CrossOrigin public class CourseController { @Autowired private CourseService courseService; @PostMapping("/addCourseInfo") public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo){ courseService.saveCourseInfo(courseInfoVo); return R.ok(); } }
2、Service
CourseService
package com.lxg.eduservice.service; import com.lxg.eduservice.entity.Course; import com.baomidou.mybatisplus.extension.service.IService; import com.lxg.eduservice.entity.vo.CourseInfoVo; /** * <p> * 课程 服务类 * </p> * * @author xiaolin * @since 2023-05-15 11:08:51 */ public interface CourseService extends IService<Course> { // 添加课程基本信息的方法 void saveCourseInfo(CourseInfoVo courseInfoVo); }
CourseServiceImpl
package com.lxg.eduservice.service.impl; import com.lxg.eduservice.entity.Course; import com.lxg.eduservice.entity.CourseDescription; import com.lxg.eduservice.entity.vo.CourseInfoVo; import com.lxg.eduservice.mapper.CourseMapper; import com.lxg.eduservice.service.CourseDescriptionService; import com.lxg.eduservice.service.CourseService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.lxg.servicebase.exceptionhandler.GuliException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * <p> * 课程 服务实现类 * </p> * * @author xiaolin * @since 2023-05-15 11:08:51 */ @Service public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService { @Autowired private CourseDescriptionService courseDescriptionService; // 添加课程基本信息的方法 @Override public void saveCourseInfo(CourseInfoVo courseInfoVo) { //1、向课程表添加课程基本信息 //CourseInfoVo对象转换为Course对象 Course eduCourse = new Course(); BeanUtils.copyProperties(courseInfoVo,eduCourse); int insert = baseMapper.insert(eduCourse); System.out.println("insert:"+insert); if (insert==0){ //添加失败 throw new GuliException(20001,"添加课程信息失败"); } //获取添加之后课程id String cid = eduCourse.getId(); //2、向课程简介表添加课程简介信息 //edu_course_description CourseDescription courseDescription = new CourseDescription(); courseDescription.setDescription(courseInfoVo.getDescription()); //设置描述id就是课程id courseDescription.setId(cid); courseDescriptionService.save(courseDescription); } }
需要把courseDescription修改一下
4.5、课程添加前端页面一
1、添加路由
注意添加了几个隐藏路由,做页面跳转
{ path: '/course', component: Layout, redirect: '/course/list', name: '课程管理', meta: { title: '课程管理', icon: 'nested' }, 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 } ] },
2、编写页面,实现接口调用
<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> <el-step title="创建课程大纲"></el-step> <el-step title="最终发布"></el-step> </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="一级分类" @change="subjectLevelOneChanged" > <el-option v-for="subject in subjectOneList" :key="subject.id" :label="subject.title" :value="subject.id" /> </el-select> --> <!-- 二级分类 --> <!-- <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> </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" placeholder="请填写课程的总课时数" /> </el-form-item> <!-- 课程简介 TODO --> <el-form-item label="课程简介"> <el-input v-model="courseInfo.description" placeholder=" " /> </el-form-item> <!-- 课程封面 TODO --> <!-- 课程封面--> <!-- <el-form-item label="课程封面"> <el-upload :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" :action="BASE_API + '/eduoss/fileoss'" class="avatar-uploader" > <img :src="courseInfo.cover" /> </el-upload> </el-form-item> --> <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: "", teacherId: "", lessonNum: 0, description: "", cover: "", price: 0, }, }; }, created() {}, methods: { saveOrUpdate() { //保存课程基本信息 course.addCourseInfo(this.courseInfo).then((response) => { //提示 this.$message({ message: "添加课程成功!", type: "success", }); //跳转到下一步 this.$router.push("/course/chapter/1"); }); }, }, }; </script>
3、编写api
import request from '@/utils/request' export default { //添加课程信息 addCourseInfo(courseInfo) { return request({ url: '/eduservice/course/addCourseInfo', method: 'post', data: courseInfo }) } }
4、接口返回课程id
controller
@PostMapping("/addCourseInfo") public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) { //返回添加之后课程id,为了后面添加大纲使用 String id = courseService.saveCourseInfo(courseInfoVo); return R.ok().data("courseId",id); }
CourseService
String saveCourseInfo(CourseInfoVo courseInfoVo);
CourseServiceImpl
5、修改保存方法
6、讲师下拉列表
<!-- 课程讲师 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>
course.js
//查询所有讲师 getTeacherList() { return request({ url: '/eduservice/teacher/findAll', method: 'get' }) },
7、课程分类二级联动
一级分类:
<el-form-item label="课程分类"> <el-select v-model="courseInfo.subjectParentId" placeholder="一级分类" @change="subjectLevelOneChanged" > <el-option v-for="subject in subjectOneList" :key="subject.id" :label="subject.title" :value="subject.id" /> </el-select> </el-form-item>
新增数据
一级分类数据接口调用
//查询所有一级分类 getOneSubject() { subject.getSubjectList().then((response) => { this.subjectOneList = response.data.list; }); },
二级分类:
<!-- 二级分类 --> <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>
methods
//点击一级分类时触发 subjectLevelOneChanged(value) { //value是一级分类的id //根据一级分类id查询二级分类 //遍历所有分类,包含一级和二级 for (let i = 0; i < this.subjectOneList.length; i++) { let subject = this.subjectOneList[i]; if (subject.id == value) { //应该要把二级分类清空 this.courseInfo.subjectId = ""; //找到了一级分类的children this.subjectTwoList = subject.children; } } },
8、课程封面上传
<!-- 课程封面 TODO --> <!-- 课程封面--> <el-form-item label="课程封面"> <el-upload :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" :action="BASE_API + '/eduoss/fileoss'" 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; },
本文作者:_xiaolin
本文链接:https://www.cnblogs.com/SilverStar/p/17415163.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)