<template>
  <el-form :inline="true" :model="pagination" v-if="searchConfig && searchConfig.length > 0" @submit.native.prevent>
    <template v-for="item in searchConfig">
      <el-form-item v-if="item.type" :label="item.label + ':'" :prop="item.prop">
        <el-input v-if="item.type === 'input'" v-model="pagination[item.prop]" :placeholder="'请输入' + item.label" @keyup.enter.native="searchFn" clearable />
        <el-input
          v-if="item.type === 'textarea'"
          type="textarea"
          v-model="pagination[item.prop]"
          :rows="4"
          :placeholder="'请输入' + item.label"
          @keyup.enter.native="searchFn"
          style="width: 90vw"
          clearable />
        <el-select
          v-else-if="item.type === 'select'"
          v-model="pagination[item.prop]"
          :placeholder="'请选择' + item.label"
          :multiple="item.multiple"
          :clearable="!item.clearable"
          @change="searchFn">
          <template v-if="item.dictKey">
            <el-option v-for="itemDict in useUserStore().allDict[item.dictKey]" :key="itemDict.dictKey" :label="itemDict.dictValue" :value="itemDict.dictKey" />
          </template>
          <template v-else-if="item.data && item.data.length > 0">
            <el-option v-for="itemDict in item.data" :key="itemDict.dictKey" :label="itemDict.dictValue" :value="itemDict.dictKey" />
          </template>
        </el-select>
        <template v-else-if="item.type === 'datetime'">
          <el-row>
            <el-col :span="11">
              <el-date-picker
                v-model="pagination[item.propList[0]]"
                type="datetime"
                placeholder="请选择起始时间"
                value-format="YYYY-MM-DD HH:mm:ss"
                style="width: 100%"
                @change="searchFn" />
            </el-col>
            <el-col :span="2" style="text-align: center">-</el-col>
            <el-col :span="11">
              <el-date-picker
                v-model="pagination[item.propList[1]]"
                type="datetime"
                placeholder="请选择结束时间"
                value-format="YYYY-MM-DD HH:mm:ss"
                style="width: 100%"
                @change="searchFn" />
            </el-col>
          </el-row>
        </template>
        <template v-else-if="item.type === 'date'">
          <el-row>
            <el-col :span="11">
              <el-date-picker
                v-model="pagination[item.propList[0]]"
                type="date"
                placeholder="请选择起始日期"
                value-format="YYYY-MM-DD"
                style="width: 100%"
                @change="searchFn" />
            </el-col>
            <el-col :span="2" style="text-align: center">-</el-col>
            <el-col :span="11">
              <el-date-picker
                v-model="pagination[item.propList[1]]"
                type="date"
                placeholder="请选择结束日期"
                value-format="YYYY-MM-DD"
                style="width: 100%"
                @change="searchFn" />
            </el-col>
          </el-row>
        </template>
        <el-radio-group v-model="pagination[item.prop]" v-else-if="item.type === 'radio-group'" @change="changeRadioGroup">
          <template v-if="item.data && item.data.length > 0">
            <el-radio-button v-for="itemDict in item.data" :label="itemDict.dictKey">{{ itemDict.dictValue }}</el-radio-button>
          </template>
        </el-radio-group>
      </el-form-item>
    </template>
    <el-form-item>
      <el-button @click="searchFn" class="searchBtn" v-preReClick>搜索</el-button>
      <el-button @click="clickReset" v-preReClick>重置</el-button>
    </el-form-item>
  </el-form>
  <div class="header-actions" v-if="getActionList.length != 0">
    <div class="overflow-box">
      <el-button
        v-for="(item, index) in getActionList"
        :key="index"
        :type="item.type || 'primary'"
        :plain="item.plain || false"
        v-bind="getHeaderActions(item)"
        @click="item.action"
        :disabled="item.disabled || false"
        size="small"
        v-preReClick>
        {{ item.text }}
      </el-button>
    </div>
  </div>
  <div class="table-list-container by-table">
    <component :is="containerTag">
      <div class="filter-form-container">
        <slot />
      </div>
      <el-table
        ref="hocElTable"
        v-loading="loading"
        :data="source"
        v-if="!hideTable"
        style="width: 100%"
        v-bind="$attrs"
        v-on="tableEvents"
        :height="tableHeight"
        :row-style="{ height: '35px' }"
        header-row-class-name="tableHeader"
        :default-expand-all="defaultExpandAll || false"
        :cell-class-name="cellClassName"
        :span-method="spanMethod"
        :border="borderStatus">
        <el-table-column
          v-for="(item, index) in config"
          :key="index"
          v-bind="getAttrsValue(item)"
          :type="item.type || ''"
          :selectable="(rowData, rowIndex) => isSelectable(rowData, rowIndex, item)">
          <template #default="scope" v-if="!item.type">
            <slot :name="item.attrs.slot" :item="scope.row" v-if="item.attrs.slot"> 插槽占位符 </slot>
            <div v-else-if="isFunction(getValue(scope, item))">
              <component
                :is="renderTypeList[getMatchRenderFunction(item)].target"
                :cell-list="getValue(scope, item)()"
                :row="scope.row"
                :parent="getParent"
                @click="
                  ($event) => {
                    handleNativeClick(getAttrsValue(item), $event, item);
                  }
                " />
            </div>
            <div v-else>
              {{ getValue(scope, item) }}
            </div>
          </template>
          <template #default="props" v-else>
            <slot :name="item.attrs.slot" :item="props.row" v-if="item.attrs.slot"> 插槽占位符 </slot>
          </template>
        </el-table-column>
      </el-table>
      <el-row v-if="!hidePagination" class="table-pagination" justify="end" type="flex">
        <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          :current-page="getPagination.pageNum"
          :page-size="getPagination.pageSize"
          :total="getPagination.total"
          @size-change="handleSizeChange"
          @current-change="handlePageChange" />
      </el-row>
    </component>
  </div>
</template>

<script>
import { isFunction as isFn, isBoolean } from "./type";
import ElementsMapping from "./ElementsMapping";
import ComponentsMapping from "./ComponentsMapping";
import { computed, defineComponent, getCurrentInstance, ref, watch } from "vue";

export default defineComponent({
  name: "Table",
  components: {
    ElementsMapping,
    ComponentsMapping,
  },
  props: {
    hideTable: {
      type: Boolean,
      default: false,
    },
    // 获取表格元数据时携带的参数
    filterParams: {
      type: Object,
      default() {
        return {};
      },
    },
    // 表格加载 loading
    loading: {
      type: Boolean,
      default: false,
    },
    // 表格元数据
    source: {
      type: Array,
      required: true,
      default() {
        return [];
      },
    },
    // 表格是否全部展开
    defaultExpandAll: {
      type: Boolean,
      default: false,
    },
    // 查询条件
    searchConfig: {
      type: Array,
      default() {
        return [];
      },
    },
    tableHeight: {
      type: Number,
      required: false,
    },
    // 指定外层容器的渲染组件
    containerTag: {
      type: String,
      default: "div",
    },
    // 是否隐藏分页
    hidePagination: {
      type: Boolean,
      default: false,
    },
    // 分页配置
    pagination: {
      type: Object,
      default() {
        return {};
      },
    },
    // 表格配置文件
    config: {
      type: Array,
      default() {
        return [];
      },
    },
    // 表头右上方的按钮组
    actionList: {
      type: Array,
      default() {
        return [{ text: "", action: () => {} }];
      },
    },
    // element table 原生事件
    tableEvents: {
      type: Object,
      default() {
        return {};
      },
    },
    searchKey: {
      type: String,
      default: "keyword",
    },
    cellClassName: {
      type: Function,
      default() {},
    },
    spanMethod: {
      type: Function,
      default() {},
    },
    borderStatus: {
      type: Boolean,
      default: false,
    },
  },
  setup(props) {
    const { proxy } = getCurrentInstance();
    const changeStatData = () => {
      statWarpHeight.value = document.getElementById("statWarp").offsetHeight;
    };
    let statWarpHeight = ref(0);
    const getAttrsValue = (item) => {
      const { attrs } = item;
      const result = {
        ...attrs,
      };
      delete result.prop;
      return result;
    };
    const renderTypeList = ref({
      render: {},
      renderHTML: {
        target: "elements-mapping",
      },
      renderComponent: {
        target: "components-mapping",
      },
      renderMoreBtn: {
        target: "more-btn",
      },
    });
    const getParent = computed(() => {
      return proxy.$parent;
    });
    const getPagination = computed(() => {
      const params = {
        pageNum: 1,
        pageSize: 10,
        total: 0,
      };
      return Object.assign({}, params, props.pagination);
    });
    const getActionList = computed(() => {
      return props.actionList.slice().filter((it) => it.text);
    });
    const getValue = (scope, configItem) => {
      const prop = configItem.attrs.prop;
      const renderName = getMatchRenderFunction(configItem);
      const renderObj = renderTypeList.value[renderName];
      if (renderObj && isFunction(configItem[renderName])) {
        return renderObj.target
          ? getRenderValue(scope, configItem, {
              name: renderName,
              type: "bind",
            })
          : getRenderValue(scope, configItem);
      }
      return scope.row[prop];
    };
    const getRenderValue = (scope, item, fn = { name: "render", type: "call" }) => {
      const prop = item.attrs.prop;
      const propValue = prop && scope.row[prop];
      scope.row.$index = scope.$index;
      const args = propValue !== undefined ? propValue : scope.row;
      return item[fn.name][fn.type](getParent.value, args, scope.row);
    };
    // 匹配 render 开头的函数
    const getMatchRenderFunction = (obj) => {
      return Object.keys(obj).find((key) => {
        const matchRender = key.match(/^render.*/);
        return matchRender && matchRender[0];
      });
    };
    const isFunction = (fn) => {
      return isFn(fn);
    };
    const searchFn = () => {
      proxy.$emit("getList", { pageNum: 1 });
    };
    const clickReset = () => {
      proxy.$emit("clickReset", "");
    };
    const changeRadioGroup = () => {
      proxy.$emit("changeRadioGroup", "");
    };
    const handlePageChange = (val) => {
      proxy.$emit("getList", Object.assign(props.filterParams, { pageNum: val }));
    };
    const handleSizeChange = (val) => {
      proxy.$emit("getList", Object.assign(props.filterParams, { pageSize: val }));
    };
    const getHeaderActions = (item) => {
      return {
        ...item.attrs,
      };
    };
    const stopBubbles = (e) => {
      const event = e || window.event;
      if (event && event.stopPropagation) {
        event.stopPropagation();
      } else {
        event.cancelBubble = true;
      }
    };
    const handleNativeClick = ({ isBubble }, e, item) => {
      // 考虑到单元格内渲染了组件，并且组件自身可能含有点击事件，故添加了阻止冒泡机制
      // 若指定 isBubble 为 false，则当前单元格恢复冒泡机制
      if (isBoolean(isBubble) && !isBubble) return;
      stopBubbles(e);
    };
    const isSelectable = (row, index, item) => {
      if (item.type === "selection") {
        if (item.attrs && item.attrs.checkAtt) {
          if (row[item.attrs.checkAtt]) {
            return row[item.attrs.checkAtt];
          }
        } else {
          return true;
        }
      }
    };
    const hocElTable = ref();
    return {
      getParent,
      getPagination,
      renderTypeList,
      getActionList,
      getAttrsValue,
      getValue,
      getRenderValue,
      getMatchRenderFunction,
      isFunction,
      handlePageChange,
      handleSizeChange,
      getHeaderActions,
      stopBubbles,
      handleNativeClick,
      searchFn,
      clickReset,
      changeRadioGroup,
      isSelectable,
      statWarpHeight,
      changeStatData,
      hocElTable,
    };
  },
});
</script>
<style>
.table-list-container th {
  color: #333 !important;
}
.by-table td .el-button + .el-button {
  margin-left: 0 !important;
}
.by-table td .el-button {
  background: none !important;
  margin: 0 !important;
  padding: 8px 6px !important;
}
.el-checkbox__input.is-disabled .el-checkbox__inner {
  background-color: #dee1e6;
  border-color: #b2b4b9;
}
.el-table .cell {
  line-height: 35px;
}
</style>
<style lang="scss" scoped>
.sortableActive {
  background: #f5f7fa !important;
}
.show-more {
  height: auto !important;
}
.stat-warp {
  margin-bottom: 20px;
  background: #fff;
  padding: 0 20px;
  height: 200px;
  overflow: hidden;
  position: relative;
  .more-btn {
    position: absolute;
    right: 0;
    top: 0;
    width: 40px;
    height: 30px;
    cursor: pointer;
    font-size: 12px;
    line-height: 30px;
    text-align: center;
  }
  ul {
    padding: 0;
    overflow: hidden;
    margin: 0;
    li {
      list-style: none;
      min-width: 285px;
      box-sizing: border-box;
      margin: 0 20px 20px 0;
      background: #eff6ff;
      float: left;
      overflow: hidden;
      padding: 20px;
      color: #333333;
      border-radius: 10px;
      .label {
        font-size: 14px;
      }
      .label::before {
        width: 10px;
        height: 10px;
        content: "";
        border-radius: 50%;
        background: #0084ff;
        display: inline-block;
        margin-right: 10px;
      }
      .num {
        margin-top: 10px;
        font-size: 24px;
        font-weight: bold;
      }
    }
    //#F5F3FF #9E64ED
    .theme2 {
      background: #f5f3ff;
      .label::before {
        background: #9e64ed;
      }
    }
    //#FFF1E1 #FF9315
    .theme3 {
      background: #fff1e1;
      .label::before {
        background: #ff9315;
      }
    }
    //#E2FBE8 #39C55A
    .theme4 {
      background: #e2fbe8;
      .label::before {
        background: #39c55a;
      }
    }
    .theme5 {
      background: #ffebe9;
      .label::before {
        background: #f94539;
      }
    }
    .theme6 {
      background: #e4f9f9;
      .label::before {
        background: #53cbcb;
      }
    }
    .multi-data {
      .label::before {
        display: none;
      }
      .label {
        font-size: 14px;
        font-weight: bold;
        color: #333;
        margin-bottom: 8px;
      }
      .num-warp {
        overflow: hidden;
        .num-box {
          float: left;
          min-width: 80px;
          margin-right: 20px;
          .num-small {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 8px;
          }
          .label-small {
            color: #666;
            font-size: 14px;
          }
        }
      }
    }
  }
}
.by-search {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  .more-icon {
    float: right;
    cursor: pointer;
    line-height: 32px;
    text-align: center;
    margin-left: 5px;
  }
}
.by-dropdown {
  position: relative;
  text-align: left;
  height: 32px;
  z-index: 1010;
  padding: 0 10px;
  transition: all 0.5s ease;
  cursor: pointer;
  line-height: 32px;

  .by-dropdown-title {
    font-size: 14px;
    background-color: #fff;
  }
  ul {
    position: absolute;
    left: 0;

    top: 32px;
    padding: 0;
    margin: 0;
    z-index: 100;
    display: none;
    white-space: nowrap;
    li {
      list-style: none;
      font-size: 12px;
      height: 30px;
      padding: 0 10px;
    }
    li:hover {
      background-color: #eff6ff;
      color: #0084ff;
    }
  }
}

.by-dropdown::before {
  display: block;
  width: 1px;
  content: " ";
  position: absolute;
  height: 14px;
  top: 8px;
  background-color: #ddd;
  right: 0;
  z-index: 1011;
}

.by-dropdown:hover {
  background: #ffffff;

  border-radius: 2px 2px 2px 2px;
  opacity: 1;
  ul {
    background: #ffffff;
    box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06);
    border-radius: 2px 2px 2px 2px;
    opacity: 1;
    display: block;
    text-align: left;
  }
}
.header-actions {
  flex: 1;
  overflow-x: auto;
  background: #fff;
  .overflow-box {
    margin-bottom: 10px;
    :deep() .el-button:nth-child(1) {
      margin-right: 10px;
    }
  }
}
.table-list-container {
  .table-pagination {
    padding: 10px 0;
  }
  .header {
    display: flex;
    padding-bottom: 20px;
  }
  .el-table {
    :deep() th {
      font-size: 14px;
    }
    :deep() td {
      font-size: 14px;
    }
  }
}
.by-dropdown-lists {
  max-height: 50vh;
  overflow-y: auto;
  line-height: 1;
}
::v-deep(.el-button--small) {
  padding: 5px 17px;
}
.searchBtn {
  background: #20b2aa;
  color: #fff;
  border: 1px solid #20b2aa;
}
::v-deep(.el-form-item) {
  margin-right: 16px !important;
}
::v-deep(.el-input__wrapper) {
  position: relative;
  .el-input__inner {
    padding-right: 18px;
  }
  .el-input__suffix {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
  }
}
</style>
