前后端分离后,后端不再写页面,只提供 JSON 数据接口(XML数据格式现在用的比较少),前端可以移动端、小程序、也可以是 PC 端,前端负责 JSON 的展示,页面跳转等都是通过前端来实现的。
Vue只关注视图层,MVVM框架
使用方式大致分为两大类:直接将Vue在页面中引入,不做 SPA 应用。SPA应用
SPA(single page web application),单页面应用,是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。
这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页应用中,所有必要的代码( HTML、JavaScript 和 CSS )都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面。
SPA 有一个缺点,因为 SPA 应用部署后只有1个页面,而且这个页面只是一堆 js 、css 引用,没有其他有效价值,因此,SPA 应用不易被搜索引擎收录,所以,一般来说,SPA 适合做大型企业后台管理系统。
安装NodeJS,npm
直接搜索下载 NodeJS 即可,安装成功之后,npm 也就有了。安装成功之后,可以 在 cmd 命令哈验证是否安装成功:

NodeJS 安装成功之后,接下来安装 Vue的工具:
执行 npm install 命令时,默认使用的是国外的下载源 ,可以通过如下代码配置为使用淘宝的镜像:
npm config set registry https://registry.npm.taobao.org
npm install -g vue-cli # 只需要第一次安装时执行
vue init webpack my-project # 使用webpack模板创建一个vue项目
cd my-project #进入到项目目录中
npm install # 下载依赖(如果在项目创建的最后一步选择了自动执行npm install,则该步骤可以省略)
npm run dev # 启动项目
启动成功后,浏览器输入 http://localhost:8080 就能看到如下页面:

Vue 项目创建完成后,使用 Web Storm 打开项目,项目目录如下:

对于开发者来说,以后 99.99% 的工作都是在 src 中完成的,src 中的文件目录如下:

main.js 内容如下:
import Vue from 'vue' //在main.js 中,首先导入 Vue 对象
import App from './App' //导入 App.vue ,并且命名为 App
import router from './router' //导入router,注意,由于router目录下路由默认文件名为 index.js ,因此可以省略
Vue.config.productionTip = false
//创建一个Vue对象,设置要被Vue处理的节点是 '#app','#app' 指提前在index.html 文件中定义的一个div
new Vue({
el:'#app',
router,//将 router 设置到 vue 对象中,这里是一个简化的写法,完整的写法是 router:router,如果 key/value 一模一样,则可以简写
components:{App},//声明一个组件 App,App 这个组件在一开始已经导入到项目中了,但是直接导入的组件无法直接使用,必须要声明。
template:' '//template 中定义了页面模板,即将 App 组件中的内容渲染到 '#app' 这个div 中
})
项目启动成功后,看到的页面效果定义在 App.vue 中
App.vue 是一个vue组件,这个组件中包含三部分内容:1.页面模板(template);2.页面脚本(script);3.页面样式(style)
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
div>
template>
<script>
export default {
name: 'App'
}
script>
<style>
#app {
font - family : 'Avenir' , Helvetica , Arial , sans - serif ;
- webkit - font - smoothing : antialiased ;
- moz - osx - font - smoothing : grayscale ;
text - align : center ;
color : #2c3e50;
margin - top : 60px ;
}
style>
即 router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.user(Router)
export default new Router({
routes:[
{
//定义的路由表,path为 / ,对应的组件为 HelloWorld,即浏览器地址为 / 时,在router-view位置显示 HelloWorld 组件
path:'/',
name:'HelloWorld',
component:HelloWorld
}
]
})
WebStorm 中启动Vue
也可以直接在 webstorm 中配置vue并启动,点击右上角进行配置:

然后配置一下脚本 :

配置完成后,点击右上角启动按钮,就可以启动一个 Vue 项目,如下:

项目编译
这么大一个前端项目,肯定没法直接发布运行,当开发者完成项目开发后,将 cmd 命令行定位到当前项目目录,然后执行如下命令对项目进行打包:
npm run build
打包成功后,当前项目目录下会生成一个 dist 文件夹,这个文件夹中有两个文件,分别是 index.html 和 static ,index.html 页面就是我们 SPA 项目中唯一的 HTML 页面了,static 中则保存了编译后的 js、css等文件,项目发布时,可以使用 nginx 独立部署 dist 中的静态文件,也可以将静态文件拷贝到 Spring Boot 项目的 static 目录下,然后对 Spring Boot 项目进行编译打包发布。
====================================================
<div id="app">
{}}插值表达式,绑定vue中的data数据-->
{{message}}
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
message:'Hello Vue'
}
})
script>
<div id="app">
<div v-bind:style="msg">单向绑定div>
<div :style="msg">单向绑定div>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
msg:'color:red;'
}
})
script>
<div id="app">
{{keyword}}
<input type="text" :value="keyword"/>
<input type="text" v-model="keyword"/>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
keyword:'jordan'
}
})
script>
v-on:事件名称=“调用方法” 简写 @事件名称
<div id="app">
<button v-on:click="show()">事件绑定button>
<button @click="show()">事件绑定2button>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
msg:'color:red;'
},
methods:{
show(){
console.log("show……")
}
}
})
script>
v-if:条件判断
v-else
<div id="app">
<input type="checkbox" v-model="ok"/>
<div v-if="ok">选中了div>
<div v-else>没有选中div>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
ok:false
}
})
script>
v-for
<div id="app">
<div v-for="user in userList">
{{user.name}}---{{user.age}}
div>
<div v-for="(user,index) in userList">
{{index}}--{{user.name}}---{{user.age}}
div>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
userList:[
{"name":"jordan","age":23},
{"name":"james","age":30}
]
}
})
script>
<div id="app">
{{msg}}
div>
<script src="vue.min.js">script>
<script>
new Vue({
el:'#app',
data:{
msg:'hello'
},
created(){
debugger
console.log('created.....')
},
mounted(){
debugger
console.log('mounted')
}
})
script>
独立于vue的一个项目,可以用于浏览器和node.js中发送ajax请求
准备一个模拟后端返回的json数据
{
"code":200,
"message":"成功",
"data":{
"items":[
{"name":"jordan","age":20},
{"name":"kobe","age":19},
{"name":"james","age":18}
]
}
}
<div id="app">
<table>
<tr v-for="user in userList">
<td>{{user.name}}td>
<td>{{user.age}}td>
tr>
table>
div>
<script src="vue.min.js">script>
<script src="axios.min.js">script>
<script>
new Vue({
el:"#app",
data:{
userList:[]
},
created(){//页面渲染之前执行,调用方法,返回json数据
this.getList()
},
methods:{
getList(){
//使用axios方式ajax请求路径
axios.get("user.json")
.then(response =>{
console.log(response)
this.userList = response.data.data.items
console.log(this.userList)
}) //请求成功
.catch(error =>{
console.log(error)
}) //请求失败
}
}
})
script>
=====================================================
一、添加路由 src/router/index.js
{
path:'hospital/list',
name: '医院列表',
component: () => import('@/views/hosp/list'), //在views/hosp目录下创建list.vue
meta: {title:'医院列表',icon:'table'}
},
{
path: 'hospital/show/:id',
name: '查看',
component: () => import('@/views/hosp/show'),
meta: {title: '查看',noCache: true},
hidden: true //隐藏路由,点击页面按钮,通过隐藏路由跳转到指定的请求
}

二、封装api请求 api/hosp.js
import request from '@/utils/request'
export default{
//医院列表
getPageList(current,limit,searchObj){
return request({
url:'/admin/hosp/hospital/list/{current}/{limit}', //与后端的Controller接口匹配
method:'get',
params: searchObj
})
},
//根据dictCode查询下级数据字典(所有省)
findByDictCode(dictCode){
return request({
url: '/admin/cmn/dict/findByDictCode/${dictCode}',
method: 'get'
})
},
//根据id查询下级数据字典
findByParentId(dictCode){
return request({
url: '/admin/cmn/dict/findChildData/${dictCode}'
method: 'get'
})
}
}
三、vue页面+element-ui
<template>
<div class="app-container">
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-select
v-model="serachObje.provinceCode"
placeholder="请选择省"
@change="provinceChanged">
<el-option
v-for="item in provinceList"
:key="item.id"
:label="item.name"
:value="item.id"/>
el-select>
el-form-item>
el-form>
div>
template>
<script>
//引入封装的api路径
import hospApi from '@/api/hosp'
export defalut{
//变量和数据
data(){
return {
listLoading: true,//数据是否正在加载
list: null,//banner列表,医院列表数据
total: 0,//数据库中的总记录数
page: 1,//默认页码
limit: 10,//每页记录数
searchObj:{},//查询表单对象
provinceList:[],//所有省集合
cityList:[]//所有市集合
}
},
//页面渲染时调用的方法
created(){
this.fetchData()//参数若不传,默认为1
this.findAllProvince()
},
methods:{
//医院列表
fetchData(page=1){
this.page = page
//调用api方法,传入以上ui界面中的参数
hospApi.getHospList(this.page,this.limit,this.searchObj)
.then(response => {//(从后端Controller中获取content/totalElements)
//每页数据集合
this.list = response.data.content
//总记录数
this.total = response.data.totalElements
//加载图标不显示
this.listLoading = false
})
},
//查询所有的省
findAllProvinc(){
hospApi.findByDictCode('Province')
.then(response => {
this.provinceList = response.data
})
},
//点击某个省显示里面的市(联动)
provinceChanged(){
//初始化变量
this.cityList = []
this.searchObj.cityCodde = null
//调用方法,根据省id查询下面的子节点
hospApi.findChildId(this.searchObj.provinceCode)
.then(response => {
this.cityList = response.data
})
},
//分页,页码切换
changeSize(){
this.limit = size
this.fetchData(1)
}
}
}
script>
============================
myheader.vue组件中有注册登录的弹出框
import Vue from 'vue'
//注册与监听事件(该方法表示在页面渲染之后执行)
mounted(){
//注册全局登录事件对象
window.loginEvent = new Vue();
//监听登录事件
loginEvent.$on('loginDialogEvent',function(){
document.getElementById("loginDialog").click();//弹框操作
})
//触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
}
预约挂号页面_hoscode.vue组件(可以调用上面页面的全局事件)
import cookie from 'js-cookie'
schedule(depcode){
//登录判断
let token = cookie.get('token')
if(!token){ //如果没有cookie就弹出登录弹框
loginEvent.$emit('loginDialogEvent')
return
}
window.location.href = '/hospital/schedule?hoscode=' + this.hospital.
}
=======================================================
npm install axios -S
在生产环境中,需要对网络请求进行封装:
①、因为网络请求可能会出错,这些错误有的是代码错误导致的,也有的是业务错误,不管是哪一种错误,都需要开发者去处理,而我们不可能在每一次发送请求时都去枚举各种错误情况。
②、需要对前端请求进行封装,封装完成后,将前端错误统一处理,这样,开发者只需要在每一次发送请求的地方处理请求成功的情况即可
③、使用 axios 自带的拦截器来实现对错误的统一处理(在 axios 中,有请求拦截器,也有响应拦截器)
④、请求拦截器中可以统一添加公共的请求参数,例如单点登录中前端统一添加 token 参数
⑤、响应拦截器则可以实现对错误的统一处理
【注意】
错误的展示需要使用一种通用的方式,而不可以和页面绑定(例如,登录失败,在用户名/密码输入框后面展示错误信息,不支持这种错误显示方式),这里推荐使用 ElementUI 中的 Massage 来展示错误信息,这是一个页面无关的组件
//导入axios和message组件
import axios from 'axios'
import {Message} from 'element-ui'
//请求拦截器
axios.interceptors.request.use(config => {
return config;
},err => {
Message.error({message:'请求超时!'});
})
//响应拦截器,第一个参数data:服务器处理成功的响应,第二个er服务器处理失败的响应。
//data表示服务端返回的数据,data中的对象就是服务端返回的具体JSON
//外面的status表示HTTP响应码,里面的status是自定义的RespBean
axios.interceptors.response.use(data => {
if(data.status && data.status == 200 && data.data.status == 500){
//业务逻辑错误,此时直接通过 Message 将错误信息展示出来,然后 return 即可
Message.error({message:data.data.msg});
return;
}
if(data.data.msg){
Message.success({message:data.data.msg});
}
return data.data;// data.data ,即将服务端返回的数据 return ,这个数据最终会来到请求调用的地方
},err => {
if(err.response.status == 504 || err.response.status == 404){
Message.error({message:'服务器被吃了'});
}else if(err.response.status == 403){
Message.error({message:'权限不足,请联系管理员!'});
}else if(err.response.status == 401){
Message.error({message:err.response.data.msg});
}else{
if(err.response.data.msg){
Message.error({message.err.response.data.msg});
}else{
Message.error({message:'未知错误!'});
}
}
})
后端接口都采用 RESTful 风格来设计,所以前端主要封装 GET\POST\PUT\DELETE 方法,然后所有的请求参数都是用 JSON
请求封装完成后,还需要对方法进行封装,方便调用:
let base = '';
export const postRequest = (url,params) => {
return axios({
method:'post',
url:'${base}${url}',
data:params,
headers:{
'Content-Type':'application/json'
}
});
}
export const putRequest = (url,params) => {
return axios({
method:'put',
url:'${base}${url}',
data:params,
headers{
'Content-Type':'applicaiton/json'
}
});
}
export const deleteRequest = (url) => {
return axios({
method: 'delete',
url: `${base}${url}`
});
}
export const getRequest = (url) => {
return axios({
method: 'get',
url: `${base}${url}`
});
}
由于以上封装的方法每次使用都需要在相关的vue文件中引入
import {postRequest} from ‘…/utils/api’
所以进一步将方法封装成Vue插件,这样在每一个 vue 文件中,不需要引入方法就能够直接调用方法了。
参考 Vue 官方文档 https://cn.vuejs.org/v2/guide/plugins.html,如下:

官方给出了 5 种插件制作方式,我们这里采用第 4 种方案。 具体操作就是在 main.js 中引入所有的封装好的方法,然后挂载到 Vue.prototype 上即可,如下:
import {postRequest} from "./utils/api";
import {putRequest} from "./utils/api";
import {deleteRequest} from "./utils/api";
import {getRequest} from "./utils/api";
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.postRequest = postRequest;
封装完成后,以后在 vue 文件中,直接通过 this 就可以获取到网络请求方法的引用了,如下:
this.postRequest("/doLogin", this.user).then(msg=>{
if (msg) {
//登录成功,页面跳转
}
})
注意 ,then 中的 msg 就是响应拦截器中返回的 msg ,这个 msg 如果没有值,表示请求失败(失败已经在拦截器中进行处理了),如果有值,表示请求成功!
在前后端分离中,前端和后端在不同的端口或者地址上运行,如果前端直接向后端发送请求,这个请求是跨域的。
但是在项目部署时,前端打包编译后拷贝到 Java 项目中,和 Java 项目一起运行,此时不存在跨域问题。
而是通过配置 NodeJS 的请求转发,来实现网络请求顺利发送。
请求转发在 vue 项目的 config/index.js 文件中配置:

添加了请求转发配置之后,一定要重启前端项目才会生效。
此时启动前端项目,就可以顺利发送网络请求了。