乐趣区

关于前端工程化:全网独发立马就能复制粘贴的轻量级前端工程化解决方案

对于gh-framework

gh-framework旨在解决 vue2 环境下 (思维可用于任何框架我的项目,不局限于 vue2) 的前端工程化问题,它将封装 vue 我的项目中罕用的工具库和配置文件并将其可移植化,例如axiosconstants(常量)directives(指令)services(数据申请层)config(配置文件)mixin(全局混入)utils(工具集)context(上下文)。本计划自己已在 5 + 我的项目上利用,包含一个大型前端我的项目。

github 地址(示例代码):https://github.com/cong1223/gh-framework

个性

  1. 高度封装:高度封装我的项目常用工具和配置,不写反复代码。
  2. 疾速移植化:封装一次,其余类型我的项目可复制粘贴,疾速聚拢反复代码,依据业务需要小局部批改即可,如果后端返回数据格式统一,那么 services 都不须要批改即可利用。
  3. 不具备破坏性:新我的项目如果想尝试这套解决方案,那么能够移植次计划,并且对你原先的我的项目不具备破坏性,能够同时兼容。
  4. 疾速开发体验:一次封装,永恒劳碌,辞别繁琐的导入 / 导出,this. 万物。

适用人群

  1. 对前端工程化具备强烈学习趣味的初中级前端程序员;
  2. 前端我的项目中表演 captain 角色的程序员;
  3. 疾速交付型守业程序员;
  4. 兼职接单程序员;

我的项目构造介绍

疏忽了 vue 我的项目根本我的项目构造文件

|-- node_mudules
|-- public
|-- src
    |-- assets // 动态资源文件夹
    |-- config // 配置文件文件夹
    |-- const // 常量配置文件夹
    |-- framework // gh-framework 文件夹
        |-- directives // 全局指令文件夹
        |-- mixin // 全局 mixin 混入文件夹
        |-- plugins // framework 外围工具集的配置入口
        |-- utils // 全局工具集文件夹
        |-- ui // 全局通用 ui 组件文件夹
        |-- config.js // 文件名映射配置文件(重要)
        |-- index.js // 导出为 vue 能装置的 framework 插件(封装为 install 函数)|-- services // 数据申请层文件夹
|-- .browserslistrc
|-- .eslintrc.js
|--    .gitignore
|-- babel.config.js
|-- package.json
|-- README.md
|-- yarn.lock

framework

framework文件夹就是 gh-framework 的核心思想所在,高度聚拢我的项目公共代码,应用变得能够疾速移植化,一个我的项目做完了,能够立马复制 framework 文件夹到另外一个新的我的项目,惟一不同的就是业务代码局部。此文件夹能够依据本人的业务需要持续扩大其余通用逻辑,封装办法参考 directivesutils 等。

directives

全局指令封装集文件夹,将我的项目中罕用指令对立治理,全局装置。

文件夹我的项目构造如下:

|-- directives
    |-- debounce.js // 避免按钮在短时间内被屡次点击,应用防抖函数限度规定工夫内只能点击一次
    |-- loadmore.js // element-ui 下拉框下拉更多
    |-- draggable.js // 实现一个拖拽指令,可在页面可视区域任意拖拽元素
    |-- copy.js // 复制粘贴指令
    |-- emoji.js // 不能输出表情和特殊字符,只能输出数字或字母等
    |-- lazyload.js // 实现一个图片懒加载指令,只加载浏览器可见区域的图片
    |-- longpress.js // 实现长按,用户须要按下并按住按钮几秒钟,触发相应的事件
    |-- permission.js // 权限指令,对须要权限判断的 Dom 进行显示暗藏
    |-- watermark.js // 给整个页面增加背景水印
    |-- // 更多其余指令...
    |-- index.js // 对立进口

以上更多通用指令请在示例我的项目中查看获取

debounce.js

// 避免按钮在短时间内被屡次点击,应用防抖函数限度规定工夫内只能点击一次
const debounce = {inserted: function (el, binding) {
    let timer
    el.addEventListener('keyup', () => {if (timer) {clearTimeout(timer)
      }
      timer = setTimeout(() => {binding.value()
      }, 1000)
    })
  }
}

export default debounce

loadmore.js

// element-ui 下拉框下拉更多
const loadmore = {bind (el, binding) {
    // 获取 element-ui 定义好的 scroll 盒子
    const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
    SELECTWRAP_DOM && SELECTWRAP_DOM.addEventListener('scroll', function () {
      const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight
      if (CONDITION) {binding.value()
      }
    })
  }
}

export default loadmore

index.js

import loadmore from './loadmore'
import debounce from './debounce'
// 自定义指令
const directives = {
  loadmore,
  debounce
}

export default {install(Vue) {Object.keys(directives).forEach((key) => {Vue.directive(key, directives[key])
    })
  }
}

mixin

全局混入,一些全局的办法和属性能够定义在此文件中,比方路由跳转,封装后能够更灵便的解决门路和参数问题,比方全局的 toast、二次弹窗等等。

文件夹我的项目构造如下:

|-- mixin
    |-- index.js

index.js

export default {data() {return {};
  },
  computed: {},
  created() {},
  mounted() {},
  methods: {
    // 二次弹窗, 命令形式调用
    $confirmBox(title, message, handleConfirm, handleCancel, options) {
      const {
        confirmButtonText = "确认",
        showCancelButton = true,
        // eslint-disable-next-line no-unused-vars
        confirmButtonType = "primary"
      } = options || {};
      return this.$messageBox({
        title,
        message,
        showCancelButton, // 依据业务需要扩大
        confirmButtonText, // 依据业务需要扩大
        customClass: "zk-confirm-box",
        closeOnClickModal: false,
        confirmValidate: (action, component, done, instance) => {
          // component : 自定义传入的 component 的组件实例
          if (action === "cancel") {if (handleCancel) {handleCancel();
            }
            return done();} else {instance.confirmButtonLoading = true;}
          handleConfirm(done, instance);
        }
      }).catch(() => {});
    },
    // 可扩大自定义弹窗内容, 给 components 传自定义组件即可
    $messageBox(
      {
        component = null,
        componentName = "",
        confirmData = {},
        confirmValidate = () => {},
        ...rest
      }
    ) {
      const h = this.$createElement;
      return new Promise((resolve, reject) => {
        this.$msgbox({
          message: h(component, {props: { confirmData}
          }),
          beforeClose: (action, instance, done) => {
            const cptInstance = instance.$children.find(child => {return child.$options.name === componentName;});
            confirmValidate(action, cptInstance, done, instance);
          },
          ...rest
        })
          .then(resolve)
          .catch(reject);
      });
    },
    // 批改告诉的默认工夫
    $toast(type, msg, duration, callback) {this.$message({ type: type, message: msg, duration: duration || 1500, onClose: callback});
    },
    getParams(type = "params", key) {const params = this.$route[type];
      if (Object.keys(params).length) {if (key) {return params[key];
        } else {return params;}
      } else {return null;}
    },
    goBack() {this.$router.go(-1);
    },
    goto(name, params = {}, isReplace) {params = params || {};
      return new Promise((resolve) => {if (name) {if (name.indexOf("/") >= 0) {if (isReplace) {
              this.$router.replace({path: name, params}, () => {resolve && resolve();
              });
            } else {
              this.$router.push({path: name, params}, () => {resolve && resolve();
              });
            }
          } else {if (isReplace) {
              this.$router.replace({name, params}, () => {resolve && resolve();
              });
            } else {
              this.$router.push({name, params}, () => {resolve && resolve();
              });
            }
          }
        }
      });
    }
  }
};

plugins

此文件夹对立收揽了所有 framework 治理下的 ” 插件 ”,那些咱们封装好了的工具集咱们称为 framework 的插件集。

文件夹我的项目构造如下:

|-- plugins
    |-- axios.js // 封装后的 axios 插件
    |-- config.js // 配置文件插件
    |-- const.js // 常量定义插件
    |-- request.js // axios 的下层申请封装插件
    |-- service.js // 接口申请,数据处理层插件
    |-- storage.js // localStorage 插件
    |-- utils.js // 自定义封装工具集插件

axios.js

// 依据本人理论业务需要配置,此配置文件仅供参考

import axios from 'axios'
import Cookies from 'js-cookie'
import {
  Message,
  MessageBox
} from 'element-ui'

const service = axios.create({
  timeout: 6000,
  headers: {
    'X-User-Agent': 'boss',
    'X-Ent': '0'
  }
})

service.interceptors.request.use(config => {if (!config.url.includes('hzzk-portal/sys/login') && Cookies.get('pro__Access-Token')) {config.headers['X-Access-Token'] = Cookies.get('pro__Access-Token');
  }
  return config;
}, err => {return Promise.reject(err);
})

// http response 拦截器
service.interceptors.response.use(
  response => {const { data} = response;
    if (data.code === 200) {return Promise.resolve(data);
    } else if (/500/.test(data.code)) {if (data.code === 50002) {Cookies.remove('pro__Access-Token');
        Message({
          message: data.message,
          type: 'error',
          duration: 2 * 1000,
          onClose: () => {location.replace('/');
          }
        })
      } else {
        Message({
          message: data.message,
          type: 'error',
          duration: 2 * 1000
        })
        return Promise.reject(data)
      }
    } else if (/400/.test(data.code)) {if (data.code === 40009) {
        // 权限有余
        Message({
          message: data.message,
          type: 'error',
          duration: 2 * 1000
        })
      } else {
        Message({
          message: data.message,
          type: 'error',
          duration: 2 * 1000
        })
        return Promise.reject(data)
      }
    } else if (/300/.test(data.code)) {
      Message({
        message: data.message,
        type: 'info',
        duration: 2 * 1000
      })
      return Promise.reject(data)
    } else {return Promise.reject(new Error(data.message || 'Error'))
    }
  },
  error => {
    // 判断申请异样信息中是否含有超时 timeout 字符串
    if (error.message.includes('timeout')) {
      Message({
        message: '申请超时',
        type: 'error'
      })
    }
    // token 生效重定向至登陆页
    if (error.response.data.status === 500 && error.response.data.message === 'Token 生效,请从新登录') {if (error.response.data.message === "Token 生效,请从新登录") {
        MessageBox.alert('Token 生效,请从新登录', '提醒', {
          confirmButtonText: '从新登录',
          callback: () => {Cookies.remove('pro__Access-Token');
            location.replace('/')
          }
        });
      }
    } else if (error.response.status === 500) {
      // 间接捕捉 http 申请的错误码,而不是后端的返回体里的错误码
      Message({
        message: '服务器异样',
        type: 'error',
        duration: 2 * 1000
      })
    } else {
      Message({
        message: error.message,
        type: 'error',
        duration: 2 * 1000
      })
    }
    return Promise.reject(error)
  },
)

export default service

config.js

import config from '../config'

let constant = {};
for (let i in config.config) {let file = config.config[i];
  constant[i] = require('../../config/' + file).default; // 具体门路依据你理论我的项目中 config 所在门路配置,这里配置实用于我以后我的项目所配置的文件夹门路
}
export default constant;

const.js

import config from '../config'

const consts = {};

for (const i in config.const) {const fileName = config.const[i];
  consts[i] = require('../../const/' + fileName).default; // 具体门路依据你理论我的项目中 const 所在门路配置
}
export default consts;

context.js

import storage from './storage'

export default {
  // 获取用户
  user() {return storage.getItem('userInfo') || {}},
  // 设置用户
  setUser(user) {storage.setItem('userInfo', user)
  },
  curEnterpriseId() {return this.user().curEnterpriseId;
  }
}

request.js

// url 配置依据本人理论我的项目进行配置

import config from './config';
import axios from './axios';
const URI = config.uri;
export default {
  // 污浊的的 get
  getRequest(url, params = {}, base = 'BASE_URL') {return new Promise((resolve, reject) => {axios.get(URI[base] + url, {params: params}).then(res => {resolve(res);
      }).catch(err => {reject(err)
      })
    })
  },
  postRequest(url, params = {}, base = 'BASE_URL') {return new Promise((resolve, reject) => {axios.post(URI[base] + url, params)
        .then(res => {resolve(res);
        })
        .catch(err => {reject(err)
        })
    });
  },
  arcApi(url, params = {}, method = 'get', base = 'HZZK_ARC_API') {if (method === 'post') {return this.postRequest(url, params, base);
    } else {return this.getRequest(url, params, base);
    }
  },
  bossApi(url, params = {}, method = 'get', base = 'BASE_URL') {if (method === 'post') {return this.postRequest('/boss' + url, params, base);
    } else {return this.getRequest('/boss' + url, params, base);
    }
  },
  sysApi(url, params = {}, method = 'get', base = 'BASE_URL') {if (method === 'post') {return this.postRequest('/sys' + url, params, base);
    } else {return this.getRequest('/sys' + url, params, base);
    }
  }
}

services.js

import config from '../config'

const service = {};

for (const i in config.service) {const fileName = config.service[i];
  Object.defineProperty(service, i, {get() {return Reflect.construct(require('../../services/' + fileName).default, []); // 实例化类
    }
  });
}
export default service;

storage.js

export default {getItem(k) {const jsonStr = window.localStorage.getItem(k.toString());
    return jsonStr ? JSON.parse(jsonStr) : null;
  },
  setItem(k, value) {value = JSON.stringify(value);
    try {window.localStorage.setItem(k, value);
    } catch (e) {this.removeItem(k);
    }
  },
  removeItem(k) {window.localStorage.removeItem(k);
  },
  clear() {window.localStorage.clear();
  },
  key(index) {return window.localStorage.key(index);
  },
  getItemByIndex(index) {
    const item = {
      keyName: '',
      keyValue: ''
    };
    item.keyName = this.key(index);
    item.keyValue = this.getItem(item.keyName);
    return item;
  }
}

utils.js

import config from '../config'

const utils = {};

for (const i in config.utils) {const fileName = config.utils[i];
  utils[i] = require('../utils/' + fileName).default; // 具体门路依据你理论我的项目中 const 所在门路配置
}
export default utils;

utils

封装的公共工具集

文件夹我的项目构造如下:

|-- utils
    |-- array.js
    |-- index.js
    |-- // 更多自定义封装的工具...

array.js

export default {
  /**
   * 依据数组中对象的某个属性值进行去重
   * @param arr: 须要去重的数组
   * @param key: 依据对象中的哪个属性进行去重
   * @returns {*}
   */
  unique(arr, key) {const res = new Map();
    return arr.filter((a) => !res.has(a[key]) && res.set(a[key], 1))
  }
  // ===== 更多工具函数 =====
}

index.js

import array from './array'

export {
  array,
  // more util func
}

ui

全局专用 ui 组件

文件夹我的项目构造如下:

|-- ui
    |-- components // 搁置组件文件夹
        |-- scroll-view // 组件名称
            |-- index.vue // 以后组件入口文件
        |-- index.js // 对立组件进口

scroll-view/index.vue

<!-- 滚动加载组件 -->
<template>
  <div id="scroll-view">
    <slot></slot>
    <p v-if="loading"> 加载中...</p>
    <p v-if="noMore"> 没有更多了 </p>
  </div>
</template>
<script>
export default {
  props: {
    // 列表的总页数
    pages: {type: [String, Number],
      default: 0
    }
  },
  data() {
    return {
      page: 1,
      currLength: 0, // 以后列表长度
      loading: false
    }
  },
  computed: {noMore () {return this.currLength >= this.total;}
  },
  mounted() {const ScrollView = document.querySelector('#scroll-view');
    ScrollView.addEventListener("scroll", (event) => {
      const scrollDistance =
        event.target.scrollHeight -
        event.target.offsetHeight -
        event.target.scrollTop;
      if (this.loading) return;
      if (this.page < this.pages && scrollDistance <= 0) {
        this.loading = true;
        this.$emit('load', ++this.page, () => {this.loading = false;})
      }
    });
  },
  methods: {refresh() {
      this.page = 1;
      this.$emit('load', this.page, () => {this.loading = false;});
    }
  }
}
</script>

<style scoped lang="scss">
 #scroll-view {
   width: 100%;
   height: 100%;
   overflow: auto;
   p {
     line-height: 1.5em;
     font-size: 14px;
     color: #5e6d82;
     text-align: center;
     padding: 16px 0;
   }
   &::-webkit-scrollbar {display:none}
 }
</style>

components/index.js

import ZkScrollView from './zk-scroll-view'
export {
  ZkScrollView,
  // more components
}

config.js

所有 framework 下的插件文件名的映射配置文件,这里配置的映射名能够应用 this 拜访,辞别繁琐的import

export default {
  const: {
    account: 'AccountConstants', // 通过 this.const.account 拜访到 AccountConstants.js 文件
    // more constants file map
  },
  service: {
    user: 'UserService', // 通过 this.service.user 拜访到 UserService.js 文件
    enterprise: 'EnterpriseService', // 通过 this.service.enterprise 拜访到 EnterpriseService.js 文件
    // more service file map
  },
  utils: {
    array: 'array', // 通过 this.service.user 拜访到 UserService.js 文件
    // more utils file map
  },
  config: {
    uri: 'uri',// 通过 this.config.uri 拜访到 config/uri.js 文件
    // more config file map
  }
};

index.js

封装 framework 的 install 办法,以供 vue 在 main.js 中装置它,并且挂载文件到 Vue.prototype,以便全局通过this 拜访到 framework 下的私有插件。

import storage from './plugins/storage.js';
import Const from './plugins/const';
import service from './plugins/service';
import config from './plugins/config';
import mixin from './mixin';
import utils from './plugins/utils';
import context from './plugins/context';
import {ZkScrollView} from './zk-ui/components';
import Directives from './directives'

export default {install(Vue, option) {
    Vue.prototype.storage = storage;
    Vue.prototype.config = config;
    Vue.prototype.context = context;
    Vue.prototype.const = Const;
    Vue.prototype.$const = Const; // 标签中不能应用 const 关键字, 而 js 中拜访的是 this 作用域下的 const 字段
    Vue.prototype.service = service;
    Vue.prototype.utils = utils;
    Vue.mixin(mixin);
    Vue.use(Directives);
    Vue.component('zk-scroll-view', ZkScrollView);
  }
}

services

servicesconstconfig 独自提到 src 下也依据理论状况而定, 思考到这三个文件夹跟理论业务非亲非故,所以独自提出来,只有在 framework 下配置好理论门路关联起来就 ok。

前端人肯定要摒弃的陋习,vue文件中大量操作 model, 甚至在template 中写大量的数据处理逻辑来渲染相干数据,这会让你的我的项目变得越来越简单且不可保护,并且会生成更多的冗余代码。

vue 我的项目四点忠告:

  1. template 中尽量不要写表达式,巧用 computed
  2. view 与 model 拆散,vue 文件尽量写 ui 相干代码,数据处理(后端无奈解决的数据须要前端解决的)在 service 中解决
  3. 常量日常化,尽量不要呈现魔法变量,这会让我的项目变得越来越不可保护
  4. 文件夹层级明显

文件夹我的项目构造如下:

|-- services
    |-- abstract
        |-- BaseService.js // service 文件中不能通过 this 获取 framework 下的插件,这里对立在基类外面援用,使其在 service 文件中也能通过 this 获取插件。|-- UserService.js
    |-- EnterpriseService.js
    |-- // 更多业务 Service

BaseService.js

import request from "../../framework/plugins/request";
import storage from "../../framework/plugins/storage";
import service from "../../framework/plugins/service";
import utils from "../../framework/plugins/utils";
import config from "../../framework/plugins/config";
import Const from "../../framework/plugins/const";
import context from "../../framework/plugins/context";
import dayjs from "dayjs";

export default class BaseService {constructor() {
    this.request = request;
    this.storage = storage;
    this.service = service;
    this.utils = utils;
    this.$dayjs = dayjs;
    this.const = Const;
    this.config = config;
    this.context = context;
  }

  /**
   * 刷选出接口返回的有用数据(data), 异样捕捉解决
   * @param promise
   * @param isTotal, 是否返回全副的 json 数据
   * @returns {Promise<T>}
   */
  output(promise, isTotal = false) {return new Promise((resolve, reject) => {promise.then((resp) => {if (!resp) {reject();
        } else {if (resp.success) {if (resp.code === 200) {
              resp = resp.result;
              if (isTotal) {resolve(resp);
              } else {resolve((resp && resp.list) || (resp && resp.data) || resp);
              }
            } else {reject(resp);
            }
          } else {reject(resp);
          }
        }
      }, (resp) => {reject(resp);
      });
    });
  }

举个例子,编写业务 service

UserService.js

import BaseService from "./abstract/BaseService";

/**
 * 用户治理
 */
export default class UserService extends BaseService {
  // eslint-disable-next-line no-useless-constructor
  constructor() {super()
  }

  /**
   * 登录
   * @returns {Promise<T>}
   */
  login(username, password) {
    const params = {
      username,
      password
    };
    return super.output(this.request.postRequest('/sys/login', params), true);
  }

  /**
   * 通用短信验证码
   * @returns {Promise<T>}
   */
  getSmsCode(params) {return super.output(this.request.postRequest('/sys/sms', params), true);
  }
  /**
   * 重置明码
   * @returns {Promise<T>}
   */
  resetPassword(params) {return super.output(this.request.postRequest('/sys/user/findBackPassword', params), true);
  }
}

EnterpriseService.js

import BaseService from "./abstract/BaseService";

/**
 * 组织架构治理
 */
export default class EnterpriseService extends BaseService {
  // eslint-disable-next-line no-useless-constructor
  constructor() {super()
  }

  /**
   * 获取企业成员
   * @param enterpriseId
   * @param page
   * @param pageSize
   * @param option
   * @param realName: 成员名字, 搜寻用
   * @returns {Promise<T>}
   */
  async getEnterpriseUserList(enterpriseId, realName, page = 1, pageSize = 50) {
    let params = {
      enterpriseId,
      option: 0,
      realName,
      page,
      pageSize
    };
    params = this.utils.obj.deleteEmptyProperty(params);
    const result = await super.output(this.request.getRequest('/structure/queryUser', params), true);
    // 数据逻辑解决在 service 中解决后返回给 View 页面应用
    if (result && result.list && result.list.user) {
      result.list.user.forEach(item => {if (item.createTime) {item.createTime = this.$dayjs(item.createTime).format("YYYY-MM-DD HH:mm:ss")
        }
        item.statusText = this.const.account.UserStatus.getCnameByValue(item.status);
      })
    }
    return result;
  }
}

const

常量定义文件所在目录

文件夹我的项目构造如下:

|-- const
    |-- abstract
        |-- BaseConstant.js    // 封装常量类工具
    |-- TemplateConstants.js
    |-- // more constants files

BaseConstant.js

/**
 * 枚举常量根底类
 * @Author 王聪
 * @cdate 2018-01-20 14:35
 */
export default class BaseConstant {constructor(name, value, cname, desc) {
    this._name = name;
    this._value = value;
    this._cname = cname;
    this._desc = desc;
  }

  name() {return this._name;}

  cname() {return this._cname;}

  value() {return this._value;}

  numberValue() {return parseInt(this.value())
  }

  desc() {return this._desc;}

  /**
   * 取得所有常量的 map
   * @returns {{}}
   */
  static getEnumMap() {const prototypeMap = Object.getOwnPropertyDescriptors(this);
    const enumMap = {};
    for (const prototypeName in prototypeMap) {const val = prototypeMap[prototypeName].value;
      if ((val instanceof BaseConstant) && val._name) {enumMap[val._name] = val;
      }
    }
    return enumMap;
  }

  /**
   * 取得所有常量的数组
   * @returns {Array}
   */
  static getEnumList() {const prototypeMap = Object.getOwnPropertyDescriptors(this);
    const enumList = [];
    for (const prototypeName in prototypeMap) {const val = prototypeMap[prototypeName].value;
      if ((val instanceof BaseConstant) && val._name) {enumList.push(val);
      }
    }
    return enumList;
  }

  static getValueByName(name) {const enumMap = this.getEnumMap();
    const _enum = enumMap[name];
    return _enum ? _enum.value() : null;}

  static getNameByValue(value) {const enumList = this.getEnumList();
    let name = null;
    enumList.find((_enum) => {if (_enum.value() == value) {name = _enum.name();
        return true;
      }
    });
    return name;
  }

  static getCnameByName(name) {const enumMap = this.getEnumMap();
    const _enum = enumMap[name];
    return _enum ? _enum.cname() : null;}

  static getCnameByValue(value) {const enumList = this.getEnumList();
    let cname = null;
    enumList.find((_enum) => {if (_enum.value() === value) {cname = _enum.cname();
        return true;
      }
    });
    return cname;
  }

  static getCnameByBitValue(value) {const enumList = this.getEnumList();
    const cnameArr = [];
    enumList.forEach((_enum) => {if ((value & _enum.value()) !== 0) {cnameArr.push(_enum.cname());
      }
    });
    return cnameArr.join(',');
  }

  /**
   * 给饿了么的 select 组件用
   * name 为组件的 value
   * cname 为组件的 label
   * @returns {*}
   */
  static getSelectOptionsByCnameAndName() {const enumList = this.getEnumList();
    const options = [];
    enumList.forEach((_enum) => {
      options.push({value: _enum.name(),
        label: _enum.cname()});
    });
    return options;
  }

  static getSelectOptionsByCnameAndNameWithAll(option = { label: '全副', value: ''}) {const options = this.getSelectOptionsByCnameAndName()
    option = !option ? {label: '全副', value: ''} : option
    options.unshift(option)
    return options;
  }

  /**
   * 给饿了么的 select 组件用
   * value 为组件的 value
   * cname 为组件的 label
   * @returns {*}
   */
  static getSelectOptionsByCnameAndValue() {const enumList = this.getEnumList();
    const options = [];
    enumList.forEach((_enum) => {
      options.push({value: _enum.value(),
        label: _enum.cname()});
    });
    return options;
  }

  static getSelectOptionsByCnameAndValueWithAll(option = { label: '全副', value: ''}) {const options = this.getSelectOptionsByCnameAndValue()
    option = !option ? {label: '全副', value: ''} : option
    options.unshift(option)
    return options;
  }

  /**
   * 查问按位与的位数的值是否在值内
   * @param value
   * @return boolean
   */
  isAttrIn(value) {if (value == null) {return false;}
    value = parseInt(value)
    return (value & this.numberValue()) == this.numberValue();}

  /**
   * 原属性中增加属性
   * @param attr
   * @return
   */
  addAttr(attr) {return this.numberValue() | (!attr ? 0 : attr);
  }

  /**
   * 原属性中去掉属性
   * @param attr
   * @return
   */
  removeAttr(attr) {if (!attr || attr <= 0) {//logger.debug("原属性值 attribute="+attr+", 不须要 remove");
      return 0;
    }
    return ~this.numberValue() & attr;}

  /**
   * 获取属性地位,相当于 log2 + 1
   * @return
   */
  getAttrPos() {return Math.log(this.numberValue()) / Math.log(2) + 1;
  }
}

举例常量类的编写: TemplateConstants.js

import BaseConstant from './abstract/BaseConstant'
export default class TemplateConstants {
  /**
   * 模板类型
   */
  static TemplateType = class TemplateType extends BaseConstant {static SYS_ENG_PROJECT = new BaseConstant("工程项目", '4', '系统工程模板');
  }

  /**
   * 模板角色权限
   * @type {TemplateConstants.Role}
   */
  static PerRole = class PerRole extends BaseConstant {static MANAGER = new BaseConstant("MANAGER", '1', '职责管理员');
    static NORMAL = new BaseConstant("NORMAL", '2', '职责一般人员');
    static ADMIN = new BaseConstant("ADMIN", '0', '所有');
  }

  /**
   * 文件夹权限
   * @type {TemplateConstants.Role}
   */
  static PerFolder = class PerFolder extends BaseConstant {static READ = new BaseConstant("READ", '0', '只读');
    static DOWNLOAD = new BaseConstant("DOWNLOAD", '2', '下载');
    static WRITE = new BaseConstant("WRITE", '3', '编辑');
    static HEAD = new BaseConstant("HEAD", '4', '负责人');
  }
}

config

我的项目配置文件目录

文件夹我的项目构造如下:

|-- config
    |-- uri.js

uri.js

let BASE_URL = ''let HZZK_FT_API =''
let HZZK_ARC_API = ''let HZZK_OCR_API =''
let PERMISSION_API = ''

switch (process.env.NODE_ENV) {
  case 'development':
    PERMISSION_API = 'https://test.xxx.com'
    BASE_URL = 'https://test.xxx.com/hzzk-portal'
    HZZK_FT_API = 'https://test.xxx.com/hzzk-ft'
    HZZK_ARC_API = 'https://test.xxx.com/hzzk-arc'
    HZZK_OCR_API = 'https://test.xxx.com/hzzk-ocr'
    break
  case 'test':
    PERMISSION_API = 'https://test.xxx.com'
    BASE_URL = 'https://test.xxx.com/hzzk-portal'
    HZZK_FT_API = 'https://test.xxx.com/hzzk-ft'
    HZZK_ARC_API = 'https://test.xxx.com/hzzk-arc'
    HZZK_OCR_API = 'https://test.xxx.com/hzzk-ocr'
    break
  case 'production':
    PERMISSION_API = 'https://hzzk.xxx.com'
    BASE_URL = 'https://hzzk.xxx.com/hzzk-portal'
    HZZK_FT_API = 'https://hzzk.xxx.com/hzzk-ft'
    HZZK_ARC_API = 'https://hzzk.xxx.com/hzzk-arc'
    HZZK_OCR_API = 'https://hzzk.xxx.com/hzzk-ocr'
    break
  case 'local':
    PERMISSION_API = 'http://192.168.0.108:20001'
    BASE_URL = 'http://192.168.0.108:20001/hzzk-portal'
    HZZK_FT_API = 'http://192.168.0.108:20001/hzzk-ft'
    HZZK_ARC_API = 'http://192.168.0.108:20001/hzzk-arc'
    HZZK_OCR_API = 'http://192.168.0.108:20001/hzzk-ocr'
    break
}

export default {BASE_URL, HZZK_FT_API, HZZK_ARC_API, HZZK_OCR_API, PERMISSION_API}

装置

关上 vue 入口文件,例如main.js:

import GhFramework from './framework';

Vue.use(Framework);

这样,就能齐全利用以上配置所有插件。

利用

举例说完了 framework 的一个整体框架和各个插件的独立配置外,以下用代码片段展现在我的项目中如何利用这些插件。

  • 自定义指令(directives)
<el-button type="primary" v-copy="content"> 点击复制 </el-button>
  • 全局混入中局部办法的利用

    // 跳转首页
    this.goto('/');
    // 返回上一页
    this.goBack();
    // showToast
    this.$toast("success", "调用胜利!");
    // $confirmBox
    this.$confirmBox("勾销下载", ` 确定要勾销该下载工作吗?`, (done, instance) => {setTimeout(() => {done();
        instance.confirmButtonLoading = false;
      }, 1500);
    });
  • 应用全局公共工具集

    uniqueArr() {const arr = [{ name: "王", age: 2}, {name: "叶", age: 4}, {name: "张", age: 2}];
      console.log(this.utils.array.unique(arr, "age")) // [{name: "王", age: 2}, {name: "叶", age: 4}]
    },
    deleteEmptyProperty() {
      const params = {
        name: '小聪忙',
        age: 24,
        address: '',
        job: undefined,
        phone: null
      };
      console.log(this.utils.obj.deleteEmptyProperty(params))  //{name: '小聪忙',age: 24}
    }
  • 应用全局 ui 组件

    <template>
      <scroll-view style="height: 300px; background-color: #d0e5f2" :pages="pages"@load="load">
        <div v-for="(num, index) in list" :key="index">
          {{num}}
            </div>
      </scroll-view>
    </template>
    export default {data() {
        return {
          pages: 3, // 总页数
          list: []};
      },
      methods: {load(page = 1, next) {setTimeout(() => {if (page === 1) {this.list = Array.from({ length: 100}, (v, k) => k);
            } else {this.list.push(...Array.from({ length: 100}, (v, k) => k));
            }
            next && next();}, 1000);
        }
      }
    }
  • 应用全局配置文件

    // 获取 uri 配置文件中的 BASE_URL
    const baseUrl = this.config.uri.BASE_URL;
  • 应用全局常量

    // 获取一般人员角色
    this.const.template.PerRole.NORMAL.value() // "2"
    this.const.template.PerRole.NORMAL.name() // "NORMAL"
    // 获取文件夹权限汇合,实用于 element-ui 的 el-select 组件
    const perOptions = this.const.template.PerRole.getSelectOptionsByCnameAndValue(); // [{label: "只读", value: ""0},{...}]
    // 依据 value 获取对应的值的形容信息(例子: 下载权限对应的 value 是 '2')
    this.const.template.PerFolder.getCnameByValue("2") // "下载"
  • 应用 services 进行后盾数据申请

    this.service.enterprise.getEnterpriseUserList(
      'xxxxx',
      this.keywords,
      this.page,
      this.pageSize
    ).then(res => {console.log(res)
      // TODO: 解决返回数据
    }).catch(e =>  {console.log(e)
      // TODO: 处理错误返回数据
    })
  • 应用本地缓存

    // 缓存用户信息
    // 这里的 key 在理论我的项目中也要配置化, 对立治理
    const user = {name: "小聪忙", wechat: "YXC19970131"};
    this.storage.setItem("USER_INFO", user);
    this.$toast("success", "保留胜利");
    
    // 获取用户名
    const user = this.storage.getItem("USER_INFO");
    if (user && user.name) {this.$toast("success", user.name);
    } else {this.$toast("error", "暂无用户缓存信息, 请先缓存");
    }

总结

gh-framework就是以封装、疾速移植为目标而诞生的一种工程化思维,能够达到帮忙开发者和小型团队疾速搭建我的项目,复制我的项目,移植我的项目外围代码的目标。其与 webpack、gitLab 等工具联合,能够实现一个编码、打包、部署联合一体的残缺前端工程。

退出移动版