import React from 'react';
import { findDOMNode } from 'react-dom';
import { Renderer, RendererProps } from '../../factory';
import { SchemaNode, Action, Schema } from '../../types';
import forEach from 'lodash/forEach';
import { filter } from '../../utils/tpl';
import './ColumnToggler';
import Checkbox from '../../components/Checkbox';
import Button from '../../components/Button';
import { TableStore, ITableStore, IColumn, IRow } from '../../store/table';
import { saveAs } from 'file-saver';
import {
  anyChanged,
  getScrollParent,
  difference,
  noop,
  autobind,
  isArrayChildrenModified,
  getVariable,
  removeHTMLTag,
  eachTree,
  isObject,
  createObject,
  isMobile
} from '../../utils/helper';
import {
  isPureVariable,
  resolveVariable,
  resolveVariableAndFilter
} from '../../utils/tpl-builtin';
import debounce from 'lodash/debounce';
import Sortable from 'sortablejs';
import { resizeSensor } from '../../utils/resize-sensor';
import find from 'lodash/find';
import { Icon } from '../../components/icons';

import { HeadCellFilterDropDown } from './HeadCellFilterDropdown';
import { HeadCellSearchDropDown } from './HeadCellSearchDropdown';
import { HeadCellBatchEditDropdown } from './HeadCellBatchEditDropdown';
import { TableContent } from './TableContent';
import {
  BaseSchema,
  SchemaApi,
  SchemaClassName,
  SchemaCollection,
  SchemaObject,
  SchemaTokenizeableString
} from '../../Schema';
import { SchemaPopOver } from '../PopOver';
import { SchemaQuickEdit } from '../QuickEdit';
import { SchemaCopyable } from '../Copyable';
import { SchemaRemark } from '../Remark';
import { toDataURL, getImageDimensions } from '../../utils/image';
import { TableBody } from './TableBody';
import { TplSchema } from '../Tpl';
import { MappingSchema } from '../Mapping';
import { isAlive, getSnapshot } from 'mobx-state-tree';
import ColumnToggler, { Temp } from './ColumnToggler';
import { BadgeSchema } from '../../components/Badge';
import offset from '../../utils/offset';
import cloneDeep from 'lodash/cloneDeep';
import { ContextMenu } from '../Lion/components/LionContextMenu';
import isEqual from 'lodash/isEqual';
import Tooltip from 'antd/lib/tooltip';
import { Drawer, message, Button as AntBtn, Modal } from 'antd';
import { Shell } from '../../utils/shell';
import { tools } from '../../utils/shell/tools';
import { MessageType } from 'antd/lib/message';
import { exchangeType } from '../../utils/utils';
import throttle from 'lodash/throttle';
import { EventEnum, EventSub } from '../../utils/sub';

/**
 * 表格列，不指定类型时默认为文本类型。
 */
export type TableColumnObject = {
  /**
   * 列标题
   */
  label: string;

  /**
   * 配置是否固定当前列
   */
  fixed?: 'left' | 'right' | 'none';

  /**
   * 绑定字段名
   */
  name?: string;

  /**
   * 配置查看详情功能
   */
  popOver?: SchemaPopOver;

  /**
   * 配置快速编辑功能
   */
  quickEdit?: SchemaQuickEdit;

  /**
   * 作为表单项时，可以单独配置编辑时的快速编辑面板。
   */
  quickEditOnUpdate?: SchemaQuickEdit;

  /**
   * 配置点击复制功能
   */
  copyable?: SchemaCopyable;

  /**
   * 配置是否可以排序
   */
  sortable?: boolean;

  /**
   * 是否可快速搜索
   */
  searchable?: boolean | SchemaObject;

  /**
   * 配置是否默认展示
   */
  toggled?: boolean;

  /**
   * 列宽度
   */
  width?: number | string;

  /**
   * 列对齐方式
   */
  align?: 'left' | 'right' | 'center' | 'justify';

  /**
   * 列样式表
   */
  className?: string;

  /**
   * 单元格样式表达式
   */
  classNameExpr?: string;

  /**
   * 列头样式表
   */
  labelClassName?: string;

  /**
   * todo
   */
  filterable?:
  | boolean
  | {
    source?: string;
    options?: Array<any>;
  };

  /**
   * 结合表格的 footable 一起使用。
   * 填写 *、xs、sm、md、lg指定 footable 的触发条件，可以填写多个用空格隔开
   */
  breakpoint?: '*' | 'xs' | 'sm' | 'md' | 'lg';

  /**
   * 提示信息
   */
  remark?: SchemaRemark;

  /**
   * 默认值, 只有在 inputTable 里面才有用
   */
  value?: any;

  /**
   * 是否唯一, 只有在 inputTable 里面才有用
   */
  unique?: boolean;

  /**
   * 配置过长是否需要隐藏
   */
  ellipsis?: boolean;
};

export type TableColumnWithType = SchemaObject & TableColumnObject;
export type TableColumn = TableColumnWithType | TableColumnObject;

/**
 * Table 表格渲染器。
 * 文档：https://baidu.gitee.io/amis/docs/components/table
 */
export interface TableSchema extends BaseSchema {
  /**
   * 指定为表格渲染器。
   */
  type: 'field-table-horizontal';

  /**
   * 是否固定表头
   */
  affixHeader?: boolean;

  /**
   * 表格的列信息
   */
  columns?: Array<TableColumn>;

  /**
   * 展示列显示开关，自动即：列数量大于或等于5个时自动开启
   */
  columnsTogglable?: boolean | 'auto';

  /**
   * 是否开启底部展示功能，适合移动端展示
   */
  footable?:
  | boolean
  | {
    expand?: 'first' | 'all' | 'none';

    /**
     * 是否为手风琴模式
     */
    accordion?: boolean;
  };

  /**
   * 底部外层 CSS 类名
   */
  footerClassName?: SchemaClassName;

  /**
   * 顶部外层 CSS 类名
   */
  headerClassName?: SchemaClassName;

  /**
   * 占位符
   */
  placeholder?: string;

  /**
   * 是否显示底部
   */
  showFooter?: boolean;

  /**
   * 是否显示头部
   */
  showHeader?: boolean;

  /**
   * 数据源：绑定当前环境变量
   */
  source?: SchemaTokenizeableString;

  /**
   * 表格 CSS 类名
   */
  tableClassName?: SchemaClassName;

  /**
   * 标题
   */
  title?: string;

  /**
   * 工具栏 CSS 类名
   */
  toolbarClassName?: SchemaClassName;

  /**
   * 合并单元格配置，配置数字表示从左到右的多少列自动合并单元格。
   */
  combineNum?: number;

  /**
   * 合并单元格配置，配置从第几列开始合并。
   */
  combineFromIndex?: number;

  /**
   * 顶部总结行
   */
  prefixRow?: Array<SchemaObject>;

  /**
   * 底部总结行
   */
  affixRow?: Array<SchemaObject>;

  /**
   * 是否可调整列宽
   */
  resizable?: boolean;

  /**
   * 行样式表表达式
   */
  rowClassNameExpr?: string;

  /**
   * 行角标
   */
  itemBadge?: BadgeSchema;

  /**
   * 开启查询区域，会根据列元素的searchable属性值，自动生成查询条件表单
   */
  autoGenerateFilter?: boolean;
  // Jay
  // 保存列配置接口
  saveColApi?: SchemaApi;
  columnInfo?: Record<string, { index: number, hidden: number }>; // 列排序、隐藏配置
  /**
   * 表格布局方式
   */
  tableLayout: 'vertical' | 'horizontal';
  /**
* 为当前表单添加排序
*/
  showIndex?: boolean;
}

export interface TableProps extends RendererProps {
  title?: string; // 标题
  header?: SchemaNode;
  footer?: SchemaNode;
  statistics?: SchemaCollection;
  actions?: Action[];
  className?: string;
  headerClassName?: string;
  footerClassName?: string;
  store: ITableStore;
  columns?: Array<any>;
  headingClassName?: string;
  toolbarClassName?: string;
  headerToolbarClassName?: string;
  footerToolbarClassName?: string;
  tableClassName?: string;
  source?: string;
  selectable?: boolean;
  selected?: Array<any>;
  maxKeepItemSelectionLength?: number;
  valueField?: string;
  draggable?: boolean;
  columnsTogglable?: boolean | 'auto';
  affixHeader?: boolean;
  affixColumns?: boolean;
  combineNum?: number | string;
  combineFromIndex?: number;
  footable?:
  | boolean
  | {
    expand?: 'first' | 'all' | 'none';
    expandAll?: boolean;
    accordion?: boolean;
  };
  expandConfig?: {
    expand?: 'first' | 'all' | 'none';
    expandAll?: boolean;
    accordion?: boolean;
  };
  itemCheckableOn?: string;
  itemDraggableOn?: string;
  itemActions?: Array<Action>;
  onSelect: (
    selectedItems: Array<object>,
    unSelectedItems: Array<object>
  ) => void;
  onPristineChange?: (data: object, rowIndexe: string) => void;
  onSave?: (
    items: Array<object> | object,
    diff: Array<object> | object,
    rowIndexes: Array<string> | string,
    unModifiedItems?: Array<object>,
    rowOrigins?: Array<object> | object,
    resetOnFailed?: boolean
  ) => void;
  onSaveOrder?: (moved: Array<object>, items: Array<object>) => void;
  onQuery: (values: object) => void;
  onImageEnlarge?: (data: any, target: any) => void;
  buildItemProps?: (item: any, index: number) => any;
  checkOnItemClick?: boolean;
  hideCheckToggler?: boolean;
  rowClassName?: string;
  rowClassNameExpr?: string;
  popOverContainer?: any;
  canAccessSuperData?: boolean;
  reUseRow?: boolean;
  itemBadge?: BadgeSchema;
  onLoadMore: () => void;
  setLoading?: (loading: boolean) => void;
  handleMutilSort?: (orderColumns: Map<string, any>, key: string | null, sort: 'asc' | 'desc' | null) => void;
  preSortAble?: boolean;
  infinteLoad?: boolean;
  renderFilter?: (val?: boolean) => JSX.Element | null;
  editStatus: boolean;
  removable?: boolean;//是否可删除
  originPrinstine?: any//表格原数据
  editIndex: number;//编辑中的行
  setEditIndex: (index: number) => void;
  tableLayput: 'vertical' | 'horizontal' | 'property-tiled' | 'line-tiled';
  setBorder?: boolean;
}

type ExportExcelToolbar = SchemaNode & {
  api?: SchemaApi;
  columns?: string[];
  filename?: string;
};

/**
 * 将 url 转成绝对地址
 */
const getAbsoluteUrl = (function () {
  let link: HTMLAnchorElement;
  return function (url: string) {
    if (!link) link = document.createElement('a');
    link.href = url;
    return link.href;
  };
})();

type CheckAllType = 0 | 1 | 2 | 3

interface TableState {
  position: [rowIndex: number, colName: string],
  contextMenuVisible: boolean,
  rowEditData: any,
  tableRotate: boolean,
  editLoading: boolean
}

export default class Table extends React.Component<TableProps, TableState> {
  static propsList: Array<string> = [
    'header',
    'headerToolbarRender',
    'footer',
    'footerToolbarRender',
    'footable',
    'expandConfig',
    'placeholder',
    'tableClassName',
    'headingClassName',
    'source',
    'selectable',
    'columnsTogglable',
    'affixHeader',
    'affixColumns',
    'headerClassName',
    'footerClassName',
    'selected',
    'multiple',
    'primaryField',
    'hideQuickSaveBtn',
    'itemCheckableOn',
    'itemDraggableOn',
    'checkOnItemClick',
    'hideCheckToggler',
    'itemAction',
    'itemActions',
    'combineNum',
    'combineFromIndex',
    'items',
    'columns',
    'valueField',
    'saveImmediately',
    'rowClassName',
    'rowClassNameExpr',
    'popOverContainer',
    'headerToolbarClassName',
    'toolbarClassName',
    'footerToolbarClassName',
    'itemBadge',
    'autoFillHeight'
  ];
  static defaultProps: Partial<TableProps> = {
    className: '',
    placeholder: 'placeholder.noData',
    tableClassName: '',
    source: '$items',
    selectable: false,
    // columnsTogglable: 'auto', //Aug
    columnsTogglable: false,
    affixHeader: true,
    headerClassName: '',
    footerClassName: '',
    toolbarClassName: '',
    headerToolbarClassName: '',
    footerToolbarClassName: '',
    primaryField: 'id',
    itemCheckableOn: '',
    itemDraggableOn: '',
    // hideCheckToggler: false,
    hideCheckToggler: isMobile(),//Aug
    footable: isMobile(), // Aug 移动端开启底部查看详情，默认只展示6列
    canAccessSuperData: false,
    resizable: true,
    tableLayout: 'vertical',
    infinteLoad: isMobile(),
    showIndex: false
  };

  table?: HTMLTableElement;
  sortable?: Sortable;
  dragTip?: HTMLElement;
  affixedTable?: HTMLTableElement;
  parentNode?: HTMLElement | Window;
  lastScrollLeft: number = -1;
  totalWidth: number = 0;
  totalHeight: number = 0;
  outterWidth: number = 0;
  outterHeight: number = 0;
  unSensor?: Function;
  updateTableInfoLazy: () => void;
  widths: {
    [propName: string]: number;
  } = {};
  widths2: {
    [propName: string]: number;
  } = {};
  heights: {
    [propName: string]: number;
  } = {};
  renderedToolbars: Array<string> = [];
  subForms: any = {};
  sortCols: any[];

  constructor(props: TableProps) {
    super(props);

    this.handleOutterScroll = this.handleOutterScroll.bind(this);
    this.affixDetect = this.affixDetect.bind(this);
    this.updateTableInfoLazy = debounce(this.updateTableInfo.bind(this), 250, {
      trailing: true,
      leading: true
    });
    this.tableRef = this.tableRef.bind(this);
    this.affixedTableRef = this.affixedTableRef.bind(this);
    this.handleAction = this.handleAction.bind(this);
    this.handleCheck = this.handleCheck.bind(this);
    this.handleCheckAll = this.handleCheckAll.bind(this);
    this.handleQuickChange = this.handleQuickChange.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleSaveOrder = this.handleSaveOrder.bind(this);
    this.reset = this.reset.bind(this);
    this.dragTipRef = this.dragTipRef.bind(this);
    this.getPopOverContainer = this.getPopOverContainer.bind(this);
    this.renderCell = this.renderCell.bind(this);
    this.renderHeadCell = this.renderHeadCell.bind(this);
    this.renderToolbar = this.renderToolbar.bind(this);
    this.renderStatistics = this.renderStatistics.bind(this);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.handleMouseLeave = this.handleMouseLeave.bind(this);
    this.subFormRef = this.subFormRef.bind(this);
    this.handleColumnToggle = this.handleColumnToggle.bind(this);
    this.renderAutoFilterForm = this.renderAutoFilterForm.bind(this);
    this.updateAutoFillHeight = this.updateAutoFillHeight.bind(this);
    this.handleColumns.bind(this);
    this.handleMultiColumnSort = this.handleMultiColumnSort.bind(this)
    this.props.getTableStore?.(this.props.store)
    this.props.getTableInstance?.(this)
    this.showContextMenu = this.showContextMenu.bind(this)
    this.handleEditRow = this.handleEditRow.bind(this);
    this.handleComfirm = throttle(this.handleComfirm.bind(this), 1000, { leading: true, trailing: false });
    this.state = {
      position: [0, ''],
      contextMenuVisible: false,
      rowEditData: null,
      tableRotate: false,
      editLoading: false
    }

    const {
      store,
      columns,
      selectable,
      columnsTogglable,
      draggable,
      orderBy,
      orderDir,
      multiple,
      footable,
      primaryField,
      itemCheckableOn,
      itemDraggableOn,
      hideCheckToggler,
      combineFromIndex,
      expandConfig,
      formItem,
      keepItemSelectionOnPageChange,
      useMobileUI,//Aug
      maxKeepItemSelectionLength,
      isPick,
      tableLayout,
      showIndex
    } = props;

    let combineNum = props.combineNum;
    if (typeof combineNum === 'string') {
      combineNum = parseInt(
        resolveVariableAndFilter(combineNum, props.data, '| raw'),
        10
      );
    }
    // Aug
    const mobileUI = isMobile() && useMobileUI
    // Aug 移动端默认只展示6项
    let _columns: any[] = columns || []
    this.sortCols = this.handleColumns(_columns, {})
    store.update({
      selectable,
      draggable,
      columns: this.sortCols, // Aug
      rawColumns: this.handleColumns(_columns, {
        sort: false,
      }), // Jay
      columnsTogglable,
      orderBy,
      orderDir,
      multiple,
      showIndex,
      footable,
      expandConfig,
      primaryField,
      itemCheckableOn,
      itemDraggableOn,
      hideCheckToggler: isMobile() && isPick ? false : hideCheckToggler,
      combineNum,
      combineFromIndex,
      keepItemSelectionOnPageChange,
      maxKeepItemSelectionLength,
      mobileUI,  //Aug
      orderColumns: new Map(),
      tableLayout
    });

    formItem && isAlive(formItem) && formItem.setSubStore(store);
    Table.syncRows(store, this.props, undefined) && this.syncSelected();
  }

  // Jay
  handleColumns(columns: any[], options: {
    sort?: boolean,
  }) {
    const { sort = true } = options || {}
    let handleCols = cloneDeep(columns)
    const columnInfo = this.props.columnInfo
    // columns的长度比columnInfo的长度大时，不进行排序
    // const flag = columnInfo && (Object.keys(columnInfo).length <= handleCols.length)
    if (columnInfo && Object.keys(columnInfo).length && sort) {
      let tempArr: any[] = []
      const tempArrNofixed: any[] = []
      const tempArrFixedLeft: any[] = []
      const tempArrFixedRight: any[] = []

      handleCols.forEach(col => {
        // 如果column的hidden是true，则不允许被设置
        if (col?.hidden) col.canSet = false
        if (col.name && columnInfo[col.name]) {
          if (columnInfo[col.name]?.hidden === 0) {
            col.hidden = col?.hidden ?? false
          } else {
            col.hidden = col?.hidden ?? true
          }
          tempArr[columnInfo[col.name].index] = col
        } else {
          if (col.fixed === 'left') {
            tempArrFixedLeft.push(col)
          } else if (col.fixed === 'right') {
            tempArrFixedRight.push(col)
          } else {
            tempArrNofixed.push(col)
          }
        }
      })

      tempArr = tempArr.filter(Boolean)
      let fixedRightBefore = tempArr.length
      for (let i = tempArr.length - 1; i >= 0; i--) {
        if (tempArr[i].fixed !== 'right') {
          fixedRightBefore = i + 1
          break
        }
      }

      tempArr.splice(fixedRightBefore, 0, ...tempArrNofixed)
      tempArr = tempArrFixedLeft.concat(tempArr.concat(tempArrFixedRight))
      handleCols = tempArr
    } else {
      handleCols.forEach(col => {
        // 如果column的hidden是true，则不允许被设置
        if (col?.hidden) col.canSet = false
      })
    }
    return handleCols
  }

  static syncRows(
    store: ITableStore,
    props: TableProps,
    prevProps?: TableProps
  ) {
    const source = props.source;
    const value = props.value || props.items;
    let rows: Array<object> = [];
    let updateRows = false;

    if (
      Array.isArray(value) &&
      (!prevProps || (prevProps.value || prevProps.items) !== value)
    ) {
      updateRows = true;
      rows = value;
    } else if (typeof source === 'string') {
      const resolved = resolveVariableAndFilter(source, props.data, '| raw');
      const prev = prevProps
        ? resolveVariableAndFilter(source, prevProps.data, '| raw')
        : null;

      if (prev && prev === resolved) {
        updateRows = false;
      } else if (Array.isArray(resolved)) {
        updateRows = true;
        rows = resolved;
      }
    }

    updateRows && store.initRows(rows, props.getEntryId, props.reUseRow);
    typeof props.selected !== 'undefined' &&
      store.updateSelected(props.selected, props.valueField);
    return updateRows;
  }

  componentDidMount() {
    let parent: HTMLElement | Window | null = getScrollParent(
      findDOMNode(this) as HTMLElement
    );

    if (!parent || parent === document.body) {
      parent = window;
    }
    this.parentNode = parent;
    this.updateTableInfo();

    const dom = findDOMNode(this) as HTMLElement;
    if (dom.closest('.modal-body')) {
      return;
    }
    const { classPrefix: ns } = this.props;
    const tableContentWrap = dom.querySelector(
      `.${ns}Table-contentWrap`
    ) as HTMLElement;
    tableContentWrap.addEventListener('scroll', this.scrollPingBox);
    this.affixDetect();
    parent.addEventListener('scroll', this.affixDetect);
    window.addEventListener('resize', this.affixDetect);
    this.updateAutoFillHeight();
    window.addEventListener('resize', this.updateAutoFillHeight);
    EventSub.on(EventEnum.DomPrint, (formName: string, cb) => {
      cb({ name: this.props.tableName, label: this.props.labelName, columns: this.props.columns })
    })
  }

  scrollPingBox = () => {
    if (!this.table) return;
    const { classPrefix: ns } = this.props;
    const table = findDOMNode(this) as HTMLElement;
    const container = table.querySelector(
      `.${ns}Table-contentWrap`
    ) as HTMLElement;
    const scrollLeft = container.scrollLeft;
    const scrollWidth = container.scrollWidth;
    const clientWidth = container.clientWidth;

    // 判断是否滚动到最左侧
    if (scrollLeft === 0) {
      container.classList.remove('table-ping-left');
    } else {
      container.classList.add('table-ping-left');
    }

    // 判断是否滚动到最右侧
    if (Math.abs(scrollLeft + clientWidth - scrollWidth) <= 1) {
      container.classList.remove('table-ping-right');
    } else {
      container.classList.add('table-ping-right');
    }
  }

  updateAutoFillHeight() {
    const { autoFillHeight, classPrefix: ns } = this.props;
    if (!this.table) return;
    const table = findDOMNode(this) as HTMLElement;
    const tableHeight = document.body.clientHeight - offset(table).top
    if (autoFillHeight && tableHeight >= 350) {
      table.style.height = tableHeight + 'px'
    }
  }

  componentDidUpdate(prevProps: TableProps) {
    const props = this.props;
    const store = props.store;
    if (
      anyChanged(
        [
          'selectable',
          'columnsTogglable',
          'draggable',
          'orderBy',
          'orderDir',
          'multiple',
          'footable',
          'primaryField',
          'itemCheckableOn',
          'itemDraggableOn',
          'hideCheckToggler',
          'combineNum',
          'combineFromIndex',
          'expandConfig'
        ],
        prevProps,
        props
      )
    ) {
      let combineNum = props.combineNum;
      if (typeof combineNum === 'string') {
        combineNum = parseInt(
          resolveVariableAndFilter(combineNum, props.data, '| raw'),
          10
        );
      }
      store.update({
        selectable: props.selectable,
        columnsTogglable: props.columnsTogglable,
        draggable: props.draggable,
        orderBy: props.orderBy,
        orderDir: props.orderDir,
        multiple: props.multiple,
        showIndex: props.showIndex,
        primaryField: props.primaryField,
        footable: props.footable,
        itemCheckableOn: props.itemCheckableOn,
        itemDraggableOn: props.itemDraggableOn,
        hideCheckToggler: props.hideCheckToggler,
        combineNum: combineNum,
        combineFromIndex: props.combineFromIndex,
        expandConfig: props.expandConfig
      });
    }

    if (prevProps.columns !== props.columns) {
      // Jay
      this.sortCols = this.handleColumns(props.columns || [], {})
      store.update({
        columns: this.sortCols,
        rawColumns: this.handleColumns(props.columns || [], { sort: false }),
      });
    }
    if (prevProps.foldColumns !== props.foldColumns) {
      store.updateOperation(props.foldColumns)
    }

    if (
      anyChanged(['source', 'value', 'items'], prevProps, props) ||
      (!props.value &&
        !props.items &&
        (props.data !== prevProps.data ||
          (typeof props.source === 'string' && isPureVariable(props.source))))
    ) {
      Table.syncRows(store, props, prevProps) && this.syncSelected();
    } else if (isArrayChildrenModified(prevProps.selected!, props.selected!)) {
      const prevSelectedRows = store.selectedRows
        .map(item => item.id)
        .join(',');
      store.updateSelected(props.selected || [], props.valueField);
      const selectedRows = store.selectedRows.map(item => item.id).join(',');
      prevSelectedRows !== selectedRows && this.syncSelected();
    }

    this.updateTableInfoLazy();
    //状态改变时，记录原先的数据
    if (prevProps.editStatus !== props.editStatus) {
      if (props.editIndex !== -1) props.editStatus ? this.beforeData = store.rows?.[props.editIndex]?.data : this.beforeData = null
      if (!props.editStatus) {
        this.hasEdited = false
        this.formInstance = null
      }
    }
  }
  //进入编辑前的行数据
  beforeData = null

  componentWillUnmount() {
    const { formItem, store, classPrefix: ns } = this.props;
    const parent = this.parentNode;
    const dom = findDOMNode(this) as HTMLElement;
    const tableContentWrap = dom.querySelector(
      `.${ns}Table-contentWrap`
    ) as HTMLElement;
    tableContentWrap.addEventListener('scroll', this.scrollPingBox);
    parent && parent.removeEventListener('scroll', this.affixDetect);
    window.removeEventListener('resize', this.affixDetect);
    window.removeEventListener('resize', this.updateAutoFillHeight);
    (this.updateTableInfoLazy as any).cancel();
    this.unSensor && this.unSensor();

    formItem && isAlive(formItem) && formItem.setSubStore(null);
    EventSub.off(EventEnum.DomPrint)
    try {
      store.orderColumns.clear()
    } catch { }
  }

  subFormRef(form: any, x: number, y: number) {
    const { quickEditFormRef } = this.props;

    quickEditFormRef && quickEditFormRef(form, x, y);
    this.subForms[`${x}-${y}`] = form;
    form && this.props.store.addForm(form.props.store, y);
  }

  handleAction(e: React.UIEvent<any>, action: Action, ctx: object) {
    const { onAction } = this.props;

    // todo
    onAction(e, action, ctx);
  }

  handleCheck(item: IRow, value: boolean, shift?: boolean) {
    const { store } = this.props;
    if (shift) {
      store.toggleShift(item);
    } else {
      item.toggle();
    }

    this.syncSelected();
  }

  // 0全选 1反选 2不选
  handleCheckAll(type: CheckAllType) {
    const { store } = this.props
    switch (type) {
      case 0:
        store.checkAll()
        break
      case 1:
        store.checkReverse()
        break
      case 2:
        store.clear()
        break
      case 3:
        store.toggleAll()
        break
      default:
        return
    }
    this.syncSelected();
  }

  handleQuickChange(
    item: IRow,
    values: object,
    saveImmediately: boolean | any = false,
    savePristine?: boolean,
    resetOnFailed?: boolean
  ) {
    if (!isAlive(item)) {
      return;
    }
    const {
      onSave,
      onPristineChange,
      saveImmediately: propsSaveImmediately,
      primaryField
    } = this.props;

    item.change(values, savePristine);

    // 值发生变化了，需要通过 onSelect 通知到外面，否则会出现数据不同步的问题
    item.modified && this.syncSelected();

    if (savePristine) {
      onPristineChange?.(item.data, item.path);
      return;
    } else if (!saveImmediately && !propsSaveImmediately) {
      return;
    }

    if (saveImmediately && saveImmediately.api) {
      this.props.onAction(
        null,
        {
          actionType: 'ajax',
          api: saveImmediately.api
        },
        values
      );
      return;
    }

    if (!onSave) {
      return;
    }

    onSave(
      item.data,
      difference(item.data, item.pristine, ['id', primaryField]),
      item.path,
      undefined,
      item.pristine,
      resetOnFailed
    );
  }

  handleSave(newRows: any[]) {
    const { onSave, primaryField } = this.props;
    const rows = newRows.map(item => ({
      ...item,
      isOrigin: true,
      isAdd: false,
      isUpdate: false
    }));
    const rowIndexes = newRows.map((item, index) => String(index));
    const diff = rows.map(item => {
      let obj = {};
      Object.keys(item).forEach(k => {
        if (k !== primaryField) {
          obj = { ...obj, [k]: item[k] }
        }
      })
      return obj;
    })
    onSave?.(
      rows,
      diff,
      rowIndexes
    );
  }

  handleSaveOrder() {
    const { store, onSaveOrder } = this.props;

    if (!onSaveOrder || !store.movedRows.length) {
      return;
    }

    onSaveOrder(
      store.movedRows.map(item => item.data),
      store.rows.map(item => item.getDataWithModifiedChilden())
    );
  }

  syncSelected() {
    const { store, onSelect } = this.props;

    onSelect &&
      onSelect(
        store.selectedRows.map(item => item.data),
        store.unSelectedRows.map(item => item.data)
      );
  }

  reset() {
    const { store } = this.props;

    store.reset();

    const subForms: Array<any> = [];
    Object.keys(this.subForms).forEach(
      key => this.subForms[key] && subForms.push(this.subForms[key])
    );
    subForms.forEach(item => item.clearErrors());
  }

  bulkUpdate(value: any, items: Array<object>) {
    const { store, primaryField } = this.props;

    if (primaryField && value.ids) {
      const ids = value.ids.split(',');
      const rows = store.rows.filter(item =>
        find(ids, (id: any) => id && id == item.data[primaryField])
      );
      const newValue = { ...value, ids: undefined };
      rows.forEach(row => row.change(newValue));
    } else {
      const rows = store.rows.filter(item => ~items.indexOf(item.pristine));
      rows.forEach(row => row.change(value));
    }
  }

  getSelected() {
    const { store } = this.props;

    return store.selectedRows.map(item => item.data);
  }

  affixDetect() {
    const { autoFillHeight = true } = this.props
    if (!this.props.affixHeader || !this.table || autoFillHeight) {
      return;
    }

    const ns = this.props.classPrefix;
    const dom = findDOMNode(this) as HTMLElement;
    const clip = (this.table as HTMLElement).getBoundingClientRect();
    const offsetY =
      this.props.affixOffsetTop ?? this.props.env.affixOffsetTop ?? 0;
    const headingHeight =
      dom.querySelector(`.${ns}Table-heading`)?.getBoundingClientRect()
        .height || 0;
    const headerHeight =
      dom.querySelector(`.${ns}Table-headToolbar`)?.getBoundingClientRect()
        .height || 0;

    const affixed =
      clip.top - headerHeight - headingHeight < offsetY &&
      clip.top + clip.height - 40 > offsetY;
    const affixedDom = dom.querySelector(`.${ns}Table-fixedTop`) as HTMLElement;

    affixedDom.style.cssText += `top: ${offsetY}px;width: ${(this.table.parentNode as HTMLElement).offsetWidth
      }px`;
    affixed
      ? affixedDom.classList.add('in')
      : affixedDom.classList.remove('in');
    // store.markHeaderAffix(clip.top < offsetY && (clip.top + clip.height - 40) > offsetY);
  }

  updateTableInfo() {
    if (!this.table) {
      return;
    }

    const table = this.table;
    const outter = table.parentNode as HTMLElement;
    const ns = this.props.classPrefix;

    // 完成宽高都没有变化就直接跳过了。
    // if (this.totalWidth === table.scrollWidth && this.totalHeight === table.scrollHeight) {
    //     return;
    // }

    this.totalWidth = table.scrollWidth;
    this.totalHeight = table.scrollHeight;
    this.outterWidth = outter.offsetWidth;
    this.outterHeight = outter.offsetHeight;

    let widths: {
      [propName: string]: number;
    } = (this.widths = {});
    let widths2: {
      [propName: string]: number;
    } = (this.widths2 = {});
    let heights: {
      [propName: string]: number;
    } = (this.heights = {});

    // heights.header = table
    //   .querySelector('thead>tr:last-child')!
    //   .getBoundingClientRect().height;
    // heights.header2 = table
    //   .querySelector('thead>tr:first-child')!
    //   .getBoundingClientRect().height;
    // Aug
    heights.header = table.querySelector('thead>tr:last-child')?.getBoundingClientRect().height || 0
    heights.header2 = table.querySelector('thead>tr:first-child')?.getBoundingClientRect().height || 0

    forEach(
      table.querySelectorAll('thead>tr:last-child>th'),
      (item: HTMLElement) => {
        widths[item.getAttribute('data-index') as string] =
          item.getBoundingClientRect().width;
      }
    );

    forEach(
      table.querySelectorAll('thead>tr:first-child>th'),
      (item: HTMLElement) => {
        widths2[item.getAttribute('data-index') as string] =
          item.getBoundingClientRect().width;
      }
    );

    forEach(
      table.querySelectorAll('tbody>tr>*:last-child'),
      (item: HTMLElement, index: number) =>
        (heights[index] = item.getBoundingClientRect().height)
    );

    // 让 react 去更新非常慢，还是手动更新吧。
    const dom = findDOMNode(this) as HTMLElement;

    forEach(
      // 折叠 footTable 不需要改变
      dom.querySelectorAll(
        `.${ns}Table-fixedTop table, .${ns}Table-fixedLeft>table, .${ns}Table-fixedRight>table`
      ),
      (table: HTMLTableElement) => {
        let totalWidth = 0;
        let totalWidth2 = 0;
        forEach(
          table.querySelectorAll('thead>tr:last-child>th'),
          (item: HTMLElement) => {
            const width = widths[item.getAttribute('data-index') as string];
            item.style.cssText += `width: ${width}px; height: ${heights.header}px`;
            totalWidth += width;
          }
        );
        forEach(
          table.querySelectorAll('thead>tr:first-child>th'),
          (item: HTMLElement) => {
            const width = widths2[item.getAttribute('data-index') as string];
            item.style.cssText += `width: ${width}px; height: ${heights.header2}px`;
            totalWidth2 += width;
          }
        );

        forEach(table.querySelectorAll('colgroup>col'), (item: HTMLElement) => {
          const width = widths[item.getAttribute('data-index') as string];
          item.setAttribute('width', `${width}`);
        });

        forEach(
          table.querySelectorAll('tbody>tr'),
          (item: HTMLElement, index) => {
            item.style.cssText += `height: ${heights[index]}px`;
          }
        );

        table.style.cssText += `width: ${Math.max(
          totalWidth,
          totalWidth2
        )}px;table-layout: auto;`;
      }
    );

    this.lastScrollLeft = -1;
    this.handleOutterScroll();
  }

  handleOutterScroll() {
    const outter = (this.table as HTMLElement).parentNode as HTMLElement;
    const scrollLeft = outter.scrollLeft;

    if (scrollLeft === this.lastScrollLeft) {
      return;
    }

    this.lastScrollLeft = scrollLeft;
    let leading = scrollLeft === 0;
    let trailing = Math.ceil(scrollLeft) + this.outterWidth >= this.totalWidth;

    const ns = this.props.classPrefix;
    const dom = findDOMNode(this) as HTMLElement;

    const fixedLeft = dom.querySelectorAll(`.${ns}Table-fixedLeft`);
    if (fixedLeft && fixedLeft.length) {
      for (let i = 0, len = fixedLeft.length; i < len; i++) {
        let node = fixedLeft[i];
        leading ? node.classList.remove('in') : node.classList.add('in');
      }
    }

    const fixedRight = dom.querySelectorAll(`.${ns}Table-fixedRight`);
    if (fixedRight && fixedRight.length) {
      for (let i = 0, len = fixedRight.length; i < len; i++) {
        let node = fixedRight[i];
        trailing ? node.classList.remove('in') : node.classList.add('in');
      }
    }
    const table = this.affixedTable;
    if (table) {
      table.style.cssText += `transform: translateX(-${scrollLeft}px)`;
    }

    // Jay
    const { store } = this.props
    const leftFixedColumns = store.leftFixedColumns
    const lastLeftFixedIndex = leftFixedColumns[leftFixedColumns.length - 1]?.index
    // 给table添加类名
    const fixedLeftLastEl = dom.querySelector('.fixed-left-last')
    if (fixedLeftLastEl) {
      const fixedLeftLastElRelativeLeft = fixedLeftLastEl.getBoundingClientRect().left -
        dom.getBoundingClientRect().left
      if (
        leading ||
        (
          fixedLeftLastElRelativeLeft > store.stickyWidths[lastLeftFixedIndex])
      ) {
        dom.classList.remove('fix-left');
      } else if (
        fixedLeftLastElRelativeLeft <= store.stickyWidths[lastLeftFixedIndex]
      ) {
        // 左边要固定的列相对于表格的水平距离小于等于其 sticky left
        dom.classList.add('fix-left');
      }
    }
    if (trailing) {
      dom.classList.remove('fix-right');
    } else {
      dom.classList.add('fix-right');
    }

    let isNaN = false
    const rightFixedColumns = cloneDeep(store.rightFixedColumns)
    const rightFixedColumnsReverse = rightFixedColumns?.reverse()
    const columnWidths: { [key: string]: number } = {}
    this.table?.querySelectorAll('thead>tr:nth-child(1)>th').forEach(item => {
      columnWidths[item.getAttribute('data-index') as string] =
        item.scrollWidth;
    });
    this.table?.querySelectorAll('thead>tr:nth-child(2)>th').forEach(item => {
      columnWidths[item.getAttribute('data-index') as string] =
        item.scrollWidth;
    });
    const stickyWidths: { [key: string]: number } = {}
    if (leftFixedColumns[0]?.index) {
      stickyWidths[`${leftFixedColumns[0].index}`] = columnWidths[`${leftFixedColumns[0].index}`]
    }
    if (rightFixedColumnsReverse?.[0]?.index) {
      stickyWidths[`${rightFixedColumnsReverse[0].index}`] = columnWidths[`${rightFixedColumns[0].index}`]
    }
    leftFixedColumns?.reduce((acc, cv, ci, arr) => {
      let a: number = acc
      if (ci !== 0) {
        a = acc + columnWidths[arr[ci - 1].index]
      }
      a !== a && (isNaN = true)
      stickyWidths[`${cv.index}`] = a
      return a
    }, 0)

    rightFixedColumnsReverse?.reduce((acc, cv, ci, arr) => {
      let a: number = acc
      if (ci !== 0) {
        a = acc + columnWidths[arr[ci - 1].index]
      }
      a !== a && (isNaN = true)
      stickyWidths[`${cv.index}`] = a
      return a
    }, -2)
    const equal = isEqual(stickyWidths, store.stickyWidths)
    // 避免重复render组件，提高性能
    if (!isNaN && !equal) {
      store.setStickyWidths(stickyWidths)
    }
  }

  tableRef(ref: HTMLTableElement) {
    this.table = ref;

    if (ref) {
      this.unSensor = resizeSensor(
        ref.parentNode as HTMLElement,
        this.updateTableInfoLazy
      );
    } else {
      this.unSensor && this.unSensor();
      delete this.unSensor;
    }
  }

  dragTipRef(ref: any) {
    if (!this.dragTip && ref) {
      this.initDragging();
    } else if (this.dragTip && !ref) {
      this.destroyDragging();
    }

    this.dragTip = ref;
  }

  affixedTableRef(ref: HTMLTableElement) {
    this.affixedTable = ref;
  }

  initDragging() {
    const store = this.props.store;
    const ns = this.props.classPrefix;
    this.sortable = new Sortable(
      (this.table as HTMLElement).querySelector('tbody') as HTMLElement,
      {
        group: 'table',
        animation: 150,
        handle: `.${ns}Table-dragCell`,
        filter: `.${ns}Table-dragCell.is-dragDisabled`,
        ghostClass: 'is-dragging',
        onEnd: (e: any) => {
          // 没有移动
          if (e.newIndex === e.oldIndex) {
            return;
          }

          const parent = e.to as HTMLElement;
          if (e.oldIndex < parent.childNodes.length - 1) {
            parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
          } else {
            parent.appendChild(e.item);
          }

          store.exchange(e.oldIndex, e.newIndex);
        }
      }
    );
  }

  destroyDragging() {
    this.sortable && this.sortable.destroy();
  }

  getPopOverContainer() {
    return findDOMNode(this);
  }

  handleMouseMove(e: React.MouseEvent<any>) {
    const tr: HTMLElement = (e.target as HTMLElement).closest(
      'tr[data-id]'
    ) as HTMLElement;

    if (!tr) {
      return;
    }

    const { store, affixColumns, itemActions } = this.props;

    if (
      (affixColumns === false ||
        (store.leftFixedColumns.length === 0 &&
          store.rightFixedColumns.length === 0)) &&
      (!itemActions || !itemActions.filter(item => !item.hiddenOnHover).length)
    ) {
      return;
    }

    const id = tr.getAttribute('data-id') as string;
    const row = store.hoverRow;

    if (row?.id === id) {
      return;
    }
    eachTree<IRow>(store.rows, (item: IRow) => item.setIsHover(item.id === id));
  }

  handleMouseLeave() {
    const store = this.props.store;
    const row = store.hoverRow;

    row?.setIsHover(false);
  }

  draggingTr: HTMLTableRowElement;
  originIndex: number;
  draggingSibling: Array<HTMLTableRowElement>;

  @autobind
  handleDragStart(e: React.DragEvent) {
    const store = this.props.store;
    const target = e.currentTarget;
    const tr = (this.draggingTr = target.closest('tr')!);
    const id = tr.getAttribute('data-id')!;
    const tbody = tr.parentNode!;
    this.originIndex = Array.prototype.indexOf.call(tbody.childNodes, tr);

    tr.classList.add('is-dragging');

    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', id);

    e.dataTransfer.setDragImage(tr, 0, 0);
    const item = store.getRowById(id)!;
    store.collapseAllAtDepth(item.depth);

    let siblings: Array<IRow> = store.rows;
    if (item.parentId) {
      const parent = store.getRowById(item.parentId)!;
      siblings = parent.children as any;
    }
    siblings = siblings.filter(sibling => sibling !== item);

    tbody.addEventListener('dragover', this.handleDragOver);
    tbody.addEventListener('drop', this.handleDrop);

    this.draggingSibling = siblings.map(item => {
      let tr: HTMLTableRowElement = tbody.querySelector(
        `tr[data-id="${item.id}"]`
      ) as HTMLTableRowElement;

      tr.classList.add('is-drop-allowed');

      return tr;
    });
    tr.addEventListener('dragend', this.handleDragEnd);
  }

  @autobind
  handleDragOver(e: any) {
    if (!e.target) {
      return;
    }
    e.preventDefault();
    e.dataTransfer!.dropEffect = 'move';

    const overTr: HTMLElement = (e.target as HTMLElement).closest('tr')!;
    if (
      !overTr ||
      !~overTr.className.indexOf('is-drop-allowed') ||
      overTr === this.draggingTr
    ) {
      return;
    }

    const tbody = overTr.parentElement!;
    const dRect = this.draggingTr.getBoundingClientRect();
    const tRect = overTr.getBoundingClientRect();
    let ratio = dRect.top < tRect.top ? 0.1 : 0.9;

    const next = (e.clientY - tRect.top) / (tRect.bottom - tRect.top) > ratio;
    tbody.insertBefore(this.draggingTr, (next && overTr.nextSibling) || overTr);
  }

  @autobind
  handleDrop() {
    const store = this.props.store;
    const tr = this.draggingTr;
    const tbody = tr.parentElement!;
    const index = Array.prototype.indexOf.call(tbody.childNodes, tr);
    const item: IRow = store.getRowById(tr.getAttribute('data-id')!) as any;

    // destroy
    this.handleDragEnd();

    store.exchange(this.originIndex, index, item);
  }

  @autobind
  handleDragEnd() {
    const tr = this.draggingTr;
    const tbody = tr.parentElement!;
    const index = Array.prototype.indexOf.call(tbody.childNodes, tr);
    tbody.insertBefore(
      tr,
      tbody.childNodes[
      index < this.originIndex ? this.originIndex + 1 : this.originIndex
      ]
    );

    tr.classList.remove('is-dragging');
    tr.removeEventListener('dragend', this.handleDragEnd);
    tbody.removeEventListener('dragover', this.handleDragOver);
    tbody.removeEventListener('drop', this.handleDrop);
    this.draggingSibling.forEach(item =>
      item.classList.remove('is-drop-allowed')
    );
  }

  @autobind
  handleImageEnlarge(info: any, target: { rowIndex: number; colIndex: number }) {
    const onImageEnlarge = this.props.onImageEnlarge;

    // 如果已经是多张了，直接跳过
    if (Array.isArray(info.list)) {
      return onImageEnlarge && onImageEnlarge(info, target);
    }

    // 从列表中收集所有图片，然后作为一个图片集合派送出去。
    const store = this.props.store;
    const column = store.columns[target.colIndex].pristine;

    let index = target.rowIndex;
    const list: Array<any> = [];
    store.rows.forEach((row, i) => {
      const src = resolveVariable(column.name, row.data);

      if (!src) {
        if (i < target.rowIndex) {
          index--;
        }
        return;
      }

      list.push({
        src,
        originalSrc: column.originalSrc
          ? filter(column.originalSrc, row.data)
          : src,
        title: column.enlargeTitle
          ? filter(column.enlargeTitle, row.data)
          : column.title
            ? filter(column.title, row.data)
            : undefined,
        caption: column.enlargeCaption
          ? filter(column.enlargeCaption, row.data)
          : column.caption
            ? filter(column.caption, row.data)
            : undefined
      });
    });

    if (list.length > 1) {
      onImageEnlarge &&
        onImageEnlarge(
          {
            ...info,
            list,
            index
          },
          target
        );
    } else {
      onImageEnlarge && onImageEnlarge(info, target);
    }
  }

  // 以下变量都是用于列宽度调整拖拽
  resizeLine: HTMLElement;
  // resizeLineLeft: number;
  targetTh: HTMLElement;
  targetThWidth: number;
  lineStartX: number;
  resizeColumn: IColumn;

  // 开始列宽度调整
  @autobind
  handleColResizeMouseDown(e: React.MouseEvent<HTMLElement>, col: IColumn) {
    this.lineStartX = e.clientX;
    const currentTarget = e.currentTarget;
    this.resizeColumn = col;
    this.resizeLine = currentTarget;
    // this.resizeLineLeft = parseInt(
    //   getComputedStyle(this.resizeLine).getPropertyValue('left'),
    //   10
    // );
    this.targetTh = this.resizeLine.parentElement! as HTMLElement;
    this.targetThWidth = this.targetTh.getBoundingClientRect().width;
    document.addEventListener('mousemove', this.handleColResizeMouseMove);
    document.addEventListener('mouseup', this.handleColResizeMouseUp);
  }

  // 垂直线拖拽移动
  @autobind
  handleColResizeMouseMove(e: MouseEvent) {
    const moveX = e.clientX - this.lineStartX;
    this.targetTh.style.width = this.targetThWidth + moveX - 20 + 'px';
  }

  // 垂直线拖拽结束
  @autobind
  handleColResizeMouseUp(e: MouseEvent) {
    const { store } = this.props;
    const newCols = store.columnsData.map(item => {
      if (item.name === this.resizeColumn.name) {
        return { ...item, pristine: { ...item.pristine, width: this.targetTh.style.width } }
      }
      return item
    })
    this.handleColumnToggle(newCols, {}, false)
    document.removeEventListener('mousemove', this.handleColResizeMouseMove);
    document.removeEventListener('mouseup', this.handleColResizeMouseUp);
  }
  // Jay
  handleColumnToggle(columns: Array<IColumn> | undefined, saveCols: Record<string, any>, canFetch: boolean, targetTemp?: Temp) {
    const { store, saveColApi } = this.props;
    if (canFetch && this.props.saveColApi && this.props.crudName) {
      this.props.env.fetcher({ url: saveColApi.url, method: saveColApi.method }, { ...targetTemp, columnInfo: saveCols }).then((res) => {
        message.info(res.msg)
      })
    }
    if (columns) {
      this.sortCols = columns.map(col => col.pristine)
      store.updateColumns(columns);
    }
  }

  renderAutoFilterForm(): React.ReactNode {
    const {
      render,
      store,
      onSearchableFromReset,
      onSearchableFromSubmit,
      onSearchableFromInit,
      classnames: cx,
      translate: __
    } = this.props;
    const searchableColumns = store.searchableColumns;
    const activedSearchableColumns = store.activedSearchableColumns;

    if (!searchableColumns.length) {
      return null;
    }

    const groupedSearchableColumns: Array<Record<string, any>> = [
      { body: [], md: 4 },
      { body: [], md: 4 },
      { body: [], md: 4 }
    ];

    activedSearchableColumns.forEach((column, index) => {
      groupedSearchableColumns[index % 3].body.push({
        ...column.searchable,
        name: column.searchable?.name ?? column.name,
        label: column.searchable?.label ?? column.label,
        mode: 'horizontal'
      });
    });

    return render(
      'searchable-form',
      {
        type: 'form',
        api: null,
        title: '',
        mode: 'normal',
        submitText: __('search'),
        body: [
          {
            type: 'grid',
            columns: groupedSearchableColumns
          }
        ],
        actions: [
          {
            type: 'dropdown-button',
            label: __('Table.searchFields'),
            className: cx('Table-searchableForm-dropdown', 'mr-2'),
            level: 'link',
            trigger: 'click',
            size: 'sm',
            align: 'right',
            buttons: searchableColumns.map(column => {
              return {
                type: 'checkbox',
                className: cx('Table-searchableForm-checkbox'),
                name: `__search_${column.searchable?.name ?? column.name}`,
                option: column.searchable?.label ?? column.label,
                value: column.enableSearch,
                badge: {
                  offset: [-10, 5],
                  visibleOn: `${column.toggable && !column.toggled && column.enableSearch
                    }`
                },
                onChange: (value: boolean) => {
                  column.setEnableSearch(value);
                }
              };
            })
          },
          {
            type: 'submit',
            label: __('search'),
            level: 'primary',
            className: 'w-18'
          },
          {
            type: 'reset',
            label: __('reset'),
            className: 'w-18'
          }
        ]
      },
      {
        key: 'searchable-form',
        panelClassName: cx('Table-searchableForm'),
        actionsClassName: cx('Table-searchableForm-footer'),
        onReset: onSearchableFromReset,
        onSubmit: onSearchableFromSubmit,
        onInit: onSearchableFromInit,
        formStore: undefined
      }
    );
  }

  renderHeading() {
    let {
      title,
      store,
      hideQuickSaveBtn,
      data,
      classnames: cx,
      saveImmediately,
      headingClassName,
      quickSaveApi,
      translate: __
    } = this.props;

    if (
      title ||
      (quickSaveApi &&
        !saveImmediately &&
        store.modified &&
        !hideQuickSaveBtn) ||
      store.moved
    ) {
      return (
        <div className={cx('Table-heading', headingClassName)} key="heading">
          {!saveImmediately && store.modified && !hideQuickSaveBtn ? (
            <span>
              {__('Table.modified', {
                modified: store.modified
              })}
              <button
                type="button"
                className={cx('Button Button--xs Button--success m-l-sm')}
              // onClick={this.handleSave}
              >
                <Icon icon="check" className="icon m-r-xs" />
                {__('Form.submit')}
              </button>
              <button
                type="button"
                className={cx('Button Button--xs Button--danger m-l-sm')}
                onClick={this.reset}
              >
                <Icon icon="close" className="icon m-r-xs" />
                {__('Table.discard')}
              </button>
            </span>
          ) : store.moved ? (
            <span>
              {__('Table.moved', {
                moved: store.moved
              })}
              <button
                type="button"
                className={cx('Button Button--xs Button--success m-l-sm')}
                onClick={this.handleSaveOrder}
              >
                <Icon icon="check" className="icon m-r-xs" />
                {__('Form.submit')}
              </button>
              <button
                type="button"
                className={cx('Button Button--xs Button--danger m-l-sm')}
                onClick={this.reset}
              >
                <Icon icon="close" className="icon m-r-xs" />
                {__('Table.discard')}
              </button>
            </span>
          ) : title ? (
            filter(title, data)
          ) : (
            ''
          )}
        </div>
      );
    }

    return null;
  }

  handleMultiColumnSort(name: string, isMultiple?: boolean, columnMapValue?: object) {
    const { store, preSortAble, handleMutilSort, onQuery, markSort } = this.props;
    markSort();
    let sort: 'asc' | 'desc' | null = null;
    // 多列排
    if (isMultiple) {
      if (store.orderColumns.has(name)) {
        if (store.getOrderColumn(name)?.order === 'desc') {
          store.deleteOrderColumn(name)
        } else {
          store.setOrderColumn(name, { order: 'desc', map: columnMapValue })
          sort = 'desc'
        }
      } else {
        store.setOrderColumn(name, { order: 'asc', map: columnMapValue })
        sort = 'asc'
      }
    } else {// 单列排
      const current = store.getOrderColumn(name)?.order
      const value = current ? (current === 'asc' ? 'desc' : null) : 'asc'
      let orderColumns = new Map()
      if (value) {
        orderColumns.set(name, { order: value, map: columnMapValue })
      }
      store.update({ orderColumns })
    }

    if (preSortAble) {
      this.props.setLoading!(true)
      handleMutilSort!(store.orderColumns, name, sort)
    } else {
      onQuery &&
        onQuery({
          orderBy: store.orderColumnsParam(),
          // orderDir: store.orderDir
        });
    }
  }

  renderHeadCell(column: IColumn & { batchEdit?: boolean }, props?: any) {
    const {
      store,
      query,
      onQuery,
      multiple,
      env,
      render,
      classPrefix: ns,
      resizable,
      classnames: cx,
      autoGenerateFilter,
      onBatchEdit,
      preSortAble,
      tableName
    } = this.props;
    const { style } = props;

    if (column.type === '__checkme') {
      return (
        <ContextMenu
          menuItems={[
            { id: 0, title: '全选', icon: 'fa fa-check-square-o' },
            { id: 1, title: '反选', icon: 'fa fa-minus-square-o' },
            { id: 2, title: '不选', icon: 'fa fa-square-o' }
          ]}
          onItemClick={index => this.handleCheckAll(index as CheckAllType)}
          key={Date()}
        >
          <th {...props} style={{ cursor: 'pointer', ...style }} className={cx(column.pristine.className)} onClick={() => { this.handleCheckAll(3) }} >
            {store.rows.length && multiple ? (
              <Checkbox
                classPrefix={ns}
                partial={!store.allChecked}
                checked={store.someChecked}
                disabled={store.disabledHeadCheckbox}
              // onChange={this.handleCheckAll}
              />
            ) : (
              <div>{'\u00A0'}</div>
            )}
          </th>
        </ContextMenu >
      );
    } else if (column.type === '__dragme') {
      return <th {...props} className={cx(column.pristine.className)} />;
    } else if (column.type === '__expandme') {
      return (
        <th {...props} className={cx(column.pristine.className)}>
          {(store.footable &&
            (store.footable.expandAll === false || store.footable.accordion)) ||
            (store.expandConfig &&
              (store.expandConfig.expandAll === false ||
                store.expandConfig.accordion)) ? null : (
            <a
              className={cx(
                'Table-expandBtn',
                store.allExpanded ? 'is-active' : ''
              )}
              // data-tooltip="展开/收起全部"
              // data-position="top"
              onClick={store.toggleExpandAll}
            >
              <Icon icon="right-arrow-bold" className="icon" />
            </a>
          )}
        </th>
      );
    } else if (column.type === '__pseudoColumn') {
      return <th {...props} className={cx(column.pristine.className, 'fixed-left-last')} >
      </th>;
    }

    let affix = null;

    if (column.searchable && column.name && !autoGenerateFilter) {
      affix = (
        <HeadCellSearchDropDown
          {...this.props}
          onQuery={onQuery}
          name={column.name}
          searchable={column.searchable}
          sortable={column.sortable}
          type={column.type}
          data={query}
          orderBy={store.orderBy}
          orderDir={store.orderDir}
          popOverContainer={this.getPopOverContainer}
        />
      );
    } else if (column.sortable && column.name) {
      affix = (
        <span
          className={cx('TableCell-sortBtn')}
        >
          <i
            className={cx(
              'TableCell-sortBtn--down',
              store.orderColumns?.has(column.name) && store.getOrderColumn?.(column.name as string)?.order === 'desc'
                ? 'is-active'
                : ''
            )}
          >
            <Icon icon="sort-desc" className="icon" />
          </i>
          <i
            className={cx(
              'TableCell-sortBtn--up',
              store.orderColumns?.has(column.name) && store.getOrderColumn?.(column.name as string)?.order === 'asc'
                ? 'is-active'
                : ''
            )}
          >
            <Icon icon="sort-asc" className="icon" />
          </i>
          <i
            className={cx(
              'TableCell-sortBtn--default',
              store.orderColumns?.has(column.name)
                ? '' : 'is-active'
            )}
          >
          </i>
        </span>
      );
    } else if (column.filterable && column.name) {
      affix = (
        <HeadCellFilterDropDown
          {...this.props}
          onQuery={onQuery}
          name={column.name}
          type={column.type}
          data={query}
          filterable={column.filterable}
          popOverContainer={this.getPopOverContainer}
        />
      );
    }

    if (column.pristine.width) {
      props.style = props.style || {};
      props.style.width = column.pristine.width;
    }
    //  else {
    //   props.style = props.style || {};
    //   props.style.maxWidth = 200;
    // }

    if (column.pristine.align) {
      props.style = props.style || {};
      props.style.textAlign = column.pristine.align;
    }

    const resizeLine = (
      <div
        className={cx('Table-content-colDragLine')}
        key={`resize-${column.index}`}
        onMouseDown={(e) => this.handleColResizeMouseDown(e, column)}
      ></div>
    );

    const order = store.getOrderColumn?.(column.name ?? '')?.order
    const title = order === 'asc' ? '点击降序' : order === 'desc' ? '点击取消排序' : '点击升序'

    const hasData = store.data.items?.length > 0
    const tableTh = (
      <Tooltip
        title={title}
        color='white'
        placement='top'
        overlayInnerStyle={{ color: 'black' }}
        overlayStyle={{ visibility: column.sortable && hasData ? 'visible' : 'hidden' }}
      >
        <div
          className={cx(
            `${ns}TableCell--title`,
            column.pristine.className,
            column.pristine.labelClassName,
            { 'ellipsis': column.pristine.ellipsis }
          )}
          style={{ display: 'flex', ...props.style }}
          onClick={e => {
            if (hasData) {
              if (column.sortable && column.name) {
                this.handleMultiColumnSort(column.name!, e.ctrlKey, column.map)
              }
            }
          }}
          onContextMenu={e => e.preventDefault()}
        >
          {column.label ? render('tpl', column.label) : null}

          {column.remark
            ? render('remark', {
              type: 'remark',
              tooltip: column.remark,
              container: env?.getTopModalContainer
            })
            : null}

          {/* Aug input-table批量修改 */}
          {column.pristine.batchEdit
            ? <HeadCellBatchEditDropdown
              {...this.props}
              onBatchEdit={onBatchEdit}
              name={column.name}
              batchEdit={column.pristine.batchEdit}
              popOverContainer={this.getPopOverContainer}
            />
            : null}
        </div>
      </Tooltip>
    )

    return (
      <th
        {...props}
        className={cx(props ? (props as any).className : '', {
          'TableCell--sortable': column.sortable,
          'TableCell--searchable': column.searchable,
          'TableCell--filterable': column.filterable,
          'Table-operationCell': column.type === 'operation'
        })}
        table-name={tableName}
        column-name={column.name}
        style={{ ...props.style, cursor: 'pointer' }}
      >
        {/* // chencicsy 用于执行复制操作 */}
        {tableTh}
        {affix}
        {resizable === false ? null : resizeLine}
      </th>
    );
  }

  renderCell(
    region: string,
    column: IColumn,
    item: IRow,
    props: any,
    ignoreDrag = false
  ) {
    const {
      render,
      store,
      multiple,
      classPrefix: ns,
      classnames: cx,
      checkOnItemClick,
      popOverContainer,
      canAccessSuperData,
      itemBadge,
      translate: __
    } = this.props;

    if (column.name && item.rowSpans[column.name] === 0) {
      return null;
    }

    // Jay
    const { rowClassName, rowClassNameExpr } = this.props
    const stickyWidths = store.stickyWidths
    const leftFixedColumns = store.leftFixedColumns
    const lastLeftFixedIndex = leftFixedColumns[leftFixedColumns.length - 1]?.index
    const rightFixedColumns = store.rightFixedColumns
    const firstRightFixedIndex = rightFixedColumns[0]?.index

    const style: any = {}
    column.fixed && (style.position = "sticky")
    column.fixed === 'left' && (style.left = stickyWidths?.[`${column.index}`])
    column.fixed === 'right' && (style.right = stickyWidths?.[`${column.index}`])
    column.fixed && (style.zIndex = 1)


    if (column.type === '__checkme') {
      return (
        <td key={props.key}
          onContextMenu={e => {
            e.preventDefault()
            this.setState({ position: [0, ''] })
          }}
          onClick={checkOnItemClick ? noop : this.handleCheck.bind(this, item)}
          className={cx(column.pristine.className,
            // Jay
            rowClassNameExpr
              ? filter(rowClassNameExpr, item.data)
              : rowClassName, {
            'fixed-left-last': column.index === lastLeftFixedIndex,
            'fixed-right-first': column.index === firstRightFixedIndex,
          })} style={{ ...style, cursor: 'pointer' }}>
          <Checkbox
            classPrefix={ns}
            type={multiple ? 'checkbox' : 'radio'}
            checked={item.checked}
            disabled={item.checkdisable}
          // onChange={
          //   checkOnItemClick ? noop : this.handleCheck.bind(this, item)
          // }
          />
        </td>
      );
    } else if (column.type === '__dragme') {
      return (
        <td key={props.key} className={cx(column.pristine.className,
          // Jay
          rowClassNameExpr
            ? filter(rowClassNameExpr, item.data)
            : rowClassName, {
          'fixed-left-last': column.index === lastLeftFixedIndex,
        })} style={style}>
          {item.draggable ? <Icon icon="drag-bar" className="icon" /> : null}
        </td>
      );
    } else if (column.type === '__expandme') {
      return (
        <td key={props.key}
          className={cx(column.pristine.className,
            // Jay
            rowClassNameExpr
              ? filter(rowClassNameExpr, item.data)
              : rowClassName, {
            'fixed-left-last': column.index === lastLeftFixedIndex,
          })} style={style}
          //Aug
          colSpan={store.mobileUI ? 2 : 1}
        >
          {item.depth > 2
            ? Array.from({ length: item.depth - 2 }).map((_, index) => (
              <i key={index} className={cx('Table-divider-' + (index + 1))} />
            ))
            : null}

          {item.expandable ? (
            // Aug 加入mobileUi
            !store.mobileUI ? (
              <a
                className={cx(
                  'Table-expandBtn',
                  item.expanded ? 'is-active' : ''
                )}
                // data-tooltip="展开/收起"
                // data-position="top"
                onClick={item.toggleExpanded}
              >
                <Icon icon="right-arrow-bold" className="icon" />
              </a>
            ) : (
              <a className={cx('')} onClick={item.toggleExpanded}>
                {item.expanded ? __('PutAway') : __('More')}
              </a>
            )
          ) : null}
        </td>
      );
    } else if (column.type === '__pseudoColumn') {
      return <td {...props} className={cx(column.pristine.className, { 'fixed-left-last': column.index === lastLeftFixedIndex })} >
        {props.rowIndex + 1}
      </td>;
    }


    let prefixContent: React.ReactNode = null;


    if (
      !ignoreDrag &&
      column.isPrimary &&
      store.isNested &&
      store.draggable &&
      item.draggable
    ) {
      prefixContent = (
        <a
          draggable
          onDragStart={this.handleDragStart}
          className={cx('Table-dragBtn')}
        >
          <Icon icon="drag-bar" className="icon" />
        </a>
      );
    }

    // Jay
    // 对于input-table-dynamic组件的原始数据做判断
    // 禁用
    const { $path } = this.props
    const isInputTableDynamic = /(input-table-dynamic)/.test($path)
    const isOrigin = item.pristine.isOrigin && isInputTableDynamic

    const subProps: any = {
      ...props,
      btnDisabled: store.dragging,
      data: item.locals,
      value: column.name
        ? resolveVariable(
          column.name,
          canAccessSuperData ? item.locals : item.data
        )
        : column.value,
      popOverContainer: popOverContainer || this.getPopOverContainer,
      rowSpan: item.rowSpans[column.name as string],
      quickEditFormRef: this.subFormRef,
      prefixContent,
      onImageEnlarge: this.handleImageEnlarge,
      canAccessSuperData,
      row: item,
      crudColumn: column.type == 'mapping' ? column : undefined,
      itemBadge,
      showBadge:
        !props.isHead &&
        itemBadge &&
        store.firstToggledColumnIndex === props.colIndex,
      // Jay
      disabled: isOrigin,
      showContextMenu: this.showContextMenu.bind(this)
    };
    delete subProps.label;
    if (column.type === 'operation') {
      return render(
        region,
        {
          ...column.pristine,
          className: cx(
            column.pristine.className,
            // Jay
            {
              'fixed-left-last': column.index === lastLeftFixedIndex,
              'fixed-right-first': column.index === firstRightFixedIndex
            }
          ),
          column: column.pristine,
          type: 'cell'
        },
        { ...subProps, isFieldTable: true }
      )
    }
    return render(
      region,
      {
        ...column.pristine,
        column: { ...column.pristine, quickEdit: false },
        type: 'cell',
        hiddenOn: null,
        quickEdit: false
      },
      { ...subProps, isFieldTable: true }
    )
  }

  showContextMenu(rowIndex: number, colName: string) {
    const visible = (colName != undefined && colName != 'operation')
    this.setState({ position: [rowIndex, colName], contextMenuVisible: visible })
  }

  renderToolbar(toolbar: SchemaNode) {
    const type = (toolbar as Schema).type || (toolbar as string);
    if (type === 'columns-toggler') {
      this.renderedToolbars.push(type);
      return this.renderColumnsToggler(toolbar as any);
    } else if (type === 'drag-toggler') {
      this.renderedToolbars.push(type);
      return this.renderDragToggler();
    } else if (type === 'export-excel') {
      this.renderedToolbars.push(type);
      return this.renderExportExcel(toolbar);
    } else if (type === 'check-all') { //Aug
      this.renderedToolbars.push(type);
      return this.renderCheckAll();
    }

    return void 0;
  }

  // Aug
  renderCheckAll() {
    const { store, multiple, selectable, classnames: cx, classPrefix: ns, translate: __ } = this.props;
    if (
      !store.selectable ||
      !multiple ||
      !selectable ||
      store.dragging ||
      !store.rows.length
    ) {
      return null;
    }

    return (
      <div key="checkall" className={cx('Mobile-checkall')} >
        <Checkbox
          classPrefix={ns}
          type={multiple ? 'checkbox' : 'radio'}
          checked={store.allChecked}
          onChange={() => this.handleCheckAll(3)}
          inline
        />
        <span>{__('Select.checkAll')}</span>
      </div>

    );
  }

  renderColumnsToggler(config?: any) {
    const {
      className,
      store,
      classPrefix: ns,
      classnames: cx,
      ...rest
    } = this.props;
    const __ = rest.translate;
    const env = rest.env;
    const render = this.props.render;

    if (!store.columnsTogglable) {
      return null;
    }

    return (
      <ColumnToggler
        {...rest}
        {...(isObject(config) ? config : {})}
        tooltip={config?.tooltip || __('Table.columnsVisibility')}
        tooltipContainer={
          env?.getTopModalContainer || undefined
        }
        align={config?.align ?? 'left'}
        isActived={store.hasColumnHidden()}
        classnames={cx}
        classPrefix={ns}
        key="columns-toggable"
        size={config?.size || 'sm'}
        label={
          config?.label || <Icon icon="columns" className="icon m-r-none" />
        }
        draggable={config?.draggable}
        columns={store.columnsData}
        getRawColumns={() => store.rawColumnsData} // Jay
        onColumnToggle={this.handleColumnToggle}
      >
        {store.toggableColumns.map(column => (
          <li
            className={cx('ColumnToggler-menuItem')}
            key={column.index}
            onClick={column.toggleToggle}
          >
            <Checkbox size="sm" classPrefix={ns} checked={column.toggled}>
              {column.label ? render('tpl', column.label) : null}
            </Checkbox>
          </li>
        ))}
      </ColumnToggler>
    );
  }

  renderDragToggler() {
    const { store, env, draggable, classPrefix: ns, translate: __ } = this.props;

    if (!draggable || store.isNested) {
      return null;
    }

    return (
      <Button
        disabled={!!store.modified}
        classPrefix={ns}
        key="dragging-toggle"
        tooltip={__('Table.startSort')}
        tooltipContainer={
          env?.getTopModalContainer || undefined
        }
        size="sm"
        active={store.dragging}
        onClick={(e: React.MouseEvent<any>) => {
          e.preventDefault();
          store.toggleDragging();
          store.dragging && store.clear();
        }}
        iconOnly
      >
        <Icon icon="exchange" className="icon" />
      </Button>
    );
  }

  renderExportExcel(toolbar: ExportExcelToolbar) {
    const {
      store,
      env,
      classPrefix: ns,
      classnames: cx,
      translate: __,
      columns,
      data
    } = this.props;

    if (!columns) {
      return null;
    }

    return (
      <Button
        classPrefix={ns}
        onClick={() => {
          import('exceljs').then(async (ExcelJS: any) => {
            let rows = [];
            let tmpStore;
            let filename = 'data';
            // 支持配置 api 远程获取
            if (typeof toolbar === 'object' && toolbar.api) {
              const res = await env.fetcher(toolbar.api, data);
              if (!res.data) {
                env.notify('warning', __('placeholder.noData'));
                return;
              }
              if (Array.isArray(res.data)) {
                rows = res.data;
              } else {
                rows = res.data.rows || res.data.items;
              }
              // 因为很多方法是 store 里的，所以需要构建 store 来处理
              tmpStore = TableStore.create(getSnapshot(store));
              tmpStore.initRows(rows);
              rows = tmpStore.rows;
            } else {
              rows = store.rows;
            }

            if (typeof toolbar === 'object' && toolbar.filename) {
              filename = filter(toolbar.filename, data, '| raw');
            }

            if (rows.length === 0) {
              env.notify('warning', __('placeholder.noData'));
              return;
            }

            const workbook = new ExcelJS.Workbook();
            const worksheet = workbook.addWorksheet('sheet', {
              properties: { defaultColWidth: 15 }
            });
            worksheet.views = [{ state: 'frozen', xSplit: 0, ySplit: 1 }];

            const filteredColumns = toolbar.columns
              ? columns.filter(column => {
                const filterColumnsNames = toolbar.columns!;
                if (filterColumnsNames.indexOf(column.name) !== -1) {
                  return true;
                }
                return false;
              })
              : columns;

            const firstRowLabels = filteredColumns.map(column => {
              return column.label;
            });
            const firstRow = worksheet.getRow(1);
            firstRow.values = firstRowLabels;
            worksheet.autoFilter = {
              from: {
                row: 1,
                column: 1
              },
              to: {
                row: 1,
                column: firstRowLabels.length
              }
            };
            // 用于 mapping source 的情况
            const remoteMappingCache: any = {};
            // 数据从第二行开始
            let rowIndex = 1;
            for (const row of rows) {
              rowIndex += 1;
              const sheetRow = worksheet.getRow(rowIndex);
              let columIndex = 0;
              for (const column of filteredColumns) {
                columIndex += 1;
                const name = column.name!;
                const value = getVariable(row.data, name);
                if (
                  typeof value === 'undefined' &&
                  !(column as TplSchema).tpl
                ) {
                  continue;
                }
                // 处理合并单元格
                if (name in row.rowSpans) {
                  if (row.rowSpans[name] === 0) {
                    continue;
                  } else {
                    // start row, start column, end row, end column
                    worksheet.mergeCells(
                      rowIndex,
                      columIndex,
                      rowIndex + row.rowSpans[name] - 1,
                      columIndex
                    );
                  }
                }

                const type = (column as BaseSchema).type || 'plain';
                const body = column?.body;

                if (type === 'image' && value) {
                  try {
                    const imageData = await toDataURL(value);
                    const imageDimensions = await getImageDimensions(imageData);
                    let imageWidth = imageDimensions.width;
                    let imageHeight = imageDimensions.height;
                    // 限制一下图片高宽
                    const imageMaxSize = 100;
                    if (imageWidth > imageHeight) {
                      if (imageWidth > imageMaxSize) {
                        imageHeight = (imageMaxSize * imageHeight) / imageWidth;
                        imageWidth = imageMaxSize;
                      }
                    } else {
                      if (imageHeight > imageMaxSize) {
                        imageWidth = (imageMaxSize * imageWidth) / imageHeight;
                        imageHeight = imageMaxSize;
                      }
                    }
                    const imageMatch = imageData.match(/data:image\/(.*);/);
                    let imageExt = 'png';
                    if (imageMatch) {
                      imageExt = imageMatch[1];
                    }
                    // 目前 excel 只支持这些格式，所以其它格式直接输出 url
                    if (
                      imageExt != 'png' &&
                      imageExt != 'jpeg' &&
                      imageExt != 'gif'
                    ) {
                      sheetRow.getCell(columIndex).value = value;
                      continue;
                    }
                    const imageId = workbook.addImage({
                      base64: imageData,
                      extension: imageExt
                    });
                    const linkURL = getAbsoluteUrl(value);
                    worksheet.addImage(imageId, {
                      // 这里坐标位置是从 0 开始的，所以要减一
                      tl: { col: columIndex - 1, row: rowIndex - 1 },
                      ext: {
                        width: imageWidth,
                        height: imageHeight
                      },
                      hyperlinks: {
                        tooltip: linkURL
                      }
                    });
                  } catch (e) {
                    console.warn(e.stack);
                  }
                } else if (type == 'link') {
                  const linkURL = getAbsoluteUrl(value);
                  sheetRow.getCell(columIndex).value = {
                    text: value,
                    hyperlink: linkURL
                  };
                } else if (type === 'mapping' || (type === "container" && body.type === 'mapping')) {
                  // 拷贝自 Mapping.tsx
                  let map = type === 'mapping' ? (column as MappingSchema).map : column?.body?.map;
                  const source = type === 'mapping' ? (column as MappingSchema).source : column?.body?.source;
                  if (source) {
                    let sourceValue = source;
                    if (isPureVariable(source)) {
                      sourceValue = resolveVariableAndFilter(
                        source as string,
                        data,
                        '| raw'
                      );
                    }

                    const mapKey = JSON.stringify(source);
                    if (mapKey in remoteMappingCache) {
                      map = remoteMappingCache[mapKey];
                    } else {
                      const res = await env.fetcher(sourceValue, data);
                      if (res.data) {
                        remoteMappingCache[mapKey] = res.data;
                        map = res.data;
                      }
                    }
                  }

                  if (
                    typeof value !== 'undefined' &&
                    map &&
                    (map[value] ?? map['*'])
                  ) {
                    const viewValue =
                      map[value] ??
                      (value === true && map['1']
                        ? map['1']
                        : value === false && map['0']
                          ? map['0']
                          : map['*']); // 兼容平台旧用法：即 value 为 true 时映射 1 ，为 false 时映射 0
                    sheetRow.getCell(columIndex).value =
                      removeHTMLTag(viewValue);
                  } else {
                    sheetRow.getCell(columIndex).value = removeHTMLTag(value);
                  }
                } else {
                  if ((column as TplSchema).tpl) {
                    sheetRow.getCell(columIndex).value = removeHTMLTag(
                      filter(
                        (column as TplSchema).tpl,
                        createObject(data, row.data)
                      )
                    );
                  } else if (type === 'lion-cell-file' || type === 'lion-cell-img') {
                    sheetRow.getCell(columIndex).value = value.value || '';
                  } else {
                    sheetRow.getCell(columIndex).value = value;
                  }
                }
              }
            }

            const buffer = await workbook.xlsx.writeBuffer();

            if (buffer) {
              var blob = new Blob([buffer], {
                type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
              });
              saveAs(blob, filename + '.xlsx');
            }
          });
        }}
        size="sm"
      >
        {(toolbar as Schema).label || __('CRUD.exportExcel')}
      </Button>
    );
  }

  renderActions(region: string) {
    let { actions, render, store, classnames: cx, data } = this.props;

    actions = Array.isArray(actions) ? actions.concat() : [];

    // Aug
    let btn
    if (
      !~this.renderedToolbars.indexOf('check-all') &&
      !store.hideCheckToggler &&
      store.mobileUI &&
      (btn = this.renderCheckAll())
    ) {
      actions.unshift({
        type: 'button',
        children: btn
      });
    }

    if (
      store.toggable &&
      region === 'header' &&
      !~this.renderedToolbars.indexOf('columns-toggler')
    ) {
      actions.push({
        type: 'button',
        children: this.renderColumnsToggler()
      });
    }

    if (
      store.draggable &&
      !store.isNested &&
      region === 'header' &&
      store.rows.length > 1 &&
      !~this.renderedToolbars.indexOf('drag-toggler')
    ) {
      actions.push({
        type: 'button',
        children: this.renderDragToggler()
      });
    }

    return Array.isArray(actions) && actions.length ? (
      <div className={cx('Table-actions')}>
        {actions.map((action, key) =>
          render(
            `action/${key}`,
            {
              type: 'button',
              ...(action as any)
            },
            {
              onAction: this.handleAction,
              key,
              btnDisabled: store.dragging,
              data: store.getData(data)
            }
          )
        )}
      </div>
    ) : null;
  }

  renderHeader(editable?: boolean) {
    const {
      header,
      headerClassName,
      toolbarClassName,
      headerToolbarClassName,
      headerToolbarRender,
      render,
      showHeader,
      store,
      classnames: cx,
      data,
      filterRender,//Aug
      translate: __,
      isPick,
      multiple
    } = this.props;
    if (showHeader === false) {
      return null;
    }

    // Aug
    if (store.mobileUI) {
      {/* 搜索 */ }
      const isFilter = !!Object.keys(data.filterParam || {}).length;
      const filterNode = filterRender && filterRender(isFilter);
      {/* 批量 */ }
      const checkAll = isPick && multiple ? this.renderCheckAll() : (this.props.headerBulkActions?.length || this.props.footerBulkActions?.length) ?
        <span
          className={cx('Mobile-batch-manage', { 'is-active': !store.hideCheckToggler })}
          onClick={e => {
            e.preventDefault()
            store.toggableHideCheck()
            store.clear()
          }} >
          {store.hideCheckToggler ?
            <><i className='fa fa-tasks batch-manage-icon'></i><span className='batch-text'>{__('CRUD.select')}</span></> : __('Wizard.finish')}
        </span> : null
      return filterNode || checkAll ? (
        <div className={cx('Mobile-header-toolbar-wrapper')}>
          <div className='filter-conditions'>
            {typeof data.total == 'number' ? __('CRUD.total', { total: data.total }) : null}
            {data.selectedItems?.length ? <span style={{ marginLeft: 10 }}>{__('CRUD.checked', { count: data.selectedItems.length })}</span> : null}
          </div>
          <div className='header-btns'>
            {filterNode}
            {checkAll}
          </div>
        </div>
      ) : null
    }

    const otherProps: any = {};

    const child = headerToolbarRender
      ? headerToolbarRender(
        {
          ...this.props,
          selectedItems: store.selectedRows.map(item => item.data),
          items: store.rows.map(item => item.data),
          unSelectedItems: store.unSelectedRows.map(item => item.data),
          ...otherProps
        },
        this.renderToolbar
      )
      : null;
    const actions = this.renderActions('header');

    const toolbarNode =
      actions || child || store.dragging ? (
        <div
          className={cx(
            'Table-toolbar Table-headToolbar',
            toolbarClassName,
            headerToolbarClassName
          )}
          key="header-toolbar"
        >
          {actions}
          {child}
          {store.dragging ? (
            <div className={cx('Table-dragTip')} ref={this.dragTipRef}>
              {__('Table.dragTip')}
            </div>
          ) : null}
        </div>
      ) : null;
    const headerNode =
      header && (!Array.isArray(header) || header.length) ? (
        <div className={cx('Table-header', headerClassName)} key="header">
          {render('header', header, {
            ...(editable === false ? otherProps : null),
            data: store.getData(data)
          })}
        </div>
      ) : null;

    return headerNode && toolbarNode
      ? [headerNode, toolbarNode]
      : headerNode || toolbarNode || null;
  }

  renderStatistics() {
    const { statistics, classnames: cx, render } = this.props
    if (Array.isArray(statistics)) {
      return (
        <div className={cx('FieldTable-statistics')}>
          {statistics.map(statistic => render('statistic', statistic))}
        </div>
      )
    }
    return null
  }

  renderFooter() {
    const {
      footer,
      toolbarClassName,
      footerToolbarClassName,
      footerClassName,
      footerToolbarRender,
      headerToolbar, //Aug
      footerToolbar,//Aug
      render,
      showFooter,
      store,
      data,
      classnames: cx
    } = this.props;

    if (showFooter === false) {
      return null;
    }

    // Aug
    let _footer = footerToolbar || []
    if (store.mobileUI) {
      // 勾选批量状态时
      if (!store.hideCheckToggler) {
        _footer = ["headerBulkActions", 'footerBulkActions']
      } else {
        if (headerToolbar) {
          // 移动端将所有操作统一放在在底部
          _footer = _footer.concat(headerToolbar)
        }
        // 非勾选状态不渲染批量操作 分页也不渲染
        _footer = _footer.filter((item: any) => {
          const type = (item as Schema).type || item
          return !['bulk-actions', 'headerBulkActions', 'footerBulkActions', 'bulkActions', 'pagination', 'statistics', 'switch-per-page'].includes(type)
        })
      }
    }

    const child = footerToolbarRender
      ? footerToolbarRender(
        {
          ...this.props,
          selectedItems: store.selectedRows.map(item => item.data),
          items: store.rows.map(item => item.data)
        },
        this.renderToolbar,
        _footer //Aug
      )
      : null;
    const actions = this.renderActions('footer');

    const toolbarNode =
      actions || child ? (
        <div
          className={cx(
            'Table-toolbar Table-footToolbar',
            toolbarClassName,
            footerToolbarClassName
          )}
          key="footer-toolbar"
        >
          {actions}
          {child}
        </div>
      ) : null;
    const footerNode =
      footer && (!Array.isArray(footer) || footer.length) ? (
        <div className={cx('Table-footer', footerClassName)} key="footer">
          {render('footer', footer, {
            data: store.getData(data)
          })}
        </div>
      ) : null;
    return footerNode && toolbarNode
      ? [toolbarNode, footerNode]
      : footerNode || toolbarNode || null;
  }

  renderTableContent() {
    const {
      classnames: cx,
      tableClassName,
      store,
      placeholder,
      render,
      checkOnItemClick,
      buildItemProps,
      rowClassNameExpr,
      rowClassName,
      prefixRow,
      locale,
      affixRow,
      tableContentClassName,
      translate,
      itemAction,
      autoFillHeight,
      itemActions,
      primaryField,
      infinteLoad,
      onLoadMore,
      loadHasMore,
      loading,
      tableType
    } = this.props;
    const { tableRotate } = this.state;
    // 理论上来说 store.rows 应该也行啊
    // 不过目前看来只有这样写它才会重新更新视图
    store.rows.length;
    const columns = store.filteredColumns

    return (
      <TableContent
        tableClassName={cx(
          store.combineNum > 0 ? 'Table-table--withCombine' : '',
          {
            'Table-table--checkOnItemClick': checkOnItemClick,
            // Aug 包含勾选框的情况
            'has-checkbox': store.mobileUI && columns.some(item => item.type === '__checkme')
          },
          tableClassName,
        )}
        tableName={this.props.tableName}
        className={tableContentClassName}
        itemActions={itemActions}
        itemAction={itemAction}
        store={store}
        classnames={cx}
        columns={columns}
        columnsGroup={store.columnGroup}
        rows={store.rows}
        placeholder={placeholder}
        render={render}
        onMouseMove={this.handleMouseMove}
        onScroll={this.handleOutterScroll}
        tableRef={this.tableRef}
        renderHeadCell={this.renderHeadCell}
        renderCell={this.renderCell}
        onCheck={this.handleCheck}
        onQuickChange={store.dragging ? undefined : this.handleQuickChange}
        footable={store.footable}
        footableColumns={store.footableColumns}
        checkOnItemClick={checkOnItemClick}
        buildItemProps={buildItemProps}
        onAction={this.handleAction}
        rowClassNameExpr={rowClassNameExpr}
        rowClassName={rowClassName}
        data={store.data}
        prefixRow={prefixRow}
        affixRow={affixRow}
        locale={locale}
        translate={translate}
        // Jay
        autoFillHeight={autoFillHeight}
        position={this.state.position}
        primaryField={primaryField}
        contextMenuVisible={this.state.contextMenuVisible}
        onContextMenuVisibleChange={visible => {
          if (!visible) {
            this.setState({ contextMenuVisible: false })
          }
        }}
        infinteLoad={infinteLoad}
        onLoadMore={onLoadMore}
        loadHasMore={loadHasMore}
        tableType={tableType}
        loading={loading}
        handleMultiColumnSort={this.handleMultiColumnSort}
        tableRotate={tableRotate}
        classPrefix={this.props.classPrefix}
        setBorder={this.props.setBorder}
        rowItems={this.props.items}
      />
    );
  }
  formInstance: any; // 获取table里的form实例
  //移动端点击行进入编辑
  getFieldsBody = () => {
    const { store, items, editIndex } = this.props;
    return store.filteredColumns.filter(item =>
      !['operation', '__checkme', '__dragme', '__expandme'].includes(item.pristine.type)).map(item => {
        const newType = item.pristine.quickEdit?.type || item.pristine.type;
        return {
          ...item.pristine,
          ...(item.pristine.quickEdit || {}),
          type: exchangeType(newType),
          staticShow: item.pristine.type === 'number' ? (item.pristine.quickEdit ? false : true) : undefined,
          quickEdit: false,
          value: editIndex === -1 ? items[items.length - 1][item?.name || ''] : store.rows[editIndex]?.data[item?.name || ''],
          hiddenOn: item.pristine?.quickEdit?.columnHiddenOn || item.pristine?.columnHiddenOn || item.pristine?.quickEdit?.hiddenOn || item.pristine?.hiddenOn || undefined,
          defaultOpen: false,
          remark: null
        }
      })
  }
  handleEditRow(): any {
    const { store } = this.props;
    const schema = {
      name: "mobileHoriTableForm",
      title: "",
      type: "form",
      wrapWithPanel: false,
      store
    }
    return {
      ...schema,
      body: this.getFieldsBody()
    }
  }

  handleComfirm = async () => {
    const { items, startRowEdit, env, data, editIndex, setEditIndex, updateApi, addApi, reloadApi } = this.props;
    const res = await this.formInstance.validate()
    if (res) {
      const dealNex = () => {
        startRowEdit(false)
        setEditIndex(-1)
        this.setState({ rowEditData: null })
      }
      if (editIndex === -1 && addApi) {
        this.setState({ editLoading: true })
        const ctx = createObject(data, { ...items[items.length - 1], ...this.lastChangeValue })
        const payload = await env.fetcher(addApi, ctx);
        if (payload.ok) {
          if (reloadApi) {
            const reloadData = await env.fetcher(reloadApi, data)
            if (reloadData.ok) {
              this.handleSave(reloadData.data)
              dealNex()
              message.success('操作成功！')
            } else {
              message.error(reloadData.msg)
            }
          } else {
            dealNex()
            message.success('操作成功！')
          }
        } else {
          message.error(payload.msg)
        }
        this.setState({ editLoading: false })
      } else if (editIndex !== -1 && updateApi) {
        this.setState({ editLoading: true })
        const ctx = createObject(data, { ...items[editIndex], ...this.lastChangeValue })
        const payload = await env.fetcher(updateApi, ctx);
        if (payload.ok) {
          if (reloadApi) {
            const reloadData = await env.fetcher(reloadApi, data)
            if (reloadData.ok) {
              this.handleSave(reloadData.data)
              dealNex()
            } else {
              message.warn(reloadData.msg)
            }
          } else {
            dealNex()
          }
        } else {
          message.warn(payload.msg)
        }
        this.setState({ editLoading: false })
      } else {
        dealNex()
      }
    }
  }

  rowEditModal = () => {
    const { labelName, items, editIndex, editStatus, translate: __ } = this.props;
    let fieldTitle = labelName || '';
    fieldTitle = fieldTitle.includes('</font>') ? fieldTitle.replace("<font color='red'>*</font>", '') : fieldTitle;
    fieldTitle = (fieldTitle ? fieldTitle + '-' : '') + (editIndex === -1 ? items.length : editIndex + 1)
    return isMobile() ? <Drawer
      placement="bottom"
      height="auto"
      mask
      className={`columns-toggler-drawer field-table-drawer ${tools.isIOS && !Shell.hasShell() ? 'ios-device' : ''}`}
      title={__(editIndex == -1 ? 'Combo.add' : 'Combo.edit') + '：' + fieldTitle}
      closable={false}
      zIndex={1011}
      destroyOnClose
      getContainer={this.props.env.getModalContainer}
      footer={this.editFooter()}
      visible={editStatus}>
      {this.editBody()}
    </Drawer> : <Modal
      closable={false}
      width={700}
      zIndex={10000}
      destroyOnClose
      className='row-edit-drawer ant-modal-adaptation-style'
      getContainer={false}
      title={this.editHeader(fieldTitle)}
      footer={this.editFooter()}
      visible={editStatus}>
      {this.editBody()}
    </Modal>
  }

  handleCloseFiled = () => {
    const { items, startRowEdit, editIndex, setEditIndex, store, originPrinstine, markoperationType, removeItem } = this.props;
    if (this.hasEdited) {
      Modal.confirm({
        content: '新的修改没有保存，确认要离开？',
        okText: '确定',
        okType: 'primary',
        cancelText: '取消',
        className: 'ant-modal-adaptation-style',
        zIndex: 1011,
        onOk: () => {
          if (editIndex !== -1) {
            markoperationType(undefined)
            setEditIndex(-1)
            setTimeout(() => {
              this.handleQuickChange(store.rows[editIndex], this.beforeData || originPrinstine[editIndex])
              startRowEdit(false)
              this.beforeData = null
            })
          } else {
            startRowEdit(false)
            markoperationType('delete')
            removeItem(editIndex === -1 ? items.length - 1 : editIndex)
            setEditIndex(-1)
          }
          this.setState({ rowEditData: null })
        }
      })
    } else {
      if (editIndex !== -1) {
        this.beforeData = null
        markoperationType(undefined)
        setEditIndex(-1)
        startRowEdit(false)
      } else {
        startRowEdit(false)
        markoperationType('delete')
        setTimeout(() => {
          removeItem(editIndex === -1 ? items.length - 1 : editIndex)
          setEditIndex(-1)
        })
      }
      this.setState({ rowEditData: null })
    }
  }

  editHeader = (fieldTitle: string) => {
    return <div className='edit-row-header'>
      <div>{fieldTitle}</div>
      <span style={{ top: '-3px', left: '12px' }} className='close-edit' onClick={this.handleCloseFiled}>
        <Icon icon={isMobile() ? 'title-left' : 'close'} className="icon" style={{ width: isMobile() ? '16px' : '12px', height: isMobile() ? '16px' : '12px' }} />
      </span>
    </div>
  }

  editFooter = () => (<>
    <AntBtn onClick={this.handleCloseFiled} loading={this.state.editLoading}>取消</AntBtn>
    <AntBtn type='primary' onClick={this.handleComfirm} loading={this.state.editLoading}>确定</AntBtn>
  </>)

  hasEdited = false;
  lastChangeValue = {};
  editBody = () => {
    const { render, store, editIndex, items, data, moneyParser } = this.props;
    const formData = createObject(data, editIndex === -1 ? items[items.length - 1] : store.rows[editIndex]?.data)
    return render('form-edit', this.handleEditRow(), {
      className: 'row-edit-form', mode: 'horizontal',
      store,
      addApi: undefined,
      deleteApi: undefined,
      data: { ...formData, ...this.state.rowEditData },
      removable: undefined,
      editable: undefined,
      getFormInstance: (form: any) => {
        if(!this.formInstance) {
          this.formInstance = form
        }},
      onChange: (value: any,
        name: any,
        submit?: boolean,
        changePristine?: boolean) => {
        const { editIndex, items, store, } = this.props;
        this.hasEdited = true;
        this.handleQuickChange(editIndex === -1 ? store.rows[items.length - 1] : store.rows[editIndex], moneyParser(name, this.getFieldsBody(), 'line'), true);
        this.setState({ rowEditData: { ...value, ...moneyParser(name, this.getFieldsBody(), 'line') } });
        this.lastChangeValue = { ...name }
      },
      formMobileHorizontal: true
    })
  }

  handleTableRotate = () => {
    const { tableRotate } = this.state
    this.setState({ tableRotate: !tableRotate })
  }

  render() {
    const {
      className,
      store,
      classnames: cx,
      autoFillHeight = true,
      autoGenerateFilter,
      isPick,
      multiple,
      editStatus,
      translate: __,
      tableLayout
    } = this.props;
    const { tableRotate } = this.state;
    this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了，已经渲染了就不重复渲染了。
    const heading = this.renderHeading();
    const header = this.renderHeader();
    const statistic = this.renderStatistics()
    const footer = isPick && multiple && isMobile() ? null : this.renderFooter();
    const tableClassName = cx(
      'Table-table',
      store.combineNum > 0 ? 'Table-table--withCombine' : '',
      this.props.tableClassName
    );

    return (
      <div
        className={cx('Table', `Table-${tableLayout}`, className, {
          'Table--unsaved': !!store.modified || !!store.moved,
          'Table--autoFillHeight': autoFillHeight,
          'is-mobile': store.mobileUI
        })}
        table-name={this.props.tableName}
      >
        {autoGenerateFilter ? this.renderAutoFilterForm() : null}
        {header}
        {heading}
        {statistic}
        <div className={cx('Table-contentWrap table-ping-right', {
          'hasHeader': header,
          'hasFooter': footer,
          // tableRotate,
          // isIos: Shell.hasShell() && tools.isIOS//处理横屏后ios中sticky样式的兼容问题
        })}
          onMouseLeave={this.handleMouseLeave}
        >
          {this.renderTableContent()}
          {/* {isMobile() && Shell.hasShell() && <div
            className='mobile-view-mode'
            onClick={this.handleTableRotate}
          >
            <Icon icon='table-rotate' className='icon' />
          </div>} */}
        </div >
        {editStatus && this.rowEditModal()}
        {footer}
      </div >
    );
  }
}

@Renderer({
  type: 'field-table-horizontal',
  storeType: TableStore.name,
  name: 'field-table-horizontal'
})
export class TableRenderer extends Table { }


