<template>
  <!-- 文件管理器 auth：weilan time：2019-10-25 github：https://github.com/hql7 -->
  <div class="wl-explorer" @contextmenu.prevent>
    <!-- 头部按钮区 -->
    <el-form
      class="wl-header-btn"
      :inline="true"
      :size="size"
      @submit.native.prevent
    >
      <el-form-item class="file-handle-box">
        <el-tooltip content="后退">
          <i class="iconfont icon-wl-left file-path-handle" :class="{ 'u-disabled': pathIsStart }" @click="pathBtn('prv')"></i>
        </el-tooltip>
      </el-form-item>

      <el-form-item class="file-search-box">
        <el-input v-model.trim="file.key" placeholder="请输入关键字搜索" clearable/>
      </el-form-item>

      <el-form-item>
        <slot name="header-btn-1"></slot>
        <el-button type="primary" @click="fileSearch">搜索</el-button>
        <el-button type="primary" @click="wlDownload">下载</el-button>
        <slot name="header-btn-2"></slot>
      </el-form-item>

      <!-- 浏览模式切换 -->
      <el-form-item class="u-right">
        <el-tooltip content="文件夹模式">
          <i class="iconfont icon-wl-list file-show-type" v-show="layout.show_list" @click="layout.show_list = !layout.show_list"/>
        </el-tooltip>
        <el-tooltip content="列表模式">
          <i class="iconfont icon-wl-grid file-show-type" v-show="!layout.show_list" @click="layout.show_list = !layout.show_list"/>
        </el-tooltip>
      </el-form-item>
    </el-form>

    <el-breadcrumb style="padding: 0 0 10px 20px" separator-class="el-icon-arrow-right">
      <el-breadcrumb-item  v-for="(item, index) in path.history" :key="item.id">
        <template v-if="path.history.length > 6">
          <el-link type="primary" :underline="false" @click="handleBreadcrumb(item)" v-if="index<2">{{ item.path ? item.path : '首页' }}</el-link>
          <el-tooltip class="item" effect="dark" :content="item.path" placement="top" v-else-if="index<path.history.length-2">
            <el-link type="primary" :underline="false" @click="handleBreadcrumb(item)">...</el-link>
          </el-tooltip>
          <el-link type="primary" :underline="false" @click="handleBreadcrumb(item)" v-else>{{ item.path ? item.path : '首页' }}</el-link>
        </template>
        <template v-else>
          <el-link type="primary" :underline="false" @click="handleBreadcrumb(item)">{{ item.path ? item.path : '首页' }}</el-link>
        </template>
      </el-breadcrumb-item>
    </el-breadcrumb>

    <!-- 主内容区 -->
    <el-scrollbar class="wl-main-scroll">
      <!-- 文件列表区 -->
      <div class="wl-main-list">
        <!-- 表格型文件列表 -->
        <el-table
          v-show="layout.show_list"
          @selection-change="filrChecked"
          highlight-current-row
          :border="showBorder"
          :data="self_data"
          :height="tableHeight"
          class="wl-table"
          ref="wl-table"
        >
          <el-table-column
            v-if="showCheckbox"
            :selectable="checkSelectable"
            align="center"
            type="selection"
            width="55"
          ></el-table-column>
          <el-table-column
            v-if="showIndex"
            align="center"
            type="index"
            label="序号"
            width="55"
          ></el-table-column>
          <slot name="table-column-top"></slot>
          <el-table-column
            v-for="i of selfColumns"
            show-overflow-tooltip
            :key="i._id"
            :prop="i.prop"
            :width="i.width"
            :label="i.label"
            :fixed="i.fixed"
            :align="i.align"
            :sort-by="i.sortBy"
            :sortable="i.sortable"
            :min-width="i.minWidth"
            :formatter="i.formatter"
            :column-key="i.columnKey"
            :class-name="i.className"
            :sort-method="i.sortMethod"
            :header-align="i.headerAlign"
            :render-header="i.renderHeader"
            :label-class-name="i.labelClassName"
          >
            <template slot-scope="scope">
              <!-- 非名称列 -->
              <template v-if="i.prop !== selfProps.name">
                {{
                  i.formatter
                    ? i.formatter(
                        scope.row,
                        scope.column,
                        scope.row[i.prop],
                        scope.$index
                      )
                    : scope.row[i.prop]
                }}
              </template>
              <!-- 名称列 -->
              <div
                v-else
                @click="enterTheLower(scope.row, scope.row[selfIsFolder])"
                class="wl-name-col wl-is-folder"
              >
                <!-- 不同文件类型图标区 -->
                <div class="namecol-iconbox">
                  <img
                    :src="fileTypeIcon(scope.row)"
                    class="name-col-icon"
                    alt="文件类型图标"
                  />
                </div>
                <!-- 不同文件类型 显示内容-->
                <div class="namecol-textbox">
                  {{
                    i.formatter
                      ? i.formatter(
                          scope.row,
                          scope.column,
                          scope.row[i.prop],
                          scope.$index
                        )
                      : scope.row[i.prop]
                  }}
                </div>
              </div>
            </template>
          </el-table-column>
          <slot name="table-column-bottom"></slot>
        </el-table>
        <!-- 列表型文件列表 -->
        <ul class="wl-list" v-show="!layout.show_list">
          <li
            class="wl-list-item wl-is-folder"
            v-for="(i, idx) in self_data"
            :key="i.Id"
          >
            <el-checkbox
              class="wl-checkbox"
              :class="{ 'wl-checkbox-checked': i._checked }"
              :disabled="!checkSelectable(i)"
              @change="listItemCheck($event, i)"
              v-model="i._checked"
            ></el-checkbox>
            <div @click="enterTheLower(i, i[selfIsFolder])">
              <img
                :src="fileTypeIcon(i)"
                class="name-col-icon"
                alt="文件类型图标"
              />
              <p class="list-item-name" :title="i[selfProps.name]">
                {{
                  i.formatter
                    ? i.formatter(i, null, i[selfProps.name], idx)
                    : i[selfProps.name]
                }}
              </p>
            </div>
          </li>
        </ul>
        <!-- 横排型文件列表 -->
        <slot name="main"></slot>
      </div>
    </el-scrollbar>
    <!-- slot 自定义dom区 -->
    <slot></slot>
    <!-- 文件预览区 -->
    <template v-if="usePreview">
      <file-view
        v-if="layout.view"
        ref="file-view"
        class="file-view-components"
        :previewType="previewType"
        :previewOptions="previewOptions"
        @close="layout.view = false"
      ></file-view>
    </template>
  </div>
</template>

<script>
import fileView from "@/components/file-view.vue"; // 导入预览组件
import { arrayToTree, splicParentsUntil, download } from "@/util"; // 导入组装树函数、拼接路径函数
const guid = "00000000-0000-0000-0000-000000000000";
export default {
  name: "wlExplorer",
  components: { fileView },
  data() {
    return {
      layout: {
        show_list: true, // 文件展示形式
        edit_path: false, // 是否编辑路径
        view: false, // 预览视图
        move: false, // 移动视图
        upload: false, // 上传视图
      }, // 视图管理
      file: {
        pid: "", // 父文件夹
        id: "", // 文件夹id
        path: "", // 文件路径
        key: "", // 关键字
      }, // 文件相关参数
      path: {
        level: 1, // 当前层级
        index: -1, // 在历史的第几步
        history: [
          {
            path: "", // 文件夹名字
            pid: "", // 路径
            id: "", // 文件夹id
            data: [], // 数据
          },
        ], // 历史路径
      }, // 记录路径历史
      self_data: [], // 当前数据
      file_checked_data: [], // 列表多选数据
      tree_path: [], // 全部路径树数据
    };
  },
  props: {
    /**
     * 头部更多操作自定义内容
     * 需要包含内容：
     * name: String 每条数据的名字
     * command: Function 每条数据的指令
     * disabled: Boolean 每条数据的禁用
     * divided: Boolean 每条数据的显示分割线
     * icon: String 每条数据的图标类名
     */
    headerDropdown: Array,
    // 是否显示复选框
    showCheckbox: {
      type: Boolean,
      default: true,
    },
    // add: 复选框是否可选择
    checkSelectable: {
      type: Function,
      default: () => true
    },
    // 是否显示顺序号
    showIndex: {
      type: Boolean,
      default: true,
    },
    // 表格是否显示边框
    showBorder: {
      type: Boolean,
      default: true,
    },
    // 文件表格数据
    data: Array,
    // 文件表头数据【[参数：所有el-Table-column Attributes] (https://element.eleme.cn/#/zh-CN/component/table)】
    columns: Array,
    /**
     * 配置项
     * isFolder: Boolean
     * name: String 表示名称列的字段
     */
    props: Object,
    // 所有文件路径 用于文件地址输入框自动提示,如不传则使用历史记录
    allPath: {
      type: Array,
      default: () => {
        return [];
      },
    },
    // 校验是否文件夹函数，（row）参数为当前行数据，用于复杂类型，当isFolderFn优先使用计算结果，不存在时使用props配置内的isFolder字段
    isFolderFn: Function,
    // 是否锁定文件、文件夹函数,true则不可进行操作
    isLockFn: Function,
    // 是否使用自带上传组件
    useUpload: {
      type: Boolean,
      default: true,
    },
    // 上传文件地址
    uploadUrl: {
      type: String,
      default: "",
    },
    //  是否校验上传文件
    uploadReg: {
      type: Boolean,
      default: false,
    },
    // 上传文件前校验函数，应返回Boolean
    uploadRegFuc: Function,
    // 上传文件头参数
    uploadHeaders: Object,
    // 上传文件参数
    uploadOptions: Object,
    // 上传个数限制
    uploadLimit: Number,
    // 是否使用自带预览组件
    usePreview: {
      type: Boolean,
      default: true,
    },
    // 预览文件类型
    previewType: {
      type: String,
      default: "img",
    },
    // 预览文件地址或配置项
    previewOptions: [Object, String],
    // 拼接路径配置项
    splicOptions: Object,
    size: {
      type: String,
      default: "medium",
    },
    // add: 固定表头
    tableHeight: Number,
  },
  methods: {
    // 搜索文件
    fileSearch() {
      this.$emit("search", this.file, true)
    },
    /**
     * 往历史里添加新的步骤
     * file: Object 路径数据{id: 路径id, pid: 父级路径id, path: 路径名}
     * data: Array 当前路径下的数据
     */
    routerPush(file, data = []) {
      splicParentsUntil(this.allPath, file, this.selfProps);
      this.clearSearchKey();
      this.path.history.push({
        ...file,
        data,
      });
      this.self_data = data;
      this.file.pid = file.pid;
      this.file.id = file.id;
      this.file.path = splicParentsUntil(this.allPath, file, this.selfProps);
      this.path.level = !file.id || file.id === guid ? 1 : 2;
      this.path.index = -1; // 将步骤从新回到原位
    },
    /**
     * 处理当前步骤数据
     * file: Object 路径数据{id: 路径id, pid: 父级路径id, path: 路径名}
     * data: Array 当前路径下的数据
     */
    routerActive(file, data) {
      this.clearSearchKey();
      this.file.pid = file.pid;
      this.file.id = file.id;
      this.file.path = splicParentsUntil(this.allPath, file, this.selfProps);
      this.self_data = data;
      this.path.level = !file.id || file.id === guid ? 1 : 2;
    },
    // 显示下载
    wlDownload() {
      if (this.file_checked_data.length === 0) {
        this.$message({
          showClose: true,
          message: "请选择要下载的文件或文件夹",
          type: "error",
        });
        return;
      }
      this.$emit("download", this.file_checked_data, download);
    },
    // 前进后退按钮操作
    pathBtn(type) {
      if (type === "prv") {
        if (this.pathIsStart) return;
        if (this.path.index === -1) {
          this.path.index = this.path.history.length - 1;
        }
        this.path.index -= 1;
        let _prv = this.path.history[this.path.index];
        this.routerActive(_prv, _prv.data);
        this.path.history.pop()
        this.$emit("backward", this.path.history)
      }
    },
    /**
     * 文件夹时进入下一级-文件时预览文件
     * row: Object 行数据
     * isFolder: Boolean 行是否文件夹
     */
    enterTheLower(row, isFolder) {
      if (!isFolder) {
        this.previewFile(row);
        return;
      }
      let _children = this.path.history.find(
        (i) => i.id === row[this.selfProps.pathId]
      );
      if (_children) {
        // 历史找到子集时
        this.path.history.splice(
          this.path.history.findIndex(
            (i) => i.id === row[this.selfProps.pathId]
          ),
          1
        );
        this.routerPush(_children, _children.data);
        return;
      }
      // 历史找不到子集时 请求更新
      this.routerPush({
        id: row[this.selfProps.pathId],
        pid: row[this.selfProps.pathPid],
        path: row[this.selfProps.pathName],
      });
      this.$emit("search", this.file, false);
    },
    // 根据文件类型显示图标
    fileTypeIcon(row) {
      let _path = "";
      // 文件夹
      if (row[this.selfIsFolder]) {
        _path = row[this.selfIsLock]
          ? require("./images/file_automatic@3x.png")
          : require("./images/folder@3x.png");
        return _path;
      }
      // 其他根据后缀类型
      let _suffix = row[this.selfProps.suffix];
      if (!_suffix) {
        _path = require("./images/file_none@3x.png");
        return _path;
      }
      if (["jpg", "jpeg", "png", "gif", "bmp"].includes(_suffix)) {
        // 图片
        _path = require("./images/file_img@3x.png");
      } else if (["zip", "rar", "7z"].includes(_suffix)) {
        _path = require("./images/file_zip@3x.png");
      } else if (
        ["avi", "mp4", "rmvb", "flv", "mov", "m2v", "mkv"].includes(_suffix)
      ) {
        _path = require("./images/file_video@3x.png");
      } else if (["mp3", "wav", "wmv", "wma"].includes(_suffix)) {
        _path = require("./images/file_mp3@3x.png");
      } else if (["xls", "xlsx"].includes(_suffix)) {
        _path = require("./images/file_excel@3x.png");
      } else if (["doc", "docx"].includes(_suffix)) {
        _path = require("./images/file_docx@3x.png");
      } else if ("pdf" == _suffix) {
        _path = require("./images/file_pdf@3x.png");
      } else if ("ppt" == _suffix) {
        _path = require("./images/file_ppt@3x.png");
      } else if ("txt" == _suffix) {
        _path = require("./images/file_txt@3x.png");
      } else {
        _path = require("./images/file_none@3x.png");
      }
      return _path;
    },
    // 记录多选列表数据
    filrChecked(val) {
      this.self_data.forEach((i) => (i._checked = false));
      val.forEach((i) => (i._checked = true));
      this.file_checked_data = val;
    },
    // 列表模式记录多选数据
    listItemCheck(check, val) {
      this.$refs["wl-table"].toggleRowSelection(val);
    },
    // 清空关键词
    clearSearchKey() {
      this.file.key = "";
    },
    // 预览文件
    previewFile(row) {
      this.$emit("preview", row, this.showPreview);
    },
    // 打开预览组件
    showPreview() {
      this.layout.view = true;
    },
    // 处理数据变动
    handleDataChange(val) {
      let _data = val || [];
      if (this.isFolderFn) {
        _data.forEach((i) => {
          i.isFolder = this.isFolderFn(i);
        });
      }
      if (this.isLockFn) {
        _data.forEach((i) => {
          i.isLock = this.isLockFn(i);
        });
      }
      if (this.file.key) {
        this.self_data = _data;
        return;
      }
      let _act = this.path.history.find((i) => i.id === this.file.id);
      if (!_act) return;
      _act.data = _data;
      this.routerActive(_act, _data);
    },
    // 面包屑点击
    handleBreadcrumb(val) {
      const index = this.path.history.findIndex(item => item.id === val.id)
      if (index === this.path.history.length) return
      this.routerActive(this.path.history[index], this.path.history[index].data)
      this.path.history = this.path.history.slice(0, index+1)
      this.$emit("backward", this.path.history)
    },
  },
  computed: {
    // 自身表头数据
    selfColumns() {
      let _data = this.columns || [];
      _data.forEach((i, idx) => {
        i._id = `_col_${idx}`;
      });
      return _data;
    },
    // 自身配置项
    selfProps() {
      return {
        isFolder: "isFolder", // Boolean 用于有布尔值字段表示数据是否文件夹类型的情况,当使用isFolderFn函数时，此参数会被忽略
        isLock: "isLock", // Boolean 用于有布尔值字段表示数据是否锁定文件类型的情况,当使用isLockFn函数时，此参数被忽略
        name: "name", // String 用于显示名称列的字段
        suffix: "suffix", // String 用于判断后缀或显示文件类型列的字段
        match: "name", // String 用于设定输入框自动补全的匹配字段
        splic: true, // Boolean 用于设定输入框自动补全的匹配字段是否需要将match字段和祖先节点拼接
        pathName: "name", // String 路径数据 显示名称字段
        pathId: "id", // String 路径数据 id字段
        pathPid: "pid", // String 路径数据 pid字段
        pathChildren: "children", // String 路径数据 children字段
        pathDisabled: "disabled", // String 路径数据 禁用字段
        pathConnector: "\\", // String 路径父子数据拼接连接符,默认为'\'
        pathParents: "parents", // String 路径数据所有直系祖先节点自增长identityId逗号拼接
        pathIdentityId: "identityId", // String 路径数据自增长id
        ...this.props,
      };
    },
    // 将是否文件夹的两种判断方式合并返回
    selfIsFolder() {
      return this.isFolderFn ? "isFolder" : this.selfProps.isFolder;
    },
    // 将是否锁定文件、文件夹的两种判断方式合并返回
    selfIsLock() {
      return this.isLockFn ? "isLock" : this.selfProps.isLock;
    },
    // 当前是否最后一步
    pathIsEnd() {
      return (
        this.path.history[this.path.history.length - 1].id === this.file.id
      );
    },
    // 当前是否最后一步
    pathIsStart() {
      return this.path.history[0].id === this.file.id;
    },
    // 自身文件路径输入框提示列表
    selfPathHistory() {
      let _all_path = this.allPath || [];
      if (this.selfProps.splic) {
        this.allPath.forEach((i) => {
          i.id = i[this.selfProps.pathId];
          i.value = splicParentsUntil(_all_path, i, this.selfProps);
        });
      } else {
        this.allPath.forEach((i) => {
          i.value = i[this.selfProps.match];
        });
      }
      return this.allPath || this.path.history || [];
    },
  },
  watch: {
    // 检测data数据更新列表
    data(val) {
      this.handleDataChange(val);
    },
    // 检测所有路径，组成树
    allPath(val) {
      let options = {
        id: this.selfProps.pathId,
        pid: this.selfProps.pathPid,
        children: this.selfProps.pathChildren,
      };
      this.tree_path = arrayToTree(val || [], options);
    },
  },
  created() {
    if (this.data && this.data.length > 0) {
      this.handleDataChange(this.data);
    }
  },
};
</script>

<style scoped src="./css/index.css"/>
<style scoped src="./css/clear.css"/>
<style scoped src="./icons/iconfont.css"/>