Created by huqi at 2019-5-5 13:01:14

Updated by huqi at 2019-5-20 15:57:37

preface

At the end of last month, the @D2 open source group opened the front end project, D2-admin-renren-security-Enterprise, which uses D2Admin for Enterprise Adaptation (Professional). See ☞D2Admin for Enterprise Adaptation. Due to the recent need to develop a back-end management system, plus many other factors, such as: In particular, they wanted to learn about outstanding open source projects, participated in @jsliang’s aggressive front ends, and have used Renren-fast-vue in previous projects. In particular, they wanted to explain why D2-Admin changed the Front end of Renren-Security in particular. Of course, I also urge myself to produce an article about this interesting learning journey.

Lead to

The so-called “good work must be good,” even I like the Copy siege lion to build the front-end basic development environment, are 9102 years ago, no node environment can not carry out front-end development, no more run d2-admin environment must have it!

  • Context For details about installation environment, see D2 Admin Quick Start section: ☞ Installation Environment

  • Fork [email protected] here to follow @fairyever’s big ideas, based on [email protected]. You can also initialize the project using D2 Admin CLI. For details, see: ☞ Download the project

  • D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin D2Admin

    The project structure
    ├─ Docs // Document ├─ ├─ SRC // source directory │ ├─ Assets │ ├─ ICONS │ ├─ Image │ ├─ Library │ │ └ ─ style │ ├ ─ components / / component │ │ ├ ─ charts │ │ ├ ─ core │ │ └ ─ demo │ ├ ─ i18n / / multilingual │ ├ ─ menu / / menu │ ├ ─ Mock / / analog data │ ├ ─ pages / / page │ ├ ─ the plugin / / plug-in │ ├ ─ the router / / routing │ ├ ─ store / / vuex │ ├ ─ utils │ ├ ─ App. Vue │ └ ─ └.js ├─ tests // Test file ├─.browserslistrc // Browser compatible Set ├─.env // Environment variables ├─.env.development // Development environment variables ├─.env.nomock // Nomock Environment variables ├─.env.travis // Generate environment variables ├─.eslintignore // ESLint Ignore ├─.eslintrc.js // ESLint configure ├─.gitignore // git Ignore ├─ .postcsSLc.js // Postcss Configure ├─.travis. Yml // Continuous Integration Service ├─ babel.config.js // Babel Configure ├─ cdNRefresh -dirs.txt // CDN Set ├─ ├─ Package. json // Package ├─ Qiniu-config // Seven NiuYun configuration ├ ─ seven cows qshell / / API service command line tools ├ ─ README. Md | - README. Useful. The md ├ ─ vue. Config. Js / / vue configurationCopy the code
  • Delete irrelevant files delete.browserslistrc,.env.nomock,.env.travis,.gitignore,.postcsrc. Js,.travis. Yml, cdNrefresh -dirs.txt Json, qiniu-config, qshell, readme.en. md, readme. md, doc/image, package/*. For details, see: ☞ Delete temporarily unused modules

  • Modify package.json to remove packages that are not currently used, such as multilingual. This version will simplify multilingual directory structures, such as chart libraries, rich text editing, right-click menus, and so on: countup.js echarts github-markdown-css highlight.js marked mockjs simplemde v-charts v-contextmenu vue-grid-layout Vue-i18n vue-json-tree-view vue-splitpane vue-ueditor-wra@kazupon /vue-i18n-loader delete build:nomock command, Add environment variable files. Env,. Env. production,. Env.production. Sit,. Env.production. Uat, etc. At this point, you can install project dependencies through NPM install or YARN, and run the project with a command like NPM run dev, which you can see in the scripts section of the package.json file.

Rewrite internationalization

As for why it’s being rewritten, ask the big guy. I can only speculate: simplify the structure! The previous structure was an index.js+lang folder, which contains multiple language folders. Now the structure is straightforward –index.js+ multiple language JS files. I only have a superficial understanding of internationalization. Although I have done some projects before, there are a lot of problems. In addition to basic translation, I also need to combine with local culture and customs. Back to @fairyever’s source code, follow him to learn the use of VUE-i18N:

  • Install dependencies
npm install vue-i18n
Copy the code
  • Introduced in the main. Js
// ...

// i18n
import i18n from '@/i18n'

// ...

new Vue({
 i18n,
 // ...
)}
Copy the code
  • New language package, build JS core code:

    index.js
    // Introduce dependencies and language packs
    import Vue from 'vue'
    import VueI18n from 'vue-i18n'
    import Cookies from 'js-cookie'
    // With a multilingual switch that introduces element-UI
    import zhCNLocale from 'element-ui/lib/locale/lang/zh-CN'
    import zhTWLocale from 'element-ui/lib/locale/lang/zh-TW'
    import enLocale from 'element-ui/lib/locale/lang/en'
    // Introduce language packs
    import zhCN from './zh-CN'
    import zhTW from './zh-TW'
    import enUS from './en-US'
    
    Vue.use(VueI18n)
    
    // Define the language to use
    export const messages = {
      'zh-CN': {
        '_lang': 'Simplified Chinese'. zhCN, ... zhCNLocale },'zh-TW': {
        '_lang': 'Traditional Chinese'. zhTW, ... zhTWLocale },'en-US': {
        '_lang': 'English'. enUS, ... enLocale } }// Read from cookie or set to Chinese by default
    export default new VueI18n({
      locale: Cookies.get('language') | |'zh-CN',
      messages
    })
    Copy the code

    Language packs take Traditional Chinese as an example:

    zh-TW.js
    // Define the language object
    const t = {}
    
    t.loading = 'Loading... '
    
    // Build the object
    t.brand = {}
    t.brand.lg = All Rights Enterprise Edition
    t.brand.mini = 'everyone'
    
    // ...
    export default t
    Copy the code
  • use

    App.vue
    // Select the language
    import Cookies from 'js-cookie'
    import { messages } from '@/i18n'
    export default {
      name: 'app'.watch: {
        '$i18n.locale': 'i18nHandle'
      },
      created () {
        this.i18nHandle(this.$i18n.locale)
      },
      methods: {
        i18nHandle (val, oldVal) {
          Cookies('language', val)
          document.querySelector('html').setAttribute('lang', val)
          document.title = messages[val].brand.lg
          // Switch language to refresh the non-login page
          if (this.$route.name ! = ='login' && oldVal) {
            window.location.reload()
          }
        }
      }
    }
    Copy the code

    Page, such as:

     // template
     {{ $t('login.motto.text') }}
     :placeholder="$t('login.form.placeholderUsername')"
     // script
     this.$t('login.motto.text')
    Copy the code
  • Practice is the sole criterion for testing truth. Change the locale locale to i18n/index.js, and you’ll see the title change. Because write dead in simplified Chinese!

Element – the UI internationalization

// i18n
import i18n from '@/i18n'
// Element
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// Element
Vue.use(ElementUI, {
  i18n: (key, value) = > i18n.t(key, value)
})
Copy the code

Once the text section is fully internationalized, you can see the obvious effect:

Multilanguage switching

Now that you have the basics of internationalization in place, implementing a small feature for multi-language switching should come naturally. See how @fairyever teaches it! I was curious to see that the flex property can be used for tags in d2-admin.

flex.css
☞ flex. Cc

This is implemented through elemen-UI’s El-Dropdown, and language Settings are modified through the command event

<el-dropdown size="small" @command="command => $i18n.locale = command">
   <span class="page-login--content-header-side-text"><d2-icon name="language"/> {{ $t('login.language') }}</span>
   <el-dropdown-menu slot="dropdown">
      <el-dropdown-item v-for="(language, index) in $languages" :key="index" :command="language.value">{{ language.label }}</el-dropdown-item>
    </el-dropdown-menu>
</el
Copy the code

Authentication code for interconnecting with everyone

Generally speaking, when we do a landing page, we more or less encounter the need for captchas, by the way, captchas are graphic captchas. The easiest way to do this is to take the image from the background and render it directly on the page, using the IMG tag or background-image. Before doing renren-fast-vue secondary development when the IMG tag, here with the background picture, the idea is the same: take the background to the picture directly render. As we all know, Just do it!

Define the utility function to get the UUID:

/** * @description [renren] get the uUID */
util.getUUID = function () {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)})}Copy the code
Get the graphic verification code using the UUID
<template slot="append">
  <div class="login-captcha" :style="{ backgroundImage: `url(${captchaPath})` }" @click="updateUUID" />
</template>
Copy the code
    // Select the language
    import Cookies from 'js-cookie'
    import { messages } from '@/i18n'
    export default {
      name: 'app'.watch: {
        '$i18n.locale': 'i18nHandle'
      },
      created () {
        this.i18nHandle(this.$i18n.locale)
      },
      methods: {
        i18nHandle (val, oldVal) {
          Cookies('language', val)
          document.querySelector('html').setAttribute('lang', val)
          document.title = messages[val].brand.lg
          // Switch language to refresh the non-login page
          if (this.$route.name ! = ='login' && oldVal) {
            window.location.reload()
          }
        }
      }
    }
Copy the code

Axios and login logic

axios

Axios simple encapsulation
Import axios from 'axios' import {Message} from 'element-ui' import Cookies from 'js-cookie' import { isPlainObject } from 'lodash' import qs from 'qs' // import util from '@/libs/util' import router from '@/router' import Function errorLog (error) {// Add to store.dispatch('d2admin/log/push', {message: 'Data request exception ', type:' Danger ', meta: {the error}}) / / print to the console if (process. The env. NODE_ENV = = = 'development') {/ / util. Log. Danger (' > > > > > > error > > > > > > ') Console. log(error)} Message({Message: error. Message, type: 'error', duration: // Create an axios instance const service = axios.create({baseURL: process.env.vue_app_api, timeout: With the credentials: True / / the current request for cross-domain type whether in association with a cookie in the request}) / * * * * / service request to intercept interceptors. Request. Use (config = > {/ / do some processing before the request is sent, Such as setting headers config. Headers [' Accept - Language] = Cookies, get (" Language ") | | 'useful - CN' config. The headers = [' token '] Cookies, get (' token ') | | '/ var/default parameters defaults = {} / / prevent cache, If (config.method === 'GET ') {config.params = {... config.params, ... {'_t': new Date().getTime()}}} if (isPlainObject(config.data)) {// Config.data = {... defaults, ... Config. data} if (/^application\/x-www-form-urlencoded/.test(config.headers['content-type'])) {// Serialize request data config.data =  qs.stringify(config.data) } } return config }, Error = > {/ / send the console. The failure log (error) return Promise. Reject (error)})/response to intercept * * * * / service. The interceptors. Response. Use ( Response = > {/ / process the response if the response. Data. Code = = = 401 | |. The response data. Code = = = 10001) {/ / clearLoginInfo () / / Alert ('TODO clearLoginInfo') // TODO: Clears user information router.replace({name: 'login' }) return Promise.reject(response.data.msg) } if (response.data.code ! == 0) { errorLog(new Error(response.data.msg)) return Promise.reject(response.data.msg) } return response.data.data }, error => { errorLog(error) return Promise.reject(error) } ) export default serviceCopy the code
    import request from '@/plugin/axios'
    
    export function login (data) {
      return request({
        url: '/login'.method: 'post',
        data
      })
    }
Copy the code

Call API for login:

// ...
import { login } from '@api/sys.login'
// ...
submit () {
  this.$refs.loginForm.validate((valid) = > {
    if(! valid)return
    login(this.form)
      .then(async res => {
        await this.login(res)
        this.$router.replace(this.$route.query.redirect || '/')
      })
      .catch(this.updateUUID)
  })
}
// ...
Copy the code

TODO Highlight highlights TODO, Fixme, and other comments in your code. I heard all the old drivers use it. Sometimes, you forget to look at the added TODO while coding before you release your code to production. So there’s this extension that reminds us that we have some notes or things that haven’t been done. Mark it!

Standardized cookie use

As a background management system, inevitably involved in the use of cookie, in accordance with the ideas of the big guy, defined the tool set function and based on JS-cookie twice encapsulated cookie. Generally speaking, the two most commonly used cookies are GET and set.

Cookie simple encapsulation
import Cookie from 'js-cookie'

@param {String} name Cookie name * @param {String} value cookie value * @param {Object}  setting cookie setting */
export const cookieSet = function (name = 'default', value = ' ', cookieSetting = {}) {
  let currentCookieSetting = {
    expires: 1
  }
  Object.assign(currentCookieSetting, cookieSetting)
  Cookie.set(`d2admin-${process.env.VUE_APP_VERSION}-${name}`, value, currentCookieSetting)
}

@param {String} name Cookie name */
export const cookieGet = function (name = 'default') {
  return Cookie.get(`d2admin-${process.env.VUE_APP_VERSION}-${name}`)}/** * @description gets all the values of the cookie */
export const cookieGetAll = function () {
  return Cookie.get()
}

/** * @description delete cookie * @param {String} name cookie name */
export const cookieRemove = function (name = 'default') {
  return Cookie.remove(`d2admin-${process.env.VUE_APP_VERSION}-${name}`)}Copy the code

Prevent over-clicking

Throttling this knowledge point I have always been ignorant, often confused with anti – shake, not deep understanding, also just stay in the literal understanding: function throttling is only executed once within a specified time interval, function anti – shake is triggered frequently only executed after the specified time interval. Refer to debouncing-throttling- Explained – Examples Here’s a straightforward use of Lodash, a library of consistent, modular, high-performance JavaScript utilities. Lodash includes apis for manipulating arrays, numbers, objects, strings, and more, as well as common utility functions such as throttle and debounce.

// ...
import { debounce } from 'lodash'
// ...
submit: debounce(function () {
  // ...
}, 1000, { 'leading': true.'trailing': false })  
// _.debounce(func, [wait=0], [options={}])  
/ / options. Leading and | or options. Before and after the trailing decision delay is after the first call waiting, still waiting for the call after first
// ...
Copy the code

Before and after: Unprocessed, triggered requests are outrageous!

After processing, the console feels refreshing

About Global Configuration

The project has done too little, especially without Java, and the understanding of the global configuration of the site is still rudimentary. Generally speaking, in web development, some version control, CDN static resources, API interface address and common public variables will be written under Window and promoted to the home page for easy management. Such method is very common in some popular H5 models of netease. In my previous use of open source renren-fast-vue, this method is more widely used, this study d2-admin also reference the use of global variables (mount variables for a while, keep mounting straight, be careful not to roll over). Anyway, a Copy operation fierce as a tiger, a look, notes accounted for 95 percent! Of course, the code has an instant back end trace, but the template syntax used in this project public/index.html comes from the Lodash template insert. For details about the public folder, see the d2-admin documentation on cli and Webpack configuration. In short, great oaks from little acorns grow, and infrastructure is very important!

Global configuration window.site_config
  window.SITE_CONFIG = {};
  window.SITE_CONFIG['version'] = '<%= process.env.VUE_APP_VERSION %>';     / / version
  window.SITE_CONFIG['nodeEnv'] = '<%= process.env.VUE_APP_NODE_ENV %>';    // node env
  window.SITE_CONFIG['apiURL'] = '<%= process.env.VUE_APP_API %>';          // the API requests the address
  window.SITE_CONFIG['storeState'] = {};                                    // Vuex stores initialization state locally (used to reset all states in the initialization project without refreshing the page)
  window.SITE_CONFIG['contentTabDefault'] = {                               // Content TAB default property object
    'name': ' '.// Name, automatically assigned by this.$route.name (default, name === route name === route path)
    'params': {},                                                           // Parameter, automatically assigned by this.$route.params
    'query': {},                                                            // Query parameters automatically assigned by this.$route.query
    'menuId': ' './ / menu id (used to select the sidebar menu, and enclosing $store. State. SidebarMenuActiveName matching)
    'title': ' './ / title
    'isTab': true.// Do you want to display content via TAB?
    'iframeURL': ' '                                                         // Do you want to use iframe to nest content? (starts with HTTP [s]://, automatic match)
  };
  window.SITE_CONFIG['menuList'] = [];                                      // Left menu list (background return, no processing)
  window.SITE_CONFIG['permissions'] = [];                                   // Page button operation permission (background return, no processing)
  window.SITE_CONFIG['dynamicRoutes'] = [];                                 // Dynamic routing list
  window.SITE_CONFIG['dynamicMenuRoutes'] = [];                             Dynamic (menu) routing list
  window.SITE_CONFIG['dynamicMenuRoutesHasAdded'] = false;                  // Dynamic (menu) Status indicator of whether the route has been added (used to determine whether data needs to be pulled again and added dynamically)
Copy the code

Mount globally configured in an H5 case in Dachang

Front-end lightweight Web progress bar -NProgress

I feel like a senior Copy level zero level engineer, for some cool page effect, in addition to sigh “awesome “, is a Copy and paste. When I saw that d2-admin was using NProgress version 0.2.0, I thought it was a relatively new third-party library. With the mentality of learning to get to the bottom, I clicked on the Github repository of NProgress and saw the author @rstacruz’s home page. I couldn’t help but say “Wow!” . Coincidentally, the @JustJavac translation of the cheatsheet is derived from the author’s Cheatsheets. Although NProgress was born in August 2013 (at that time I was still trying to pick up girls in school and I only knew JS by F12 by mistake),@rstacruz has maintained her for 5 years and currently has 18.8K star, and @rstacruz itself is worth my generation to look up to.

/ /...
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
/ /...
NProgress.start()
/ /...
NProgress.done()
Copy the code

NProgress implementation principle is also very good to understand, the source code is relatively simple, probably is the start call load, load complete call done, as for the loading progress, specific load to which, do not care, the middle state is random progress, from the source code to see about 99.4% of the load to stop.

NProgress core source code
  NProgress.inc = function(amount) {
    var n = NProgress.status;

    if(! n) {return NProgress.start();
    } else if(n > 1) {
      return;
    } else {
      if (typeofamount ! = ='number') {
        if (n >= 0 && n < 0.2) { amount = 0.1; }
        else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
        else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
        else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
        else { amount = 0; }
      }

      n = clamp(n + amount, 0.0.994);
      returnNProgress.set(n); }};/ /...
    /** * Helpers */

  function clamp(n, min, max) {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  }
Copy the code

Interested students can look at the source to learn to learn! ☞ nprogress. Js

The support of the iframe

In d2-admin, there is a content page component that implements the type of iframe — d2-container-frame. From the source, the iframe is nested in the d2-Container component, and the absolute location of the iframe is used to fill the D2-Container box.

D2-container-frame simple implementation
<template>
  <d2-container v-bind="$attrs">
    <iframe
      class="d2-container-frame"
      :src="src"
      frameborder="0"/>
  </d2-container>
</template>

<script>
export default {
  name: 'd2-container-frame'.props: {
    src: {
      type: String.required: false.default: 'https://doc.d2admin.fairyever.com/zh/'}}}</script>

<style lang="scss" scoped>
.d2-container-frame {
  position: absolute;
  top: 0px;
  left: 0px;
  height: 100%;
  width: 100%;
}
</style>
Copy the code

In the transformation of Renren project, the big man clever use of the way of assembly route, the implementation of iframe separate rendering, see the source code:

// ...
// Assemble the route
var route = {
  path: ' '.component: null.name: ' '.meta: {
    ...window.SITE_CONFIG['contentTabDefault'].menuId: menuList[i].id,
    title: menuList[i].name
  }
}
// ...
route['path'] = route['name'] = `i-${menuList[i].id}`
route['meta'] ['iframeURL'] = URL
route['component'] = {
  render (h) {
    return h('d2-container', {}, [
      h('iframe', {
        style: {
          position: 'absolute'.top: '0px'.left: '0px'.height: '100%'.width: '100%'
        },
        attrs: {
          src: URL,
          frameborder: 0}})])}}// ...

Copy the code

Afterword.

The source code was not reviewed, but was copied roughly step by step based on commits. The whole process is very interesting. After all, I participated in open source. I also raised issues and caught bugs. However, generally speaking, there are still many knowledge points not carefully looked, such as vue mixins, the concrete implementation of many pages, the use of iconFONT, the use of Vuex, the implementation of custom skin, the implementation of the top menu bar, etc., during the compilation also encountered some problems, such as the pit Error of el-table: If there’s nested data, rowKey is required. I feel that the whole process of learning is not practical, many knowledge points are just a search with, or the project may do less. It’s a long road, so feel your way. The leader of HR called to urge him to go home. He wrote in a hurry and ended this article. Goodbye in the river’s lake!