You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by li...@apache.org on 2021/01/29 12:01:10 UTC

[incubator-dolphinscheduler-website] branch master updated: Add dev front-end development documentation (#286)

This is an automated email from the ASF dual-hosted git repository.

lidongdai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-dolphinscheduler-website.git


The following commit(s) were added to refs/heads/master by this push:
     new 877d6ba  Add dev front-end development documentation (#286)
877d6ba is described below

commit 877d6ba6a05ce518bd5e350e7e4050ba3be9e767
Author: break60 <79...@qq.com>
AuthorDate: Fri Jan 29 20:01:03 2021 +0800

    Add dev front-end development documentation (#286)
    
    * Front-end development documents supplement Content-Type request interface parameter transfer method
    
    * Add dev development documentation
---
 development/en-us/dev-frontend-development.md | 768 ++++++++++++++++++++++++++
 development/zh-cn/dev-frontend-development.md | 764 +++++++++++++++++++++++++
 site_config/development.js                    |   8 +
 3 files changed, 1540 insertions(+)

diff --git a/development/en-us/dev-frontend-development.md b/development/en-us/dev-frontend-development.md
new file mode 100644
index 0000000..2dede33
--- /dev/null
+++ b/development/en-us/dev-frontend-development.md
@@ -0,0 +1,768 @@
+# Front-end development documentation
+
+### Technical selection
+```
+Vue mvvm framework
+
+Es6 ECMAScript 6.0
+
+Element-ui
+
+form-create JSON form generator
+
+D3  Visual Library Chart Library
+
+Jsplumb connection plugin library
+
+Lodash high performance JavaScript utility library
+```
+
+
+### Development environment
+
+- #### Node installation
+Node package download (Note: The node version must be a long-term version, currently the highest version of Node supports 14+) `https://nodejs.org/en/`
+Node.js extension, you can install nvm management node version, install nrm management mirror source
+
+
+- #### Front-end project construction
+Use the command line mode `cd` to enter the `dolphinscheduler-ui` project directory, check the usage status of the npm mirror source `npm get registry`, if it is not a Taobao mirror, set the mirror to Taobao mirror `npm config set registry http://registry.npm.taobao.org`, then execute `npm install` to pull project dependency packages
+
+
+- Configure `.env` file for the interface to interact with the backend
+
+Open the `.env` file in the `dolphinscheduler-ui` directory, and add the back-end service ip address and port to the file to interact with the back-end. The contents of the `.env` file are as follows:
+```
+# Proxy interface address (modified by yourself)
+API_BASE = http://192.168.xx.xx:12345(Note: API interface must be written in the form of IP address, not http://localhost:12345)
+
+# If you need to access the project with ip, you can remove the "#" (example)
+#DEV_HOST = 192.168.xx.xx
+```
+
+> #####  ! ! ! Special attention here. If the project reports a "node-sass error" error while pulling the dependency package, execute the following command again after execution.
+```
+npm install node-sass --unsafe-perm //Install node-sass dependency separately
+```
+
+- #### Development environment operation
+- `npm start` project development environment (after startup address http://localhost:8888/#/)
+
+
+#### Front-end project release
+
+- `npm run build` project packaging (after packaging, the root directory will create a folder called dist for publishing Nginx online)
+
+Run the `npm run build` command to generate a package file (dist) package
+
+Copy it to the corresponding directory of the server (front-end service static page storage directory)
+
+Visit address` http://localhost:8888/#/`
+
+
+#### Start with node and daemon under Linux
+
+Install pm2 `npm install -g pm2`
+
+Execute `pm2 start npm -- run dev` to start the project in the project `dolphinscheduler-ui `root directory
+
+#### command
+
+- Start `pm2 start npm -- run dev`
+
+- Stop `pm2 stop npm`
+
+- delete `pm2 delete npm`
+
+- Status  `pm2 list`
+
+```
+
+[root@localhost dolphinscheduler-ui]# pm2 start npm -- run dev
+[PM2] Applying action restartProcessId on app [npm](ids: 0)
+[PM2] [npm](0) ✓
+[PM2] Process successfully started
+┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────┬──────────┐
+│ App name │ id │ version │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem      │ user │ watching │
+├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────┼──────────┤
+│ npm      │ 0  │ N/A     │ fork │ 6168 │ online │ 31      │ 0s     │ 0%  │ 5.6 MB   │ root │ disabled │
+└──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────┴──────────┘
+ Use `pm2 show <id|name>` to get more details about an app
+
+```
+
+
+### Project directory structure
+
+`build` some webpack configurations for packaging and development environment projects
+
+`node_modules` development environment node dependency package
+
+`src` project required documents
+
+`src => images` public image storage
+
+`src => js` js/vue
+
+`src => lib` internal components of the company (company component library can be deleted after open source)
+
+`src => sass` sass file One page corresponds to a sass file
+
+`src => view` page file One page corresponds to an html file
+
+```
+> Projects are developed using vue single page application (SPA)
+- All page entry files are in the `src/js/conf/${ corresponding page filename => home} index.js` entry file
+- The corresponding sass file is in `src/sass/conf/${corresponding page filename => home}/index.scss`
+- The corresponding html file is in `src/view/${corresponding page filename => home}/index.html`
+```
+
+Public module and utill `src/js/module`
+
+`components` => internal project common components
+
+`download` => download component
+
+`echarts` => chart component
+
+`filter` => filter and vue pipeline
+
+`i18n` => internationalization
+
+`io` => io request encapsulation based on axios
+
+`mixin` => vue mixin public part for disabled operation
+
+`permissions` => permission operation
+
+`util` => tool
+
+
+### System function module
+
+Home  => `http://localhost:8888/#/home`
+
+Project Management => `http://localhost:8888/#/projects/list`
+```
+| Project Home
+| Workflow relationship
+| Workflow
+  - Workflow definition
+  - Workflow instance
+  - Task instance
+```
+
+Resource Management => `http://localhost:8888/#/resource/file`
+```
+| File Management
+| udf Management
+  - Resource Management
+  - Function management
+
+
+
+```
+
+Data Source Management => `http://localhost:8888/#/datasource/list`
+
+monitoring Center => `http://localhost:8888/#/monitor/servers/master`
+```
+| Service management
+  - Master
+  - Worker
+  - Zookeeper
+  - DB
+| Statistics management
+  - Statistics
+```
+
+Security Center => `http://localhost:8888/#/security/tenant`
+```
+| Tenant Management
+| User Management
+| Alarm Group Management
+| Alarm instance management
+| Worker Group Management
+| Yarn queue management
+| Token Management
+```
+
+User Center => `http://localhost:8888/#/user/account`
+
+
+## Routing and state management
+
+The project `src/js/conf/home` is divided into
+
+`pages` => route to page directory
+```
+ The page file corresponding to the routing address
+```
+
+`router` => route management
+```
+vue router, the entry file index.js in each page will be registered. Specific operations: https://router.vuejs.org/zh/
+```
+
+`store` => status management
+```
+The page corresponding to each route has a state management file divided into:
+
+actions => mapActions => Details:https://vuex.vuejs.org/zh/guide/actions.html
+
+getters => mapGetters => Details:https://vuex.vuejs.org/zh/guide/getters.html
+
+index => entrance
+mutations => mapMutations => Details:https://vuex.vuejs.org/zh/guide/mutations.html
+
+state => mapState => Details:https://vuex.vuejs.org/zh/guide/state.html
+
+Specific action:https://vuex.vuejs.org/zh/
+
+```
+
+## Menu added
+The public menu only needs to be in `/src/js/conf/home/router/index.js` Just add it,Such as: Home
+```
+  {
+    path: '/home',
+    name: 'home',
+    component: resolve => require(['../pages/home/index'], resolve),
+    meta: {
+      title: `${i18n.$t('Home')} - DolphinScheduler`,
+      refresh_in_switched_tab: true
+    }
+  }
+```
+
+## Exception handling
+Intercept all request requests in the `/src/js/module/io/index.js file`, and analyze and handle the status code returned by the interface through the response interceptor. The code is as follows
+```
+import io from '@/module/axios/index'
+import cookies from 'js-cookie'
+
+const apiPrefix = '/dolphinscheduler'
+const reSlashPrefix = /^\/+/
+
+const resolveURL = (url) => {
+  if (url.indexOf('http') !== -1) {
+    return url
+  }
+  if (url.charAt(0) !== '/') {
+    return `${apiPrefix}/${url.replace(reSlashPrefix, '')}`
+  }
+
+  return url
+}
+
+/**
+ * Resolve backend api url
+ */
+export { resolveURL }
+
+/**
+ * Set io default instance resolveUrl globally
+ */
+io.config.resolveURL = resolveURL
+io.config.timeout = 0
+io.config.maxContentLength = 200000
+io.config.validateStatus = function (status) {
+  if (status === 401 || status === 504) {
+    window.location.href = `${PUBLIC_PATH}/view/login/index.html`
+    return
+  }
+  return status
+}
+
+// io.config.emulateJSON = false
+const _propRequest = io.request
+
+// Add a local request interceptor
+io.request = (spec) => {
+  return _propRequest.call(io, spec)
+}
+
+// Global response interceptor registion
+io.interceptors.response.use(
+  response => {
+    return response
+  }, error => {
+    // Do something with response error
+    return Promise.reject(error)
+  }
+)
+
+// Global request interceptor registion
+io.interceptors.request.use(
+  config => {
+    const sIdCookie = cookies.get('sessionId')
+    const sessionId = sessionStorage.getItem('sessionId')
+    const requstUrl = config.url.substring(config.url.lastIndexOf('/') + 1)
+    if ((!sIdCookie || (sessionId && sessionId !== sIdCookie)) && requstUrl !== 'login') {
+      window.location.href = `${PUBLIC_PATH}/view/login/index.html`
+    } else {
+      const { method } = config
+      if (method === 'get') {
+        config.params = Object.assign({}, config.params, {
+          _t: Math.random()
+        })
+      }
+      config.headers = config.headers || {}
+      const language = cookies.get('language')
+      if (language) config.headers.language = language
+      if (sIdCookie) config.headers.sessionId = sIdCookie
+      return config
+    }
+  }, error => {
+    // Do something with request error
+    return Promise.reject(error)
+  }
+)
+
+export default io
+```
+
+## specification
+## Vue specification
+##### 1.Component name
+The component is named multiple words and is connected with a wire (-) to avoid conflicts with HTML tags and a clearer structure.
+```
+// positive example
+export default {
+    name: 'page-article-item'
+}
+```
+
+##### 2.Component files
+The internal common component of the `src/js/module/components` project writes the folder name with the same name as the file name. The subcomponents and util tools that are split inside the common component are placed in the internal `_source` folder of the component.
+```
+└── components
+    ├── header
+        ├── header.vue
+        └── _source
+            └── nav.vue
+            └── util.js
+    ├── conditions
+        ├── conditions.vue
+        └── _source
+            └── search.vue
+            └── util.js
+```
+
+##### 3.Prop
+When you define Prop, you should always name it in camel format (camelCase) and use the connection line (-) when assigning values to the parent component.This follows the characteristics of each language, because it is case-insensitive in HTML tags, and the use of links is more friendly; in JavaScript, the more natural is the hump name.
+
+```
+// Vue
+props: {
+    articleStatus: Boolean
+}
+// HTML
+<article-item :article-status="true"></article-item>
+```
+
+The definition of Prop should specify its type, defaults, and validation as much as possible.
+
+Example:
+
+```
+props: {
+    attrM: Number,
+    attrA: {
+        type: String,
+        required: true
+    },
+    attrZ: {
+        type: Object,
+        //  The default value of the array/object should be returned by a factory function
+        default: function () {
+            return {
+                msg: 'achieve you and me'
+            }
+        }
+    },
+    attrE: {
+        type: String,
+        validator: function (v) {
+            return !(['success', 'fail'].indexOf(v) === -1) 
+        }
+    }
+}
+```
+
+##### 4.v-for
+When performing v-for traversal, you should always bring a key value to make rendering more efficient when updating the DOM.
+```
+<ul>
+    <li v-for="item in list" :key="item.id">
+        {{ item.title }}
+    </li>
+</ul>
+```
+
+v-for should be avoided on the same element as v-if (`for example: <li>`) because v-for has a higher priority than v-if. To avoid invalid calculations and rendering, you should try to use v-if Put it on top of the container's parent element.
+```
+<ul v-if="showList">
+    <li v-for="item in list" :key="item.id">
+        {{ item.title }}
+    </li>
+</ul>
+```
+
+##### 5.v-if / v-else-if / v-else
+If the elements in the same set of v-if logic control are logically identical, Vue reuses the same part for more efficient element switching, `such as: value`. In order to avoid the unreasonable effect of multiplexing, you should add key to the same element for identification.
+```
+<div v-if="hasData" key="mazey-data">
+    <span>{{ mazeyData }}</span>
+</div>
+<div v-else key="mazey-none">
+    <span>no data</span>
+</div>
+```
+
+##### 6.Instruction abbreviation
+In order to unify the specification, the instruction abbreviation is always used. Using `v-bind`, `v-on` is not bad. Here is only a unified specification.
+```
+<input :value="mazeyUser" @click="verifyUser">
+```
+
+##### 7.Top-level element order of single file components
+Styles are packaged in a file, all the styles defined in a single vue file, the same name in other files will also take effect. All will have a top class name before creating a component.
+Note: The sass plugin has been added to the project, and the sas syntax can be written directly in a single vue file.
+For uniformity and ease of reading, they should be placed in the order of  `<template>`、`<script>`、`<style>`.
+
+```
+<template>
+  <div class="test-model">
+    test
+  </div>
+</template>
+<script>
+  export default {
+    name: "test",
+    data() {
+      return {}
+    },
+    props: {},
+    methods: {},
+    watch: {},
+    beforeCreate() {
+    },
+    created() {
+    },
+    beforeMount() {
+    },
+    mounted() {
+    },
+    beforeUpdate() {
+    },
+    updated() {
+    },
+    beforeDestroy() {
+    },
+    destroyed() {
+    },
+    computed: {},
+    components: {},
+  }
+</script>
+
+<style lang="scss" rel="stylesheet/scss">
+  .test-model {
+
+  }
+</style>
+
+```
+
+
+## JavaScript specification
+
+##### 1.var / let / const
+It is recommended to no longer use var, but use let / const, prefer const. The use of any variable must be declared in advance, except that the function defined by function can be placed anywhere.
+
+##### 2.quotes
+```
+const foo = 'after division'
+const bar = `${foo},ront-end engineer`
+```
+
+##### 3.function
+Anonymous functions use the arrow function uniformly. When multiple parameters/return values are used, the object's structure assignment is used first.
+```
+function getPersonInfo ({name, sex}) {
+    // ...
+    return {name, gender}
+}
+```
+The function name is uniformly named with a camel name. The beginning of the capital letter is a constructor. The lowercase letters start with ordinary functions, and the new operator should not be used to operate ordinary functions.
+
+##### 4.object
+```
+const foo = {a: 0, b: 1}
+const bar = JSON.parse(JSON.stringify(foo))
+
+const foo = {a: 0, b: 1}
+const bar = {...foo, c: 2}
+
+const foo = {a: 3}
+Object.assign(foo, {b: 4})
+
+const myMap = new Map([])
+for (let [key, value] of myMap.entries()) {
+    // ...
+}
+```
+
+##### 5.module
+Unified management of project modules using import / export.
+```
+// lib.js
+export default {}
+
+// app.js
+import app from './lib'
+```
+
+Import is placed at the top of the file.
+
+If the module has only one output value, use `export default`,otherwise no.
+
+## HTML / CSS
+
+##### 1.Label
+
+Do not write the type attribute when referencing external CSS or JavaScript. The HTML5 default type is the text/css and text/javascript properties, so there is no need to specify them.
+```
+<link rel="stylesheet" href="//www.test.com/css/test.css">
+<script src="//www.test.com/js/test.js"></script>
+```
+
+##### 2.Naming
+The naming of Class and ID should be semantic, and you can see what you are doing by looking at the name; multiple words are connected by a link.
+```
+// positive example
+.test-header{
+    font-size: 20px;
+}
+```
+
+##### 3.Attribute abbreviation
+CSS attributes use abbreviations as much as possible to improve the efficiency and ease of understanding of the code.
+
+```
+// counter example
+border-width: 1px;
+border-style: solid;
+border-color: #ccc;
+
+// positive example
+border: 1px solid #ccc;
+```
+
+##### 4.Document type
+
+The HTML5 standard should always be used.
+
+```
+<!DOCTYPE html>
+```
+
+##### 5.Notes
+A block comment should be written to a module file.
+```
+/**
+* @module mazey/api
+* @author Mazey <ma...@mazey.net>
+* @description test.
+* */
+```
+
+
+## interface
+
+##### All interfaces are returned as Promise 
+Note that non-zero is wrong for catching catch
+
+```
+const test = () => {
+  return new Promise((resolve, reject) => {
+    resolve({
+      a:1
+    })
+  })
+}
+
+// transfer
+test.then(res => {
+  console.log(res)
+  // {a:1}
+})
+```
+
+Normal return
+```
+{
+  code:0,
+  data:{}
+  msg:'success'
+}
+```
+
+Error return
+```
+{
+  code:10000, 
+  data:{}
+  msg:'failed'
+}
+```
+If the interface is a post request, the Content-Type defaults to application/x-www-form-urlencoded; if the Content-Type is changed to application/json,
+Interface parameter transfer needs to be changed to the following way
+```
+io.post('url', payload, null, null, { emulateJSON: false } res => {
+  resolve(res)
+}).catch(e => {
+  reject(e)
+})
+```
+
+##### Related interface path
+
+dag related interface `src/js/conf/home/store/dag/actions.js`
+
+Data Source Center Related Interfaces  `src/js/conf/home/store/datasource/actions.js`
+
+Project Management Related Interfaces `src/js/conf/home/store/projects/actions.js`
+
+Resource Center Related Interfaces `src/js/conf/home/store/resource/actions.js`
+
+Monitoring center Related Interface `src/js/conf/home/store/monitor/actions.js`
+
+Security Center Related Interfaces `src/js/conf/home/store/security/actions.js`
+
+User Center Related Interfaces `src/js/conf/home/store/user/actions.js`
+
+
+
+## Extended development
+
+##### 1.Add node
+
+(1) First place the icon icon of the node in the `src/js/conf/home/pages/dag/img `folder, and note the English name of the node defined by the `toolbar_${in the background. For example: SHELL}.png`
+
+(2)  Find the `tasksType` object in `src/js/conf/home/pages/dag/_source/config.js` and add it to it.
+```
+'DEPENDENT': {  //  The background definition node type English name is used as the key value
+  desc: 'DEPENDENT',  // tooltip desc
+  color: '#2FBFD8'  // The color represented is mainly used for tree and gantt
+}
+```
+
+(3)  Add a `${node type (lowercase)}`.vue file in `src/js/conf/home/pages/dag/_source/formModel/tasks`. The contents of the components related to the current node are written here. Must belong to a node component must have a function _verification () After the verification is successful, the relevant data of the current component is thrown to the parent component.
+```
+/**
+ * Verification
+*/
+  _verification () {
+    // datasource subcomponent verification
+    if (!this.$refs.refDs._verifDatasource()) {
+      return false
+    }
+
+    // verification function
+    if (!this.method) {
+      this.$message.warning(`${i18n.$t('Please enter method')}`)
+      return false
+    }
+
+    // localParams subcomponent validation
+    if (!this.$refs.refLocalParams._verifProp()) {
+      return false
+    }
+    // store
+    this.$emit('on-params', {
+      type: this.type,
+      datasource: this.datasource,
+      method: this.method,
+      localParams: this.localParams
+    })
+    return true
+  }
+```
+
+(4) Common components used inside the node component are under` _source`, and `commcon.js` is used to configure public data.
+
+##### 2.Increase the status type
+
+(1) Find the `tasksState` object in `src/js/conf/home/pages/dag/_source/config.js` and add it to it.
+
+```
+ 'WAITTING_DEPEND': {  // 'WAITTING_DEPEND': {  //Backend defines state type, frontend is used as key value
+  id: 11,  // front-end definition id is used as a sort
+  desc: `${i18n.$t('waiting for dependency')}`,  // tooltip desc
+  color: '#5101be',  // The color represented is mainly used for tree and gantt
+  icoUnicode: '&#xe68c;',  // font icon
+  isSpin: false  // whether to rotate (requires code judgment)
+}
+```
+
+##### 3.Add the action bar tool
+(1)  Find the `toolOper` object in `src/js/conf/home/pages/dag/_source/config.js` and add it to it.
+```
+{
+  code: 'pointer',  // tool identifier
+  icon: '&#xe781;',  // tool icon
+  disable: disable,  // disable
+  desc: `${i18n.$t('Drag node and selected item')}`  // tooltip desc
+}
+```
+
+(2) Tool classes are returned as a constructor  `src/js/conf/home/pages/dag/_source/plugIn`
+
+`downChart.js`  =>  dag image download processing
+
+`dragZoom.js`  =>  mouse zoom effect processing
+
+`jsPlumbHandle.js`  =>  drag and drop line processing
+
+`util.js`  =>   belongs to the `plugIn` tool class
+
+
+The operation is handled in the `src/js/conf/home/pages/dag/_source/dag.js` => `toolbarEvent` event.
+
+
+##### 3.Add a routing page
+
+(1) First add a routing address`src/js/conf/home/router/index.js` in route management
+```
+routing address{
+  path: '/test',  // routing address
+  name: 'test',  // alias
+  component: resolve => require(['../pages/test/index'], resolve),  // route corresponding component entry file
+  meta: {
+    title: `${i18n.$t('test')} - EasyScheduler`  // title display
+  }
+},
+```
+
+(2)Create a `test` folder in `src/js/conf/home/pages` and create an `index.vue `entry file in the folder.
+
+    This will give you direct access to`http://localhost:8888/#/test`
+
+
+##### 4.Increase the preset mailbox
+
+Find the `src/lib/localData/email.js` startup and timed email address input to automatically pull down the match.
+```
+export default ["test@analysys.com.cn","test1@analysys.com.cn","test3@analysys.com.cn"]
+```
+
+##### 5.Authority management and disabled state processing
+
+The permission gives the userType according to the backUser interface `getUserInfo` interface: `"ADMIN_USER/GENERAL_USER" `permission to control whether the page operation button is `disabled`.
+
+specific operation:`src/js/module/permissions/index.js`
+
+disabled processing:`src/js/module/mixin/disabledState.js`
+
diff --git a/development/zh-cn/dev-frontend-development.md b/development/zh-cn/dev-frontend-development.md
new file mode 100644
index 0000000..e6cf12e
--- /dev/null
+++ b/development/zh-cn/dev-frontend-development.md
@@ -0,0 +1,764 @@
+# 前端开发文档
+
+### 技术选型
+```
+Vue mvvm框架
+
+Es6 ECMAScript 6.0
+
+Element-ui
+
+form-create JSON表单生成器
+
+D3 可视化库图表库
+
+Jsplumb 连线插件库
+
+Lodash 高性能的 JavaScript 实用工具库
+```
+
+
+### 开发环境搭建
+   
+- #### Node安装
+Node包下载 (注意:node版本必须是长期版本,目前Node最高版本支持14+) `https://nodejs.org/zh-cn/` 
+node.js扩展,可以安装nvm管理node版本,安装nrm管理镜像源
+
+
+- #### 前端项目构建
+用命令行模式 `cd`  进入 `dolphinscheduler-ui`项目目录,查看npm镜像源使用状态`npm get registry`,如果不是淘宝镜像,将镜像设置成淘宝镜像`npm config set registry http://registry.npm.taobao.org`,然后执行 `npm install` 拉取项目依赖包
+
+
+- 配置`.env`文件,用于跟后端交互的接口
+
+在`dolphinscheduler-ui`目录下打开`.env`文件,在文件里添加后端服务的ip地址和端口,用于跟后端交互,`.env`文件内容如下:
+```
+# 代理的接口地址(自行修改)
+API_BASE = http://192.168.xx.xx:12345(注意:API接口一定要写成IP地址的形式,不要写成http://localhost:12345)
+
+# 如果您需要用ip访问项目可以把 "#" 号去掉(例)
+#DEV_HOST = 192.168.xx.xx
+```
+
+> #####  !!!这里特别注意 项目如果在拉取依赖包的过程中报 " node-sass error " 错误,请在执行完后再次执行以下命令
+```
+npm install node-sass --unsafe-perm //单独安装node-sass依赖
+```
+
+- #### 开发环境运行
+- `npm start` 项目开发环境 (启动后访问地址 http://localhost:8888/#/)
+
+
+#### 前端项目发布
+
+- `npm run build` 项目打包 (打包后根目录会创建一个名为dist文件夹,用于发布线上Nginx)
+
+运行 `npm run build` 命令,生成打包文件(dist)包
+
+再拷贝到服务器对应的目录下(前端服务静态页面存放目录)
+
+
+访问地址 `http://localhost:8888/#/` 
+
+
+#### Linux下使用node启动并且守护进程
+
+安装pm2 `npm install -g pm2`
+
+在项目`dolphinscheduler-ui`根目录执行 `pm2 start npm -- run dev` 启动项目
+
+#### 命令
+
+- 启用 `pm2 start npm -- run dev`
+
+- 停止 `pm2 stop npm`
+
+- 删除 `pm2 delete npm`
+
+- 状态 `pm2 list`
+
+```
+
+[root@localhost dolphinscheduler-ui]# pm2 start npm -- run dev
+[PM2] Applying action restartProcessId on app [npm](ids: 0)
+[PM2] [npm](0) ✓
+[PM2] Process successfully started
+┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────┬──────────┐
+│ App name │ id │ version │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem      │ user │ watching │
+├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────┼──────────┤
+│ npm      │ 0  │ N/A     │ fork │ 6168 │ online │ 31      │ 0s     │ 0%  │ 5.6 MB   │ root │ disabled │
+└──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────┴──────────┘
+ Use `pm2 show <id|name>` to get more details about an app
+
+```
+
+
+### 项目目录结构
+
+`build` 打包及开发环境项目的一些webpack配置
+
+`node_modules` 开发环境node依赖包
+
+`src` 项目所需文件
+
+`src => images` 公共图片存放
+
+`src => js` js/vue
+
+`src => lib` 公司内部组件(公司组件库开源后可删掉)
+
+`src => sass` sass文件 一个页面对应一个sass文件
+
+`src => view` 页面文件 一个页面对应一个html文件
+
+```
+> 项目采用vue单页面应用(SPA)开发
+- 所有页面入口文件在 `src/js/conf/${对应页面文件名 => home}` 的 `index.js` 入口文件
+- 对应的sass文件则在 `src/sass/conf/${对应页面文件名 => home}/index.scss`
+- 对应的html文件则在 `src/view/${对应页面文件名 => home}/index.html`
+```
+
+公共模块及util `src/js/module`
+
+`components` => 内部项目公共组件
+
+`download` => 下载组件
+
+`echarts` => 图表组件
+
+`filter` => 过滤器和vue管道
+
+`i18n` => 国际化
+
+`io` => io请求封装 基于axios
+
+`mixin` => vue mixin 公共部分 用于disabled操作
+
+`permissions` => 权限操作
+
+`util` => 工具
+
+
+### 系统功能模块
+
+首页 => `http://localhost:8888/#/home`
+
+项目管理 => `http://localhost:8888/#/projects/list`
+```
+| 项目首页
+| 工作流关系
+| 工作流
+  - 工作流定义
+  - 工作流实例
+  - 任务实例
+```
+ 
+资源中心 => `http://localhost:8888/#/resource/file`
+```
+| 文件管理
+| UDF管理
+  - 资源管理
+  - 函数管理
+```
+
+数据源中心 => `http://localhost:8888/#/datasource/list`
+
+监控中心 => `http://localhost:8888/#/monitor/servers/master`
+```
+| 服务管理
+  - Master
+  - Worker
+  - Zookeeper
+  - DB
+| 统计管理
+  - Statistics
+```
+
+
+安全中心 => `http://localhost:8888/#/security/tenant`
+```
+| 租户管理
+| 用户管理
+| 告警组管理
+| 告警实例管理
+| Worker分组管理
+| Yarn队列管理
+| 令牌管理
+```
+
+用户中心 => `http://localhost:8888/#/user/account`
+
+
+## 路由和状态管理
+
+项目 `src/js/conf/home` 下分为
+
+`pages` => 路由指向页面目录
+```
+ 路由地址对应的页面文件
+```
+
+`router` => 路由管理
+```
+vue的路由器,在每个页面的入口文件index.js 都会注册进来 具体操作:https://router.vuejs.org/zh/
+```
+
+`store` => 状态管理
+```
+每个路由对应的页面都有一个状态管理的文件 分为:
+
+actions => mapActions => 详情:https://vuex.vuejs.org/zh/guide/actions.html
+
+getters => mapGetters => 详情:https://vuex.vuejs.org/zh/guide/getters.html
+
+index => 入口
+
+mutations => mapMutations => 详情:https://vuex.vuejs.org/zh/guide/mutations.html
+
+state => mapState => 详情:https://vuex.vuejs.org/zh/guide/state.html
+
+具体操作:https://vuex.vuejs.org/zh/
+
+```
+
+## 菜单添加
+公共的菜单只需要在 `/src/js/conf/home/router/index.js` 中添加就可以了,如:首页
+```
+  {
+    path: '/home',
+    name: 'home',
+    component: resolve => require(['../pages/home/index'], resolve),
+    meta: {
+      title: `${i18n.$t('Home')} - DolphinScheduler`,
+      refresh_in_switched_tab: true
+    }
+  }
+```
+
+## 异常处理
+在 `/src/js/module/io/index.js` 文件中对所有的 request请求进行拦截, 通过response 拦截器对接口返回的状态码进行分析与异常处理,代码如下
+```
+import io from '@/module/axios/index'
+import cookies from 'js-cookie'
+
+const apiPrefix = '/dolphinscheduler'
+const reSlashPrefix = /^\/+/
+
+const resolveURL = (url) => {
+  if (url.indexOf('http') !== -1) {
+    return url
+  }
+  if (url.charAt(0) !== '/') {
+    return `${apiPrefix}/${url.replace(reSlashPrefix, '')}`
+  }
+
+  return url
+}
+
+/**
+ * Resolve backend api url
+ */
+export { resolveURL }
+
+/**
+ * Set io default instance resolveUrl globally
+ */
+io.config.resolveURL = resolveURL
+io.config.timeout = 0
+io.config.maxContentLength = 200000
+io.config.validateStatus = function (status) {
+  if (status === 401 || status === 504) {
+    window.location.href = `${PUBLIC_PATH}/view/login/index.html`
+    return
+  }
+  return status
+}
+
+// io.config.emulateJSON = false
+const _propRequest = io.request
+
+// Add a local request interceptor
+io.request = (spec) => {
+  return _propRequest.call(io, spec)
+}
+
+// Global response interceptor registion
+io.interceptors.response.use(
+  response => {
+    return response
+  }, error => {
+    // Do something with response error
+    return Promise.reject(error)
+  }
+)
+
+// Global request interceptor registion
+io.interceptors.request.use(
+  config => {
+    const sIdCookie = cookies.get('sessionId')
+    const sessionId = sessionStorage.getItem('sessionId')
+    const requstUrl = config.url.substring(config.url.lastIndexOf('/') + 1)
+    if ((!sIdCookie || (sessionId && sessionId !== sIdCookie)) && requstUrl !== 'login') {
+      window.location.href = `${PUBLIC_PATH}/view/login/index.html`
+    } else {
+      const { method } = config
+      if (method === 'get') {
+        config.params = Object.assign({}, config.params, {
+          _t: Math.random()
+        })
+      }
+      config.headers = config.headers || {}
+      const language = cookies.get('language')
+      if (language) config.headers.language = language
+      if (sIdCookie) config.headers.sessionId = sIdCookie
+      return config
+    }
+  }, error => {
+    // Do something with request error
+    return Promise.reject(error)
+  }
+)
+
+export default io
+```
+
+
+## 规范
+## Vue规范
+##### 1.组件名
+组件名为多个单词,并且用连接线(-)连接,避免与 HTML 标签冲突,并且结构更加清晰。
+```
+// 正例
+export default {
+    name: 'page-article-item'
+}
+```
+
+##### 2.组件文件
+`src/js/module/components`项目内部公共组件书写文件夹名与文件名同名,公共组件内部所拆分的子组件与util工具都放置组件内部 `_source`文件夹里。
+```
+└── components
+    ├── header
+        ├── header.vue
+        └── _source
+            └── nav.vue
+            └── util.js
+    ├── conditions
+        ├── conditions.vue
+        └── _source
+            └── search.vue
+            └── util.js
+```
+
+##### 3.Prop
+定义 Prop 的时候应该始终以驼峰格式(camelCase)命名,在父组件赋值的时候使用连接线(-)。
+这里遵循每个语言的特性,因为在 HTML 标记中对大小写是不敏感的,使用连接线更加友好;而在 JavaScript 中更自然的是驼峰命名。
+```
+// Vue
+props: {
+    articleStatus: Boolean
+}
+// HTML
+<article-item :article-status="true"></article-item>
+```
+
+Prop 的定义应该尽量详细的指定其类型、默认值和验证。
+
+示例:
+
+```
+props: {
+    attrM: Number,
+    attrA: {
+        type: String,
+        required: true
+    },
+    attrZ: {
+        type: Object,
+        // 数组/对象的默认值应该由一个工厂函数返回
+        default: function () {
+            return {
+                msg: '成就你我'
+            }
+        }
+    },
+    attrE: {
+        type: String,
+        validator: function (v) {
+            return !(['success', 'fail'].indexOf(v) === -1) 
+        }
+    }
+}
+```
+
+##### 4.v-for
+在执行 v-for 遍历的时候,总是应该带上 key 值使更新 DOM 时渲染效率更高。
+```
+<ul>
+    <li v-for="item in list" :key="item.id">
+        {{ item.title }}
+    </li>
+</ul>
+```
+
+v-for 应该避免与 v-if 在同一个元素(`例如:<li>`)上使用,因为 v-for 的优先级比 v-if 更高,为了避免无效计算和渲染,应该尽量将 v-if 放到容器的父元素之上。
+```
+<ul v-if="showList">
+    <li v-for="item in list" :key="item.id">
+        {{ item.title }}
+    </li>
+</ul>
+```
+
+##### 5.v-if / v-else-if / v-else
+若同一组 v-if 逻辑控制中的元素逻辑相同,Vue 为了更高效的元素切换,会复用相同的部分,`例如:value`。为了避免复用带来的不合理效果,应该在同种元素上加上 key 做标识。
+```
+<div v-if="hasData" key="mazey-data">
+    <span>{{ mazeyData }}</span>
+</div>
+<div v-else key="mazey-none">
+    <span>无数据</span>
+</div>
+```
+
+##### 6.指令缩写
+为了统一规范始终使用指令缩写,使用`v-bind`,`v-on`并没有什么不好,这里仅为了统一规范。
+```
+<input :value="mazeyUser" @click="verifyUser">
+```
+
+##### 7.单文件组件的顶级元素顺序
+样式后续都是打包在一个文件里,所有在单个vue文件中定义的样式,在别的文件里同类名的样式也是会生效的所有在创建一个组件前都会有个顶级类名
+注意:项目内已经增加了sass插件,单个vue文件里可以直接书写sass语法
+为了统一和便于阅读,应该按 `<template>`、`<script>`、`<style>`的顺序放置。
+
+```
+<template>
+  <div class="test-model">
+    test
+  </div>
+</template>
+<script>
+  export default {
+    name: "test",
+    data() {
+      return {}
+    },
+    props: {},
+    methods: {},
+    watch: {},
+    beforeCreate() {
+    },
+    created() {
+    },
+    beforeMount() {
+    },
+    mounted() {
+    },
+    beforeUpdate() {
+    },
+    updated() {
+    },
+    beforeDestroy() {
+    },
+    destroyed() {
+    },
+    computed: {},
+    components: {},
+  }
+</script>
+
+<style lang="scss" rel="stylesheet/scss">
+  .test-model {
+
+  }
+</style>
+
+```
+
+
+## JavaScript规范
+
+##### 1.var / let / const
+建议不再使用 var,而使用 let / const,优先使用 const。任何一个变量的使用都要提前申明,除了 function 定义的函数可以随便放在任何位置。
+
+##### 2.引号
+```
+const foo = '后除'
+const bar = `${foo},前端工程师`
+```
+
+##### 3.函数
+匿名函数统一使用箭头函数,多个参数/返回值时优先使用对象的结构赋值。
+```
+function getPersonInfo ({name, sex}) {
+    // ...
+    return {name, gender}
+}
+```
+函数名统一使用驼峰命名,以大写字母开头申明的都是构造函数,使用小写字母开头的都是普通函数,也不该使用 new 操作符去操作普通函数。
+
+##### 4.对象
+```
+const foo = {a: 0, b: 1}
+const bar = JSON.parse(JSON.stringify(foo))
+
+const foo = {a: 0, b: 1}
+const bar = {...foo, c: 2}
+
+const foo = {a: 3}
+Object.assign(foo, {b: 4})
+
+const myMap = new Map([])
+for (let [key, value] of myMap.entries()) {
+    // ...
+}
+```
+
+##### 5.模块
+统一使用 import / export 的方式管理项目的模块。
+```
+// lib.js
+export default {}
+
+// app.js
+import app from './lib'
+```
+
+import 统一放在文件顶部。
+
+如果模块只有一个输出值,使用 `export default`,否则不用。
+
+
+## HTML / CSS
+
+###### 1.标签
+在引用外部 CSS 或 JavaScript 时不写 type 属性。HTML5 默认 type 为 `text/css` 和 `text/javascript` 属性,所以没必要指定。
+```
+<link rel="stylesheet" href="//www.test.com/css/test.css">
+<script src="//www.test.com/js/test.js"></script>
+```
+
+##### 2.命名
+Class 和 ID 的命名应该语义化,通过看名字就知道是干嘛的;多个单词用连接线 - 连接。
+```
+// 正例
+.test-header{
+    font-size: 20px;
+}
+```
+
+##### 3.属性缩写
+CSS 属性尽量使用缩写,提高代码的效率和方便理解。
+
+```
+// 反例
+border-width: 1px;
+border-style: solid;
+border-color: #ccc;
+
+// 正例
+border: 1px solid #ccc;
+```
+
+##### 4.文档类型
+应该总是使用 HTML5 标准。
+```
+<!DOCTYPE html>
+```
+
+##### 5.注释
+应该给一个模块文件写一个区块注释。
+```
+/**
+* @module mazey/api
+* @author Mazey <ma...@mazey.net>
+* @description test.
+* */
+```
+
+
+## 接口
+
+##### 所有的接口都以 Promise 形式返回 
+注意非0都为错误走catch
+
+```
+const test = () => {
+  return new Promise((resolve, reject) => {
+    resolve({
+      a:1
+    })
+  })
+}
+
+// 调用
+test.then(res => {
+  console.log(res)
+  // {a:1}
+})
+```
+
+正常返回
+```
+{
+  code:0,
+  data:{}
+  msg:'成功'
+}
+```
+
+错误返回
+```
+{
+  code:10000, 
+  data:{}
+  msg:'失败'
+}
+```
+接口如果是post请求,Content-Type默认为application/x-www-form-urlencoded;如果Content-Type改成application/json,
+接口传参需要改成下面的方式
+```
+io.post('url', payload, null, null, { emulateJSON: false } res => {
+  resolve(res)
+}).catch(e => {
+  reject(e)
+})
+```
+
+##### 相关接口路径
+
+dag 相关接口 `src/js/conf/home/store/dag/actions.js`
+
+数据源中心 相关接口 `src/js/conf/home/store/datasource/actions.js`
+
+项目管理 相关接口 `src/js/conf/home/store/projects/actions.js`
+
+资源中心 相关接口 `src/js/conf/home/store/resource/actions.js`
+
+监控中心 相关接口 `src/js/conf/home/store/monitor/actions.js`
+
+安全中心 相关接口 `src/js/conf/home/store/security/actions.js`
+
+用户中心 相关接口 `src/js/conf/home/store/user/actions.js`
+
+
+
+## 扩展开发
+
+##### 1.增加节点
+
+(1) 先将节点的icon小图标放置`src/js/conf/home/pages/dag/img`文件夹内,注意 `toolbar_${后台定义的节点的英文名称 例如:SHELL}.png`
+(2) 找到 `src/js/conf/home/pages/dag/_source/config.js` 里的 `tasksType` 对象,往里增加
+```
+'DEPENDENT': {  // 后台定义节点类型英文名称用作key值
+  desc: 'DEPENDENT',  // tooltip desc
+  color: '#2FBFD8'  // 代表的颜色主要用于 tree和gantt 两张图
+}
+```
+
+(3) 在 `src/js/conf/home/pages/dag/_source/formModel/tasks` 增加一个 `${节点类型(小写)}`.vue 文件,跟当前节点相关的组件内容都在这里写。 属于节点组件内的必须拥有一个函数 `_verification()` 验证成功后讲当前组件的相关数据往父组件抛。
+```
+/**
+ * 验证
+*/
+  _verification () {
+    // datasource 子组件验证
+    if (!this.$refs.refDs._verifDatasource()) {
+      return false
+    }
+
+    // 验证函数
+    if (!this.method) {
+      this.$message.warning(`${i18n.$t('请输入方法')}`)
+      return false
+    }
+
+    // localParams 子组件验证
+    if (!this.$refs.refLocalParams._verifProp()) {
+      return false
+    }
+    // 存储
+    this.$emit('on-params', {
+      type: this.type,
+      datasource: this.datasource,
+      method: this.method,
+      localParams: this.localParams
+    })
+    return true
+  }
+``` 
+
+(4) 节点组件内部所用到公共的组件都在`_source`下,`commcon.js`用与配置公共数据
+
+##### 2.增加状态类型
+(1) 找到 `src/js/conf/home/pages/dag/_source/config.js` 里的 `tasksState` 对象,往里增加
+```
+'WAITTING_DEPEND': {  //后端定义状态类型 前端用作key值
+  id: 11,  // 前端定义id 后续用作排序
+  desc: `${i18n.$t('等待依赖')}`,  // tooltip desc
+  color: '#5101be',  // 代表的颜色主要用于 tree和gantt 两张图
+  icoUnicode: '&#xe68c;',  // 字体图标 
+  isSpin: false  // 是否旋转(需代码判断)
+}
+```
+
+##### 3.增加操作栏工具
+(1) 找到 `src/js/conf/home/pages/dag/_source/config.js` 里的 `toolOper` 对象,往里增加
+```
+{
+  code: 'pointer',  // 工具标识
+  icon: '&#xe781;',  // 工具图标 
+  disable: disable,  // 是否禁用
+  desc: `${i18n.$t('拖动节点和选中项')}`  // tooltip desc
+}
+```
+
+(2) 工具类都以一个构造函数返回 `src/js/conf/home/pages/dag/_source/plugIn`
+
+`downChart.js`  =>  dag 图片下载处理 
+
+`dragZoom.js`  =>  鼠标缩放效果处理 
+
+`jsPlumbHandle.js`  =>  拖拽线条处理 
+
+`util.js`  =>   属于 `plugIn` 工具类
+
+
+操作则在 `src/js/conf/home/pages/dag/_source/dag.js` => `toolbarEvent` 事件中处理。
+
+
+##### 3.增加一个路由页面
+
+(1) 首先在路由管理增加一个路由地址`src/js/conf/home/router/index.js`
+```
+{
+  path: '/test',  // 路由地址 
+  name: 'test',  // 别名
+  component: resolve => require(['../pages/test/index'], resolve),  // 路由对应组件入口文件
+  meta: {
+    title: `${i18n.$t('test')} - DolphinScheduler`  // title 显示
+  }
+},
+```
+
+(2) 在`src/js/conf/home/pages` 建一个 `test` 文件夹,在文件夹里建一个`index.vue`入口文件。
+
+    这样就可以直接访问 `http://localhost:8888/#/test`
+
+
+##### 4.增加预置邮箱
+
+找到`src/lib/localData/email.js`启动和定时邮箱地址输入可以自动下拉匹配。
+```
+export default ["test@analysys.com.cn","test1@analysys.com.cn","test3@analysys.com.cn"]
+```
+
+##### 5.权限管理及disabled状态处理
+
+权限根据后端接口`getUserInfo`接口给出`userType: "ADMIN_USER/GENERAL_USER"`权限控制页面操作按钮是否`disabled`
+
+具体操作:`src/js/module/permissions/index.js`
+
+disabled处理:`src/js/module/mixin/disabledState.js`
+
diff --git a/site_config/development.js b/site_config/development.js
index c74af21..4a3e6bc 100644
--- a/site_config/development.js
+++ b/site_config/development.js
@@ -21,6 +21,10 @@ export default {
             link: '/en-us/development/frontend-development.html',
           },
           {
+            title: 'Dev Frontend Development',
+            link: '/en-us/development/dev-frontend-development.html',
+          },
+          {
             title: 'plugin-development',
             link: '/en-us/development/plugin-development.html',
           },
@@ -55,6 +59,10 @@ export default {
             link: '/zh-cn/development/frontend-development.html',
           },
           {
+            title: 'dev前端开发',
+            link: '/zh-cn/development/dev-frontend-development.html',
+          },
+          {
             title: '插件开发',
             link: '/zh-cn/development/plugin-development.html',
           },