2
头图

About gh-framework

gh-framework aims to solve vue2 environment (the idea can be used in any framework project, not limited to vue2). It will encapsulate the tool libraries and configuration files commonly used in the vue axios , constants, directives, services (data request layer), config (configuration file), mixin (global mixin), utils (tool set), context (context). This program has been applied to 5+ projects, including a large-scale front-end project.

github address (sample code): https://github.com/cong1223/gh-framework

characteristic

  1. Highly encapsulated: Highly encapsulate common tools and configurations of the project, without writing repetitive code.
  2. Fast portability: Package once, copy and paste other types of items, quickly gather duplicate codes, and modify a small part according to business requirements. If the back-end returns the data format is consistent, then services can be applied without modification.
  3. Non-destructive: If you want to try this set of solutions for a new project, you can transplant the sub-scheme, and it is not destructive to your original project and can be compatible at the same time.
  4. Rapid development experience: once package, permanent comfort, bid farewell to cumbersome import/export, this . Everything.

For people

  1. Junior and intermediate-level front-end programmers who have a strong learning interest in front-end engineering;
  2. Programmers who play the role of captain in front-end projects;
  3. Fast delivery entrepreneurial programmer;
  4. Part-time order programmer;

Project structure introduction

Ignore the basic project structure file of the vue project

|-- 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 folder is gh-framework . The public code of the project is highly gathered, and the use can be quickly transplanted. After a project is completed, you can immediately copy the framework folder to another new project. The only difference is the business code part. This folder can continue to expand other general logic according to your own business needs. For packaging methods, refer to directives , utils etc.

directives

The global instruction package set folder, unified management of common instructions in the project, and global installation.

The folder project structure is as follows:

|-- directives
    |-- debounce.js // 防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次
    |-- loadmore.js // element-ui下拉框下拉更多
    |-- draggable.js // 实现一个拖拽指令,可在页面可视区域任意拖拽元素
    |-- copy.js // 复制粘贴指令
    |-- emoji.js // 不能输入表情和特殊字符,只能输入数字或字母等
    |-- lazyload.js // 实现一个图片懒加载指令,只加载浏览器可见区域的图片
    |-- longpress.js // 实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件
    |-- permission.js // 权限指令,对需要权限判断的 Dom 进行显示隐藏
    |-- watermark.js // 给整个页面添加背景水印
    |-- // 更多其他指令...
    |-- index.js // 统一出口
For more general instructions above, please view and obtain in the sample project

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

Global mix-in, some global methods and attributes can be defined in this file, such as routing jump, after encapsulation, it can handle path and parameter issues more flexibly, such as global toast, secondary pop-up window and so on.

The folder project structure is as follows:

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

This folder uniformly collects all the "plug-ins" managed by the framework, and the set of tools we have packaged is called the plug-in set of the framework.

The folder project structure is as follows:

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

Packaged public toolset

The folder project structure is as follows:

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

Global public ui components

The folder project structure is as follows:

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

The mapping configuration file of all plug-in file names under the framework, the mapping name configured here can be accessed using this , bid farewell to the cumbersome 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

Package the install method of the framework for vue to install it in Vue.prototype , and mount the file to 060f53cdd2211a, so that the public plug-ins under the framework can this

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

services , const , config separately src also depends on the actual situation. Considering that these three folders are closely related to the actual business, they are proposed separately, as long as the actual path is configured under the framework and it is ok.

Front-end people must abandon bad habits, a large number of operations model vue file, and even a template to render related data, which will make your project more and more complex and unmaintainable, and will generate more Many redundant codes.

Four points of advice for the vue project:

  1. Try not to write expressions in the template, use computed cleverly
  2. View and model are separated, Vue files should try to write ui related code, and data processing (data that cannot be processed on the back end needs to be processed by the front end) is processed in the service
  3. Constants are routine, try not to show magic variables, which will make the project more and more unmaintainable
  4. Clear folder hierarchy

The folder project structure is as follows:

|-- 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);
      });
    });
  }

For example, write business 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

The directory where the constant definition file is located

The folder project structure is as follows:

|-- 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;
  }
}

Example of writing constant class: 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

Project configuration file directory

The folder project structure is as follows:

|-- 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 }

installation

Open the vue entry file, for example main.js :

import GhFramework from './framework';

Vue.use(Framework);

In this way, all the plug-ins configured above can be fully applied.

application

For example, after finishing an overall framework of the framework and the independent configuration of each plug-in, the following code snippets show how to apply these plug-ins in the project.

  • Custom directives (directives)
<el-button type="primary" v-copy="content">点击复制</el-button>
  • Application of some methods in global mixing
// 跳转首页
this.goto('/');
// 返回上一页
this.goBack();
// showToast
this.$toast("success", "调用成功!");
// $confirmBox
this.$confirmBox("取消下载", `确定要取消该下载任务吗?`, (done, instance) => {
  setTimeout(() => {
    done();
    instance.confirmButtonLoading = false;
  }, 1500);
});
  • Use the global public toolset
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}
}
  • Use global ui components
<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);
    }
  }
}
  • Use global configuration files
// 获取uri配置文件中的BASE_URL
const baseUrl = this.config.uri.BASE_URL;
  • Use global constants
// 获取普通人员角色
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") // "下载"
  • Use services for background data requests
this.service.enterprise.getEnterpriseUserList(
  'xxxxx',
  this.keywords,
  this.page,
  this.pageSize
).then(res => {
  console.log(res)
  // TODO: 处理返回数据
}).catch(e =>  {
  console.log(e)
  // TODO: 处理错误返回数据
})
  • Use local cache
// 缓存用户信息
// 这里的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", "暂无用户缓存信息,请先缓存");
}

to sum up

gh-framework is an engineering thinking born for the purpose of encapsulation and rapid transplantation. It can help developers and small teams to quickly build projects, copy projects, and transplant core code of projects. It can be combined with webpack, gitLab and other tools to realize a complete front-end project that integrates coding, packaging, and deployment.


MangoGoing
780 声望1.2k 粉丝

开源项目:详见个人详情