1.3.10 • Published 2 years ago

bamboosnaketool v1.3.10

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago


bamboosnaketool

快速上手 - 安装

npm install bamboosnaketool

// the github project url
https://github.com/liuyunqi/BambooSnake-ReactAntd

简单介绍

技术栈React + Antd-UI,内置常用的基础工具函数、通用组件、业务组件/函数。统一规范,减少重复开发提升交付质量效率,聚焦内容实现。

(如果为支持firefox-v5.0~5.3版,依赖antd的版本需锁死在v4.15.6,目前发现主要是table表格的支持,低版本position-Fixed会失效。)

推荐使用Typora的.md文档查看工具打开本地modules内的README.md文件,会有明确的模块目录分栏及内容段落,极大提高可读性。

基本依赖

import Bamboo, { enumDataMode as _enumDataMode } from 'bamboosnaketool';
import { Utiles, Components } from 'bamboosnaketool';

// 包含大量的工具函数 及 组件模块
const { AccessorOverdue, dateTransformer: _dateTransformer, CollectionElementCtrl, Table } = Bamboo;
const { Table: { default: KTTable } } = Components;

// 例: 转换日期 - 完整型
let xsdate = _dateTransformer(new Date(), _enumDataMode.FULL);

// 例:表格组件 - KTTable (可任意自定义模块名称)
<KTTable/>

工具函数/ 小工具合集

内置基本工具函数。

// Date对象 转换为 时间字符串(多种款)
dateTransformer = (dt: Date, viewMode?: enumDataMode): string

// 嵌套层级字符替换数据转换函数
/*
  desc: 将对象数组数据进行无限递归转换,替换数据原字段为自定义字段。(且可选是否移除原字段数据)

  @data: [{...}, ...],  // 源数据
  @tranConf: {          // 转换字段配置
    O:N,                // 【O】:old,转换目标字段。【N】:new,更新的定义字段。
    ...
  },
  @options:             // 相关配置
*/
recursionDataKeyNameTransform = <T>(data: T[], tranConf: recursionDataKeyTransformConf = { isChildrenKey: 'children' }, options: recursionDataKeyTransformOpt = {
  needDeleteBefore: true
}): T[]
import Bamboo from 'bamboosnaketool';
const { dateTransformer, mergeUrl, ArrayVeidooTransfrom } = Bamboo;

// 日期转换器 - dateTransformer
dateTransformer = (dt: Date | number, viewMode?: enumDataMode): string;

// 路径合并, 去除拼接处 多或少的 '/'
mergeUrl = (arr: string[]): string;

// 一维数组转二维切割
ArrayVeidooTransfrom = (list: any[], mo: number) :array[];
RegExpValidator - 校验器
基本使用

针对{}对象进行校验。

import Bamboo from 'bamboosnaketool';

const { RegExpValidator, RegExpType } = Bamboo;

// 初始化配置
const creatValidtor = new Validtor({
  regConfig: [
    {
      key: 'name',
      type: RegExpType.CN,
      name: '姓名',
      require: true
    }, {
      key: 'phone',
      type: RegExpType.PHONE,
      name: '手机',
      require: true
    }, {
      key: 'enCode',
      type: RegExpType.EN,
      name: '英文代号',
      require: true
    }
  ]
});


// 执行校验数据
let validResult = creatValidtor.exceValid({
  name: '刘德华2',
  phone: '18163741749',
  enCode: 'code'
});

console.log(validResult);

// 验证结果 - 执行后的 console - validResult
{	// 如果 - 校验失败
    validErrors: [
      {
        key: "name"
        msg: "仅允许中文汉字"
        name: "姓名"
        require: true
        type: 0
        value: "刘德华2"
       }
    ],
    validPassState: false
}

{	// 如果 - 校验成功
    validErrors: [],
    validPassState: true
}
多项联合校验

支持多重联合校验,下例中分别写了CN/EN/PHONE,当然你甚至可以写更多;

// 初始化配置 - 多重联合校验
const creatValidtor = new Validtor({
  regConfig: [
    {
      key: 'name',
      type: [
          RegExpType.CN,
          RegExpType.EN,
          RegExpType.PHONE
      ],
      name: '姓名',
      require: true
    }
  ]
});
便捷调用

同时支持单数据快捷校验,无需实例化直接调用。

import Bamboo from 'bamboosnaketool';

const { RegExpValidator, RegExpType } = Bamboo;

// 单数据的便捷校验
let singleValid = Validtor.exceOnce('', RegExpType.CN);							// 单项校验
let multiSingleValid = Validtor.exceOnce('', [ RegExpType.CN, RegExpType.IP]);	// 多项校验

// 分别的打印结果
/* is singleValid
{
	validErrors: "仅允许中文汉字"
	validPassState: false
}
*/

/* is multiSingleValid
{
	validErrors: {type: 'CN', msg: '仅允许中文汉字'},
				 {type: 'IP', msg: '必须是正确ip格式'}
	validPassState: false
}
*/
配置项说明

可支持的内置校验类型。

// 校验单元格配置格式
interface RegExpValidatorConfItem {
  key: string;            // 关键key
  type: RegExpType | RegExpType[];   // 校验内置类型
  name: string;           // 校验项名称
  require?: boolean;      // 是否必填
}

export enum RegExpType {
  CN,                   // 中文
  EN,                   // 英文字母
  EN_NUMBER,            // 任意英文+数字
  DECIMAL,              // 小数浮点数类型
  INTEGER,              // 整数类型 - 正负都可以
  POSITIVE_INTEGER,     // 整数类型 - 正数
  UP_ZERO_INTEGER,      // 0+正数 (<=0)
  UP_ONE_INTEGER,       // 1+正数 (<=1)
  NUMBER,               // 数字类型
  EMAIL,                // 电子邮箱
  PHONE,                // 手机 18165221849
  TEL,                  // 电话 0731-5632575
  PHONE_TEL,            // 手机 + 电话
  POSTCODE,             // 邮政编码
  HTTP,                 // http (支持 - :post,www,ip,http,https)
  IP,                   // ip地址
  IDENTITY_CARD,        // 身份证
  SYMBOL_CN,          	// 全角
  SYMBOL_EN           	// 半角
}

返回格式

// 校验返回格式
interface RegExpValidatorErrorResponse {
  validPassState: boolean;					// 校验结果状态
  validErrors: RegExpValidatorErrorItem[];	// 校验错误项集合
}
CollectionElementCtrl - HTML元素集合控制器
基本使用

针对元素对象集合进行遍历操作。

内置索引记录器,可以通过使用指令对其进行遍历,对遍历目标元素进行 Attr 属性赋值(如class、style),对非当前active元素进行清理值。

支持对目标元素进行class/ style的配置自定义。

import Bamboo from 'bamboosnaketool';

const { CollectionElementCtrl } = Bamboo;

// 元素集合操作
const $CollectionElementCtrl = new CollectionElementCtrl({
  collectionsElements: [],	// dom对象集合 (可初始化设置/ 通过.setCollectionsElements()插入调用)
  indexRecord: 0,			// 行索引 - rowIndex
  columnIndex: 4,			// 列索引 - 锁定单元格
  needFoucusHTMLElementInput: true	// 是否聚焦input - 是否需要检查当前 纵及列<td>单元格内是否包含input
});
组合使用

可结合 Bamboo.KeywordsContrl 的键盘事件组件搭配使用。(如:操作表格,通过键盘(上下左右)指令在表格的row及td中的元素进行操作。)

演示表格行通过 ↑ ↓ 键盘来做到行来回选中效果。

const TableScanRef = React.createRef<HTMLDivElement>(); // TABLE - 表格 (用于控制-rowSelected)

// 获取Element集合
const tbody = (TableRef.current as HTMLDivElement).children[0].querySelector('.ant-table-tbody');
let trNodes: HTMLTableRowElement[] = [];
tbody?.querySelectorAll('tr').forEach((trElement: HTMLTableRowElement, index: number) => {
    if (index > 0) {
        trNodes.push(trElement);
    }
});

// 装载DOM元素集合到 组件内
$CollectionElementCtrl.setCollectionsElements(trNodes);

// print 是 Bamboo.KeywordsContrl 事件回调里的按键标识
if (print === 'top') {
    $CollectionElementCtrl.reduce();
} else if (print === 'bottom') {
    $CollectionElementCtrl.addition();
}
AccessorOverdue - 存取器 - 过期限制 (save data to localSession):
基本使用

让存储在 localStorage/ sessionStorage 的数据具备过期能力,满足一些需要数据失效的应用场景。

import Bamboo from 'bamboosnaketool';
const { AccessorOverdue } = Bamboo;

// 创建实例
const xAccessorOverdue = new AccessorOverdue({  // 过期存储
  overdueTimeSecond: 3600     // 设置过期时间 - 单位-秒
});

/* 默认的配置 component default optiosn [可选]

{
  projectAlias: 'baseToolAccessOverdue',                            // 命名空间
  saveObject: accessOverdue_saveObjectEnum.sessionStorage,          // 存储对象 'sessionStorage' | 'localStorage'
  overdueTimeSecond: 60                                             // 过期时间 - 1 = 1s (单位/秒)
}
*/

// 写入存储
xAccessorOverdue.setItem('mockKey', {name: '我是需要保持的数据'});

// 提取存储
xAccessorOverdue.getItem('mockKey');		// 根据 [配置项-过期时间] 判断是否过期
xAccessorOverdue.getItem('mockKey', 1);		// 根据本次形参 - 判断是否过期 (单位/秒 = 1s过期)

// 指定删除
xAccessorOverdue.removeItem('mockKey');			// 删除一项
xAccessorOverdue.removeItem(['mockKey', 'mockKey1', 'mockKey2']);	// 删除多选

配置项中,projectAlias 有效防止命名冲突,saveObject可以指定存储的方式。

提取返回

不管isInvalid状态是否过期,数据永远是会保留返回。

// 执行 .getItem('mockKey')
{
  isInvalid: boolean;			// 是否已过期 - (true-无效已过期、false有效)
  value: any;					// setItem 之前存储的数据内容
}
配置项说明
// 配置props - options
export interface accessOverdue_defaultOption {
  projectAlias?: string;
  saveObject?: accessOverdue_saveObjectEnum;
  overdueTimeSecond?: number;
}

export enum accessOverdue_saveObjectEnum {
  sessionStorage = 'sessionStorage',
  localStorage = 'localStorage'
}

// 实例可调用的api
{
  setItem (key: string, data: any): void        // 写入存储
  getItem (key: string, overdueTimeSecond?: number): { isInvalid: boolean, value: { creatertime: number, value: any }}  // 提取存储
  removeItem (keys: string | string[]): void    // 指定删除
}
Debounced / Throttle - 防抖/ 节流 (control event frequency):

防止高频触发、开销; 防抖 - 短暂时间内的多次执行,以最后一次执行为触发; 节流 - 一段周期内只执行一次;

import Bamboo from 'bamboosnaketool';
const { Debounced, Throttle } = Bamboo;

// 防抖 - 创建实例
const debounced = new Debounced().use((fn: Function) => {
  typeof fn === 'function' && fn();
}, 500);

// 调用-防抖
debounced(() => {
  // u can do any things...
});

// 实例可调用的api
{
  /**
   * @param func 需要包装的函数
   * @param delay 延迟时间,单位ms
   * @param immediate 是否默认执行一次(第一次不延迟)
   */
  use = (func: Function, delay: number, immediate: boolean = false): Function
}

---------------------------[ 不华丽且低调的分割线 ]----------------------------------

// 节流 - 创建实例
const throttle = new Throttle().use((fn: Function) => {
  typeof fn === 'function' && fn();
}, 500);

// 调用
destroy();     // 销毁
open();        // 开启
close();       // 关闭

{
  /**
   * @param func 需要包装的函数
   * @param delay 延迟时间,单位ms
   * @param immediate 是否默认执行一次(第一次不延迟)
   */
  use = (func: Function, delay: number, immediate: boolean = false): Function
}
KeywordsContrl - 键盘事件捕捉器

方便快捷的获取按键捕捉及回调事件。

import Bamboo from 'bamboosnaketool';

const { KeywordsContrl } = Bamboo;

// 键盘事件
const $keywordsContrl = new KeywordsContrl();

// 按键事件回调
$keywordsContrl.allEventCallback = (eventName, data) => {
    const { keyCode, print } = data;
    
    // keyCode if enter -> 13
    // print if up -> 'top | bottom | enter ... keywordsContrl_eventType'
}

// 事件类型定义
export enum keywordsContrl_eventType {
  prev = 'prev',
  next = 'next',
  top  = 'top',
  right = 'right',
  bottom = 'bottom',
  left = 'left',
  delete = 'delete',
  enter = 'enter',
  focus = 'focus',
  blur = 'blur',
  tableIsReady = 'tableIsReady',
  init = 'init'
}
CreatTableHtmlElement - 打印模板表格

适用于生成表格相关的纸质打印单据。

此组件会很方便快速构造一个表格,并将数据渲染在表格内,有需要时可以达成纸质分页及各处自定义内容插槽(为了更符合单据的各种样式),同时支持尺寸自定义。

基本使用

下列示例仅根据 表头配置 + 数据 就能快速生成一个表格。

import { CreatTableHtmlElement } from 'bamboosnaketool';

// 配置表头 width 29.7cm
const printColumns = [
  {
    key: 'mainBarcode',
    name: '条码',
    width: 3
  }, {
    key: 'goodsName',
    name: '品名、剂型号、规格、生产企业',
    width: 5.5
  }, {
    key: 'permitHolder',
    name: '上市许可持有人',
    width: 3.7
  }
];

// 模拟数据
const mockDatas = [
  { mainBarcode: '98500001', goodsName: '阿莫西林-5g*20/盒-江苏制药六厂', permitHolder: '刘备'  },
  { mainBarcode: '98500002', goodsName: '云南白药牙膏250g/支-云南利民制药厂', permitHolder: '孙权'  },
  { mainBarcode: '98500003', goodsName: '天然养生枸杞 500g/罐-六元堂股份药业', permitHolder: '曹操'  },
  // more...
];

let TABLEhtml = CreatTableHtmlElement(printColumns, mockDatas)();

可以自定义尺寸属性,如示例

let options = {
  cssUnit: 'cm',		// 内置默认是 'px',这里的单位会影响 printColumns[ item.width ]
  ...					// ’欧佛阔斯‘,还有很多其他的属性
};
let TABLEhtml = CreatTableHtmlElement(printColumns, mockDatas, options)(/** 有需的话可配置插槽对象 **/);
插槽 Slot

该组件中插槽其实是满足打印需求非常关键的一个功能,因为很多实际应用场景下,需要打印的纸质单据并不是内容仅有表格,可能会有下列罗列情况:

1.打印多张,但第一张有个额外的小表格或一段前言声明之类的(那么第一张纸由于被其他内容占据可能表格被裁切高度只能有50%高,而后面其他页都是100%全高);

2.打印多张,需要在每一张都盖上 印章;

3.打印的每一张,内容表格的前后都需要添加一些内容(可能是一个元素结构,也可能是一段文本内容);

不再多举例,下面我们看看如何达成这些需求:

import { CreatTableHtmlElement, CreatTableHtmlElement_InnterSlotModeEnum } from 'bamboosnaketool';

// 复制一份上述示例,增加插槽内容演示
let TABLEhtml = CreatTableHtmlElement(printColumns, mockDatas, options)(
    {
      slotInner: [            // 表格集合组插槽 - 每个表格单元中的内容
        {
          mode: CreatTableHtmlElement_InnterSlotModeEnum.ONLY_FIRST_BEFORE,
          HTMLtext: '仅第一个表格前方渲染的内容'
        },
        {
          mode: CreatTableHtmlElement_InnterSlotModeEnum.ONLY_LAST_BEFORE,
          HTMLtext: '仅最后一个表格的前方渲染的内容'
        },
        // has more...
      ],
      // 表格集合组外部插槽
      slotStamp: `<img src="${setimageDataUrl}" style="position: absolute; right: 1.5cm; bottom: 1.5cm; width: 4cm; height: 4cm;"/>`,
      slotBefore: '开端xxx内容',
      slotAfter: '落款xxxx内容',
});
打印单据示例

下面是一个带有表头的单据打印需求场景(由于无法上图)

import CreatTableHtmlElement, { CreatTableHtmlElement_InnterSlotModeEnum } from 'bamboosnaketool';

let setimageDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA......';

let title = {
    billName: '大标题XXXXX单据',
    storeName: '香樟东路门店',
    billCode: '90000816',
    shippingDate: '2021-10-21 19:58:23'
};
let deliveryHTMLtext = `
  <div style="margin-bottom: 0.5cm;">
    <h3 style="text-align: left; height: 0.8cm; line-height: 0.8cm;">${ title.billName }</h3>
    <div style="display: flex;">
      <div style="flex: 1;">
        <span>收货门店:${title.storeName}</span>
      </div>
      <div style="flex: 1;">
        <span>单据编号:${title.billCode}</span>
      </div>
      <div style="flex: 1;">
        <span>开票日期:${title.shippingDate}</span>
      </div>
    </div>
  </div>
`;

let TABLEhtml = CreatTableHtmlElement(printColumns, list.map(item => {
  item['supplierState'] = '合格';
  return item;
}), {
  pageLimit: 14,
  cssUnit: 'cm',          // 单位 px/ pt/ cm...
  pagerPhysicsWidth: '29.7cm',
  tableHeight: '20.4cm',
  tablePaddingTop: '1cm',           // 内部padding-top
  tablePaddingBottom: '1cm',        // 内部padding-bottom
  tableSpace: '0',
  tableBorderWidth: '0.01cm',
  tableTHPadding: '0.06cm',
  tableTDPadding: '0.2cm 0.1cm',
  tableFontSize: '0.2cm',

  valueBoxHeight: '0.36cm',       // 高度
  // valueBoxMaxHeight: '0.72cm',    // 最大高度
  valueBoxMaxHeight: '1.08cm',    // 最大高度
  valueBoxLineHeight: '0.36cm',   // 行高  h 1/2
  valueBoxLineClamp: '2',         // 最大显示行数
})({
  slotInner: [{
    mode: CreatTableHtmlElement_InnterSlotModeEnum.ONLY_FIRST_BEFORE,
    HTMLtext: deliveryHTMLtext
  }],
  slotStamp: `<img src="${setimageDataUrl}" style="position: absolute; right: 1.5cm; bottom: 1.5cm; width: 4cm; height: 4cm;"/>`
});
打印应用

创建一个新窗口文档,将已生成好的表格插入。

// 打开弹窗打印执行
const openWindowPrint = (TITLE: string, HTMtext: string, options = {}) => {
  try {
    let wind = window.open('', '_blank');
    (wind as Window).document.body.innerHTML = creatPrintDocumentWrapper(TITLE, HTMtext, options);
    (wind as Window).print();
  } catch (err) {
    // 发生错误时进行交互提示
    /*
    Modal.error({
      title: '发生错误',
      content: '打开打印弹窗失败,当前游览器可能弹窗已被阻止,通过【设置 - 允许弹窗]】后再重试打印。'
    });
    */
  }
}


/**
 * 新建文档包裹容器
 * @param title    新建文档标题
 * @param HTMLdom  新建文档元素内容
 * @param options  配置项
 */

interface creatPrintDocumentWrapper_options {
  htmlWidth?: string;        // 文档整宽 (例:21cm)
  page_sizeMode?: 'A4';      // 尺寸标准名
  page_composeType?: 'landscape' | 'portrait'; // landscape 横版 / portrait 竖版
}

const creatPrintDocumentWrapper = (title: string = 'Print Document', HTMLdom: string, options: creatPrintDocumentWrapper_options = {}) => {

  let defOpts: creatPrintDocumentWrapper_options = {
    htmlWidth: '21cm',                      // 文档整宽
    page_sizeMode: 'A4',                    // 尺寸标准名
    page_composeType: 'portrait',           // landscape 横版 / portrait 竖版
    ...options
  };

  return `
    <!DOCTYPE html>
    <html lang=\"en\">
      <head>
        <meta charset=\"UTF-8\">
        <title>${ title }</title>
      </head>
      <style>
        @page {
          size: ${defOpts.page_sizeMode} ${defOpts.page_composeType};
        }
        * {
          margin: 0;
          padding: 0;
        }
        html {
          width: ${defOpts.htmlWidth};
          font-size: 0.3cm;
        }
        body {
          margin: 0;
        }
      </style>
      <body>
        ${ HTMLdom }
      </body>
    </html>
  `;
}
结构关系

特别准备了这一部分,因为对组件构成的关系理解对使用组件到实际应用场景非常关键,由于用不了图片进行关系示意,所以相互结构关系的理解非常重要,我尝试将它说清楚一些。

我们先列出构成文档的参与者,按文档流的形式 由上至下。

[顶部插槽]start
[内容插槽组:
  [
    [第一个表格
       [首个前]
       [每个前]
       [表格]
       [每个后]
       [首个后]
       [印章]
    ],
    [第二个表格(也许)
       [每个前]
       [表格]
       [每个后]
       [印章]
    ],
    [ 也许中间有若干个表格 ....],
    [最后一个表格
       [末尾前]
       [每个前]
       [表格]
       [每个后]
       [末尾后]
       [印章]
    ],
  ]
]
[底部插槽]end

上述假设了完整的配置结构组成了一个大篇幅,可能会存在分页,原则上每个页面内容在纵向填充是尽可能饱满(不浪费打印纸)。

分页情况:

首页会顶部插槽+第一个表格的组成;

中间的每个页面表格+前后填充饱满;

末尾页表格+底部插槽内容;

配置项说明
// 表格列配置
export interface CreatTableHtmlElement_columnsConf {
  key: string,
  name: string,
  width?: string | number,
  align?: string
}

// 每个单元表格 - 插槽配置 CreatTableHtmlElement_concatSlot.slotInner 
interface CreatTableHtmlElement_slotInnerInter {
  HTMLtext: string;
  mode: CreatTableHtmlElement_InnterSlotModeEnum;
}

// 全插槽配置
export interface CreatTableHtmlElement_concatSlot {
  slotBefore?: string;                    // 插入总结构的头始
  slotAfter?: string;                     // 插入总结构内的末尾
  slotStamp?: string;                     // 插入公章
  slotInner?: [] | CreatTableHtmlElement_slotInnerInter[];      // 插入 table-item 内的具体位置
}

// 每个单元表格 - 插槽类型 slotInner集合的mode类型
export enum CreatTableHtmlElement_InnterSlotModeEnum {    // 插入模式 - 可能插入多个或单个于  table-item DIV 中
  ONLY_FIRST_BEFORE = 'ONLY_FIRST_BEFORE', // 仅首个 - 表格之前插入
  ONLY_FIRST_AFTER = 'ONLY_FIRST_AFTER',   // 仅首个 - 表格之后插入
  ONLY_LAST_BEFORE = 'ONLY_LAST_BEFORE',   // 仅末尾 - 表格之前插入
  ONLY_LAST_AFTER = 'ONLY_LAST_AFTER',     // 仅末尾 - 表格之后插入
  EVERY_BEFORE = 'EVERY_BEFORE',           // 每个之前
  EVERY_AFTER = 'EVERY_AFTER',             // 每个之后
  UNDO = 'UNDO',                           // 无动作
};

// 组件配置项
export interface CreatTableHtmlElement_options {
  pageLimit?: number;                 // 是否需要区别表格 - 数据总长度超出单页时将创建多个表格(便于打印)
  cssUnit?: 'px' | 'pt' | 'cm';       // 尺寸单位,影响内部style配置及column.width

  pagerPhysicsWidth?: string;         // 纸张横向尺寸
  tableHeight?: string;               // 是否限制 - 单个表格容器高度

  tablePaddingTop?: string;           // 表格单元内边距 padding-top
  tablePaddingBottom?: string;        // 表格单元内边距 padding-bottom
  tableSpace?: string;                // 表格单元 margin-top
  tableBorderColor?: string;          // 表格边框色
  tableBorderWidth?: string;          // 表格边框线宽
  tableZebraColor?: string;           // 表头thead - 背景色
  tableTHPadding?: string;            // 表头thead - 内边距 padding
  tableTDPadding?: string;            // 表格td 内边距 padding
  tableFontSize?: string;             // 表格th、td - 字体大小

  valueBoxHeight?: string;            // 高度
  valueBoxMaxHeight?: string;         // 最大高度
  valueBoxLineHeight?: string;        // 行高  h 1/2
  valueBoxLineClamp?: string;         // 最大显示行数
}

公共组件

React、antd-UI

组件:

Table - 表格组件:(github - public)

内置pagination分页、支持表格列单元格自定义模板渲染、ACTIONS操作栏多功能渲染等;

目前表格使用 antd-UI ,表格组件封装了 Table-antd 和 Pagination-antd;

基本使用
// 使用bamboo表格组件
import Bamboo from 'bamboosnaketool';
const { Table: BamTable } = Bamboo;

return (
    <BamTable dataSource={ dataSourceList } columns={ columns } rowKey={ 'id' } ALLEVENTCallback={ tableLLEVENTCallback }/>
)
lib - 页面模块组成

目前开发作者习惯性的文件方式搭配。假设有一个页面模块demoPage

src <
    demoPage <
    	index.tsx;			// 页面模块
		index.less;			// 页面样式
		columns.ts;			// 根据key生成的export对象,该对象符合 antd-column-item 及 结合组件内部封装的新属性
		columns.d.ts;		// key - tableCloumn 前端枚举 - 页面中如有用到关键字段推荐从这里统一使用
column - 列头配置

推荐方式为独立的 列头配置文件 和 列头.d。

基础的列头配置如下(类似antd的常规使用格式),如果是涉及到内置组件的后面会详细提及。

// 根目录
...,
mock,
pages --> homePage --> [ index.tsx, index.less, columns.ts, columns.d.ts ],
...otherlibs
// columns.ts

import { ColumnsType } from 'antd/es/table';
import { columnItemDecorator, Table_ColumnsTypeMine } from 'bamboosnaketool';
import { keys } from './columns.d';

// 字段配置
const originalConfMax = (
  columnItemDecorator
)({
  [keys.mainbarCode]: { name: '商品条码' },
  [keys.goodCode]: { name: '商品编码' }
}, keys);

/*
originalConfMax =  // 生成后的数据格式为:type is Object
{
  mainbarCode:{
    dataIndex: "mainbarCode",
    key: "mainbarCode",
    name: "商品条码",
    title: "商品条码"
  },
  ...
}
columnItemDecorator 是数据装饰的函数,通过编码人员简单的columnitem配置就能生产完整的数据,数据符合antd的api及bamboo组件封装props要求,且写少做多。
*/

// 将其导出
export const column_ALL: Table_ColumnsTypeMine = [
  originalConfMax[keys.goodName],
  originalConfMax[keys.mainbarCode]
];
// columns.d.ts

// ts 字段枚举配置
export enum keys {
  mainbarCode = 'mainbarCode',      // 商品条码
  goodCode = 'goodCode',            // 商品条码
  ...
}

columns.d.ts 的keys是枚举对象,键值对形式,其中【键】命名后不可更改,【值】则根据实际数据字段自由更改(建议项目中任何使用该字段时都引用 keys )。

这里之所以使用columns.tscolumns.d.ts构成方式,而不直接定义在index.tsx里是因为有以下几种应用情况:

  1. keys 适用于导入提供其他相关模块引用,做到字段易维护统一;(比如页面的逻辑、mock模拟数据、等)

  2. column.ts 内可通过函数生成单或多个配置内容,适用于某页面有多个tabs切换多表格场景;

columnItemDecorator 是执行 ’编译‘ 配置的关键函数,如涉及特殊情况需要在其他页面配置也是可以的,如下代码。

1.下面变量如 hasModelPredict, billRunStatus 可以是 useState/ Props,可以在任何地方嵌入任何变量作为条件进行逻辑约束;

2.condition 中使用了 evalString 方式,这种方式非常灵活,可以支持非常繁琐复杂的判断逻辑;

import { keys } from 'somePageModule';
import Bamboo, {
    Table_ColumnCustomType, columnItemDecorator
} from 'bamboosnaketool';

const columnFactory = (
    columnItemDecorator
)({
    [keys.forecastCount]: {
        name: '预测数量',
        width: 80,
        condition: [
            // 条件满足 ->> 开启模型预测 + 且模型运行已完成 + 预测数量有值
            `${Boolean(
                (hasModelPredict && billRunStatus) &&
                (
                    billRunStatus === ENUM_InitiativeBillRunModelStatus.MODEL_RUN_SUCCESS ||
                    billRunStatus === ENUM_InitiativeBillRunModelStatus.DELIVERY_CREAT_ING ||
                    billRunStatus === ENUM_InitiativeBillRunModelStatus.DELIVERY_CREAT_FAIL ||
                    billRunStatus === ENUM_InitiativeBillRunModelStatus.DELIVERY_CREAT_SUCCESS
                )
            )} && record['${[keys.forecastCount]}']!==undefined`,
            {
                customType: Table_ColumnCustomType.LINKBUTTON,
                optionsApi: {
                    style: { width: 56 },
                    maxLength: 4
                }
            },
            {	// 其实该处整个{}对象配置可以省略,组件内部会默认使用 NORMALRENDER 渲染正常文本显示。
                customType: Table_ColumnCustomType.NORMALRENDER,  // 默认渲染普通文本 - 该处可不设
            }
        ]
    }
});

let setOverColumns = columnCombo.map((icloum: any, index: number) => {
    if (icloum.key === keys.forecastCount) {
        return columnFactory[keys.forecastCount];
    }
    return icloum;
});
operation - 操作栏配置

(多功能配置ACTIONS)

操作栏支持多种可调配的交互操作方式,如下:

点击回调、点击模态框弹窗(Modal)、点击确认气泡框(Popconfirm)、隐藏、隐藏且占位、禁用锁定、仅某一条符合某条件、多渲染模式同时并存、仅图标、图标+文字、悬停是否显示title、仅图标锁定。

// 配置单个操作对象的大致包含
{
    text: '查看明细',	  // 文本
    eventType,			// 事件交互方式
    eventSubstance,		// 对应各个eventType的不同格式配置
    condition,			// 显示方式
    viewMode 			// 渲染方式
}

下面内容过于丰富,为便于理解,这里系统的归纳下:

事件交互方式:

{ ..., eventType: ? }

通过内置的枚举进行模式选择。(默认方式:CALLBACK)

对象名称说明
Table_enumEventType.CALLBACK函数回调触发会响应绑定的函数进行事件响应。
Table_enumEventType.MODALBOX模态框可自定义模态框内容文本,并绑定弹窗 confrim/cancel 事件函数。
Table_enumEventType.POPCONFIRM气泡确认框点击目标文字会在点击附近出现气泡确认框再次确认。功能与模态框类似可自定义文本内容及事件绑定。

物料:

{ ..., eventSubstance: ? }

配套对应不同 eventType , 配置完全不同;

对象名称配置格式说明
Table_enumEventType.CALLBACK函数回调( record: any ) => void;-
Table_enumEventType.MODALBOX模态框{ title: (string | JSX.html), content: (string | JSX.html), ok: Function, cancel: Function}-
Table_enumEventType.POPCONFIRM气泡确认框模态框 一致。↑↑↑-

显示方式:

{..., condition: { ?key: boolean | evalString }}

渲染条件允许配置多个,内含 { hide, transparent, locked }, 优先级按此序列;条件书写为 eval(string), 内置关键字为 'record';

对象名称优先级权重说明
hide隐藏1(最高)条件成立则不显示,不干涉元素布局占位
transparent占位隐藏2条件成立则不显示,干涉元素布局占位
locked锁定3条件成立则禁用置灰

渲染方式:

{ ..., viewMode: ? }

渲染显示模式有三种,文字/ icon, { Table_enumViewMode.DEFAULT, Table_enumViewMode.ICON, Table_enumViewMode.ICONTEXT }。

对象名称说明
Table_enumViewMode.DEFAULT默认只显示文本
Table_enumViewMode.ICON仅图标只显示自定义配置的图标
Table_enumViewMode.ICONTEXT图标兼文本同时显示图标和文本的组合体

代码参考如下:

// tableMock.tsx 
import { HomeOutlined } from '@ant-design/icons';
import { Table_enumEventType, Table_enumViewMode, Table_sActions as ActionInterface } from 'bamboosnaketool';


// 这是一个配置好的 操作栏 - ACTIONS
export const setActionsTestMock : ActionInterface[] = [
  // 事件交互
  {
    text: '点击回调',
    condition: {
      hide: `['2', '3'].includes(record.key)`
    },
    eventType: Table_enumEventType.CALLBACK,
    eventSubstance(record: RowProps) {
      alert('can run xxx function event.');
    }
  }, {
    text: '模态框',
    condition: {
      hide: `['2', '3'].includes(record.key)`
    },
    eventType: Table_enumEventType.MODALBOX,
    eventSubstance: {
      title: '模态框标题',
      content: (
      <div>
        这是一段询问的内容,吧啦吧啦。。。
      </div>),
      ok(record) {
        alert('ok')
      },
      cancel() {
        alert('cancel')
      }
    }
  }, {
    text: '气泡确认框',
    condition: {
      hide: `['2', '3'].includes(record.key)`
    },
    eventType: Table_enumEventType.POPCONFIRM,
    eventSubstance: {
      title: '气泡标题',
      content: '嗯哼?',
      ok(record) {
        alert('ok')
      },
      cancel() {
        alert('cancel')
      }
    }
  },

  // 按鈕狀態
  {
    text: '隐藏且占位',
    condition: {
      transparent: `true`,
      hide: `['1', '3'].includes(record.key)`
    },
    eventType: Table_enumEventType.CALLBACK,
    eventSubstance(record: RowProps) {
      alert('can run xxx function event.');
    }
  },
  {
    text: '显示图标',
    icon: <HomeOutlined/>,
    condition: {
      hide: `['1', '2'].includes(record.key)`
    },
    eventType: Table_enumEventType.CALLBACK,
    eventSubstance() {

    },
    viewMode: Table_enumViewMode.ICONTEXT
  }
  ....    /* 还有非常多种 */
]

上述还有非常多种,具体可以参考 后期补全,示例文件删了。(同时你可以使用该文件内的数据生成示例参考,datasource、columns、actions 都是齐全的)

eventType : 事件响应模式 { Table_enumEventType.CALLBACK, Table_enumEventType.MODALBOX, Table_enumEventType.POPCONFIRM };
eventSubstance : 配套对应不同 eventType , 配置完全不同;
condition : 渲染条件,内含 { hide, transparent, locked }, 优先级按此序列;条件书写为 eval(string), 内置关键字为 'record';
viewMode : 渲染显示模式,文字/ icon, { Table_enumViewMode.DEFAULT, Table_enumViewMode.ICON, Table_enumViewMode.ICONTEXT }。

将配置完成的 操作栏 数据与 column 数据进行数组合并。

import { setActionsTestMock } from '../tableMock.tsx';
import { ColumnRender_operationAction } from 'bamboosnaketool';

export const setColumns = [
  {
    title: '创建人',
    dataIndex: 'str3',
    key: 'str3',
  },
  {
    title: '操作',
    key: 'action',
    render: (text: string, record: any, index: number) => {

      let setActionsTest = setActionsTestMock;

      // if 条件变更 setActions ...
      return ColumnRender_operationAction(text, record, index, setActionsTest);
    }
  }
];

更多细节建议利用mock数据做一个例子,并花30分逐个尝试并理解为最佳。这里就没有逐一全部写清楚了。

column-Item - 配置定制化模板

支持表格单元格渲染,输入框类型、货币格式转换、时间戳转字符串、进度条(节状、直条、或任意款式)、高亮link内容等错综复杂的万象需求...,都可按照目前组件提供的内置模板模式达成。

配置示例:

// 这里就写局部结构,完整内容参考已上述过的【列头配置】
{
  [keys.salesForecast]: {
    name: '链接文本',             // 其实这里是  列头名称,为方便理解这个改为了 ’示例模板名称‘
    condition: [
      'record.id !== "3"',
      {
        customType: Table_ColumnCustomType.LINKBUTTON,
        customSettings: {
          style: {
            fontWeight: 'bold'
          }
        }
      }
    ]
  },

  [keys.replenishNumber]: {
    name: '输入框',               // 其实这里是  列头名称,为方便理解这个改为了 ’示例模板名称‘
    condition: [
      'record.id !== "3"',
      {
        customType: Table_ColumnCustomType.INPUT,
        optionsApi: {
          style: { width: 56 },
          maxLength: 4
        }
      },
      {                                                   // 其实该处整个{}对象配置可以省略,组件内部会默认使用 NORMALRENDER 渲染正常文本显示。
        customType: Table_ColumnCustomType.NORMALRENDER,  // 默认渲染普通文本 - 该处可不设
      }
    ]
  }
}

上述代码示例中出现了三个组件内置模板,Table_ColumnCustomType { LINKBUTTON, INPUT, NORMALRENDER }。

optionsApi 为antd-api,这里的设置参数全部是直接 setProps 到 antd-DOM 上的。根据不同的组件查看官方具体的api。 customSettings 设置一些自定义的dom属性,比如 customSettings.style, 这些最终也会以 setProps 方式应用到 DOM 的行间样式属性中;

为了方便理解,从概念上 optionsApi 可看作【setApis】,customSettings可看作【setPropertys】。

渲染条件 condition

这里condition(条件)要特别说下,它决定了每个列配置的最终渲染结果,根据条件满足与否执行A/ B;类似if else 无限递归

condition本身是数组,参考概念格式为 condition = boolean, A, B boolean, A

c[0] 是布尔类型,成功则执行A,否则执行B;  
A/B  是一个单个模板完整的配置方案;  
B 非必填,不填时则渲染默认模板 Table_ColumnCustomType.NORMALRENDER(默认文本显示);  

c[0]:书写可以是 true/ false/ 表达式, 或 eval(string),string数据关键字为 'record';

同时为满足相对复杂的业务需求,这里支持无限层级的类似 if else 嵌套,代码如下:

// 类似 if else 方式
{
  name: '补货数量',
  condition: [
    true,
    {
      customType: Table_ColumnCustomType.INPUT,
      optionsApi: {
        style: { width: 56 },
        maxLength: 4
      }
    },{
      customType: Table_ColumnCustomType.TEXT,
      optionsApi: {
        style: { width: 56 },
        maxLength: 4
      }
    }
  ]
}

// 伪代码理解
// [false, 3, [false, 5, [true, 18]]]    - 结果:18
// [true, 996, [true, 42, [false, 23]]]  - 结果:996
// [false, 5]						     - 结果:由于未设置B,则指向默认Table_ColumnCustomType.NORMALRENDER,或优先外部配置的 isItemRender


// 递归无限层级
{
  name: '补货数量',
  condition: [
    true,
    {
      customType: Table_ColumnCustomType.INPUT,
      optionsApi: {
        style: { width: 56 },
        maxLength: 4
      }
    },[
      true,
      {
        customType: Table_ColumnCustomType.TEXT,
        optionsApi: {
          style: { width: 56 },
          maxLength: 4
        }
      }, [ 无限层级... ]
    ]
  ]
}
模板 template

目前使用的模板都是内置的。在 ./template.tsx 中。 需要新增则需要扩展 Table_ColumnCustomType 类型名称 及 对应的 render 函数。

理想的最优解为之后迭代,让今后使用者能够外部定义追加扩展模板及模板类型名称。

表格事件 eventHandle

通过配置表格组件Props - ALLEVENTCallback, 传入一个方法,该方法仅会响应 template 模板事件。

// 全表格事件回调捕捉函数
const tableLLEVENTCallback: ALLEVENTCallbackType = (TYPE, data) => {

  // 输入框 - 每次输入
  if (TYPE === Enum_ALLEVENT.INPUT_onChange) {
    const { e, record, columnItem, index } = data;
    let key = columnItem.key;
    let value = e.target.value;
    
    //...
  }
  // 输入框 - 回车 / 失焦
  else if (TYPE === Enum_ALLEVENT.INPUT_onPressEnter || TYPE === Enum_ALLEVENT.INPUT_onBlur) {

  }
  // 点击 预测销量
  else if (TYPE === Enum_ALLEVENT.LINKBUTTON_onClick) {
    // do anything...
  }
}

顺便提一嘴,操作栏 ACTION 的事件回调函数为 eventSubstance。在配置 ACTION-item 时被一并配置,根据ACTION类型不同会有所不同。

Props Api
interface IProps  {
  dispatch: Dispatch;

  columns: ColumnsType<ColumnsTypeMine>;   // 表格列头
  dataSource: any[];                       // 表格数据
  rowKey?: string | undefined;             // 自定义关键参数 default: id
  isShowPagination?: boolean;              // 是否显示分页组件
  defaultFirstPage?: number | undefined;   // 默认开始页码
  pageCurrent?: number | undefined;        // 当前页码
  pageTotal?: number | undefined;          // 总页码数
  pageLimit?: number | undefined;          // 单页数据数量
  pageSizeOptions?: string[] | undefined;  // 单页数量变更

  onPaginationChange?: ((page: number, pageSize: number) => void) | undefined;   		// 分页组件事件回调
  onPaginationShowSizeChange?: ((current: number, size: number) => void) | undefined;	// 是否显示分页组件
  ALLEVENTCallback?: ALLEVENTCallbackType; // 公共回调函数 (可用于解决column-render组合任何组件的无限触发事件回调)

  reloadApiTable?: TableProps<any>;        // antd - table - api
  reloadApiPagination?: PaginationProps;   // antd - pagination- api
}
TS报错

参数声明 isItemRender 是 boolean, ColumnCustomType,但如果直接写成 isItemRender: true, ColumnCustomType.TEXTLINESINGLE,会造成props报错,原因是TS自动解析为 联合类型,而不是索引指定类型;

import Bamboo from 'bamboosnaketool';
const { Table: BamTable } = Bamboo;
// 这样指定个声明可以解决TS报错误识别问题
// 如不额外将 setIsItemRender 拿出来做TS会自动认为 [true, ColumnCustomType.TEXTLINESINGLE] 是 (boolean | ColumnCustomType)[],fuok!
const setIsItemRender: [boolean, ColumnCustomType] = [true, ColumnCustomType.TEXTLINESINGLE];

// table setProps - FAST
const setTableProps = {
  dataSource: dataSourceList,
  columns: saColumns,
  isItemRender: setIsItemRender,
  // isItemRender: [true, ColumnCustomType.TEXTLINESINGLE]
}

// render
<BamTable { ...setTableProps } />

解决其它含antd@type 的报错解决。页面调用时由于TS默认会识别 ‘small’ | ‘checkbox’ 为 string 类型。

import { SizeType } from 'antd/lib/config-provider/SizeContext.d';
import { RowSelectionType } from 'antd/lib/table/interface.d';
import Bamboo from 'bamboosnaketool';
const { Table: BamTable } = Bamboo;

const sizeType: SizeType = 'small';
const rowSelectionType: RowSelectionType = 'checkbox';

/* 或亦可如此,更爲简洁
{ 
	size: ('small' as SizeType),
	rowSelection: {
		type: 'checkbox' as RowSelectionType
	}
}
*/

// table setProps - FAST
const setTableProps = {
  dataSource: dataSourceList,
  columns: saColumns,
  reloadApiTable: {
      size: sizeType,
      rowSelection: {
        type: rowSelectionType,
        onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
          setCheckeds(prev => {
            return [...selectedRowKeys]
          });
        },
        getCheckboxProps: (record: any) => ({
          // disabled: record.name === 'Disabled User', // Column configuration not to be checked
          name: record.name,
        }),
      }
  }
}

// render
<BamTable { ...setTableProps } />
TreeAbundant - 丰富的树组件 (Tabs + Tree):

使用antd的 tree + tabs 组件拼搭的封装组件。

基本使用
import Bamboo, { Components } from 'bamboosnaketool';

const { TreeAbundant, TreeAbundant_enumEVENTTYPE, TreeAbundant_eventHandle } = Bamboo;

// 丰富树组件 - 全事件回调
const treeAbundant_eventHandle: TreeAbundant_eventHandle = (TYPE, data) => {
  if (TreeAbundant_enumEVENTTYPE.RADIO_CHANGE === TYPE) {
    let value = data;
  } else if (TreeAbundant_enumEVENTTYPE.TREE_CHECKED === TYPE) {
    let { checkedKeysValue, checkedNodes } = data;
  }
}

// 门店-丰富树props配置
const setTreeAbund = {
  treeDataSource: storeTreeDatasource,        // 树状嵌套数组数据
  defaultTreeExpandedKeys: storeTreeDatasource.length > 0 ? [storeTreeDatasource[0].key] : [],   // 默认展开yf首层级
  effectTreeCheckedKeys: storeConfrimCodes,   // array
  isShowTabs: false,                          // 是否显示tabs
  eventHandle: treeAbundant_eventHandle
};

// render ...
<TreeAbundant { ...setTreeAbund }/>

*为确保同步更新,effectTreeCheckedKeys必须始终配置,如果有没配置则填入空数组“[]”。

api参考

interface IProps  {
  dispatch: Dispatch;
  
  treeDataSource: any[];                    // tree组件数据
  eventHandle: intfEventHandle;             // 公共回调事件
  effectTreeCheckedKeys: React.Key[];       // 再次定义多选 (default: 'key')

  dataNodeKeyConf?: intfDataNodeKeyConf;    // 关键字段自定义配置 [title, key, children]
  isShowTabs?: boolean;                     // tab选项卡 - 是否显示
  tabsItems?: intfTabsItem[];               // tab选项卡渲染数据  [{ label: string, value: string }, ...]
  defaultRadioValue?: string;               // 默认radioValue
  defaultTreeExpandedKeys?: React.Key[];    // 设置默认展开 (default: 'key')
  defaultTreeCheckedKeys?: React.Key[];     // 设置默认多选 (default: 'key')
  defaultSelectedKeys?: React.Key[];        // 设置默认选中 (default: 'key')
  treeOptiosApis?: TreeProps;               // 树组件 apis - antd

  mainStyle?: React.CSSProperties;          // 主结构样式自定义
  tabBoxStyle?: React.CSSProperties;        // tab容器结构样式自定义
  treeBoxStyle?: React.CSSProperties;       // 树容器结构样式自定义
}
TableColumnsLayer - 表格列显示/隐藏组件
功能
  1. 动态变动表格列数据;
  2. 支持列数据存储/取赋值;
  3. 支持列数据拖动排序;
基本使用
  1. 给定表格列数据,此列数据需要可以变动
import Bamboo, { Utiles, Components } from 'bamboosnaketool';
import initColumn from './columns';			// 附属xx页面模块的表格配置

const { default: TableColumnsLayer } = Components;
const [ columns, setColumns ] = useState(initColumn);  // 声明

// 定义列变动通知方法
const onColumnChange = (data:any, close:any) => {
	// 选择的列数据
	let { columns, width } = data;

	// 如果操作列单独拿出来的,需要手动放进去
	setColumns([...columns, ACTION_OPERATION]);

	// 关闭列选择弹出窗
	close();
}

// JSX中渲染注册触发事件按钮,及参数配置项
<TableColumnsLayer columns={ columns } onChange={ onColumnChange }>
	<Button type="primary" ghost>
	  列选择
	</Button>
</TableColumnsLayer>
配置项
配置项类型说明默认值
columns数组表格的列数据必填
onChange函数回调函数,会回调给用户2个参数,一个data(包含columns和width),一个是关闭弹出窗口的方法必填
tableref默认插件会自己去找table,用户也可以传入table的ref,这样会更加准确非必填
needStorage布尔值是否存储此次操作的列数据,如果存储,下次进来会读取存储中的数据false
注意事项
  1. 未传入ref,组件会自己去找,多个表格的情况有可能会出错,最好把表格的ref传入
  2. 如果操作列没有放到column,而是后面加入,那么回调方法中回调的列数据不包含操作列,需要手动把列数据放入,否则可能会出现操作列不见的情况
FileUploadExcelToData - 上传文件组件

内置生成一个button按钮,点击该按钮后弹窗windows-fileSelectWindow(windows系统自带文件选择窗口); 可以配置一些导入数量的相关数量限制tips阻断;

基本使用
import Bamboo, { Components, FileUploadExcelToData_callback } from 'bamboosnaketool';

const { FileUploadExcelToData } = Bamboo;
// 或 const { FileUploadExcelToData } = Components;

const excelAnaylsDataDispose: FileUploadExcelToData_callback = (
    data,
    options
) => {
    const { file, onSuccess, onError } = options;
}
/*
import { UploadRequestOption } from 'rc-upload/lib/interface';
(
    data: { jsonData: any[], excelColumns: string[] },
    options: UploadRequestOption
) => any
*/

// 文件上传props
const fileExcelUploadProps = {
    isLoading: isLoadingImportExcel,
    isDisabled: isLockEdit,
    buttonText: '上传文件',
    singleUploadMaxLength: 100000,
    slowMaybeMaxLength: 100000,
    callback: excelAnaylsDataDispose,
    uploadApiOptions: {
        multiple: false,
        maxCount: 1
    },
    buttonApiOptions: {
        icon: <UploadOutlined />
    }
};

// render...
<FileUploadExcelToData { ...fileExcelUploadProps } />
    
// 前置拦截功能待未来实现 (比如点击不是直接选择文件,而是判断条件)
功能

校验导入的文件类型MIME是否正确;

将导入文件的二进制类型转换为可读JSON数据对象;

对超出预设数量的导入操作进行拦截/提示;

自定义按钮;

配置项说明
配置项类型说明默认值必填
isDisabledboolean是否禁用false
isLoadingboolean是否是载入状态false
fileTypeStr{ override: boolean; // 是否覆盖 names: string[]; // 文件类型编码}文件类型编码,MIME{ false, [] }
singleUploadMaxLengthnumber单次上传最大数量,超过限制时候会msg.warn + 逻辑阻断;3000
slowMaybeMaxLengthnumber执行时可能较慢提示,modal.confrim,确认后再执行2000
buttonTextstring按钮文本"导入"
callbackfunctionnull
uploadApiOptions{ ...antd-options }antd-Upload,的相关再定义api。(不要覆盖customRequest){}
buttonApiOptions{ ...antd-options }antd-Button,的相关再定义api。{ type: 'primary', ghost: true, title: '选择一份excel文件' }
ImportFileExcelDataVerifyTool - Excel导入数据校验与转换

场景:想把excel的中文列头转成英文字段并且拿到转换后的数组,key是excel的列头中文,toKey是需要将原来的中文列头转成什么字段,type表示列值的类型,如果设置了type,那么转换后的数据类型会转成设置的type类型,类型枚举可使用ExcelType,目前包括的枚举(STRING, NUMBER, BOOLEAN, DATETIME, DATE, TIME, FLOAT)

基本使用
import Bamboo, { Utiles, Components } from 'bamboosnaketool';
const { ImportFileExcelDataVerifyTool } = Bamboo; 
const { ExcelRule } = Utiles;

new importFileExcelDataVerifyTool({
  file: ev.target.files[0], //file对象 必传
  columns: [
    { 
      key: "列名比如商品编码", 
      type: ExcelType.STRING, //数据类型,用于转换数据,具体类型可以查看ExcelType枚举
      toKey: "把列头字段转换成的字段比如goodCode", 
    },
  ],
})
.valid() // 开始进行数据校验 必须有
.then((res)=>{
  // 没有任何错误,将返回数据
  // res 为 { total: 总行数, data: 数据数组 }
  console.log(res);
})
设置预警值

场景:有限定用户excel数据量大小,并且需要告知用户数据量已经超出询问用户是否继续导入

import Bamboo, { Utiles, Components } from 'bamboosnaketool';
const { ImportFileExcelDataVerifyTool } = Bamboo; 
const { ExcelRule } = Utiles;

new importFileExcelDataVerifyTool({
  file: ev.target.files[0], //file对象 必传
  columns: [
    { 
      key: "列名比如商品编码", 
      type: 'string|number|boolean|float', //数据类型,用于转换数据
      toKey: "把列头字段转换成的字段比如goodCode", 
    },
  ],
})
.warn((msg, confirm, cancel)=>{
  //配置了warn字段必须加
  // confirm() // 调用此函数表示继续操作
  // cancel() // 此函数表示取消操作
})
.valid() // 开始进行数据校验 必须有
.then((res)=>{
  // 没有任何错误,将返回数据
  // res 为 { total: 总行数, data: 数据数组 }
  console.log(res);
})
设置起始行

场景:需要从指定的excel行开始读取数据,可以设置startIndex配置,默认从第一行开始

import Bamboo, { Utiles, Components } from 'bamboosnaketool';

const { ImportFileExcelDataVerifyTool } = Bamboo; 
const { ExcelRule } = Utiles;

importFileExcelDataVerifyTool({
  startIndex:0, //开始行数
  file: ev.target.files[0], //file对象 必传
  columns: [
    { 
      key: "列名比如商品编码", 
    },
  ],
})
.valid()
.then((res)=>{
  // 没有任何错误,将返回数据
  // res 为 { total: 总行数, data: 数据数组 }
  console.log(res);
})
验证

场景:需要对上传的excel数据字段值进行校验,插件本身提供内置的验证,具体可使用插件暴露出的ExcelRule枚举查看,如果想要自己定义验证,可以使用ExcelRule.Customer,然后定义valid回调函数,回调函数会传入读取到的行数据(注:如果定义了转换的key,那么读取到的行数据中的列字段是经过转换的,不是excel显示的中文),回调函数如果返回true,表示验证通过,验证结束后,错误的验证消息会通过catch方法来进行捕捉,错误的消息可以通过配置msg字段来提示,如果不配置插件会使用自己定义的消息提示,一般来说如果使用了内置的验证会提示内置的验证提示,比如使用身份证号验证将默认提示xxx必须是身份证号,没有的内置验证将提示xxx格式错误

import Bamboo, { Utiles, Components } from 'bamboosnaketool';
const { ImportFileExcelDataVerifyTool } = Bamboo; 
const { ExcelRule } = Utiles;

new importFileExcelDataVerifyTool({
  file: ev.target.files[0], //file对象 必传
  columns: [
    { 
      key: "列名比如商品编码", 
      toKey: "把列头字段转换成的字段比如goodCode", 
      rules:[
        {type:ExcelRule.CUSTOMER, valid:(row)=>{return row.goodCode=='a'}, msg:'自定义错误消息'}, //自定义校验
        {type:ExcelRule.NUMBER, msg:'商品编码必须是数字'}, //使用内部校验规则 可以使用 ExcelRule枚举提供的校验规则
      ]
    },
  ],
})
.valid() // 开始进行数据校验 必须有
.then((res)=>{
  // 没有任何错误,将返回数据
  // res 为 { total: 总行数, data: 数据数组 }
  console.log(res);
}).catch((error)=>{ 

  // 1.校验规则错误 2.interceptor设置的函数返回false
  console.log(error); // {msg:}
})
手动添加数据

场景:1. 需要在行数据上添加字段 2. 想要遍历指定字段然后中断读取操作 可以使用interceptor方法,此方法会回调给用户行数据和验证过后的错误消息,此函数如果返回false则表示中断excel读取操作,如果返回false此函数会触发catch回调

new importFileExcelDataVerifyTool({
  file: ev.target.files[0], //file对象 必传
  columns: [
    { 
      key: "列名比如商品编码", 
      type: 'string|number|boolean|float', //数据类型,用于转换数据
      toKey: "把列头字段转换成的字段比如goodCode", 
    },
  ],
})
.interceptor((rowData:any, err:[])=>{
  // 此方法不是必需要要有的,如果有数据转换或者要在行数据里面加些字段可以使用
  // 使用interceptor来进行数据装换, data为行数据
  rowData.field = Math.random();
  console.log(rowData);

  // return false直接中断操作抛出异常
  return true;
})
.valid() // 开始进行数据校验 必须有
.then((res)=>{
  // 没有任何错误,将返回数据
  // res 为 { total: 总行数, data: 数据数组 }
  console.log(res);
}).catch((error)=>{ 

  // 1.校验规则错误 2.interceptor设置的函数返回false
  console.log(error); // {msg:}
})
配置项说明
配置项类型说明默认
filefile对象file对象必填
sheet数字excel中如果有多个工作簿,可以通过sheet配置项来配置读取第几个工作簿的数据0
isDebugger布尔是否显示警告在控制台中false
mimeType数组设置可以读取的文件类型,默认"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
max数字最大行数500
warn数字预警行数500
columns数组配置excel列必填
columns.key字符串列名比如商品编码
columns.typeExcelType列名类型,描述此列数值的类型
columns.toKey字符串重命名key,比如key为商品编码,toKey为goodCode,那么导出的数据就是{goodCode:''}这种形式
columns.rules数组rules:{type:ExcelRule.CUSTOMER, valid:(row)=>{return row.goodCode=='a'}, msg:'自定义错误消息'},{type:ExcelRule.NUMBER, msg:'商品编码必须是数字'} 注:msg可以为空,如果为空则会采用插件自带的提示 验证类型的可使用ExcelRule的枚举类型
方法说明
方法说明
valid校验方法,必须调用
warn配置了warn字段必须调用,会有三个参数回调给用户(msg, confirm, cancel) msg为消息,confirm为用户继续操作,cancel表示取消数据读取
interceptor此方法会回调给用户当前行数据和错误消息,如果用户在此方法中返回false,会中断读取数据操作,并且会调用catch抛出异常
then数据读取完成无错误会回调给用户一个对象,{ total: 总行数, data: 数据数组 }
catch数据读取中如果有校验错误或者在intercepotor里返回false都会触发此方法,并回调给用户一个对象{type:'error', msg:'错误消息'}
枚举类型
类型说明
ExcelRule内置规则枚举(NUMBER整数验证 MOBILE手机号验证 IDCARD身份证验证 NUMERIC小数验证 NOT_EMPTY非空验证 CUSTOMER自定义验证)
ExcelType列类型枚举(STRING字符串 NUMBER整数数值包括负数 FLOAT小数 BOOLEAN布尔类型 DATETIME年月日时分秒格式 DATE年月日格式 TIME时分秒格式)
FreeCustomRowLine - 增减行自由定义配置组件

支持能预先自由配置各种元素组合的单行模板(文字/ antd组件/ 自定义元素...)。 根据配置的单行模板,能够去生成n行该模板的独立数据控制,排序样式,单元素填值时的基础校验,最终生成表单数据。 同时支持单行的删除。(未来会支持 up down调序)。

基本使用

如下述示例,组成的是支持整单满[?]元,赠送[x?],[number?]的设置项;

import Bamboo, { FreeCustomRowLine, FreeCustomRowLine_EVENTTYPE, FreeCustomRowLine_ELEMENTTEMP } from 'bamboosnaketool';
const { FreeCustomRowLine } = Bamboo;

// 配置增减行
const freeCustomRowLineConfProps = {
  templateRow: [
    {
        elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INNERHTML,
        style: { width: 38, marginRight: 8 }
    },
    '整单满',
    {
      elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INPUTNUMBER,
      placeholder: '价格',
      maxLength: 8,
      style: { width: 100, marginLeft: 4, marginRight: 4 },
      apiOptions: {
        min: 0
      }
    },
    '元,赠送',
    {
      elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INPUT,
      placeholder: '搜索商品',
      maxLength: 10,
      style: { width: 200, marginLeft: 4, marginRight: 4 }
    },
    ',',
    {
      elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INPUTNUMBER,
      placeholder: '数量',
      maxLength: 4,
      style: { width: 90, marginLeft: 4, marginRight: 4 },
      apiOptions: {
        min: 0
      }
    }
  ],
  callback: (TYPE: EVENTTYPE, data: any) => {
    console.log(TYPE, data)
  }
};

// render...
<FreeCustomRowLine {...freeCustomRowLineConfProps} />

如何简单理解上述呢?templateRow 是确定单行模板的如下:

// 支持类型
{
    templateRow: [ string文本 | 内置模板对象 | JSX.Element ]
}

最终组件内部会平铺该数组内的所有内容组成完整的单行预模板;

初始化插入数据

支持通过数据对组件进行首次初始化的渲染生成。

下面的配置会初始化生成下列文本(由于无法附图,文字描述):

条件1:整单满 inputNumber 元,赠送 input-autocomplate ,数量 inputNumber 责任人 select,备注:input

import Bamboo, { FreeCustomRowLine_ELEMENTTEMP } from 'bamboosnaketool';

const __awardLineMockDatas = [
  [
    { "value": `<span style="padding: 1px 5px; color: #fff; background-color: #FF7157; display: inline-block">类型</span>` },
    { "value": 128, disabled: true },
    {
      "value": "异步2",
      "valueId": "async2",
      "options": [
        { "value": "async1", "label": "异步1" },
        { "value": "async2", "label": "异步2" },
        { "value": "async3", "label": "异步3" }
      ]
    },
    { "value": 1 },
    { "value": "选项1", "valueId": "a1" },
    { "value": "撒大大" }
  ]
];

// 配置增减行
const shoppingAwardGiftConfProps = {
  datasource: __awardLineMockDatas,
  templateRow: [
      {
        elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INNERHTML,
        style: { width: 38, marginRight: 8 }
      },
      '整单满',
      {
        stamp: 'PRICE',
        elementTEMP: ELEMENTTEMP.INPUTNUMBER,
        placeholder: '价格',
        maxLength: 8,
        style: { width: 100, marginLeft: 4, marginRight: 4 },
        apiOptions: {
          min: 0
        }
      },
      '元,赠送',
      {
        stamp: 'SEARCH_GOODS',
        elementTEMP: ELEMENTTEMP.AUTOCOMPLETE,
        placeholder: '搜索商品',
        maxLength: 10,
        style: { width: 200, marginLeft: 4, marginRight: 4 }
      },
      ',数量',
      {
        stamp: 'NUMBER',
        elementTEMP: ELEMENTTEMP.INPUTNUMBER,
        placeholder: '数量',
        maxLength: 4,
        style: { width: 90, marginLeft: 4, marginRight: 4 },
        apiOptions: {
          min: 0
        }
      },
      '责任人',
      {
        stamp: 'PRINCIPAL',
        elementTEMP: ELEMENTTEMP.SELECT,
        placeholder: '选择性',
        style: { width: 90, marginLeft: 4, marginRight: 4 },
        options: [
          { value: 'fzr1', label: '吴亦凡' },
          { value: 'fzr2', label: '鹿晗' },
          { value: 'fzr3', label: '黄子韬' }
        ],
        apiOptions: {}
      },
      ',备注:',
      {
        stamp: 'REMARK',
        elementTEMP: ELEMENTTEMP.INPUT,
        placeholder: '请输入备注内容',
        maxLength: 10,
        style: { width: 200, marginLeft: 4, marginRight: 4 }
      }
  ]
}
顶部按钮组

组件内置了顶部按钮组 ,该组可配置 新增一行重置自定义;

其中 新增一行重置为内置,组件Props { showAddButton, showResetButton } 为 true 时显示,否则隐藏;

同时也支持配置多个自定义按钮,通过配置后可以更灵活的实现应用。

// 可通过配置属性 customManaButtons 实现
import Bamboo, { FreeCustomRowLine_ManaButton } from 'bamboosnaketool';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
let freeCustomRowLineConfProps = {
    ...,
    customManaButtons: [
    	{
    		text: '自定义按钮1',
    		type: FreeCustomRowLine_ManaButton.CUSTOM,
    		stamp: 'ANY_NAME_1',
    		style: {
    			color: 'red'
			},
    		apiOptions: {
                icon: <PlusOutlined/>
            }
		},
        {
    		text: '自定义按钮2',
    		type: FreeCustomRowLine_ManaButton.CUSTOM,
    		stamp: 'ANY_NAME_2',
    		style: {
    			color: 'yellow'
			},
    		apiOptions: {
                icon: <DeleteOutlined/>
            }
		}
    ]
}

// render...
<FreeCustomRowLine {...freeCustomRowLineConfProps} />
// 事件响应回调 - 顶部按钮组

import Bamboo, { FreeCustomRowLine_ManaButtonProps } from 'bamboosnaketool';
let freeCustomRowLineConfProps = {
    ...,
    buttonsCallback: (TYPE: FreeCustomRowLine_ManaButton, data?: Pick<FreeCustomRowLine_ManaButtonProps, 'stamp'>) => {
        if (TYPE === FreeCustomRowLine_ManaButton.CUSTOM) {
            // 这里使用配置的stamp值进行多个自定义按钮情况的区别
            let stamp = data?.stamp;
            // do somethings ...
        }
    }
};
选项的构成 - options

我们把配置项中 templateRow 里的每一个索引内容称之为 '单元'。

options配置合法字段使用 { value: any, label: string };

那么单元目前除String文本外,就都是antd的封装组件了,其中 autocomplate、selecet都会是涉及到多选的options选项,那么这个怎么配?

下拉选项首先我们分为两种 初始配置异步设置 的,这两者带来的方式上也是不一样的,初始配置的只需配置一次覆盖所有生成行,异步设置的可以支持到 每一行的都不一样;

初始配置:适合固定的选择内容,比如每行都需要指定的选择 '负责人',而关联这次数据的的所有负责人池一共8人,那么原则上所有行的下拉负责人选项都可以是一致的;

异步配置:适合非固定选择内容,比如每行都需要模糊搜索到某个指定的内容,然后从模糊匹配到的下拉结果中选出一项,那么每一行最终选中的对象可能都不一致;

(!!!异步配置优先级高于初始配置,故异步配置动作是后置于初始配置,最终数据的渲染显示会是异步配置动作为准)

**初始配置

// 方式一  -  初始配置
const shoppingAwardGiftConfProps = {
  templateRow: [
    '选择责任人',
    {
      stamp: 'PRINCIPAL',
      elementTEMP: ELEMENTTEMP.SELECT,
      placeholder: '请选择负责人',
      style: { width: 90, marginLeft: 4, marginRight: 4 },
      options: [
        { value: 'fzr1', label: '吴亦凡' },
        { value: 'fzr2', label: '鹿晗' },
        { value: 'fzr3', label: '黄子韬' }
      ],
      apiOptions: {}
    }
 ],
 // other - config
}

**异步配置

异步的数据插入是根据行的,需要在回调内进行插入。

下面模拟在INPUT框输入后,模拟请求获取XX数据,使用内置函数options进行插入。

(!!!函数 slotFn ,在部分回调事件中不存在,只在具体行被操作后的响应动作存在,这是一个闭包函数,内置包裹了当前操作的rowIndex行索引和rItemIndex单元索引,直接对其进行数据操作插入即可)

import { FreeCustomRowLine_EVENTTYPE, FreeCustomRowLine_callBackProps } from 'bamboosnaketool';

// 方式二  -  异步配置
const shoppingAwardGiftConfProps = {
  templateRow: [
    '选择商品',
    {
        stamp: 'SEARCH_GOODS',
        elementTEMP: ELEMENTTEMP.AUTOCOMPLETE,
        placeholder: '搜索商品',
        maxLength: 10,
        style: { width: 200, marginLeft: 4, marginRight: 4 }
    }
 ],
 callback: (TYPE: FreeCustomRowLine_EVENTTYPE, data: FreeCustomRowLine_callBackProps) => {
     
     const { stamp, slotFn } = data;
     
     if (TYPE === FreeCustomRowLine_EVENTTYPE.INPUT) {
         if (stamp === 'SEARCH_GOODS') {
          let arr = [
            { value: 'async1', label: '异步1'},
            { value: 'async2', label: '异步2'},
            { value: 'async3', label: '异步3'}
          ];
          slotFn && slotFn({ options: arr });
        }
     }
 }
 // other - config
}
内置模板

演示内置模板单元的使用;

[
	...,
    {
      elementTEMP: FreeCustomRowLine_ELEMENTTEMP.INPUTNUMBER,
      placeholder: '数量',
      maxLength: 4,
      style: { width: 90, marginLeft: 4, marginRight: 4 },
      apiOptions: {
         min: 0
      }
    }
]

// 内置的模板 elementTEMP 作为它的type类型指定,可通过枚举 ELEMENTTEMP 来选取指定 (完整的单模板内容参考下面#完整参数类型)
// 目前支持 几种应用场景
// 普通输入框、纯数字输入框、支持搜索下拉的自动完成输入框、和普通文本SPAN标签元素,如下
enum FreeCustomRowLine_ELEMENTTEMP {
  INPUT,             // 输入框 - 普通
  INPUTNUMBER,       // 输入框 - 数字
  AUTOCOMPLETE,      // 输入框 - 自动完成
  SELECT,            // 下拉选择项
  SPAN,              // 文本内容
  INNERHTML          // 元素方式
}

// 根据对元素类型的规范理解使用 [placeholder/ maxLength/ value ];
// [style] 为通用类型,可对样式进行实际展示需要微调;
// apiOptions - 是antd的内置API支持,这些单元模板枚举完整的对应了antd的实际组件,具体可参考官方文档;
配置按钮

配置单行模板后缀(右侧)的控制按钮组。

支持删除一行,锁定一行(貌似没有需求会用到,暂未开发),向上移动一行,向下移动一行,

import { FreeCustomRowLine_LINEBUTTONS } from 'bamboosnaketool';

// 默认仅显示[删除]按钮,目前完整按钮包含4个 [ 删除/ 锁定行/ 向上移动/ 向下移动 ]
{
  lineButtons: [
  	 [ true,  { type: FreeCustomRowLine_LINEBUTTONS.DELETE }],
  	 [ false, { type: FreeCustomRowLine_LINEBUTTONS.LOCK }],
  	 [ false, { type: FreeCustomRowLine_LINEBUTTONS.MOVE_UP }],
  	 [ false, { type: FreeCustomRowLine_LINEBUTTONS.MOVE_DOWN }]
  ],
  // ...
}
// 为 true 则显示反之隐藏,渲染顺序会根据当前配置内的数组排序为准

// 按钮会默认赋值内置的中文文本名称,也可以对text属性进行自定义,例↓:
[ true, { type: FreeCustomRowLine_LINEBUTTONS.DELETE, text: '剔除' }]

// 如需隐藏文本名称,可使用 hideText 配置如下:
[ true, { type: FreeCustomRowLine_LINEBUTTONS.DELETE, hideText: true }]
    
// 如何给按钮配置icon,且能同时结合 hideText 达到效果  (apiOptions - 参考antd组件api)
import { DeleteOutlined } from '@ant-design/icons';
[ true, { type: FreeCustomRowLine_LINEBUTTONS.DELETE, hideText: true, apiOtions: { icon: <DeleteOutlined>} }]
禁用

对单元进行禁用属性设置。(仅对支持disabled的对象有效)

方式有两种:渲染数据内 / 初始配置;

{ disabled: true }

回调响应

所有事件的响应都会callback统一抛出,可根据返回的行号几行内单元号进行具体的需求操作。

import { FreeCustomRowLine_callBackProps } from 'bamboosnaketool';
const freeCustomRowLineConfProps = {
  templateRow: [
    ...
  ],
  callback: (TYPE: FreeCustomRowLine_EVENTTYPE, data: FreeCustomRowLine_callBackProps) => {
    const { allLineDatas, rowIndex, valIndex, value, slotFn?, prevAllLineDatas?, stamp? } = data;
    console.log(TYPE, data);
  }
};

// 回调响应事件类型
export enum FreeCustomRowLine_EVENTTYPE {
  INIT  ='INIT',        // 初始化
  INPUT = 'INPUT',      // 输入响应
  SELECT = 'SELECT',    // 选中
  ADD   = 'ADD',        // 新增一行
  DELETE = 'DELETE',    // 删除一行
  FOCUS = 'FOCUS',      // INPUT类型聚焦
  BLUR  = 'BLUR'        // INPUT类型失焦
}

// 响应回调返回的data数据
data = {
  allLineDatas,		// 完整的数据
  rowIndex,			// 行的下标
  valIndex,         // 单行内的下标
  value,			// 响应 value / id
  slotFn,           // 行数据设置函数 - 闭包(直接设参,内置精确索引)
  stamp				// 印章 - 可用户多一种途径区别单元类型
}
完整参数类型
// 控件模板类型(antd)
export enum FreeCustomRowLine_ELEMENTTEMP {
  INPUT,             // 输入框 - 普通
  INPUTNUMBER,       // 输入框 - 数字
  AUTOCOMPLETE,      // 输入框 - 自动完成
  SELECT,            // 下拉选择项
  SPAN,              // 文本内容
  INNERHTML          // 元素方式
}

// 模板单item配置
export interface templateRowItemConf {
  elementTEMP: FreeCustomRowLine_ELEMENTTEMP; // 模板类型
  stamp?: string;                    // 印记 - 可用于回调事件做单元区别(多提供一种定位单元手动,处index定位外)
  text?: string;                            // 文本
  value?: string | number;                  // inputValue -  仅input [select/ input/ textarea/ ...]类型支持
  options?: any[];                          // select-options / autocomplate-options (仅支持antd - select/ autocomplate 组件)
  disabled?: boolean;                       // 是否禁用该单元
  style?: CSSStyleDeclaration | {};         // 样式
  placeholder?: string;                     // 框内描述      - 仅input类型支持
  maxLength?: string | number;              // 最大输入长度  -  仅input类型支持
  apiOptions?: {[x:string]: any}            // antd-ui api
}

// 完整单行描述
export type templateRows = Array<templateRowItemConf | string | JSX.Element>

// 回调响应事件类型
export enum FreeCustomRowLine_EVENTT
1.3.10

2 years ago

1.2.8

2 years ago

1.2.7

2 years ago

1.3.9

2 years ago

1.3.8

2 years ago

1.3.7

2 years ago

1.3.6

2 years ago

1.3.5

2 years ago

1.3.4

2 years ago

1.3.3

2 years ago

1.3.2

2 years ago

1.3.0

2 years ago

1.2.9

2 years ago

1.2.0

2 years ago

1.12.3

2 years ago

1.12.2

2 years ago

1.12.1

2 years ago

1.12.0

2 years ago

1.12.5

2 years ago

1.12.4

2 years ago

1.2.6

2 years ago

1.2.5

2 years ago

1.2.4

2 years ago

1.2.3

2 years ago

1.2.2

2 years ago

1.2.1

2 years ago

1.10.30

2 years ago

1.10.31

2 years ago

1.10.32

2 years ago

1.10.26

2 years ago

1.10.27

2 years ago

1.10.25

2 years ago

1.10.28

2 years ago

1.10.29

2 years ago

1.10.24

2 years ago

1.10.22

2 years ago

1.10.23

2 years ago

1.10.20

2 years ago

1.10.21

2 years ago

1.10.15

2 years ago

1.10.16

2 years ago

1.10.19

2 years ago

1.10.17

2 years ago

1.10.18

2 years ago

1.10.13

3 years ago

1.10.14

3 years ago

1.10.12

3 years ago

1.10.9

3 years ago

1.10.11

3 years ago

1.10.10

3 years ago

1.10.8

3 years ago

1.10.7

3 years ago

1.10.6

3 years ago

1.10.5

3 years ago

1.10.4

3 years ago

1.10.3

3 years ago

1.10.2

3 years ago

1.10.1

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.10.0

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago