vue前端的使用

简单梳理一下这次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 表单验证:

  1. 必须给 el-from 组件绑定 model 为表单数据对象
  2. 给需要验证的表单项 el-form-item 绑定 prop 属性
    注意:prop 属性需要指定表单对象中的数据名称
  3. 通过 el-from 组件的 rules 属性配置验证规则
    如果内置的验证规则不满足,也可以自定义验证规则

手动触发表单验证:

  1. 给 el-form 设置 ref 起个名字(随便起名,不要重复即可)
  2. 通过 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 表格组件

  1. 把需要展示的数组列表数据绑定给 table 组件的 data 属性
    注意:你不用去 v-for 遍历,它自己会遍历

  2. 设计表格列 el-table-column
    width 可以设定表格列的宽度
    label 可以设定列的标题
    prop 用来设定要渲染的列表项数据字段,只能展示文本

  3. 表格列默认只能渲染普通文本,如果需要展示其它内容,例如放个按钮啊、放个图片啊,那就需要自定义表格列模板了: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="width: 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="width: 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)
如果是在模板中进行路由导航,那就是这样的:

User
以上的方式虽然简单粗暴,但是通过拼接字符串得到完整路由路径进行导航不太直观。

所以更好的方式就是给路由配置对象起一个名字,就像下面这样,这个 name 和 path 没有任何关系,它就是一个代号,需要注意的是路由的 name 不能重复。

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

现在你可以这样处理路由导航:

router.push({ name: 'user', params: { userId: 123 }})
User
所以结论就是:无论是否需要使用路由的 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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章