前端开发文档

技术选型

  1. Vue mvvm框架
  2. Es6 ECMAScript 6.0
  3. Element-ui
  4. form-create JSON表单生成器
  5. D3 可视化库图表库
  6. Jsplumb 连线插件库
  7. 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文件内容如下:

  1. # 代理的接口地址(自行修改)
  2. API_BASE = http://192.168.xx.xx:12345(注意:API接口一定要写成IP地址的形式,不要写成http://localhost:12345)
  3. # 如果您需要用ip访问项目可以把 "#" 号去掉(例)
  4. #DEV_HOST = 192.168.xx.xx
!!!这里特别注意 项目如果在拉取依赖包的过程中报 “ node-sass error “ 错误,请在执行完后再次执行以下命令
  1. npm install node-sass --unsafe-perm //单独安装node-sass依赖

前端项目发布

  • 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

  1. [root@localhost dolphinscheduler-ui]# pm2 start npm -- run dev
  2. [PM2] Applying action restartProcessId on app [npm](ids: 0)
  3. [PM2] [npm](0)
  4. [PM2] Process successfully started
  5. ┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────┬──────────┐
  6. App name id version mode pid status restart uptime cpu mem user watching
  7. ├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────┼──────────┤
  8. npm 0 N/A fork 6168 online 31 0s 0% 5.6 MB root disabled
  9. └──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────┴──────────┘
  10. 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文件

  1. > 项目采用vue单页面应用(SPA)开发
  2. - 所有页面入口文件在 `src/js/conf/${对应页面文件名 => home}` `index.js` 入口文件
  3. - 对应的sass文件则在 `src/sass/conf/${对应页面文件名 => home}/index.scss`
  4. - 对应的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

  1. | 项目首页
  2. | 工作流关系
  3. | 工作流
  4. - 工作流定义
  5. - 工作流实例
  6. - 任务实例

资源中心 => http://localhost:8888/#/resource/file

  1. | 文件管理
  2. | UDF管理
  3. - 资源管理
  4. - 函数管理

数据源中心 => http://localhost:8888/#/datasource/list

监控中心 => http://localhost:8888/#/monitor/servers/master

  1. | 服务管理
  2. - Master
  3. - Worker
  4. - Zookeeper
  5. - DB
  6. | 统计管理
  7. - Statistics

安全中心 => http://localhost:8888/#/security/tenant

  1. | 租户管理
  2. | 用户管理
  3. | 告警组管理
  4. | 告警实例管理
  5. | Worker分组管理
  6. | Yarn队列管理
  7. | 令牌管理

用户中心 => http://localhost:8888/#/user/account

路由和状态管理

项目 src/js/conf/home 下分为

pages => 路由指向页面目录

  1. 路由地址对应的页面文件

router => 路由管理

  1. vue的路由器,在每个页面的入口文件index.js 都会注册进来 具体操作:https://router.vuejs.org/zh/

store => 状态管理

  1. 每个路由对应的页面都有一个状态管理的文件 分为:
  2. actions => mapActions => 详情:https://vuex.vuejs.org/zh/guide/actions.html
  3. getters => mapGetters => 详情:https://vuex.vuejs.org/zh/guide/getters.html
  4. index => 入口
  5. mutations => mapMutations => 详情:https://vuex.vuejs.org/zh/guide/mutations.html
  6. state => mapState => 详情:https://vuex.vuejs.org/zh/guide/state.html
  7. 具体操作:https://vuex.vuejs.org/zh/

菜单添加

公共的菜单只需要在 /src/js/conf/home/router/index.js 中添加就可以了,如:首页

  1. {
  2. path: '/home',
  3. name: 'home',
  4. component: resolve => require(['../pages/home/index'], resolve),
  5. meta: {
  6. title: `${i18n.$t('Home')} - DolphinScheduler`,
  7. refresh_in_switched_tab: true
  8. }
  9. }

异常处理

/src/js/module/io/index.js 文件中对所有的 request请求进行拦截, 通过response 拦截器对接口返回的状态码进行分析与异常处理,代码如下

  1. import io from '@/module/axios/index'
  2. import cookies from 'js-cookie'
  3. const apiPrefix = '/dolphinscheduler'
  4. const reSlashPrefix = /^\/+/
  5. const resolveURL = (url) => {
  6. if (url.indexOf('http') !== -1) {
  7. return url
  8. }
  9. if (url.charAt(0) !== '/') {
  10. return `${apiPrefix}/${url.replace(reSlashPrefix, '')}`
  11. }
  12. return url
  13. }
  14. /**
  15. * Resolve backend api url
  16. */
  17. export { resolveURL }
  18. /**
  19. * Set io default instance resolveUrl globally
  20. */
  21. io.config.resolveURL = resolveURL
  22. io.config.timeout = 0
  23. io.config.maxContentLength = 200000
  24. io.config.validateStatus = function (status) {
  25. if (status === 401 || status === 504) {
  26. window.location.href = `${PUBLIC_PATH}/view/login/index.html`
  27. return
  28. }
  29. return status
  30. }
  31. // io.config.emulateJSON = false
  32. const _propRequest = io.request
  33. // Add a local request interceptor
  34. io.request = (spec) => {
  35. return _propRequest.call(io, spec)
  36. }
  37. // Global response interceptor registion
  38. io.interceptors.response.use(
  39. response => {
  40. return response
  41. }, error => {
  42. // Do something with response error
  43. return Promise.reject(error)
  44. }
  45. )
  46. // Global request interceptor registion
  47. io.interceptors.request.use(
  48. config => {
  49. const sIdCookie = cookies.get('sessionId')
  50. const sessionId = sessionStorage.getItem('sessionId')
  51. const requstUrl = config.url.substring(config.url.lastIndexOf('/') + 1)
  52. if ((!sIdCookie || (sessionId && sessionId !== sIdCookie)) && requstUrl !== 'login') {
  53. window.location.href = `${PUBLIC_PATH}/view/login/index.html`
  54. } else {
  55. const { method } = config
  56. if (method === 'get') {
  57. config.params = Object.assign({}, config.params, {
  58. _t: Math.random()
  59. })
  60. }
  61. config.headers = config.headers || {}
  62. const language = cookies.get('language')
  63. if (language) config.headers.language = language
  64. if (sIdCookie) config.headers.sessionId = sIdCookie
  65. return config
  66. }
  67. }, error => {
  68. // Do something with request error
  69. return Promise.reject(error)
  70. }
  71. )
  72. export default io

规范

Vue规范

1.组件名

组件名为多个单词,并且用连接线(-)连接,避免与 HTML 标签冲突,并且结构更加清晰。

  1. // 正例
  2. export default {
  3. name: 'page-article-item'
  4. }
2.组件文件

src/js/module/components项目内部公共组件书写文件夹名与文件名同名,公共组件内部所拆分的子组件与util工具都放置组件内部 _source文件夹里。

  1. └── components
  2. ├── header
  3. ├── header.vue
  4. └── _source
  5. └── nav.vue
  6. └── util.js
  7. ├── conditions
  8. ├── conditions.vue
  9. └── _source
  10. └── search.vue
  11. └── util.js
3.Prop

定义 Prop 的时候应该始终以驼峰格式(camelCase)命名,在父组件赋值的时候使用连接线(-)。 这里遵循每个语言的特性,因为在 HTML 标记中对大小写是不敏感的,使用连接线更加友好;而在 JavaScript 中更自然的是驼峰命名。

  1. // Vue
  2. props: {
  3. articleStatus: Boolean
  4. }
  5. // HTML
  6. <article-item :article-status="true"></article-item>

Prop 的定义应该尽量详细的指定其类型、默认值和验证。

示例:

  1. props: {
  2. attrM: Number,
  3. attrA: {
  4. type: String,
  5. required: true
  6. },
  7. attrZ: {
  8. type: Object,
  9. // 数组/对象的默认值应该由一个工厂函数返回
  10. default: function () {
  11. return {
  12. msg: '成就你我'
  13. }
  14. }
  15. },
  16. attrE: {
  17. type: String,
  18. validator: function (v) {
  19. return !(['success', 'fail'].indexOf(v) === -1)
  20. }
  21. }
  22. }
4.v-for

在执行 v-for 遍历的时候,总是应该带上 key 值使更新 DOM 时渲染效率更高。

  1. <ul>
  2. <li v-for="item in list" :key="item.id">
  3. {{ item.title }}
  4. </li>
  5. </ul>

v-for 应该避免与 v-if 在同一个元素(例如:<li>)上使用,因为 v-for 的优先级比 v-if 更高,为了避免无效计算和渲染,应该尽量将 v-if 放到容器的父元素之上。

  1. <ul v-if="showList">
  2. <li v-for="item in list" :key="item.id">
  3. {{ item.title }}
  4. </li>
  5. </ul>
5.v-if / v-else-if / v-else

若同一组 v-if 逻辑控制中的元素逻辑相同,Vue 为了更高效的元素切换,会复用相同的部分,例如:value。为了避免复用带来的不合理效果,应该在同种元素上加上 key 做标识。

  1. <div v-if="hasData" key="mazey-data">
  2. <span>{{ mazeyData }}</span>
  3. </div>
  4. <div v-else key="mazey-none">
  5. <span>无数据</span>
  6. </div>
6.指令缩写

为了统一规范始终使用指令缩写,使用v-bindv-on并没有什么不好,这里仅为了统一规范。

  1. <input :value="mazeyUser" @click="verifyUser">
7.单文件组件的顶级元素顺序

样式后续都是打包在一个文件里,所有在单个vue文件中定义的样式,在别的文件里同类名的样式也是会生效的所有在创建一个组件前都会有个顶级类名 注意:项目内已经增加了sass插件,单个vue文件里可以直接书写sass语法 为了统一和便于阅读,应该按 <template><script><style>的顺序放置。

  1. <template>
  2. <div class="test-model">
  3. test
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: "test",
  9. data() {
  10. return {}
  11. },
  12. props: {},
  13. methods: {},
  14. watch: {},
  15. beforeCreate() {
  16. },
  17. created() {
  18. },
  19. beforeMount() {
  20. },
  21. mounted() {
  22. },
  23. beforeUpdate() {
  24. },
  25. updated() {
  26. },
  27. beforeDestroy() {
  28. },
  29. destroyed() {
  30. },
  31. computed: {},
  32. components: {},
  33. }
  34. </script>
  35. <style lang="scss" rel="stylesheet/scss">
  36. .test-model {
  37. }
  38. </style>

JavaScript规范

1.var / let / const

建议不再使用 var,而使用 let / const,优先使用 const。任何一个变量的使用都要提前申明,除了 function 定义的函数可以随便放在任何位置。

2.引号
  1. const foo = '后除'
  2. const bar = `${foo},前端工程师`
3.函数

匿名函数统一使用箭头函数,多个参数/返回值时优先使用对象的结构赋值。

  1. function getPersonInfo ({name, sex}) {
  2. // ...
  3. return {name, gender}
  4. }

函数名统一使用驼峰命名,以大写字母开头申明的都是构造函数,使用小写字母开头的都是普通函数,也不该使用 new 操作符去操作普通函数。

4.对象
  1. const foo = {a: 0, b: 1}
  2. const bar = JSON.parse(JSON.stringify(foo))
  3. const foo = {a: 0, b: 1}
  4. const bar = {...foo, c: 2}
  5. const foo = {a: 3}
  6. Object.assign(foo, {b: 4})
  7. const myMap = new Map([])
  8. for (let [key, value] of myMap.entries()) {
  9. // ...
  10. }
5.模块

统一使用 import / export 的方式管理项目的模块。

  1. // lib.js
  2. export default {}
  3. // app.js
  4. import app from './lib'

import 统一放在文件顶部。

如果模块只有一个输出值,使用 export default,否则不用。

HTML / CSS

1.标签

在引用外部 CSS 或 JavaScript 时不写 type 属性。HTML5 默认 type 为 text/csstext/javascript 属性,所以没必要指定。

  1. <link rel="stylesheet" href="//www.test.com/css/test.css">
  2. <script src="//www.test.com/js/test.js"></script>
2.命名

Class 和 ID 的命名应该语义化,通过看名字就知道是干嘛的;多个单词用连接线 - 连接。

  1. // 正例
  2. .test-header{
  3. font-size: 20px;
  4. }
3.属性缩写

CSS 属性尽量使用缩写,提高代码的效率和方便理解。

  1. // 反例
  2. border-width: 1px;
  3. border-style: solid;
  4. border-color: #ccc;
  5. // 正例
  6. border: 1px solid #ccc;
4.文档类型

应该总是使用 HTML5 标准。

  1. <!DOCTYPE html>
5.注释

应该给一个模块文件写一个区块注释。

  1. /**
  2. * @module mazey/api
  3. * @author Mazey <mazey@mazey.net>
  4. * @description test.
  5. * */

接口

所有的接口都以 Promise 形式返回

注意非0都为错误走catch

  1. const test = () => {
  2. return new Promise((resolve, reject) => {
  3. resolve({
  4. a:1
  5. })
  6. })
  7. }
  8. // 调用
  9. test.then(res => {
  10. console.log(res)
  11. // {a:1}
  12. })

正常返回

  1. {
  2. code:0,
  3. data:{}
  4. msg:'成功'
  5. }

错误返回

  1. {
  2. code:10000,
  3. data:{}
  4. msg:'失败'
  5. }

接口如果是post请求,Content-Type默认为application/x-www-form-urlencoded;如果Content-Type改成application/json, 接口传参需要改成下面的方式

  1. io.post('url', payload, null, null, { emulateJSON: false } res => {
  2. resolve(res)
  3. }).catch(e => {
  4. reject(e)
  5. })
相关接口路径

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 对象,往里增加

  1. 'DEPENDENT': { // 后台定义节点类型英文名称用作key值
  2. desc: 'DEPENDENT', // tooltip desc
  3. color: '#2FBFD8' // 代表的颜色主要用于 tree和gantt 两张图
  4. }

(3) 在 src/js/conf/home/pages/dag/_source/formModel/tasks 增加一个 ${节点类型(小写)}.vue 文件,跟当前节点相关的组件内容都在这里写。 属于节点组件内的必须拥有一个函数 _verification() 验证成功后讲当前组件的相关数据往父组件抛。

  1. /**
  2. * 验证
  3. */
  4. _verification () {
  5. // datasource 子组件验证
  6. if (!this.$refs.refDs._verifDatasource()) {
  7. return false
  8. }
  9. // 验证函数
  10. if (!this.method) {
  11. this.$message.warning(`${i18n.$t('请输入方法')}`)
  12. return false
  13. }
  14. // localParams 子组件验证
  15. if (!this.$refs.refLocalParams._verifProp()) {
  16. return false
  17. }
  18. // 存储
  19. this.$emit('on-params', {
  20. type: this.type,
  21. datasource: this.datasource,
  22. method: this.method,
  23. localParams: this.localParams
  24. })
  25. return true
  26. }

(4) 节点组件内部所用到公共的组件都在_source下,commcon.js用与配置公共数据

2.增加状态类型

(1) 找到 src/js/conf/home/pages/dag/_source/config.js 里的 tasksState 对象,往里增加

  1. 'WAITTING_DEPEND': { //后端定义状态类型 前端用作key值
  2. id: 11, // 前端定义id 后续用作排序
  3. desc: `${i18n.$t('等待依赖')}`, // tooltip desc
  4. color: '#5101be', // 代表的颜色主要用于 tree和gantt 两张图
  5. icoUnicode: '&#xe68c;', // 字体图标
  6. isSpin: false // 是否旋转(需代码判断)
  7. }
3.增加操作栏工具

(1) 找到 src/js/conf/home/pages/dag/_source/config.js 里的 toolOper 对象,往里增加

  1. {
  2. code: 'pointer', // 工具标识
  3. icon: '&#xe781;', // 工具图标
  4. disable: disable, // 是否禁用
  5. desc: `${i18n.$t('拖动节点和选中项')}` // tooltip desc
  6. }

(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

  1. {
  2. path: '/test', // 路由地址
  3. name: 'test', // 别名
  4. component: resolve => require(['../pages/test/index'], resolve), // 路由对应组件入口文件
  5. meta: {
  6. title: `${i18n.$t('test')} - DolphinScheduler` // title 显示
  7. }
  8. },

(2) 在src/js/conf/home/pages 建一个 test 文件夹,在文件夹里建一个index.vue入口文件。

  1. 这样就可以直接访问 `http://localhost:8888/#/test`
4.增加预置邮箱

找到src/lib/localData/email.js启动和定时邮箱地址输入可以自动下拉匹配。

  1. 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