阶段三:前端

关于VScode

要先生成工作区,再在工作区中执行前端代码
image.png


ES6

一些语法

let

  1. <script>
  2. // var 声明的变量没有局部作用域
  3. // let 声明的变量 有局部作用域
  4. {
  5. var a = 0
  6. let b = 1
  7. }
  8. console.log(a) // 0
  9. // console.log(b) // ReferenceError: b is not defined
  10. // var 可以声明多次
  11. // let 只能声明一次
  12. var m = 1
  13. var m = 2
  14. let n = 3
  15. // let n = 4
  16. console.log(m) // 2
  17. console.log(n) // Identifier 'n' has already been declared
  18. </script>

const

<script>
        // 1、声明之后不允许改变 
        const PI = "3.1415926"
        PI = 3 // TypeError: Assignment to constant variable.

        // 2、一但声明必须初始化,否则会报错
        const MY_AGE // SyntaxError: Missing initializer in const declaration
    </script>

解构赋值

<script>
        //1、数组解构
        // 传统
        let a = 1, b = 2, c = 3
        console.log(a, b, c)
        // ES6
        let [x, y, z] = [1, 2, 3]
        console.log(x, y, z)
        //2、对象解构
        let user = { name: 'Helen', age: 18 }
        // 传统
        let name1 = user.name
        let age1 = user.age
        console.log(name1, age1)
        // ES6
        let { name, age } = user    //注意:结构的变量必须是user中的属性
        console.log(name, age)
    </script>

模板字符串

<script>
        // 1、多行字符串, 使用了反引号 ` `
        let string1 = `Hey,
    can you stop angry now?`
        console.log(string1)
        // Hey,
        // can you stop angry now?

        // 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
        let name = "Mike"
        let age = 27
        let info = `My Name is ${name},I am ${age + 1} years old next year.`
        console.log(info)
        // My Name is Mike,I am 28 years old next year.


        // 3、字符串中调用函数
        function f() {
            return "have fun!"
        }
        let string2 = `Game start,${f()}`
        console.log(string2); // Game start,have fun!
    </script>

声明对象简写

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

自定义方法简写

<script>
        // 传统
        const person1 = {
            sayHi: function () {
                console.log("Hi")
            }
        }
        person1.sayHi();//"Hi"


        // ES6
        const person2 = {
            sayHi() {
                console.log("Hi")
            }
        }
        person2.sayHi() //"Hi"
    </script>

对象拓展运算符

<script>
        // 1、拷贝对象
        let person1 = { name: "Amy", age: 15 }
        let someone = { ...person1 }
        console.log(someone) //{name: "Amy", age: 15}

        // 2、合并对象
        let age = { age: 15 }
        let name = { name: "Amy" }
        let person2 = { ...age, ...name }
        console.log(person2) //{age: 15, name: "Amy"}
    </script>

箭头函数

 <script>
        // 传统
        var f1 = function (a) {
            return a
        }
        console.log(f1(1))

        // ES6
        var f2 = a => a
        console.log(f2(1))

        // 当箭头函数没有参数或者有多个参数,要用 () 括起来。
        // 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
        // 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。


        //多行语句需用{}包裹
        var f3 = (a, b) => {
            let result = a + b
            return result
        }
        console.log(f3(6, 2)) // 8
        // 省略{}
        var f4 = (a, b) => a + b
    </script>

VUE

准备工作

安装vue有不同方法,这里使用script标签内引入.每个vue文件都要引入。或直接把 vue.js拖入VScode
配置代码片段:vueh代码片段.txt

<body>
    <!-- id标识vue作用的范围 -->
    <div id="app">
        <!-- {{}} 插值表达式,绑定vue中的data数据 -->
        {{ message }}
    </div>
    <script src="vue.min.js"></script>
    <script>
        // 创建一个vue对象
        new Vue({
            el: '#app',//绑定vue作用的范围
            data: {//定义页面中显示的模型数据
                message: 'Hello Vue!'
            }
        })
    </script>
</body>

基本流程

image.png

基本语法

单向绑定 :v-bind

页面中的数据变化不影响data中数据的变化


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

双向绑定:v-model

页面中数据和data中数据互相绑定,要变一起变

body>
    <div id="app">
        <!-- v-bind:value只能进行单向的数据渲染 -->
        <input type="text" v-bind:value="searchMap.keyWord">
        <!-- v-model 可以进行双向的数据绑定 -->
        <input type="text" v-model="searchMap.keyWord">
        <p>您要查询的是:{{searchMap.keyWord}}</p>
    </div>
    <script src="vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                searchMap: {
                    keyWord: '谷'
                }
            }
        })
    </script>
</body>

事件

<body>
    <div id="app">
        <!-- v-on 指令绑定事件,click指定绑定的事件类型,事件发生时调用vue中methods节点中定义的方法 -->
        <button v-on:click="search()">查询</button><br>

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

        <p>您要查询的是:{{searchMap.keyWord}}</p><br>
        <p><a v-bind:href="result.site" target="_black">{{result.title}}</a></p>


    </div>
    <script src="vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                searchMap: {
                    keyWord: '尚硅谷'
                },
                //查询结果
                result: {}
            },
            methods: {
                search() {
                    console.log('search');
                    this.result = {
                        "title": "尚硅谷",
                        "site": "http://www.baidu.com"
                    }
                }
            }
        })
    </script>
</body>

修饰符

prevent,阻止默认行为

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

条件渲染 ¥¥

v-if & v-show

<body>
    <div id="app">
        <input type="checkbox" v-model="ok">同意许可协议
        <!-- v:if条件指令:还有v-else、v-else-if 切换开销大 -->
        <h1 v-if="ok">if:Lorem ipsum dolor sit amet.</h1>
        <h1 v-else>no</h1>
        <br><br><br>
        <!-- v:show 条件指令 初始渲染开销大 -->
        <h1 v-show="ok">show:Lorem ipsum dolor sit amet.</h1>
        <h1 v-show="!ok">no</h1>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                ok: false
            }
        })
    </script>
</body>

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销 毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会 开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基 于 CSS 进行切换。 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频 繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

列表渲染 ¥¥

简单列表渲染

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

遍历列表数据

<body>
    <div id="app">
        <!-- 2、遍历数据列表 -->
        <table border="1">
            <!-- <tr v-for="item in userList"></tr> -->
            <tr v-for="(item, index) in userList">
                <td>{{index}}</td>
                <td>{{item.id}}</td>
                <td>{{item.username}}</td>
                <td>{{item.age}}</td>
            </tr>
        </table>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                userList: [
                    { id: 1, username: 'helen', age: 18 },
                    { id: 2, username: 'peter', age: 28 },
                    { id: 3, username: 'andy', age: 38 }
                ]
            }
        })
    </script>
</body>

组件¥¥¥

局部组件

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

全局组件

image.png

生命周期

<body>
    <div id="app">
        <button @click="update">update</button>
        <h3 id="h3">{{ message }}</h3>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                message: '床前明月光'
            },
            methods: {
                show() {
                    console.log('执行show方法')
                },
                update() {
                    this.message = '玻璃好上霜'
                }
            },
        })
    </script>
</body>

主要使用created和mounte方法,渲染之前和渲染之后

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

路由

自定义路由和路由组件,创建router实例和vue实例,把路由组件加到路由中,把路由加到route实例中,把router加到vue实例中

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


    <script>
        // 1. 定义(路由)组件。
        // 可以从其他文件 import 进来
        const Welcome = { template: '<div>欢迎</div>' }
        const Student = { template: '<div>student list</div>' }
        const Teacher = { template: '<div>teacher list</div>' }

        // 2. 定义路由
        // 每个路由应该映射一个路由组件。
        const routes = [
            { path: '/', redirect: '/welcome' }, //设置默认指向的路径
            { path: '/welcome', component: Welcome },
            { path: '/student', component: Student },
            { path: '/teacher', component: Teacher }
        ]

        // 3. 创建 router 实例,然后传 `routes` 配置
        const router = new VueRouter({
            routes // (缩写)相当于 routes: routes
        })


        // 4. 创建和挂载根实例。
        // 从而让整个应用都有路由功能
        const app = new Vue({
            el: '#app',
            router
        })


 // 现在,应用已经启动了!
    </script>
</body>

Axios

axios.js
image.png
前端通过axios.get调用后台的数据,以json形式返回
注:data{} created{} 渲染之前执行 methods{}

一些工具

element-ui

  1. 官网: http://element-cn.eleme.io/#/zh-CN
  2. 饿了么前端出品的基于vue.js的后台组件库,方便程序员进行页面快速布局和构建 。
  3. 使用先引入,引入有多种方法 .这里使用标签引入

    //引入样式
    <!-- import CSS -->
    <link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">
    //引入js
    <!-- import Vue before Element -->
    <script src="vue.min.js"></script>
    <!-- import JavaScript -->
    <script src="element-ui/lib/index.js"></script>
    
  4. 简单使用 ```html

    Button

    Try Element

//其中el就是引入的element组件库标签

<a name="ocXGD"></a>
### Node.js  
 Node.js 就是运行在服务端的 JavaScript  ;安装之后可在终端运行需在浏览器控制台中的JavaScript代码,可在终端连接后台,代替了Tomcat服务器的工作.

1. 如创建js文件后,在其中写入: console.log('Hello Node.js')  ;在终端运行此js文件: node 文件名.js  ,即可执行此JavaScript代码。
1. 在终端指定网址输出,实现了Tomcat的功能
```javascript
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/');

NPM

 NPM全称Node Package Manager,是Node.js包管理工具  ;通过npm 可以很方便地下载js库,管理前端工程。 <br />Node.js默认安装的npm包和工具的位置:Node.js目录\node_modules  <br />版本依赖存于package.json中,其版本号被package-lock.json锁定。在项目中使用npm install 下载之前定义好的指定版本依赖,即可运行项目

一些指令

1. 初始化
#建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。
#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
--------------------------------------------------------

2.配置镜像
#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
npm config set registry https://registry.npm.taobao.org
#查看npm配置信息
npm config list

--------------------------------------------------------
3. 安装
#使用 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

--------------------------------------------------------
4. 其他命令
#更新包(更新到最新版本)
npm update 包名
#全局更新
npm update -g 包名
#卸载包
npm uninstall 包名
#全局卸载
npm uninstall -g 包名

Babel

转码器,把es6代码转为代码。因为有的低版本浏览器不支持es6代码

1.安装命令行转码工具
npm install --global babel-cli
#查看是否安装成功
babel --version


2.初始化项目: nom init -y  更新package.json


3.创建配置文件 .babelrc
{
 "presets": ["es2015"],        //[]内是转码规则
 "plugins": []
}


4.安装转码器
npm install --save-dev babel-preset-es2015



5.编写js文件测试转码,显示命令行代码
# 转码结果写入一个文件
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

模块化

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其 他文件不可见。

js模块化

CommonJS使用exportsrequire来导出和导入模块

  1. 编写js文件,此文件实现四则运算,编写导出 ```javascript // 定义成员: 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 }


2. 在当前模块中引入要使用的模块,即可使用
```javascript
//引入模块,注意:当前路径必须写 ./
const m = require('./四则运算.js')
console.log(m)
const result1 = m.sum(1, 2)
const result2 = m.subtract(1, 2)
console.log(result1, result2)
  1. 运行: node 文件名.js

    es6模块化

    ES6使用 export 和 import 来导出、导入模块。
  2. 编写方法并导出

    export function getList() {
    console.log('获取数据列表')
    }
    export function save() {
    console.log('保存数据')
    }
    
  3. 使用导出的模块

    //只取需要的方法即可,多个方法用逗号分隔
    import { getList, save } from "./userApi.js"
    getList()
    save()
    
  4. 注意:这时的程序无法运行的,因为ES6的模块化无法在Node.js中执行,需要用Babel编辑成ES5后再 执行。
    ```javascript //或者导出时并不指定名,在引入时指出。此处指出为user export default { getList() { console.log(‘获取数据列表2’) }, save() { console.log(‘保存数据2’) }


import user from “./userApi2.js” user.getList() user.save()

<a name="fZqPb"></a>
### Webpack  
     Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按 照指定的规则生成对应的静态资源。  <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1631089333100-a77e0998-9908-4013-b794-9e69f08d03fe.png#clientId=ud4c74903-5f6a-4&from=paste&height=422&id=u12a05c73&margin=%5Bobject%20Object%5D&name=image.png&originHeight=422&originWidth=770&originalType=binary&ratio=1&size=90387&status=done&style=none&taskId=uc56eecf0-e898-462f-ac2b-60487832f35&width=770)
<a name="H3fQb"></a>
#### JS打包
webpack就是对js文件进行打包处理,多个js→ 1个js
```bash
1.安装,初始化
npm install -g webpack webpack-cli
npm init -y


2.编写相关js,进行测试把1.js,2.js,3.js打包成一个js文件---bundle.js
exports.info = function (str) {
 document.write(str);
}
-------------
exports.add = function (a, b) {
 return a + b;
}
-----------
const common = require('./1.js');
const utils = require('./2.js');
common.info('Hello world!' + utils.add(100, 200));


3.配置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' //输出文件
 }
}



4.测试
webpack #有黄色警告
webpack --mode=development #没有警告,以为设置了当前模式为开发模式
#执行后查看bundle.js 里面包含了上面两个js文件的内容并进行了代码压缩

可在html中引入此js文件查看效果

--------------------------
设置当前模式为开发模式,需在package.json中进行配置
"scripts": {
 //...,
 "dev": "webpack --mode=development"
 }
  • 注:出现在cmd中可执行webpack而vscode终端无法使用webpack情况,

以下是解决方法:1、输入命令Set-ExecutionPolicy -Scope CurrentUser 2、然后再输入RemoteSigned

打包中加入css

 Webpack 本身只能处理 JavaScript 模块,如果要在对js的打包过程中处理其他类型的文件,就需要使用 loader 进行转换。 Loader 可以理解为是模块和资源的转换器。  
1.安装转换器loader
npm install --save-dev style-loader css-loader

2.修改webpack.config.js,加上module的rules
const path = require("path"); //Node.js内置模块
module.exports = {
    entry: './3.js', //配置入口文件
    output: {
        path: path.resolve(__dirname, './'), //输出路径,__dirname:当前文件所在路径
        filename: 'bundle.js' //输出文件
    },
    module: {
        rules: [
            {
                test: /\.css$/, //打包规则应用到以css结尾的文件上
                use: ['style-loader', 'css-loader']
            }]
    }
}


3.测试
编写style.css,并在3.js中require此css文件
webpack --mode=development        进行打包
在html中引入js (require('./style.css'),查看效果
-----------------
style.css:
body{
 background:pink;
}

前端开发

前端模板介绍

vue-element-admin是基于element-ui 的一套后台管理系统集成方案。

# 解压压缩包
# 进入目录
cd vue-element-admin-master
# 安装依赖
npm install
# 启动。执行后,浏览器自动弹出并访问http://localhost:9527/
npm run dev
 vueAdmin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模 板进行二次开发。  
# 解压压缩包
# 进入目录
cd vue-admin-template-master
# 安装依赖
npm install
# 启动。执行后,浏览器自动弹出并访问http://localhost:9528/
npm run dev

一些修改,准备工作

  1. 前端项目入口:index.html或main.js

image.png

  1. 关于Eslint语法检测,此时可以关闭,此插件要求相当严格。 更改参数:useEslint: false
  1. 更改后端接口地址

image.png

  1. 临时改登录地址为本地,后续再用SpringSecurity修改

image.png
真实的login地址应为这两部分的拼接
image.png

  1. 用@CrossOrigin解决跨域问题,把注解加在Controller上

image.png

  1. 开启国际化

在src/main.js中: import locale from ‘element-ui/lib/locale/lang/zh-CN’ // lang i18n


连接前后端测试

开启前后端,数据库,利用前端页面向后端发起请求,后端响应请求返回给前端结果。


流程介绍

  1. 路由管理:在router中的index.js中配置,每个{}是一个模块,这里设置了讲师管理,课程管理模块。每个路由的 name不能相同

    1. 每个path是一个路由,每个路由下面还可以设置子路由
    2. 可以通过hidden设置隐藏路由,不需要在父路由中显示 ```javascript 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: ‘/edu/teacher’, component: Layout, redirect: ‘/edu/teacher/list’, name: ‘Teacher’, meta: { title: ‘讲师管理’, icon: ‘peoples’ },

children: [ //子路由1
{ path: ‘list’, name: ‘EduTeacherList’, component: () => import(‘@/views/edu/teacher/list’), //给出组件的地址 meta: { title: ‘讲师列表’ } }, //子路由2 { path: ‘create’, name: ‘EduTeacherCreate’, component: () => import(‘@/views/edu/teacher/form’), meta: { title: ‘添加讲师’ } }, //子路由3,隐藏路由 { path: ‘edit/:id’, name: ‘EduTeacherEdit’, component: () => import(‘@/views/edu/teacher/form’), meta: { title: ‘编辑讲师’, noCache: true }, hidden: true },

//测试路由 { path: ‘test’, name: ‘测试页面’, component: () => import(‘@/views/edu/teacher/test’), meta: { title: ‘测试页面’, icon: ‘tree’ } },

] },

{ path: ‘*’, redirect: ‘/404’, hidden: true }

]


2. 添加页面:点击路由跳出新增的页面,使用component引入,页面的具体样式在component中编写
```javascript
<template>
<div class="app-container">
    测试页面  test
</div>
</template>
  1. 定义接口:编写接口
    1. 页面呈现需要和后端进行交互,所以需要编写响应操作的接口。让后台与数据库交换返回数据到前端
    2. 接口文件存放在api中,这里举例讲师接口teacher.js
    3. 通过url和method对后端进行访问并返回数据 ```java import request from ‘@/utils/request’

export default { //查询所有的教师信息 getTeacherList() { return request({ url: /serviceedu/edu-teacher/findAll, method: ‘GET’ }) }, }


4. 在之前编写的component里的script中引入teacher.js,这样页面中就拥有了数据。具体的数据的呈现可以用html和css进行编写

![image.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1631193658246-03645f34-bcd6-4f33-bbba-20b8bf492dfa.png#clientId=uc09a7f75-4d08-4&from=paste&height=281&id=hPqft&margin=%5Bobject%20Object%5D&name=image.png&originHeight=374&originWidth=821&originalType=binary&ratio=1&size=108942&status=done&style=none&taskId=uca5e6ef4-80ce-4772-8a1c-e08ebe74c7e&width=616)

5. 返回数据的键要对应

![image.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1631194157776-4f531f99-a4eb-455b-a8ea-20d29a46549d.png#clientId=uc09a7f75-4d08-4&from=paste&height=449&id=u819b741a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=897&originWidth=1422&originalType=binary&ratio=1&size=196513&status=done&style=none&taskId=u114a36af-4362-412b-90f7-8f29c3aef70&width=711)

---

<a name="cHnuo"></a>
### CURD
<a name="UYDAw"></a>
#### 查
分页条件查询

1. 先实现分页查
```javascript
//在methods中添加getList方法,获取数据
getList(page = 1) {
      this.page = page;
      teacherApi
        .getTeacherListPage(this.page, this.limit, this.teacherQuery)
        .then((response) => {
          //请求成功
          //response接口返回的数据
          //console.log(response)
          this.list = response.data.rows;
          this.total = response.data.total;
          console.log(this.list);
          console.log(this.total);
        })
        .catch((error) => {
          console.log(error);
        });
    },
 =========================================================
//再加上分页的样式,放置数据
       <!-- 表格 -->
    <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 }}
          <!-- {{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="'/teacher/add/' + 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="removeTeacher(scope.row.id)"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-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="getList"
    />
  </div>
  1. 再增加条件查 ```vue //以下查询条件的样式。每一个是一个表单,其中使用了v-model双向绑定,所以当点击查询时,getList会重新定义查询条件,根据新的条件调用getList,返回查询到的值。 //此逻辑中之前的分页条件查询,条件构造器的设置,以及条件值非空的判断

讲师列表

  <el-form-item>
    <el-select v-model="teacherQuer.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="teacherQuer.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="teacherQuer.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="getList()">查询</el-button>
  <el-button type="default" @click="resetData()">清空</el-button>
</el-form>

3. 再加上条件清空功能
```javascript
//清空查询表单中的条件,并返回到原始用户列表
//当用户点击清空,会触发自定义的  resetData 方法,达到上述目的

 resetData() {
      this.teacherQuery = {};
      this.getList();
    },

  1. 定义api

    removeById(teacherId) {
    return request({
    url: `${api_name}/${teacherId}`,
    method: 'delete'
    })
    }
    
  2. 在组件中引入方法,并设置弹框

    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: '删除失败'
             })
         }
     })
    }
    

  1. 配置api

    save(teacher) {
    return request({
    url: api_name,
    method: 'post',
    data: teacher
    })
    }
    
  2. 引入新增页面

    <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>
    
  3. 编写新增js

    < script > import teacher from '@/api/edu/teacher'export
    default {
         data() {
             return {
                 teacher:
                 {
                     name:
                     '',
                     sort: 0,
                     level: 1,
                     career: '',
                     intro: '',
                     avatar: ''
                 },
                 saveBtnDisabled: false // 保存按钮是否禁用,
             }
         },
         methods: {
             saveOrUpdate() {
                 this.saveBtnDisabled = true this.saveData()
             },
             // 保存
             saveData() {
                 teacher.save(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: '保存失败'
                     })
                 })
             }
         }
     } < /script>/
    

  4. 回显

    1. 更新页面要回显要修改的数据 ``javascript 1.编写api //获取讲师详细信息 getTeacherIno(id) { return request({ url:/serviceedu/edu-teacher/getTeacher/${id}`, method: ‘get’ }) },
===========================
  1. 编写方法
    // 根据id查询讲师信息 getInfo(id) { teacher.getById(id) .then(response => {
    this.teacher = response.data.item
    
    }) .catch((response) => {
    this.$message({
      type: 'error',
      message: '获取数据失败'
    })
    
    })

    },

    3.在created中引入回显方法 created() { console.log(‘created’) if (this.$route.params && this.$route.params.id) { const id = this.$route.params.id this.fetchDataById(id) } }

2. 更新
```java
1.编写api
//更新讲师信息,传入的是teacher,但是路由是用teacher.id结尾
  update(teacher) {
    return request({
      url: `/serviceedu/edu-teacher/update/${teacher.id}`,
      method: 'put',
      data: teacher
    })
  },

2. 组件中调用api
// 修改
    updateTeacher() {
      // this.saveBtnDisabled = true
      teacherApi.update(this.teacher)
        .then(response => {
          // 提示信息
          this.$message({
            type: 'success',
            message: '修改成功'
          })
          // 路由跳转
          this.$router.push({
            path: '/teacher/list'
          })
        })
        // eslint-disable-next-line handle-callback-err
        .catch(error => {
          this.$message({
            type: 'success',
            message: '修改失败'
          })
        })
    },

注:因为增加和更新公用一个页面,所以当点击此页面的保存按钮时,要更具根据是否已有id进行方法选择

 saveOrUpdate() {
      //判断修改还是添加
      //根据teacher是否有id
      if(!this.teacher.id) {
        //添加
        this.saveTeacher()
      } else {
        //修改
        this.updateTeacher()
      }
    },

存在的BUG

添加讲师和更新讲师公用一个页面,两次跳转同一个页面,但是created方法只会在第一次跳转时执行。所以初始化就只在第一次跳转时执行,第二次及以后跳转此页面会保留原先的数据。
通过监听watch来解决,当请求路由出现变化就执行Init()方法进行初始化。

// $route(to, from)代表当路由出现从to到from的变化,watch中方法将会执行
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.getInfo(id);
      } else {
        this.teacher = {};
      }
    }
  }

Nginx

http://nginx.org/en/download.html
反向代理服务器。因为前端的BASE_API中保存的是一个地址,但后端的微服务使用的不仅仅是一个端口,这就需要nginx来进行转发。
需要再nginx.config中进行配置
image.png

  1. nginx的安装和使用
    1. nginx安装后,尽量在文件夹位置使用cmd打开exe,关闭尽量在任务管理器中关闭
  2. 配置conf,在nginx.conf后加上转发规则 ```bash 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 ~ /educms/ {
         proxy_pass http://localhost:8004;
     }
     location ~ /ucenterservice/ {
         proxy_pass http://localhost:8006;
     }
     location ~ /edumsm/ {
         proxy_pass http://localhost:8005;
     }
     location ~ /orderservice/ {
         proxy_pass http://localhost:8007;
     }
     location ~ /staservice/ {
         proxy_pass http://localhost:8008;
     }
     location ~ /admin/ {
         proxy_pass http://localhost:8009;
     }
    

    }


3. 修改前端BASE_API
```bash
BASE_API: '"http://localhost:9001"',

头像上传

  1. OSS(Object storage Service)使用

后端集成OSS

  1. 新建子模块service-oss模块,引入依赖

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

    服务端口

    server.port=8002

    服务名

    spring.application.name=service-oss

环境设置:dev、test、prod

spring.profiles.active=dev

阿里云 OSS

不同的服务器,地址不同

aliyun.oss.file.endpoint=your endpoint aliyun.oss.file.keyid=your accessKeyId aliyun.oss.file.keysecret=your accessKeySecret

bucket可以在控制台创建,也可以使用java代码创建

aliyun.oss.file.bucketname=guli-file


4. 在启动类上对之前编写的配置进行扫描,此服务不使用数据库,只是文件上传,所以要加上第三行指令
```java
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class OssApplication {
    public static void main(String[] args) {
        SpringApplication.run(OssApplication.class, args);
    }
}

后端实现oss上传

  1. 创建读取配置文件的常用类

    @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;
    
     public void afterPropertiesSet() throws Exception {
         END_POINT = endpoint;
         ACCESS_KEY_ID = keyId;
         ACCESS_KEY_SECRET = keySecret;
         BUCKET_NAME = bucketName;
     }
    }
    
  2. 创建接口和实现类 ```java public interface OssService { //上传头像到oss中 String uploadFileAvator(MultipartFile file); }


@Service public class OssServiceImpl implements OssService {

//头像上传到oss
@Override
public String uploadFileAvator(MultipartFile file) {

    // 工具类获取值
    String endpoint = ConstantPropertiesUtils.END_POINT;
    String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
    String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
    String bucketName = ConstantPropertiesUtils.BUCKET_NAME;

    try {
        // 创建OSS实例
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 上传文件流。
        InputStream inputStream = file.getInputStream();
        //获取文件名称
        String fileName  = file.getOriginalFilename();

        //在文件名称里面添加随机唯一的值
        String uuid = UUID.randomUUID().toString().replaceAll("-","");
        fileName = uuid + fileName;

        //把文件按日期进行分类
        // 2020/10/08/1.jpg
        //获取当前日期
        String datePath = new DateTime().toString("yyyy/MM/dd");
        //拼接
        fileName = datePath + "/" + fileName;

        //调用oss方法实现上传
        //第一个参数  Bucket名称
        //第二个参数  上传到oss文件路径和文件名称  aa/bb/1.jpg
        //第三个参数  上传文件输入流
        ossClient.putObject(bucketName, fileName, inputStream);

        // 关闭OSSClient。
        ossClient.shutdown();

        //把上传之后文件路径返回
        //需要把上传到阿里云oss路径手动拼接出来
        //   https://guli-edu-20201.oss-cn-beijing.aliyuncs.com/1.jpg
        String url = "https://"+bucketName+"."+endpoint+"/"+fileName;
        return url;
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

}


3. 编写controller
```java
@Api(description="阿里云文件管理")
@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {

    @Autowired
    private OssService ossService;

    @ApiOperation(value = "文件上传")
    @PostMapping("/upload")
    public R uploadOssFile(@ApiParam(name = "file", value = "文件", required = true)
                            @RequestParam("file") MultipartFile file){
        //返回上传到oss的路径
        String url = ossService.uploadFileAvator(file);
        return R.ok().data("url",url);
    }

}

前端实现头像上传

  1. 在组件中add.vue中加入头像上传组件

    <!-- 讲师头像 -->
    <el-form-item label="讲师头像">
    
     <!-- 头衔缩略图 -->
     <pan-thumb :image="teacher.avatar"/>
     <!-- 文件上传按钮 -->
     <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
     </el-button>
    
     <!--
    v-show:是否显示上传组件
    :key:类似于id,如果一个页面多个图片上传控件,可以做区分
    :url:后台上传的url地址
    @close:关闭上传组件
    @crop-upload-success:上传成功后的回调 
    <input type="file" name="file"/>
    -->
     <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>
    
  2. 在js中引入第三方组件,并实现上传 ```javascript export default { components: { ImageCropper, PanThumb }, data() { return { //其它数据模型 ……,

    //上传弹框组件是否显示 imagecropperShow:false, imagecropperKey:0,//上传组件key值 BASE_API:process.env.BASE_API,//获取dev.env.js里面地址 saveBtnDisabled: false//保存按钮是否禁用 } }, //……., methods:{ //关闭上传弹框的方法 close() { this.imagecropperShow = false //上传组件初始化 this.imagecropperKey = this.imagecropperKey + 1 }, //上传成功方法 cropSuccess(data) { this.imagecropperShow=false //上传之后接口返回图片地址 this.teacher.avatar=data.url //上传组件初始化 this.imagecropperKey = this.imagecropperKey + 1 }, //其他函数 ……, } }


4. 默认头像
```javascript
1. 设置默认地址
OSS_PATH: '"https://guli-file.oss-cn-beijing.aliyuncs.com"'

2. 设置avatar的初始值
const defaultForm = {
  ......,
  avatar: process.env.OSS_PATH + '/avatar/default.jpg'
}