vue-element-admin筆記第一篇

2021-02-20 厚溥技術棧

前言: 作為後臺程序猿介入前端開發,雖然有一定的經驗,但實際操作起來,也會碰到不少問題。我個人認為最有效的方式,就是動手,動手,動手,然後看文檔。下面就隨著我一起來進入vue-element-admin的開發之路吧。

這裡我們不會從 0 開始,本項目是選擇依託開源的vue-element-admin作為起步的框架,結合自身後臺開發的專長,從而理解一些常用的方法。

目錄結構
├── build                      # 構建相關
├── plop-templates             # 基本模板
├── public                     # 靜態資源
│   │── favicon.ico            # favicon圖標
│   └── index.html             # html模板
├── src                        # 原始碼
│   ├── api                    # 所有請求
│   ├── assets                 # 主題 字體等靜態資源
│   ├── components             # 全局公用組件
│   ├── directive              # 全局指令
│   ├── filters                # 全局 filter
│   ├── icons                  # 項目所有 svg icons
│   ├── lang                   # 國際化 language
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局樣式
│   ├── utils                  # 全局公用方法
│   ├── vendor                 # 公用vendor
│   ├── views                  # views 所有頁面
│   ├── App.vue                # 入口頁面
│   ├── main.js                # 入口文件 加載組件 初始化等
│   └── permission.js          # 權限管理
    └── settings.js            # 頁面自定義顯示配置按鈕
├── tests                      # 測試
├── .env.xxx                   # 環境變量配置
├── .eslintrc.js               # eslint 配置項
├── .babelrc                   # babel-loader 配置
├── .travis.yml                # 自動化CI配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json

這裡去掉了原始項目的 mock 模擬數據目錄,採用的是後臺的數據接口。

登錄

登錄是首先要做的事情,涉及到要考慮的問題還是比較多。我們這裡從登錄先講起,這裡要講到兩種登錄,密碼登錄、簡訊驗證碼登錄。

帳戶登錄界面簡訊登錄界面

對應的代碼

    <div class="title-container">
        <h3 class="title">系 統 登 錄</h3>
        <div class="login_header">
          <a
            @click="loginForm.login_type = 0"
            :class="{ active: loginForm.login_type == 0 }"
            >密碼登錄</a
          >
          <a
            @click="loginForm.login_type = 1"
            :class="{ active: loginForm.login_type == 1 }"
            >簡訊登錄</a
          >
        </div>
      </div>

【代碼解讀】
這裡使用<a>標籤配合@click點擊事件,此點擊事件十分簡單,就是給login_type賦值。

  1.  當'login_type == 0'表示密碼登錄
2. 當'login_type == 1'表示簡訊登錄

為了更明確當前的面板指向,加上:class動態樣式,通過active樣式的值來實現文章下劃線的效果。

激活下劃線樣式

.active樣式如下

.active {
color: #fff;
padding-bottom: 10px;
border-bottom: 3px solid #fff;
}

密碼登錄

先來看看密碼登錄的代碼

<!-- 在login_type==0時此板塊顯示 其他時候此板塊不顯示 -->
        <div v-if="loginForm.login_type == 0" class="Cbody_item">
          <el-form-item prop="loginname">
            <span class="svg-container">
              <svg-icon icon-class="user" />
            </span>
            <el-input
              ref="loginname"
              v-model="loginForm.loginname"
              placeholder="工號/用戶名/手機號"
              name="loginname"
              type="text"
              tabindex="1"
              autocomplete="on"
            />
          </el-form-item>

          <el-tooltip
            v-model="capsTooltip"
            content="大寫鎖定已開啟"
            placement="right"
            manual
          >
            <el-form-item prop="password">
              <span class="svg-container">
                <svg-icon icon-class="password" />
              </span>
              <el-input
                :key="passwordType"
                ref="password"
                v-model="loginForm.password"
                :type="passwordType"
                placeholder="登錄密碼(初始密碼123456)"
                name="password"
                tabindex="2"
                autocomplete="on"
                @keyup.native="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter.native="handleLogin"
              />
              <span class="show-pwd" @click="showPwd">
                <svg-icon
                  :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
                />
              </span>
            </el-form-item>
          </el-tooltip>
        </div>

這裡使用的是element-ui:表單,官方文檔已經描寫的相當清楚了,這裡就不繼續贅述。
在表單裡面使用了<svg>標籤引入 icon 圖標,網上關於<svg>此類的文章也挺多,這裡就稍微演示一下如何使用。
icon圖標哪裡找,阿里巴巴這裡有。
例如我們這裡需要用到微信的圖標,搜索一下:

搜索

從上找到你看中的 icon,點擊下載。

下載

複製 SVG 代碼

<svg t="1611990623145" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14306" width="200" height="200"><path d="M826.2 823.3C887.9 785.4 928 723.9 928 654.4c0-111.8-104.1-203.1-234.7-208.2-14.1-132.3-141.7-235.8-297-235.8C231.5 210.4 98 326.9 98 470.5c0 85.8 47.7 161.9 121.1 209.3 8.2 5.3-27.3 80-18.5 84.6 14.9 7.7 74.7-55.6 91-50.2 32.5 10.6 67.8 16.4 104.6 16.4 18.4 0 36.5-1.5 54-4.3 34.6 79.6 125.1 136.5 231.3 136.5 29 0 56.9-4.2 82.7-12 8.5-2.6 65.9 48.4 74 45.1 13.1-5.3-23.7-65.5-12-72.6zM590.8 603c-14.3 0-25.9-11.7-25.9-26.2s11.6-26.2 25.9-26.2c14.3 0 25.9 11.7 25.9 26.2S605.1 603 590.8 603z m-155.6 51.4c0 7.7 0.5 15.3 1.5 22.8-13.1 1.8-26.6 2.8-40.4 2.8-34.4 0-67.2-6-96.9-16.7-3-1.1-13.5-4.3-19.3 0C267.3 672.9 250 693 250 693s6.3-14.6 8.4-35.9c1-9.7-13.8-16.6-16.7-18.6-56-38.2-91.9-101.4-91.9-167.1 0-115.2 110.3-208.6 246.4-208.6 127.1 0 231.7 81.4 245 186-116.8 16.3-206 102.1-206 205.6zM746.4 603c-14.3 0-25.9-11.7-25.9-26.2s11.6-26.2 25.9-26.2c14.3 0 25.9 11.7 25.9 26.2 0.1 14.5-11.5 26.2-25.9 26.2z"  p-id="14307"></path><path d="M500 419.8c21.5 0 38.9-17.6 38.9-39.3 0-21.7-17.4-39.3-38.9-39.3s-38.9 17.6-38.9 39.3c0 21.7 17.4 39.3 38.9 39.3zM292.5 419.8c21.5 0 38.9-17.6 38.9-39.3 0-21.7-17.4-39.3-38.9-39.3s-38.9 17.6-38.9 39.3c0 21.7 17.4 39.3 38.9 39.3z"  p-id="14308"></path></svg>

然後在 src\icons\svg 目錄下新建一個 svg 文件,例如這裡叫icon-wechat.svg。然後在頁面中就可以通過<svg-icon icon-class="icon-wechat" />來使用了。可以使用font-size ,color等標籤來控制其樣式。

說明:從iconfont複製的svg代碼需要刪除裡面的 fill="#",
這樣它才會使用當前的顏色`currentColor`

icon-wechat下面簡單解讀一下其工作原理。

在 src\components 組件下創建了一個 SvgIcon 組件

  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
    <use :xlink:href="iconName" />
  </svg>

向外暴露了兩個屬性,其中icon-class是必填項,class-name是自定義樣式名稱。

  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },

通過 computed 監控 icon 的名字和其自定義的樣式,當沒有指定自定義樣式時候,會採用默認樣式,否則會再加上自定義 class

  computed: {

    iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
  }

下面是固定寫法,默認的樣式。

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

在 src\icons 中的 index.js 中引入 svg 組件
import IconSvg from '@/components/IconSvg'
使用全局註冊 icon-svg
Vue.component('icon-svg', IconSvg)
這樣就可以在項目中任意地方使用。
為了便於集中管理圖標,所有圖標均放在 @/icons/svg。

const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

require.context 有三個參數:

useSubdirectories:是否檢索子目錄

這樣就可自行添加或者刪除圖標,所有圖標都會被自動導入,無需手動操作。
最後在@/main.js中引入import './icons'這樣在任意頁面就可以成功使用組件了

<svg-icon icon-class="icon-wechat" class-name="wechat" />

下面我們再看看代碼中一些其它屬性ref: ref 被用來給元素或子組件註冊引用信息。引用信息將會註冊在父組件的 $refs 對象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件。refs: refs 直接在實例裡面獲取 ref 在上面綁定的元素。所以後面在對 form 表單或單個 input 使用校驗,就可以採取this.$refs.loginForm.validate或者this.$refs.loginForm.validateField。tabindex: 讓普通 dom 元素變為可聚焦的元素;讓普通 dom 元素可以參與順序鍵盤導航(通常使用 Tab 鍵,因此得名)。

它接受一個整數作為值,具有不同的結果,具體取決於整數的值:
tabindex=負值 (通常是 tabindex=「-1」),表示元素是可聚焦的,但是不能通過鍵盤導航來訪問到該元素,用 JS 做頁面小組件內部鍵盤導航的時候非常有用。
tabindex="0" ,表示元素是可聚焦的,並且可以通過鍵盤導航來聚焦到該元素,它的相對順序是當前處於的 DOM 結構來決定的。
tabindex=正值,表示元素是可聚焦的,並且可以通過鍵盤導航來訪問到該元素;它的相對順序按照 tabindex 的數值遞增而滯後獲焦。如果多個元素擁有相同的 tabindex,它們的相對順序按照他們在當前 DOM 中的先後順序決定。

autocomplete: 默認為 on,其含義代表是否讓瀏覽器自動記錄輸入的值。@keyup.native="checkCapslock" 和 @blur="capsTooltip = false":用於判斷用戶輸入大寫提示
  checkCapslock(e) {
      const { key } = e
      this.capsTooltip = key && key.length === 1 && key >= 'A' && key <= 'Z'
    },

@keyup.enter.native="handleLogin":當點擊鍵盤enter鍵時,觸發handleLogin事件<el-tooltip>: 查看官方文檔Tooltip 文字提示@click.native.prevent: 給vue組件綁定事件時候,必須加上native ,否則會認為監聽的是來自Item組件自定義的事件。prevent 是用來阻止默認的 ,相當於原生的event.preventDefault()簡訊登錄

這裡在獲取驗證碼的時候,對手機號進行了校驗,避免不必要的簡訊發送。

簡訊發送成功後,發送簡訊按鈕置灰並且倒計時一分鐘,刷新瀏覽器倒計時依舊存在,除非清除瀏覽器緩存(因為存儲在Cookie中了)。

        <!-- 在login_type==1時此板塊顯示 其他時候此板塊不顯示 -->
        <div v-if="loginForm.login_type == 1" class="Cbody_item">
          <el-form-item prop="loginname">
            <span class="svg-container">
              <!-- <svg-icon icon-class="el-icon-phone" /> -->
              <i class="el-icon-phone" />
            </span>
            <el-input
              ref="loginname"
              v-model="loginForm.loginname"
              placeholder="請輸入手機號碼"
              name="loginname"
              type="text"
              tabindex="1"
              autocomplete="on"
            />
          </el-form-item>

          <el-tooltip
            v-model="capsTooltip"
            content="大寫鎖定已開啟"
            placement="right"
            manual
          >
            <el-form-item prop="password">
              <span class="svg-container">
                <svg-icon icon-class="password" />
              </span>
              <el-input
                ref="password"
                v-model="loginForm.password"
                type="text"
                placeholder="請輸入驗證碼"
                name="password"
                tabindex="2"
                maxlength="6"
                auto-complete="on"
                @keyup.native="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter.native="handleLogin"
              />
              <span class="show-pwd">
                <el-button
                  :loading="sending"
                  :disabled="sendDisabled"
                  size="small"
                  @click.native.prevent="onSendSms"
                  >{{ sendButtonText }}</el-button
                >
              </span>
            </el-form-item>
          </el-tooltip>
        </div>

發送簡訊代碼如下:

 async onSendSms() {
      this.$refs.loginForm.validateField('loginname', err => {
        if (!err) {
          // 先校驗手機號是否存在
          this.sendDisabled = true
          this.sending = true
          const tel = this.loginForm.loginname
          checkTel(tel)
            .then(res => {
              if (!res.flag) {
                // 手機號不存在
                this.$message.error(resTel.message)
                this.sending = true
                return false
              } else {
                sendSms(tel)
                  .then(() => {
                    this.$message.success('簡訊發送成功,請注意查收')
                    Cookie.set('last-send-time', new Date().toISOString())
                    this.timer = 60
                    this.sending = true
                  })
                  .catch(e => {
                    this.$message.error('網絡異常')
                    this.sendDisabled = false
                    console.log(e)
                  })
                  .finally(() => (this.sending = false))
              }
            })
            .catch(err => {
              this.sending = false
              this.sendDisabled = false
              console.log(err)
              return false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },

倒計時功能代碼如下:

  watch: {
    $route: {
      handler: function(route) {
        const query = route.query
        if (query) {
          this.redirect = query.redirect
          this.otherQuery = this.getOtherQuery(query)
        }
      },
      immediate: true
    },
    timer: {
      handler(num) {
        if (num > 0) {
          setTimeout(() => {
            this.timer = --num
            this.sendDisabled = this.timer == 0 ? false : true
          }, 1000)
        }
      }
    },
    'loginForm.login_type': {
      handler(newVal) {
        this.loginForm.loginname = ''
        this.loginForm.password = ''
      },
      deep: true
    }
  },

其中的timer對應的是監控倒計時timer的值,當其值>0時,使用定時器實現倒計時功能,當timer==0時,將發送簡訊按鈕變為可用狀態this.sendDisabled =false

順便將watch裡面的另外兩處代碼解讀一下:

$route:獲取登錄後重定向的path路徑,例如:redirect: "/dashboard"。this.getOtherQuery(query):

    getOtherQuery(query) {
    return Object.keys(query).reduce((acc, cur) => {
      if (cur !== 'redirect') {
        acc[cur] = query[cur]
      }
      return acc
    }, {})
  }

Object.keys:獲取所有的請求對象的KEY組成一個數組。reduce:ES6中的語法

reduce 為數組中的每一個元素依次執行回調函數,不包括數組中被刪除或從未被賦值的元素,接受四個參數:初始值(或者上一次回調函數的返回值),當前元素值,當前索引,調用 reduce 的數組。

當數組中的key不等於redirect時候,就將其存入對象中,最終返回acc,其中acc初始化值為{}對象。例如{page:1,size:10}
這樣就獲得了跳轉的路徑以及查詢的參數
immediate:true 代表如果在 wacth 裡聲明了之後,就會立即先去執行裡面的handler方法

loginForm.login_type:目的是在切換登錄方式後,將表單清空登錄功能實現
  handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store
            .dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({
                path: this.redirect || '/',
                query: this.otherQuery
              })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          return false
        }
      })
    },

當登錄路徑中的path不存在時,進行初始化path: this.redirect || '/',再經過permission.js中的router.beforeEach攔截,在沒有token的情況下,都會跳轉到登錄頁

next(`/login?redirect=${to.path}`)

登錄成功後,重定向到/即:router中定義的首頁

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: '首頁', icon: 'dashboard', affix: true }
      }
    ]
  },

login.vue完整代碼
<template>
  <div class="login-container">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginForm.login_type == 0 ? loginRules : loginRulesTel"
      class="login-form"
      autocomplete="on"
      label-position="left"
    >
      <div class="title-container">
        <h3 class="title">系 統 登 錄</h3>
        <div class="login_header">
          <a
            @click="loginForm.login_type = 0"
            :class="{ active: loginForm.login_type == 0 }"
            >密碼登錄</a
          >
          <a
            @click="loginForm.login_type = 1"
            :class="{ active: loginForm.login_type == 1 }"
            >簡訊登錄</a
          >
        </div>
      </div>
      <div class="login_content">
        <!-- 在login_type==0時此板塊顯示 其他時候此板塊不顯示 -->
        <div v-if="loginForm.login_type == 0" class="Cbody_item">
          <el-form-item prop="loginname">
            <span class="svg-container">
              <svg-icon icon-class="user" />
            </span>
            <el-input
              ref="loginname"
              v-model="loginForm.loginname"
              placeholder="工號/用戶名/手機號"
              name="loginname"
              type="text"
              tabindex="1"
              autocomplete="on"
            />
          </el-form-item>

          <el-tooltip
            v-model="capsTooltip"
            content="大寫鎖定已開啟"
            placement="right"
            manual
          >
            <el-form-item prop="password">
              <span class="svg-container">
                <svg-icon icon-class="password" />
              </span>
              <el-input
                :key="passwordType"
                ref="password"
                v-model="loginForm.password"
                :type="passwordType"
                placeholder="登錄密碼(初始密碼123456)"
                name="password"
                tabindex="2"
                autocomplete="on"
                @keyup.native="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter.native="handleLogin"
              />
              <span class="show-pwd" @click="showPwd">
                <svg-icon
                  :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
                />
              </span>
            </el-form-item>
          </el-tooltip>
        </div>
        <!-- 在login_type==1時此板塊顯示 其他時候此板塊不顯示 -->
        <div v-if="loginForm.login_type == 1" class="Cbody_item">
          <el-form-item prop="loginname">
            <span class="svg-container">
              <!-- <svg-icon icon-class="el-icon-phone" /> -->
              <i class="el-icon-phone" />
            </span>
            <el-input
              ref="loginname"
              v-model="loginForm.loginname"
              placeholder="請輸入手機號碼"
              name="loginname"
              type="text"
              tabindex="1"
              autocomplete="on"
            />
          </el-form-item>

          <el-tooltip
            v-model="capsTooltip"
            content="大寫鎖定已開啟"
            placement="right"
            manual
          >
            <el-form-item prop="password">
              <span class="svg-container">
                <svg-icon icon-class="password" />
              </span>
              <el-input
                ref="password"
                v-model="loginForm.password"
                type="text"
                placeholder="請輸入驗證碼"
                name="password"
                tabindex="2"
                maxlength="6"
                auto-complete="on"
                @keyup.native="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter.native="handleLogin"
              />
              <span class="show-pwd">
                <el-button
                  :loading="sending"
                  :disabled="sendDisabled"
                  size="small"
                  @click.native.prevent="onSendSms"
                  >{{ sendButtonText }}</el-button
                >
              </span>
            </el-form-item>
          </el-tooltip>
        </div>
      </div>
      <el-button
        :loading="loading"
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        @click.native.prevent="handleLogin"
        >登錄</el-button
      >
    </el-form>
  </div>
</template>

<script>
import { sendSms, checkTel } from '@/api/user'

import Cookie from 'js-cookie'

export default {
  name: 'Login',
  data() {
    const validateUsername = (rule, value, callback) => {
      if (value == null || value.trim() == '') {
        callback(new Error('用戶名不能為空'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (value == null || value.trim() == '') {
        callback(new Error('密碼不能為空'))
      } else {
        callback()
      }
    }
    const validateCode = (rule, value, callback) => {
      if (value == null || value.trim() == '') {
        callback(new Error('驗證碼不能為空'))
      } else {
        callback()
      }
    }
    const validateTel = (rule, value, callback) => {
      let reg = /^((13[0-9])|(17[0-1,6-8])|(15[^4,\\D])|(18[0-9]))\d{8}$/
      value = value != null ? value.replace(/\s*/g, '') : value
      if (value == null || value.length < 1) {
        callback(new Error('請輸入手機號'))
      } else if (!reg.test(value)) {
        callback(new Error('手機號輸入不合法'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        loginname: '',
        password: '',
        login_type: 0 //0:密碼登錄 ; 1:簡訊登錄
      },
      loginRules: {
        loginname: [
          {
            required: true,
            trigger: 'blur',
            validator: validateUsername
          }
        ],
        password: [
          {
            required: true,
            trigger: 'blur',
            validator: validatePassword
          }
        ]
      },
      loginRulesTel: {
        loginname: [
          {
            required: true,
            // trigger: 'blur',
            validator: validateTel
          }
        ],
        password: [
          {
            required: true,
            trigger: 'blur',
            validator: validateCode
          }
        ]
      },
      sending: false,
      sendDisabled: false,
      timer: 0,
      passwordType: 'password',
      capsTooltip: false,
      loading: false,
      showDialog: false,
      redirect: undefined,
      otherQuery: {}
    }
  },
  computed: {
    sendButtonText() {
      if (this.timer === 0) {
        return '發送驗證碼'
      } else {
        return `${this.timer}秒後重發`
      }
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        const query = route.query
        if (query) {
          this.redirect = query.redirect
          this.otherQuery = this.getOtherQuery(query)
        }
      },
      immediate: true
    },
    timer: {
      handler(num) {
        if (num > 0) {
          setTimeout(() => {
            this.timer = --num
            this.sendDisabled = this.timer == 0 ? false : true
          }, 1000)
        }
      }
    },
    'loginForm.login_type': {
      handler(newVal) {
        this.loginForm.loginname = ''
        this.loginForm.password = ''
      },
      deep: true
    }
  },
  created() {
    const lastSendTime = Cookie.get('last-send-time')
    if (lastSendTime) {
      const timer =
        60 - this.$moment.utc().diff(this.$moment(lastSendTime), 'seconds')
      this.timer = timer > 0 ? timer : 0
    }
  },
  mounted() {
    if (this.loginForm.loginname === '') {
      this.$refs.loginname.focus()
    } else if (this.loginForm.password === '') {
      this.$refs.password.focus()
    }
  },
  destroyed() {
    // window.removeEventListener('storage', this.afterQRScan)
  },
  methods: {
    async onSendSms() {
      this.$refs.loginForm.validateField('loginname', err => {
        if (!err) {
          // 先校驗手機號是否存在
          this.sendDisabled = true
          this.sending = true
          const tel = this.loginForm.loginname
          checkTel(tel)
            .then(res => {
              if (!res.flag) {
                // 手機號不存在
                this.$message.error(resTel.message)
                this.sending = true
                return false
              } else {
                sendSms(tel)
                  .then(() => {
                    this.$message.success('簡訊發送成功,請注意查收')
                    Cookie.set('last-send-time', new Date().toISOString())
                    this.timer = 60
                    this.sending = true
                  })
                  .catch(e => {
                    this.$message.error('網絡異常')
                    this.sendDisabled = false
                    console.log(e)
                  })
                  .finally(() => (this.sending = false))
              }
            })
            .catch(err => {
              this.sending = false
              this.sendDisabled = false
              console.log(err)
              return false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    checkCapslock(e) {
      const { key } = e
      this.capsTooltip = key && key.length === 1 && key >= 'A' && key <= 'Z'
    },
    showPwd() {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store
            .dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({
                path: this.redirect || '/',
                query: this.otherQuery
              })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          return false
        }
      })
    },
    getOtherQuery(query) {
      return Object.keys(query).reduce((acc, cur) => {
        if (cur !== 'redirect') {
          acc[cur] = query[cur]
        }
        return acc
      }, {})
    }
  }
}
</script>

<style lang="scss">
/* 修復input 背景不協調 和光標變色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg: #283443;
$light_gray: #fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>
<style lang="scss" scoped>
a {
  color: #fff;
  margin: 50px;
}
.login_header {
  margin-bottom: 30px;
  text-align: center;
}
.login_header span {
  margin-right: 20px;
  cursor: pointer;
}
.Cbody_item {
  border: 0px solid #999;
  overflow: hidden;
}
.active {
  color: #fff;
  padding-bottom: 10px;
  border-bottom: 3px solid #fff;
}
</style>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;
  .wechat {
    font-size: 1.5em;
    color: yellow;
  }
  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }

  .thirdparty-button {
    position: absolute;
    right: 0;
    bottom: 6px;
  }

  @media only screen and (max-width: 470px) {
    .thirdparty-button {
      display: none;
    }
  }
}
</style>

佔坑專用:下篇我們來了解一下登錄後的token鑑權操作

相關焦點

  • vue-element-admin 介紹
    簡介vue-element-admin 是一個後臺集成解決方案,它基於 vue 和 element。它使用了最新的前端技術棧,內置了 i18 國際化解決方案,動態路由,權限驗證,提煉了典型的業務模型,提供了豐富的功能組件,它可以幫助你快速搭建企業級中後臺產品原型。相信不管你的需求是什麼,本項目都能幫助到你。
  • Vue-element-admin 安裝配置及發布
    最近從手機端項目轉戰網頁端項目,經推薦使用了了vue-element-admin。
  • vue-elemnt-admin學習
    vue-elemnt-admin學習vue-element-admin是一個基於vue,element-ui
  • vue-element-admin v3.6.4 發布,後臺集成方案
    vue-element-admin v3.6.4 已發布,更新如下:優化: index.html script 標籤插入位置 #507優化: 使用 2 空格替代一個
  • Vue框架vue-admin-template登陸問題解決
    一、引言下面來學習一下在開發中可以直接使用的Vue兩個模板框架,vue-element-admin和vue-admin-template,這兩者具體的介紹
  • 讓程式設計師變懶的 「vue-admin-template」 後臺管理系統
    vue-element-admin 是一個後臺前端解決方案,它基於 vue 和 element-ui實現。github地址:https://github.com/PanJiaChen/vue-element-admin2、基礎模板: vue-admin-templatevue-admin-template 是一個極簡的 vue admin 管理後臺。
  • Android Studio 3.1 正式發布,默認 D8 Dex 編譯器;vue-element-admin 3.6.4 發布
    Android Studio 3.1 正式發布,默認使用 D8 Dex 編譯器vue-element-admin v3.6.4 發布,後臺集成方案
  • 手摸手,帶你用vue擼後臺 系列四(vueAdmin 一個極簡的後臺基礎模板)
    前言做這個 vueAdmin-template 的主要原因是: vue-element-admin 這個項目的初衷是一個vue的管理後臺集成方案,把平時用到的一些組件或者經驗分享給大家,同時它也在不斷的維護和拓展中,比如最近重構了dashboard,加入了全屏功能,新增了tabs-view等等。
  • 用 Vue+ElementUI 搭建後臺管理極簡模板
    先跑起來git clone https://github.com/tuture-dev/vue-admin-template.gitcd vue-admin-templatenpm install --registry=https://registry.npm.taobao.orgnpm run dev本文所涉及的原始碼都放在了
  • Vue入門10 vue+elementUI
    npm i element-ui -SCDN目前可以通過unpkg.com/element-ui獲取到最新版本的資源,在頁面上會js和css文件即可開始使用。<!/dist/vue.js"></script> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script> new Vue({ el: '#app', data: function() { return { visible
  • 前端技術棧:5分鐘入門VUE+Element UI
    vue-cli是vue的腳手架,所謂腳手架說白了就是快速創建模板化的項目,不用每次創建項目都去重複那些固定化的操作,注意我們安裝的是vue2.0的腳手架npm install -g vue-cli不要和vue3.0的混淆,vue3.0是npm install -g @vue/cli,此處不需要安裝3.0,否則會衝突全局安裝webpackwebpack
  • VUE+ElementUI
    /2.2 Element使用步驟 1.引入Element樣式文件 element-ui/lib/theme-chalk/index.css 2.引入Vue核心JS文件 vue.js 3.引入Element核心js文件 element-ui/lib/index.js 4.編寫相關Element代碼 5.通過Vue核心對象加載元素 new
  • Vue + TypeScript + Element 項目實戰及踩坑記
    此項目是基於 Vue 全家桶 + TypeScript + Element-UI  的技術棧,且已經開源,github 地址 blog-vue-typescript 。因為之前寫了篇純 Vue 項目搭建的相關文章 基於vue+mint-ui的mobile-h5的項目說明 ,有不少人加我微信,要源碼來學習,但是這個是我司的項目,不能提供原碼。
  • Vue3 的學習教程匯總、源碼解釋項目、支持的 UI 組件庫、優質實戰項目
    https://element-plus.org/#/zh-CNelement 3https://github.com/anncwb/vue-vben-adminvue-admin-beautiful基於 vue3.0 的開源 admin 項目,同時支持電腦,手機,平板,🔥🔥🔥默認分支使用vue3.x+antdv 開發,master 分支使用的是 vue2.x+element
  • Element UI for Vue 3.0 來了!
    正如 vue-next 之於 vue,一次 100% 的重構雖然解決了很多歷史遺留問題,但也不可避免的引入一些新的 bug 和問題,而獨立的 issue 和 pr 區可以減少大家使用和反饋的心智成本,也能更加方便我們定位問題,並行維護迭代。
  • vue+element-ui簡單實現表格可編輯效果
    關於vue項目表格可編輯編輯在此記錄兩個demo,以便於大家學習,兩種方式處理的原理有所不同,第一種方式是通過數據控制當前表格是否是編輯狀態,第二種則是一種投機,利用elementui選中時候class的變化去控制。第二種方式很簡單,但個人認為第一種方式更安全,大家可以細細的品。方式一<!
  • 跟我一起編寫Vue3版ElementUI
    Fork代碼首先需要將vue代碼通過github的fork功能複製一份到自己的githubgithub.com/kkbjs/eleme…複製後的結果github.com/su37josephx…2. clone到本地git clone git@github.com:su37josephxia/element3.git複製代碼3.
  • Springboot Vue Login(從零開始實現Springboot+Vue登錄)
    進入到創建的 Vue 項目目錄,添加依賴框架:cd vue-spring-login-summed (進入到項目根目錄)vue add element (添加 element,一個 element 風格的 UI 框架)npm install axios (安裝 axios,用於網絡請求)npm install vuex --save(安裝 Vuex,用於管理狀態)
  • Element-UI還香嗎,是否真的已死?
    例如很多在Element上進行二次開發的框架,比如最有名的一些Element上層生態產品:Avuejs: https://avuejs.com/Vue Admin Beautiful: https://github.com/chuzhixin/vue-admin-beautifulPanJiaChen/vue-element-admin: https://github.com
  • Element UI for Vue 3.0 來了!【官方總結】
    正如 vue-next 之於 vue,一次 100% 的重構雖然解決了很多歷史遺留問題,但也不可避免的引入一些新的 bug 和問題,而獨立的 issue 和 pr 區可以減少大家使用和反饋的心智成本,也能更加方便我們定位問題,並行維護迭代。