简单梳理一下这次vue项目的知识点
utils
request.js
主要是封装axios模块用得
非组件模块可以这样加载使用 element 的 message 提示组件
import { Message } from 'element-ui'
// 创建一个 axios 实例,说白了就是复制了一个 axios
// 我们通过这个实例去发请求,把需要的配置配置给这个实例来处理
const request = axios.create({
baseURL: 'http://ttapi.research.itcast.cn/', // 请求的基础路径
// 定义后端返回的原始数据的处理
// 参数 data 就是后端返回的原始数据(未经处理的 JSON 格式字符串)
transformResponse: [function (data) {
// Do whatever you want to transform the data
// console.log(data)
// 后端返回的数据可能不是 JSON 格式字符串
// 如果不是的话,那么 JSONbig.parse 调用就会报错
// 所以我们使用 try-catch 来捕获异常,处理异常的发生
try {
// 如果转换成功,则直接把结果返回
return JSONbig.parse(data)
} catch (err) {
console.log('转换失败', err)
// 如果转换失败了,则进入这里
// 我们在这里把数据原封不动的直接返回给请求使用
return data
}
// axios 默认在内部使用 JSON.parse 来转换处理原始数据
// return JSON.parse(data)
}]
})
在这里面可以设置请求拦截器,响应拦截器
请求拦截器可以对一些请求进行预处理,比如加入token啥的
// 请求拦截器
request.interceptors.request.use(
// 任何所有请求会经过这里
// config 是当前请求相关的配置信息对象
// config 是可以修改的
function (config) {
const user = JSON.parse(window.localStorage.getItem('user'))
// 如果有登录用户信息,则统一设置 token
if (user) {
config.headers.Authorization = `Bearer ${user.token}`
}
// 然后我们就可以在允许请求出去之前定制统一业务功能处理
// 例如:统一的设置 token
// 当这里 return config 之后请求在会真正的发出去
return config
},
// 请求失败,会经过这里
function (error) {
return Promise.reject(error)
}
)
api
permission.js
role.js
user.js
都会从utils中导入request,为不同的请求设置访问后端的端口号,请求方法添加参数
然后再export出去,让别的模块调用
router
index.js
路由是设置访问的时候会到达哪个自己定义的组件上去,也可以做一些权限控制,同时也可以形成父子组件的形式。
import Vue from 'vue'
import VueRouter from 'vue-router'
// 在 VueCLI 创建的项目中 @ 表示 src 目录
// 它是 src 目录的路径别名
// 好处:它不受当前文件路径影响
// 注意:@ 就是 src 路径,后面别忘了写那个斜杠
// 使用建议:如果加载的资源路径就在当前目录下,那就正常写
// 如果需要进行父级路径查找的都使用 @
import Login from '@/views/login/'
import Layout from '@/views/layout/'
import Home from '@/views/root'
import User from '@/views/user'
import Permission from '@/views/permission'
// 非组件模块可以这样加载使用 element 的 message 提示组件
// 注意import要写在Vue.use(VueRouter)前面
import { Message } from 'element-ui'
Vue.use(VueRouter)
// 路由配置表
const routes = [
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/',
// 命名路由 layout 有一个默认子路由,这个名字没有意义,所以警告
// 正确的做法是:如果有默认子路由,就不要给父路由起名字了
// name: 'layout',
component: Layout,
children: [
{
path: '', // path 为空,会作为默认子路由渲染
// 路由的名字是干啥的?
// 参考:https://gitee.com/lipengzhou/toutiao-publish-admin/issues/I1F1BA
name: 'home',
component: Home
},
{
path: '/user',
name: 'user',
component: User
},
{
path: '/permission',
name: 'permission',
component: Permission
}
]
}
]
const router = new VueRouter({
routes
})
// 路由导航守卫(拦截器)的作用就是控制页面的访问状态
// beforeEach 是全局前置守卫,任何页面的访问都要经过这里
// 路由导航守卫:说白了所有页面的导航都会经过这里
// 守卫页面的导航的
// to:要去的路由信息
// from:来自哪里的路由信息
// next:放行方法
router.beforeEach((to, from, next) => {
// 如果要访问的页面不是 /login,校验登录状态
// 如果没有登录,则跳转到登录页面
// 如果登录了,则允许通过
// 允许通过
// next()
var user = JSON.parse(window.localStorage.getItem('user'))
if (to.path !== '/login') {
if (user) {
// 校验非登录页面的登录状态
var roleNames = []
var roleList = user.roleList
for (var i = 0; i < roleList.length; i++) {
roleNames.push(roleList[i].roleName)
console.log(roleNames)
}
if (to.path === '/permission') {
if (roleNames.indexOf('permissionControl') !== -1) {
next()
} else {
Message.error('不好意西,没有权限')
next('/')
}
} else {
next()
}
} else {
// 没有登录,跳转到登录页面
next('/login')
}
} else {
// 登录页面,正常允许通过
next()
}
})
// 我们在组件中使用的 this.$router 其实就是这个模块中的 router
export default router
Views
login/index.vue
登录组件
表单
el-form 表单组件
每个表单项都必须使用 el-form-item 组件包裹
表单提交可以设置:loading 让他转起来等待
配置 Form 表单验证:
- 必须给 el-from 组件绑定 model 为表单数据对象
- 给需要验证的表单项 el-form-item 绑定 prop 属性
注意:prop 属性需要指定表单对象中的数据名称 - 通过 el-from 组件的 rules 属性配置验证规则
如果内置的验证规则不满足,也可以自定义验证规则
手动触发表单验证:
- 给 el-form 设置 ref 起个名字(随便起名,不要重复即可)
- 通过 ref 获取 el-form 组件,调用组件的 validate 进行验证
formRules: { // 表单验证规则配置
// 要验证的数据名称:规则列表[]
account: [
{ required: true, message: '请输入手机号', trigger: 'change' },
{ pattern: /^[0-9]*$/, message: '请输入正确的号码格式', trigger: 'change' }
],
password: [
{ required: true, message: '验证码不能为空', trigger: 'change' },
{ pattern: /^[0-9]*$/, message: '请输入正确密码格式' }
],
agree: [
{
// 自定义校验规则: 因为复选框里面没有这个required,需要自己定义规则,所以需要使用validator
// 验证通过:callback()
// 验证失败:callback(new Error('错误消息'))
// value为是否勾选上,数据值
validator: (rule, value, callback) => {
if (value) {
callback()
} else {
callback(new Error('请同意用户协议'))
}
},
// message: '请勾选同意用户协议',
trigger: 'change'
}
是否同意协议验证要在点击登录的时候进行验证,所以开启手动验证
methods: {
onLogin () {
// 获取表单数据(根据接口要求绑定数据)
// const user = this.user
// 表单验证
// validate 方法是异步的 参数可以是两个,一个valid是验证的结果,另一个是error,缺失的项
this.$refs['login-form'].validate((valid) => {
// 如果表单验证失败,停止请求提交
if (!valid) {
return
}
// 验证通过,请求登录
this.login()
})
}
}
登录成功了,把usre放入本地存储中
window.localStorage.setItem('user', JSON.stringify(res.data.data))
并且关闭转圈圈,简单提示一下
this.$message({
message: '登录成功',
type: 'success'
})
layout/index.vue
layout/components/asside.vue
先说一下传递参数的问题,在index.vue,点击左上角,然后asside组件向右滑动,再点击划出来
首先index页面点击图标触发事件,@click="isCollapse = !isCollapse",因为asside组件双向绑定了isCollapse值
<app-aside class="aside-menu" :is-collapse="isCollapse"/>
所以在asside.vue中
export default {
name: 'AppAside', // 注意这个名字,父组件可以使用AppAside,也可以app-aside
components: {},
props: ['is-collapse'], // 注意这里传过来的参数是is-collapse,但是要使用的话写的是isCollapse
data () {
return {
// isCollapse: true
}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
然后动态的改变图标样式
<!--
class 样式处理
{
css类名: 布尔值
}
true:作用类名
false:不作用类名
-->
<i
:class="{
'el-icon-s-fold': isCollapse,
'el-icon-s-unfold': !isCollapse
}"
@click="isCollapse = !isCollapse"
></i>
一些注意事项
<el-dropdown-item>设置</el-dropdown-item>
<!--
组件默认是不识别原生事件的,除非内部做了处理
https://cn.vuejs.org/v2/guide/components-custom-events.html#%E5%B0%86%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%88%B0%E7%BB%84%E4%BB%B6
-->
<el-dropdown-item
@click.native="onLogout"
>退出</el-dropdown-item>
permission/index.vue
主要有个树形表格的使用,主要在el-table中有个
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
对于属性表格数据的处理,还是递归来写的
getTreeData (permissionData, pid) {
var res = []
for (var i = 0; i < permissionData.length; i++) {
var node = permissionData[i]
if (pid !== null && node.pid === pid) {
node.children = this.getTreeData(permissionData, node.id)
res.push(node)
}
}
return res
}
.sync用法
当子组件需要更新 title 的值时,它需要显式地触发一个更新事件:
this.$emit('update:title', newValue)
这样title的属性在子组件内部更新,父组件也能感知的到,实现了“双向绑定”。
Table 表格组件
-
把需要展示的数组列表数据绑定给 table 组件的 data 属性
注意:你不用去 v-for 遍历,它自己会遍历 -
设计表格列 el-table-column
width 可以设定表格列的宽度
label 可以设定列的标题
prop 用来设定要渲染的列表项数据字段,只能展示文本 -
表格列默认只能渲染普通文本,如果需要展示其它内容,例如放个按钮啊、放个图片啊,那就需要自定义表格列模板了:https://element.eleme.cn/#/zh-CN/component/table#zi-ding-yi-lie-mo-ban
<el-table-column
prop="userName"
label="用户名">
<template slot-scope="scope">
<el-popover
placement="top-start"
title="tips"
width="200"
trigger="hover"
content="点击查看详情">
<el-tag slot="reference" @click="getDetail1(scope.row.id)" style=" 100px">{{ scope.row.userName }}</el-tag>
</el-popover>
</template>
</el-table-column>
<el-table-column
label="操作">
<!-- 如果需要自定义表格列模板,则把需要自定义的内容放到 template 里面 -->
<template slot-scope="scope">
<!-- Form -->
<el-button
circle
icon="el-icon-edit"
type="primary"
@click="getDetail(scope.row.id)"
></el-button>
<el-dialog :title="addOrUpdate" :visible.sync="dialogFormVisible" :append-to-body="true" >
<el-form :model="form" :rules="rules" style=" 450px">
<el-form-item label="用户名" :label-width="formLabelWidth" prop="userName" >
<el-input v-model="form.userName" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="账号" :label-width="formLabelWidth" prop="account">
<el-input v-model="form.account" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="formLabelWidth" prop="password">
<el-input v-model="form.password" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="角色" style="margin-left: 80px">
<el-checkbox-group v-model="checkList" :disabled="inputFlag">
<el-checkbox v-for="(role,index) in roleList"
:key="index"
:label="role.id"
:value="role.id">{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="toUpdate()">确 定</el-button>
</div>
</el-dialog>
<el-button
style="margin-left: 20px"
type="danger"
icon="el-icon-delete"
circle
@click="toDelete(scope.row.id)"
></el-button>
</template>
</el-table-column>
数据分页
<!-- /数据列表 -->
<!-- 列表分页 -->
<!--
total 用来设定总数据的条数
它默认按照 10 条每页计算总页码
page-size 每页显示条目个数,支持 .sync 修饰符,默认每页 10 条
90 3 90 / 3 = 30
-->
<el-pagination
layout="prev, pager, next"
background
:total="totals"
:page-size="limit"
:disabled="loading"
:current-page.sync="page"
@current-change="onCurrentChange"
/>
<!-- onCurrentChange为自己定义的触发页数改变时的方法-->
<!-- /列表分页 -->
路由的name
如果你要使用 JavaScript 跳转到这个动态路由,则你需要这样写:
this.$router.push('/user/' + 用户ID)
如果是在模板中进行路由导航,那就是这样的:
以上的方式虽然简单粗暴,但是通过拼接字符串得到完整路由路径进行导航不太直观。
所以更好的方式就是给路由配置对象起一个名字,就像下面这样,这个 name 和 path 没有任何关系,它就是一个代号,需要注意的是路由的 name 不能重复。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
现在你可以这样处理路由导航:
router.push({ name: 'user', params: { userId: 123 }})
所以结论就是:无论是否需要使用路由的 name,都建议给它写上,当你需要的时候就非常有用了,这是一个建议的做法。
后端
对于后端,最好都要配上RestController,然后POST或者GET要和前端的保持一致,还有参数问题,要加上RequestBody 或者RequestParam注解
问题点1:
如果Content-Type设置为“application/x-www-form-urlencoded;charset=UTF-8”无论是POST请求还是GET请求都是可以通过这种方式成功获取参数,但是如果前端POST请求中的body是Json对象的话,会报上述错误。
请求中传JSON时设置的Content-Type 如果是application/json或者text/json时,JAVA中request.getParameter("")怎么也接收不到数据。这是因为,Tomcat的HttpServletRequest类的实现类为org.apache.catalina.connector.Request(实际上是org.apache.coyote.Request)。
问题点2:
当前端请求的Content-Type是Json时,可以用@RequestBody这个注解来解决。@RequestParam 底层是通过request.getParameter方式获得参数的,换句话说,@RequestParam 和request.getParameter是同一回事。因为使用request.getParameter()方式获取参数,可以处理get 方式中queryString的值,也可以处理post方式中 body data的值。所以,@RequestParam可以处理get 方式中queryString的值,也可以处理post方式中 body data的值。@RequestParam用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST。
@RequestBody接受的是一个json对象的字符串,而不是Json对象,在请求时往往都是Json对象,用JSON.stringify(data)的方式就能将对象变成json字符串。
总结:
前端请求传Json对象则后端使用@RequestParam;
前端请求传Json对象的字符串则后端使用@RequestBody。