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!