Vuex前端saas人力资源中台管理项目第六天 主题设计和部署上线

全屏插件的引用

目标:实现页面的全屏功能

全屏功能可以借助一个插件来实现

第一步,安装全局插件screenfull

$ npm i screenfull

第二步,封装全屏显示的插件·· src/components/ScreenFull/index.vue

<template>
  <!-- 放置一个图标 -->
  <div>
    <!-- 放置一个svg的图标 -->
    <svg-icon icon-class="fullscreen" style="color:#fff; width: 20px; height: 20px" @click="changeScreen" />
  </div>
</template>

<script>
import ScreenFull from 'screenfull'
export default {
  methods: {
    //   改变全屏
    changeScreen() {
      if (!ScreenFull.isEnabled) {
        // 此时全屏不可用
        this.$message.warning('此时全屏组件不可用')
        return
      }
      // document.documentElement.requestFullscreen()  原生js调用
      //   如果可用 就可以全屏
      ScreenFull.toggle()
    }
  }
}
</script>

<style>

</style>

第三步,全局注册该组件 src/components/index.js

import ScreenFull from './ScreenFull'
Vue.component('ScreenFull', ScreenFull) // 注册全屏组件

第四步,放置于**layout/navbar.vue**中

<screen-full class="right-menu-item" />
.right-menu-item {
   vertical-align: middle;
}

提交代码

本节任务: 实现页面的全屏功能

动态主题的设置

目标: 实现动态主题的设置

我们想要实现在页面中实时的切换颜色,此时页面的主题可以跟着设置的颜色进行变化

简单说明一下它的原理: element-ui 2.0 版本之后所有的样式都是基于 SCSS 编写的,所有的颜色都是基于几个基础颜色变量来设置的,所以就不难实现动态换肤了,只要找到那几个颜色变量修改它就可以了。 首先我们需要拿到通过 package.json 拿到 element-ui 的版本号,根据该版本号去请求相应的样式。拿到样式之后将样色,通过正则匹配和替换,将颜色变量替换成你需要的,之后动态添加 style 标签来覆盖原有的 css 样式。

第一步, 封装颜色选择组件 ThemePicker 代码地址:@/components/ThemePicker

注意:本章节重点在于集成,内部的更换主题可以先不用关心。

实现代码

<template>
  <el-color-picker
    v-model="theme"
    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />

</template>

<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
  data() {
    return {
      chalk: '', // content of theme-chalk css
      theme: ''
    }
  },
  computed: {
    defaultTheme() {
      return this.$store.state.settings.theme
    }
  },
  watch: {
    defaultTheme: {
      handler: function(val, oldVal) {
        this.theme = val
      },
      immediate: true
    },
    async theme(val) {
      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
      if (typeof val !== 'string') return
      const themeCluster = this.getThemeCluster(val.replace('#', ''))
      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
      console.log(themeCluster, originalCluster)
      const $message = this.$message({
        message: '  Compiling the theme',
        customClass: 'theme-message',
        type: 'success',
        duration: 0,
        iconClass: 'el-icon-loading'
      })
      const getHandler = (variable, id) => {
        return () => {
          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
          let styleTag = document.getElementById(id)
          if (!styleTag) {
            styleTag = document.createElement('style')
            styleTag.setAttribute('id', id)
            document.head.appendChild(styleTag)
          }
          styleTag.innerText = newStyle
        }
      }
      if (!this.chalk) {
        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
        await this.getCSSString(url, 'chalk')
      }
      const chalkHandler = getHandler('chalk', 'chalk-style')
      chalkHandler()
      const styles = [].slice.call(document.querySelectorAll('style'))
        .filter(style => {
          const text = style.innerText
          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
        })
      styles.forEach(style => {
        const { innerText } = style
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
      })
      this.$emit('change', val)
      $message.close()
    }
  },
  methods: {
    updateStyle(style, oldCluster, newCluster) {
      let newStyle = style
      oldCluster.forEach((color, index) => {
        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
      })
      return newStyle
    },
    getCSSString(url, variable) {
      return new Promise(resolve => {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
            resolve()
          }
        }
        xhr.open('GET', url)
        xhr.send()
      })
    },
    getThemeCluster(theme) {
      const tintColor = (color, tint) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)
        if (tint === 0) { // when primary color is in its rgb space
          return [red, green, blue].join(',')
        } else {
          red += Math.round(tint * (255 - red))
          green += Math.round(tint * (255 - green))
          blue += Math.round(tint * (255 - blue))
          red = red.toString(16)
          green = green.toString(16)
          blue = blue.toString(16)
          return `#${red}${green}${blue}`
        }
      }
      const shadeColor = (color, shade) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)
        red = Math.round((1 - shade) * red)
        green = Math.round((1 - shade) * green)
        blue = Math.round((1 - shade) * blue)
        red = red.toString(16)
        green = green.toString(16)
        blue = blue.toString(16)
        return `#${red}${green}${blue}`
      }
      const clusters = [theme]
      for (let i = 0; i <= 9; i++) {
        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
      }
      clusters.push(shadeColor(theme, 0.1))
      return clusters
    }
  }
}
</script>

<style>
.theme-message,
.theme-picker-dropdown {
  z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
  height: 26px !important;
  width: 26px !important;
  padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
  display: none;
}
.el-color-picker {
  height: auto !important;
}
</style>

注册代码

import ThemePicker from './ThemePicker'
Vue.component('ThemePicker', ThemePicker)

第二步, 放置于**layout/navbar.vue**中

   <!-- 放置全屏插件 -->
    <theme-picker class="right-menu-item" />

提交代码

多语言实现

**目标**实现国际化语言切换

初始化多语言包

本项目使用国际化 i18n 方案。通过 vue-i18n而实现。

第一步,我们需要首先国际化的包

 $ npm i vue-i18n

第二步,需要单独一个多语言的实例化文件 src/lang/index.js

import Vue from 'vue' // 引入Vue
import VueI18n from 'vue-i18n' // 引入国际化的包
import Cookie from 'js-cookie' // 引入cookie包
import elementEN from 'element-ui/lib/locale/lang/en' // 引入饿了么的英文包
import elementZH from 'element-ui/lib/locale/lang/zh-CN' // 引入饿了么的中文包
Vue.use(VueI18n) // 全局注册国际化包
export default new VueI18n({
  locale: Cookie.get('language') || 'zh', // 从cookie中获取语言类型 获取不到就是中文
  messages: {
    en: {
      ...elementEN // 将饿了么的英文语言包引入
    },
    zh: {
      ...elementZH // 将饿了么的中文语言包引入
    }
  }
})

上面的代码的作用是将Element的两种语言导入了

第三步,在main.js中对挂载 i18n的插件,并设置element为当前的语言

// 设置element为当前的语言
Vue.use(ElementUI, {
  i18n: (key, value) => i18n.t(key, value)
})

new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

引入自定义语言包

此时,element已经变成了zh,也就是中文,但是我们常规的内容怎么根据当前语言类型显示?

这里,针对英文和中文,我们可以提供两个不同的语言包 src/lang/zh.js , src/lang/en.js

该语言包,我们已经在资源中提供

第四步,在index.js中同样引入该语言包

import customZH from './zh' // 引入自定义中文包
import customEN from './en' // 引入自定义英文包
Vue.use(VueI18n) // 全局注册国际化包
export default new VueI18n({
  locale: Cookie.get('language') || 'zh', // 从cookie中获取语言类型 获取不到就是中文
  messages: {
    en: {
      ...elementEN, // 将饿了么的英文语言包引入
      ...customEN
    },
    zh: {
      ...elementZH, // 将饿了么的中文语言包引入
      ...customZH
    }
  }
})

在左侧菜单中应用多语言包

自定义语言包的内容怎么使用?

第五步,在左侧菜单应用

当我们全局注册i18n的时候,每个组件都会拥有一个**$t**的方法,它会根据传入的key,自动的去寻找当前语言的文本,我们可以将左侧菜单变成多语言展示文本

layout/components/SidebarItem.vue

<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="$t('route.'+onlyOneChild.name)" />

注意:当文本的值为嵌套时,可以通过**$t(key1.key2.key3...)**的方式获取

现在,我们已经完成了多语言的接入,现在封装切换多语言的组件

封装多语言插件

第六步,封装多语言组件 src/components/lang/index.vue

<template>
  <el-dropdown trigger="click" @command="changeLanguage">
    <!-- 这里必须加一个div -->
    <div>
      <svg-icon style="color:#fff;font-size:20px" icon-class="language" />
    </div>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item command="zh" :disabled="'zh'=== $i18n.locale ">中文</el-dropdown-item>
      <el-dropdown-item command="en" :disabled="'en'=== $i18n.locale ">en</el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</template>

<script>
import Cookie from 'js-cookie'
export default {
  methods: {
    changeLanguage(lang) {
      Cookie.set('language', lang) // 切换多语言
      this.$i18n.locale = lang // 设置给本地的i18n插件
      this.$message.success('切换多语言成功')
    }
  }
}
</script>


第七步,在Navbar组件中引入

 <!-- 放置切换多语言 -->
      <lang class="right-menu-item" />
      <!-- 放置主题 -->
      <theme-picker class="right-menu-item" />
      <!-- 放置全屏插件 -->
      <screen-full class="right-menu-item" />

最终效果

image-20200804001654730

提交代码

tab页的视图引入

目标: 实现tab页打开路由的功能

当前我们实现的打开页面,看到一个页面之后,另一个页面就会关闭,为了显示更加有效率,我们可以引入多页签组件

多页签的组件的代码过于繁杂,开发实际需要的是集成和调用能力,所以我们只是将开发好的组件集成到当前的功能项中即可。

在资源目录中,**多页签**目录下放置的是 组件和vuex模块

第一步,将组件TagsView目录放置到**src/components** , 并全局注册

import TagsView from './TagsView'
Vue.component('TagsView', TagsView)

第二步,将Vuex模块**tagsView.js**放置到 src/store/modules

并在store中引入该模块

import tagsView from './modules/tagsView'
const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    permission,
    tagsView
  },
  getters
})

第三步,在**src/layout/Index.vue**中引入该组件

<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <sidebar class="sidebar-container" />
    <div class="main-container">
      <div :class="{'fixed-header':fixedHeader}">
        <navbar />
        <!-- 放置tabsview -->
        <tags-view />
      </div>
      <app-main />
    </div>
  </div>
</template>

效果如下

image-20200804012117287

提交代码

打包之前的路由模式

**目标**配置打包之前的路由模式

在SPA单页应用中,有两种路由模式

hash模式 : #后面是路由路径,特点是前端访问,#后面的变化不会经过服务器

history模式:正常的/访问模式,特点是后端访问,任意地址的变化都会访问服务器

开发到现在,我们一直都在用hash模式,打包我们尝试用history模式

改成history模式非常简单,只需要将路由的mode类型改成history即可

const createRouter = () => new Router({
  mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes] // 改成只有静态路由
})

假设我们的地址是这样的 www.xxxx/com/hr/a www.xxxx/com/hr/b

我们会发现,其实域名是**www.xxxx/com**,hr是特定的前缀地址,此时我们可以配置一个base属性,配置为hr

const createRouter = () => new Router({
  mode: 'history', // require service support
  base: '/hr/', // 配置项目的基础地址
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes] // 改成只有静态路由
})

此时,我们会发现地址已经变成我们想要的样子了

image-20200804014626686

提交代码

性能分析和CDN的应用

目标: 对开发的应用进行性能分析和CDN的应用

性能分析

我们集成了 功能,写了很多组件,最终都会打包成一堆文件,那么真实运行的性能如何呢?

我们可以使用vue-cli本身提供的性能分析工具,对我们开发的所有功能进行打包分析

它的应用非常简单

$ npm run preview -- --report

这个命令会从我们的**入口main.js**进行依赖分析,分析出最大的包,方便我们进行观察和优化

执行完这个命令,我们会看到如下的页面

image-20200804015849396

如图所以,方块越大,说明该文件占用的文件越大,文件越大,对于网络带宽和访问速度的要求就越高,这也就是我们优化的方向

像这种情况,我们怎么优化一下呢

webpack排除打包

CDN是一个比较好的方式

文件不是大吗?我们就不要把这些大的文件和那些小的文件打包到一起了,像这种xlsx,element这种功能性很全的插件,我们可以放到CDN服务器上,一来,减轻整体包的大小,二来CDN的加速服务可以加快我们对于插件的访问速度

使用方式

先找到 vue.config.js, 添加 externalswebpack 不打包 xlsxelement

vue.config.js

 // 排除 elementUI xlsx  和 vue 
  externals:
      {
        'vue': 'Vue',
        'element-ui': 'ELEMENT',
        'xlsx': 'XLSX'
     }

再次运行,我们会发现包的大小已经大幅减小

CDN文件配置

但是,没有被打包的几个模块怎么处理?

可以采用CDN的方式,在页面模板中预先引入

vue.config.js

const cdn = {
  css: [
    // element-ui css
    'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 样式表
  ],
  js: [
    // vue must at first!
    'https://unpkg.com/vue/dist/vue.js', // vuejs
    // element-ui js
    'https://unpkg.com/element-ui/lib/index.js', // elementUI
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js',
    'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js'
  ]
}

但是请注意,这时的配置实际上是对开发环境和生产环境都生效的,在开发环境时,没有必要使用CDN,此时我们可以使用环境变量来进行区分

let cdn = { css: [], js: [] }
// 通过环境变量 来区分是否使用cdn
const isProd = process.env.NODE_ENV === 'production' // 判断是否是生产环境
let externals = {}
if (isProd) {
  // 如果是生产环境 就排除打包 否则不排除
  externals = {
    // key(包名) / value(这个值 是 需要在CDN中获取js, 相当于 获取的js中 的该包的全局的对象的名字)
    'vue': 'Vue', // 后面的名字不能随便起 应该是 js中的全局对象名
    'element-ui': 'ELEMENT', // 都是js中全局定义的
    'xlsx': 'XLSX' // 都是js中全局定义的
  }
  cdn = {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 提前引入elementUI样式
    ], // 放置css文件目录
    js: [
      'https://unpkg.com/vue/dist/vue.js', // vuejs
      'https://unpkg.com/element-ui/lib/index.js', // element
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js', // xlsx 相关
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js' // xlsx 相关
    ] // 放置js文件目录
  }
}

注入CDN文件到模板

之后通过 html-webpack-plugin注入到 index.html之中:

config.plugin('html').tap(args => {
  args[0].cdn = cdn
  return args
})

找到 public/index.html。通过你配置的CDN Config 依次注入 css 和 js。

<head>
  <!-- 引入样式 -->
  <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
    <link rel="stylesheet" href="<%=css%>">
  <% } %>
</head>

<!-- 引入JS -->
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%=js%>"></script>
<% } %>

最后,进行打包

$ npm run build:prod

在nodejs环境中应用并代理跨域

**目标**将打包好的代码打包上线,并在nodejs中代理跨域

使用koa框架部署项目

到现在为止,我们已经完成了一个前端工程师的开发流程,按照常规的做法,此时,运维会将我们的代码部署到阿里云的ngix服务上,对于我们而言,我们可以将其部署到本机的nodejs环境中

部署 自动化部署 /手动部署

第一步,建立web服务文件夹 hrServer

$ mkdir hrServer #建立hrServer文件夹 

第二步,在该文件夹下,初始化npm

$ npm init -y

第三步,安装服务端框架koa(也可以采用express或者egg)

$ npm i koa koa-static

第四步,拷贝上小节打包的dist目录到**hrServer/public**下

第五步,在根目录下创建app.js,代码如下

const Koa  = require('koa')
const serve = require('koa-static');

const app = new Koa();
app.use(serve(__dirname + "/public")); //将public下的代码静态化
app.listen(3333, () => {
     console.log('人资项目启动')
})

此时,我们可以访问,http://localhost:3333

页面出来了

image-20200805012430884

解决history页面访问问题

但是,此时存在两个问题,

  1. 当我们刷新页面,发现404

这是因为我们采用了history的模式,地址的变化会引起服务器的刷新,我们只需要在app.js对所有的地址进行一下处理即可

安装 koa中间件

$ npm i koa2-connect-history-api-fallback #专门处理history模式的中间件

注册中间件

const Koa  = require('koa')
const serve = require('koa-static');
const  { historyApiFallback } = require('koa2-connect-history-api-fallback');
const path = require('path')
const app = new Koa();
// 这句话 的意思是除接口之外所有的请求都发送给了 index.html
app.use(historyApiFallback({ 
     whiteList: ['/prod-api']
 }));  // 这里的whiteList是 白名单的意思
app.use(serve(__dirname + "/public")); //将public下的代码静态化

app.listen(3333, () => {
     console.log('人资项目启动')
})

解决生产环境跨域问题

  1. 当点击登录时,发现接口404

前面我们讲过,vue-cli的代理只存在于开发期,当我们上线到node环境或者ngix环境时,需要我们再次在环境中代理

在nodejs中代理

安装跨域代理中间件

$ npm i koa2-proxy-middleware

配置跨越代理

const proxy = require('koa2-proxy-middleware')

app.use(proxy({
  targets: {
    // (.*) means anything
    '/prod-api/(.*)': {
        target: 'http://ihrm-java.itheima.net/api', //后端服务器地址
        changeOrigin: true,
        pathRewrite: { 	
            '/prod-api': ""
        }
    }
  }
}))

注意:这里之所以用了pathRewrite,是因为生产环境的请求基础地址是 /prod-api,需要将该地址去掉

此时,我们的项目就可以跨域访问了!

到现在为止,我们在十几天的时间里,完成了一个较为复杂的中台项目的解剖和开发,任何一个复杂的项目都是各种各样的功能通过合理的设计和布局组装而成的,所以未来我们每个同学要掌握的能力就是不论项目的难度和复杂度,都要学会技术的解耦和设计,这样我们的开发能力就会变得越来越强

That is All !

感谢 ! ! !

Element的表单校验补充

我们尝试通过一个案例对Element的表单校验进行一下补充

实现表单基本结构

创建项目

$ vue create login

选择babel / eslint

安装Element

开发时依赖 : 开发环境所需要的依赖 -> devDependencies

运行时依赖: 项目上线依然需要的依赖 -> dependencies

$ npm i element-ui

在main.js中对ElementUI进行注册

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

接下来,利用Element组件完成如图的效果

image-20200906184428291

代码如下

<template>
  <div id="app">
    <!-- 卡片组件 -->
    <el-card class='login-card'>
      <!-- 登录表单 -->
      <el-form style="margin-top: 50px">
        <el-form-item>
          <el-input placeholder="请输入手机号"></el-input>
        </el-form-item>
        <el-form-item>
          <el-input placeholder="请输入密码"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" style="width: 100%">登录</el-button>
        </el-form-item>

      </el-form>
    </el-card>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {

  }
}
</script>

<style>
 #app {
   width: 100%;
   height: 100vh;
   background-color: pink;
   display: flex;
   justify-content: center;
   align-items: center;
 }
 .login-card {
   width: 440px;
   height: 300px;
 }
</style>

表单校验的先决条件

接下来,完成表单的校验规则如下几个先决条件

image-20200906184604982

model属性 (表单数据对象)

  data () {
    // 定义表单数据对象
    return {
      loginForm: {
        mobile: '',
        password: ''
      }
    }
  }

绑定model

 <el-form style="margin-top:40px" :model="loginForm" >

rules规则 先定义空规则,后续再详解

loginRules: {}
<el-form style="margin-top: 50px" model="loginForm" :rules="loginRules">

设置prop属性

校验谁写谁的字段

<el-form-item prop="mobile">
   ...
<el-form-item prop="password">
   ...

给input绑定字段属性

<el-input v-model="loginForm.mobile"></el-input>
<el-input v-model="loginForm.password"></el-input>

表单校验规则

此时,先决条件已经完成,要完成表单的校验,需要编写规则

ElementUI的表单校验规则来自第三方校验规则参见 async-validator

我们介绍几个基本使用的规则

规则说明
required如果为true,表示该字段为必填
message当不满足设置的规则时的提示信息
pattern正则表达式,通过正则验证值
min当值为字符串时,min表示字符串的最小长度,当值为数字时,min表示数字的最小值
max当值为字符串时,max表示字符串的最大长度,当值为数字时,max表示数字的最大值
trigger校验的触发方式,change(值改变) / blur (失去焦点)两种,
validator如果配置型的校验规则不满足你的需求,你可以通过自定义函数来完成校验

校验规则的格式

{ key(字段名): value(校验规则) => [{}] }

根据以上的规则,针对当前表单完成如下要求

手机号 1.必填 2.手机号格式校验 3. 失去焦点校验

密码 1.必填 2.6-16位长度 3. 失去焦点校验

规则如下

      loginRules: {
        mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' },
          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
        password: [{ required: true, message: '密码不能为空', trigger: 'blur' }, {
          min: 6, max: 16, message: '密码应为6-16位的长度', trigger: 'blur'
        }]
      }

自定义校验规则

自定义校验规则怎么用

validator是一个函数, 其中有三个参数 (rule(当前规则),value(当前值),callback(回调函数))

var  func = function (rule, value, callback) {
    // 根据value进行进行校验 
    // 如果一切ok  
    // 直接执行callback
    callback() // 一切ok 请继续
    // 如果不ok 
    callback(new Error("错误信息"))
}

根据以上要求,增加手机号第三位必须是9的校验规则

如下

// 自定义校验函数
    const checkMobile = function (rule, value, callback) {
      value.charAt(2) === '9' ? callback() : callback(new Error('第三位手机号必须是9'))
    }

 mobile: [
          { required: true, message: '手机号不能为空', trigger: 'blur' },
          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }, {
            trigger: 'blur',
            validator: checkMobile
   }],

手动校验的实现

最后一个问题,如果我们直接点登陆按钮,没有离开焦点,那该怎么校验 ?

此时我们需要用到手动完整校验 案例

form表单提供了一份API方法,我们可以对表单进行完整和部分校验

方法名说明参数
validate对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promiseFunction(callback: Function(boolean, object))
validateField对部分表单字段进行校验的方法Function(props: array | string, callback: Function(errorMessage: string))
resetFields对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
clearValidate移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果Function(props: array | string)

这些方法是el-form的API,需要获取el-form的实例,才可以调用

采用ref进行调用

<el-form ref="loginForm" style="margin-top:40px" :model="loginForm" :rules="loginRules">

调用校验方法

  login () {
      // 获取el-form的实例
      this.$refs.loginForm.validate(function (isOK) {
        if (isOK) {
          // 说明校验通过
          // 调用登录接口
        }
      }) // 校验整个表单
    }

Async 和 Await

针对异步编程,我们学习过Ajax的回调形式,promise的链式调用形式

ajax回调模式

// 回调形式调用
$.ajax({
    url,
    data,
    success:function(result){
        $.ajax({
            data:result,
            success: function(result1){
                $.ajax({
                    url,
                    data: result1
              })
            }
        })
    }
})

promise的链式回调函数

// 链式调用 没有嵌套
axios({ url, data}).then(result => {
    return  axios({ data:result }) 
}).then(result1 => {
     return  axios({ data:result1 }) 
}).then(result2 => {
   return axios({ data: result2 }) 
}).then(result3 => {
    return axios({ data: result3 }) 
})

关于Promise你必须知道几件事

关于Promise你必须知道几件事

如何声明一个Promise

new Promise(function(resolve, reject){ })

如果想让Promise成功执行下去,需要执行resolve,如果让它失败执行下去,需要执行reject

new Promise(function(resolve, reject) { 
    resolve('success')  // 成功执行
}).then(result => {
    alert(result)
})

new Promise(function(resolve, reject) { 
    reject('fail')  // 成功执行
}).then(result => {
    alert(result)
}).catch(error => {
     alert(error)
})

如果想终止在某个执行链的位置,可以用Promise.reject(new Error())

new Promise(function(resolve, reject) {
    resolve(1)
}).then(result => {
    return result + 1
}).then(result => {
    return result + 1
}).then(result => {
  return  Promise.reject(new Error(result + '失败'))
   // return result + 1
}).then(result => {
    return result + 1
}).catch(error => {	
    alert(error)
})

异步编程的终极方案 async /await

async 和 await实际上就是让我们像写同步代码那样去完成异步操作

await 表示强制等待的意思,await关键字的后面要跟一个promise对象,它总是等到该promise对象resolve成功之后执行,并且会返回resolve的结果

 async test () {
      // await总是会等到 后面的promise执行完resolve
      // async /await就是让我们 用同步的方法去写异步
      const result = await new Promise(function (resolve, reject) {
        setTimeout(function () {
          resolve(5)
        }, 5000)
      })
      alert(result)
    }

上面代码会等待5秒之后,弹出5

async 和 await必须成对出现

由于await的强制等待,所以必须要求使用await的函数必须使用async标记, async表示该函数就是一个异步函数,不会阻塞其他执行逻辑的执行

async test () {
      const result = await new Promise(function(resolve){  
         setTimeout(function(){
             resolve(5)
         },5000)
       })
       alert(result)
    },
    test1(){
      this.test()
      alert(1)
    }

通过上面的代码我们会发现,异步代码总是最后执行,标记了async的函数并不会阻塞整个的执行往下走

如果你想让1在5弹出之后再弹出,我们可以这样改造

   async test1(){
     await this.test()
      alert(1)
   }
// 这充分说明 被async标记的函数返回的实际上也是promise对象

如果promise异常了怎么处理?

promise可以通过catch捕获,async/ await捕获异常要通过 try/catch

   async  getCatch () {
      try {
        await new Promise(function (resolve, reject) {
          reject(new Error('fail'))
        })
        alert(123)
      } catch (error) {
        alert(error)
      }
   }

t () {
// await总是会等到 后面的promise执行完resolve
// async /await就是让我们 用同步的方法去写异步
const result = await new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(5)
}, 5000)
})
alert(result)
}


上面代码会等待5秒之后,弹出5

**async 和 await必须成对出现**

由于await的强制等待,所以必须要求使用**await**的函数必须使用**async**标记, async表示该函数就是一个异步函数,不会阻塞其他执行逻辑的执行

```js
async test () {
      const result = await new Promise(function(resolve){  
         setTimeout(function(){
             resolve(5)
         },5000)
       })
       alert(result)
    },
    test1(){
      this.test()
      alert(1)
    }

通过上面的代码我们会发现,异步代码总是最后执行,标记了async的函数并不会阻塞整个的执行往下走

如果你想让1在5弹出之后再弹出,我们可以这样改造

   async test1(){
     await this.test()
      alert(1)
   }
// 这充分说明 被async标记的函数返回的实际上也是promise对象

如果promise异常了怎么处理?

promise可以通过catch捕获,async/ await捕获异常要通过 try/catch

   async  getCatch () {
      try {
        await new Promise(function (resolve, reject) {
          reject(new Error('fail'))
        })
        alert(123)
      } catch (error) {
        alert(error)
      }
   }

async / await 用同步的方式 去写异步

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

办公模板库 素材蛙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值