index.js

/**
 * @file 简化复合状态对于代码复杂性的影响,增强代码可读性
 * @author leenty <leenty@qq.com>
 * @version 1.0.0
 * @example

import ComplexState from 'complexState'

// 获得如下数据及其取值范围
const stateObj = {
  state1: true, // 类型{Boolean} 取值[true|false]
  state2: 0, // 类型{Number} 取值[0|1|2|3]
  state3: 'STATE_1' // 类型{String} 取值['STATE_1'|'STATE_2']
}

// 定义复合状态关系表
const source = [
  ['state1', 'state2', 'state3'],
  [  true  ,    0    , 'STATE_1', 'showLogoA', 'this is infoA'],
  [  true  ,    0    , 'STATE_2', 'showLogoB', 'this is infoB'],
  [  true  ,  [1,2]  , 'STATE_1', 'showLogoC', 'this is infoC']
]
// 复合关系表格式:
//      \ | ...子状态                        | token(复合状态的令牌)| value(复合状态的值)
// -----: | ------------------------------- | :----------------: | :----------------:
//    表头 | 'state1', 'state2', 'state3'    | ----------------- | ------------------
// 复合关系 | true    ,   0     , 'STATE_1'  | 'showLogoA'        | 'this is infoA'
// 复合关系 | true    ,   0     , 'STATE_2'  | 'showLogoB'        | 'this is infoB'
// 复合关系 | true    ,  [1,2]  , 'STATE_3'  | 'showLogoC'        | 'this is infoC'

// 实例化ComplexState对象
const demoResource = new ComplexState(source)

// 得到复合状态值(value)---返回复合状态值
demoResource.getState({state1: true, state2: 0, state3: 'STATE_1'}) // this is infoA
demoResource.getState(true, 0, 'STATE_1') // this is infoA
demoResource.getStateByMap(['state2', 'state3', 'state1'], 0, 'STATE_1', true) // this is infoA

const getResource1 = demoResource.getStateResource()
getResource1({state1: true, state2: 0, state3: 'STATE_1'}) // this is infoA
getResource1(true, 0, 'STATE_1') // this is infoA

const getResource2 = demoResource.getStateResource(['state2', 'state3', 'state1'])
getResource2({state1: true, state2: 0, state3: 'STATE_1'}) // this is infoA
getResource2(0, 'STATE_1', true) // this is infoA

// 验证复合状态值(value)---返回true或false
demoResource.isState('showLogoB', {state1: true, state2: 0, state3: 'STATE_2'}) // true
demoResource.isState('showLogoB', true, 0, 'STATE_2') // true
demoResource.isStateByMap(['state2', 'state3', 'state1'], 'showLogoB', 0, 'STATE_2', true) // true

const showLogoB_1 = demoResource.isStateResource('showLogoB')
showLogoB_1({state1: true, state2: 0, state3: 'STATE_2'}) // true
showLogoB_1(true, 0, 'STATE_2') // true

const showLogoB_2 = demoResource.isStateResource('showLogoB', ['state2', 'state3', 'state1'])
showLogoB_2({state1: true, state2: 0, state3: 'STATE_2'}) // true
showLogoB_2(0, 'STATE_2', true) // true

// 关于复合状态关系表的source[3][1] 为 [1,2]的问题
// 若复合状态关系表里存在数组,将被视为是逻辑:或,即[1,2]为,1或2
demoResource.getState({state1: true, state2: 0, state3: 'STATE_3'}) // undefined
demoResource.getState({state1: true, state2: 1, state3: 'STATE_3'}) // this is infoC
demoResource.getState({state1: true, state2: 2, state3: 'STATE_3'}) // this is infoC

demoResource.isState('showLogoC', {state1: true, state2: 0, state3: 'STATE_3'}) // false
demoResource.isState('showLogoC', {state1: true, state2: 1, state3: 'STATE_3'}) // true
demoResource.isState('showLogoC', {state1: true, state2: 2, state3: 'STATE_3'}) // true
 */



/**
 * @classdesc
 * 复合状态类
 *
 * @class
 */
class complexState {
  /**
   * 复合状态的关系对象注册入口
   * @param  {Array} sources 复合状态关系表
   * @return {Object}        复合关系对象
   */
  constructor(sources) {
    this.sources = sources
  }
  /**
   * 【类方法】错误处理
   * @param  {String} error 错误信息
   * @param  {Boolean} silent 是否屏蔽错误
   * @return {Error}        报错对象
   */
  static _stateMapError(errorMsg, silent) {
    const error = new Error(`[stateMap]: ${errorMsg}`)
    silent || console.error(error)
    return error
  }
  /**
   * 【类方法】解码参数方法,用于序列化不同方式的入参
   * @param  {Array} states   入参集合
   * @param  {Boolean} silent 是否屏蔽错误
   * @return {Object}         查询参数对象
   */
  static _decodeStates(states, sourceHead, silent) {
    const stateObj = {}
    // 如果参数只有一个且为一个对象
    if (states.length === 1 && states[0].constructor === Object) {
      sourceHead.forEach(map => {
        if (states[0].hasOwnProperty(map))
          stateObj[map] = states[0][map]
      })
    // 否则,视为多参数方式传入
    } else {
      sourceHead.forEach((map, index) => {
        if (index < states.length) {
          stateObj[map] = states[index]
        }
      })
    }
    // 如果解析结果为空
    if (JSON.stringify(stateObj) === '{}') {
      return this._stateMapError('无可用入参', silent)
    }
    return stateObj
  }
  /**
   * 【类方法】查询预定义状态
   * @param  {Object} stateObj 查询参数对象
   * @param  {Array} sources   预定义状态集合
   * @param  {Array} map       自定义查询顺序集合
   * @param  {Boolean} silent  是否在控制台抛出错误
   * @return {Array}           返回查询到的整条预定义的状态关系集合
   */
  static _findState(stateObj, sources, map, silent) {
    let resultSources = sources
    map.every(sourceName => {
      let state
      // 如果不查询这个字段,则直接跳过
      if (stateObj.hasOwnProperty(sourceName)) {
        state = stateObj[sourceName]
      } else {
        state = undefined
      }
      resultSources = this._filterSources(sourceName, state, resultSources, silent)
      // 因为数组第一项为source索引,也就是字段,所以,真正的值从第二项开始,故判断数据要大于1才会有有效匹配
      const hasSources = resultSources.length > 1
      // 如果返回false会退出循环,就不会在继续剩余的查询
      return hasSources
    })
    // 删除索引头(也就是数组第一段(map),代表表头的位置)
    resultSources.shift()
    // 如果结果长度为0,则代表查询不到结果
    if (!resultSources.length) {
      this._stateMapError(`查询不到结果${JSON.stringify(stateObj)}`, silent)
      return
    }
    if (resultSources.length > 1) {
      // 获得第一个复合状态的值
      const firstResult = resultSources[0].slice(-1)
      this._stateMapError(`查询到多个结果,返回第一个结果【${firstResult}】。请及时纠正重复内容。`, silent)
    }
    // 返回复合状态关系集合
    return resultSources[0]
  }
  /**
   * 【类方法】过滤出符合条件的项
   * @param  {String}                      sourceName 竖向查找的字段名
   * @param  {Array|String|Boolean|Number} states     竖向查询字段的匹配值
   * @param  {Array}                       sources    存放值分布的表
   * @param  {Boolean} silent 是否屏蔽错误
   * @return {Array}                                  返回是过滤出符合条件的项集合
   */
  static _filterSources(sourceName, states, sources, silent) {
    // 获取字段列的index
    const sourceIndex = sources[0].indexOf(sourceName)
    // 如果找不到匹配字段
    if (!~sourceIndex) {
      this._stateMapError(`找不到匹配的字段:${sourceName}`, silent)
      return false
    }
    // 过滤出符合条件的结果
    return sources.filter((source, index) => {
      // 跳过第一行,也就是表头部分
      if (index === 0){
        return true
      }
      // 如果不存在当前字段(源里面当前字段为空),则返回
      // if (!source[sourceIndex]) {
      //   return false
      // }
      // 检查当前单元格存在内容,并且为一个数组(这意味着这个字段可以等于取多个状态)
      if (source[sourceIndex] && source[sourceIndex].constructor === Array) {
        // 判断参数的值是否存在于预定义内容内
        return source[sourceIndex].includes(states)
      } else {
        // 否则直接判断是否对应
        return source[sourceIndex] === states
      }
    })
  }
  /**
   * 【类方法】查询主入口
   * @param  {Array}   states  参与查询的状态合集
   * @param  {Array}   sources 预定义的状态与复合状态关系表
   * @param  {Array}   map     自定义需要查询字段集合
   * @param  {String}  mode    查询模式:{getState|isState}
   * @param  {Boolean} silent  是否屏蔽错误
   * @return {result}          返回中文或英文查询结果
   */
  static _mapState(states, sources, map, mode = 'getState', silent) {
    // 解析传入参数,统一转为对象参数,如果实际解析时找不到可用参数,则会返回一个错误对象
    const newStates = this._decodeStates(states, map, silent)
    // 判断是否是对象,如果是Error对象,则说明没有有用参数,那么久没有继续的必要,直接返回
    if (newStates.constructor === Object) {
      // 获取真正表头
      const realMap = sources[0]
      // 查询匹配复合状态结果
      const resultState = this._findState(newStates, sources, realMap, silent)
      // 如果查询不到结果,直接返回空字符串
      if (!resultState) {
        return ''
      }
      // 如果查询到结果,并且是查询值的模式
      if (mode === 'getState') {
        // 则返回复合状态的值
        return resultState.slice(-1)[0]
      } else {
        // 否则返回状态标志
        return resultState.slice(-2, -1)[0]
      }
    }
    return ''
  }
  /**
   * 【实例方法】获得预定义的状态
   * @param  {Array|String|Boolean|Number} ...states 查询状态的子参数
   * @return {result}                                返回预定义的状态
   */
  getState(...states) {
    const map = this.sources[0]
    return this.constructor._mapState(states, this.sources, map, 'getState', false)
  }
  /**
   * 【实例方法】通过自定义map,获得预定义的状态
   * @param  {Array}                       map    自定义判断流程
   * @param  {Array|String|Boolean|Number} states 参与判断的参数集合
   * @return {result}                             返回预定义的状态
   */
  getStateByMap(map, ...states) {
    return this.constructor._mapState(states, this.sources, map, 'getState', false)
  }
  /**
   * 【实例方法】输入自定义map,获得一个已经预设自定义map的处理函数,直接调用即可
   * @param  {Array} map    用于覆盖预定义mao,这是一个可省参数,省略时默认为原来的map
   * @return {Function}     预设了map的状态获取函数,返回后的函数参数参考 getState
   */
  getStateResource(map = this.sources[0]) {
    return this.getStateByMap.bind(this, map)
  }
  /**
   * 【实例方法】检查是否为指定状态
   * @param  {String}                      isState 要查询的状态
   * @param  {Array|String|Boolean|Number} states  查询状态的参数集合
   * @return {Boolean}                             返回是否为指定要查询的状态
   */
  isState(isState, ...states) {
    const map = this.sources[0]
    return isState === this.constructor._mapState(states, this.sources, map, 'isState', true)
  }
  /**
   * 【实例方法】输入自定义map,检查是否为指定状态
   * @param  {Array}                       map     用于覆盖预定义mao,这是一个可省参数,省略时默认为原来的map
   * @param  {String}                      isState 要查询的状态
   * @param  {Array|String|Boolean|Number} states  查询状态的参数集合
   * @return {Boolean}                             返回是否为指定要查询的状态
   */
  isStateByMap(map = this.sources[0], isState, ...states) {
    return isState === this.constructor._mapState(states, this.sources, map, 'isState', true)
  }
  /**
   * 【实例方法】输入自定义map,获得一个已经预设自定义map的处理函数,直接调用即可
   * @param  {String} isState  要查询的状态
   * @param  {Array}  map      查询状态的参数集合
   * @return {Function}        预设了map的状态获取函数,返回后的函数参数参考 getState
   */
  isStateResource(isState, map = this.sources[0]) {
    return this.isStateByMap.bind(this, map, isState)
  }
}

module.exports = complexState