<template>
  <el-container class="x-admin" :class="{'is-immersion':immersion, 'is-tab-dragging':isTabDragging}">
    <el-header v-if="!immersion" class="x-admin-header" height="48px" :style="bannerStyle">
      <div class="x-admin-logo" @click="handleLogoClick">
        <slot name="logo">
          <img src="./assets/logo.png" alt="">
        </slot>
      </div>
      <NavTabs v-model="currentTabName"
               ref="navTabs"
               :items="tabs"
               :limit="tabLimit"
               :beforeRemoveTab="beforeRemoveTab"
               @tab-click="loadByTab"></NavTabs>
      <div class="x-admin-extend-tool" v-if="$slots['extend-tool']">
        <slot name="extend-tool"></slot>
      </div>
      <IconButtonBar :items="tools" :skin="skin" :default-theme="defaultSkin" :sizes="sizes"
                     :default-size="defaultSize">
        <slot v-for="n in tools" :name="`tool-${n.name}`" :slot="n.name"></slot>
      </IconButtonBar>
      <UserInfo v-if="user" v-bind="user" :tools="userTools"></UserInfo>

    </el-header>
    <el-container class="x-admin__wrapper" :class="{'is-immersion':immersion}">
      <ElAside v-if="collapsed && !immersion" class="x-admin-sidebar-placeholder" width="30px">
        <i class="el-icon-s-unfold"></i>
      </ElAside>
      <Sidebar v-if="!immersion"
               v-resizable="{handles:'e',minWidth:200,maxWidth:500}"
               :collapsed.sync="collapsed"
               :menus="menus"
               :favorites="favorites"
               :flatMenus="flatMenus"
               :item-adapter="menuAdapter"
               :menu-props="mergeMenuProps"
               @menu-select="handleMenuSelect"></Sidebar>
      <el-main class="x-admin-main" :class="{'is-no-margin':!mainMargin}">
        <div class="x-admin-main__wrapper" :class="{'is-no-padding':!mainPadding}">
          <keep-alive ref="alive" :exclude="exclude">
            <router-view v-if="viewEnable" ref="view" :key="$route.fullPath"></router-view>
          </keep-alive>
          <XDialog v-for="item in dialogs"
                   :key="`dialog_${item._id}`"
                   :modal="false"
                   :name="item._id"
                   :body-padding="mainPadding"
                   :before-close="beforeCloseDialog"
                   v-bind="item"
                   @minimized="dialogMinimized(item)"
                   @mode-change="dialogModeChange($event, item)"
                   @close="removeDialog(item)"></XDialog>
        </div>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
/**
 * 后台管理系统框架
 * @module XAdmin
 */
import {
  Container as ElContainer,
  Header as ElHeader,
  Main as ElMain,
  Aside as ElAside
} from 'element-ui';
import { on, off } from 'element-ui/lib/utils/dom';
import { XDialog } from '@newpearl/ui';
import { Lock } from '@newpearl/icons';
import Resizable from '@newpearl/ui/lib/directive/resizable';
import Sidebar from './components/Sidebar';
import NavTabs from './components/NavTabs';
import IconButtonBar from './components/IconButtonBar';
import UserInfo from './components/UserInfo';

export default {
  name: 'XAdmin',
  provide() {
    return {
      $admin: this
    };
  },
  components: {
    ElContainer,
    ElHeader,
    ElMain,
    ElAside,
    Sidebar,
    NavTabs,
    IconButtonBar,
    UserInfo,
    XDialog
  },
  directives: {
    Resizable
  },
  /**
   *  属性参数
   *  @member props
   *  @property {string} [banner] 头部banner背景图片
   *  @property {string} [homePath=/] 系统首页路由路径
   *  @property {string} [homeTabName=__x_home__] 系统首页tab名称
   *  @property {object[]} [tools]工具按钮配置
   *  @property {string} [tools.name] 按钮名称标识
   *  @property {string|Object} [tools.icon] 按钮图标
   *  @property {number} [tools.badge] badge数值
   *  @property {object} [user] 用户信息
   *  @property {string} [user.name] 用户名称
   *  @property {string} [user.avatar] 用户头像
   *  @property {object[]} [userTools] 用户下拉菜单项配置
   *  @property {string} [userTools.name] 菜单项名称标识
   *  @property {string} [userTools.label] 菜单项显示文本
   *  @property {object[]} [menus] 导航菜单数据
   *  @property {string} [menus.text] 菜单项文本
   *  @property {string|object} [menus.icon] 菜单项图标
   *  @property {string} [menus.index] 菜单项的标识，通常用菜单对应的页面路由地址
   *  @property {number} [menus.badge] 显示菜单项的badge
   *  @property {string} [menus.trigger] 菜单的打开方式，可选：route、href、blank，默认route
   *  @property {object[]} [menus.children] 子菜单项
   *  @property {function} [menuAdapter] 菜单项适配函数, 把menus的数据转换成标准的数据
   *  @property {object} [menuProps] 菜单组件配置参数，配置项参考 XMenu 组件
   *  @property {object[]} [favorites] 收藏菜单数据数组
   *  @property {string} [favorites.text] 菜单项文本
   *  @property {string|object} [favorites.icon] 菜单项图标
   *  @property {string} [favorites.index] 菜单项的标识，通常用菜单对应的页面路由地址
   *  @property {number} [favorites.badge] 显示菜单项的badge
   *  @property {string} [favorites.trigger] 菜单的打开方式，可选：route、href、blank，默认route
   *  @property {number} [tabLimit=10]  banner上显示的tab最大数量
   *  @property {number} [tabMax=20] 最大支持tab数量,需要比tabLimit大，超过tabLimit的数量放入到更多下拉
   *  @property {function} [beforeRemoveTab] 关闭tab前回调函数，return false 阻止关闭, 支持Promise
   *  @property {function} [beforeCloseDialog] 关闭Dialog前回调函数，return false 阻止关闭, 支持Promise
   *  @property {function} [tabAdapter] tab数据适配函数
   *  @property {boolean} [immersion] 开启沉浸式， 隐藏菜单和头部
   *  @property {boolean} [mainMargin=true] 主体内容是否有边距
   *  @property {boolean|Object[]} [skin] 皮肤设置
   *  @property {string} [skin.name] 皮肤名称
   *  @property {string} [skin.theme] 皮肤标识
   *  @property {string} [defaultSkin=blue] 默认皮肤
   *  @property {boolean|Object[]} [sizes] 组件尺寸设置
   *  @property {string} [sizes.name] 尺寸名称
   *  @property {string} [sizes.size] 尺寸标识
   *  @property {string} [defaultSize=tiny] 默认尺寸
   *  @property {function} [menuFinder] 菜单项查找函数，传入menuItem, tab ，返回true标识匹配该菜单项
   *  @property {function} [beforeMenuSelect] 点击菜单跳转前回调，return false阻止跳转，支持异步
   *  @property {boolean} [enableDialog] 开启页签弹窗功能
   *  @property {boolean} [enableBeforeUnload] 开启关闭浏览器提示
   */
  props: {
    // banner 背景图片
    banner: {
      type: String,
      default: require('./assets/banner.png')
    },
    // 首页路径
    homePath: {
      type: String,
      default: '/'
    },
    homeTabName: {
      type: String,
      default: '__x_home__'
    },
    // 工具按钮配置 [{icon,name,badge}]
    tools: {
      type: Array,
      default() {
        return [
          { icon: Lock, name: 'lock' },
          { icon: 'el-icon-switch-button', name: 'exit' }
        ];
      }
    },
    // 用户信息 {name, avatar}
    user: {
      type: Object
    },
    // 用户下拉菜单项
    // [{name, label}]
    userTools: {
      type: Array,
      default() {
        return [
          { name: 'password', label: '修改密码' },
          { name: 'logout', label: '退出登录' }
        ];
      }
    },
    // 菜单数据
    // [{text, icon, index, badge, children, trigger}]
    menus: {
      type: Array,
      default() {
        return [];
      }
    },
    // 菜单项适配函数
    menuAdapter: {
      type: Function
    },
    // 菜单组件参数
    menuProps: {
      type: Object
    },
    // 收藏菜单数据数组
    // [{text, icon, index, badge, trigger}]
    favorites: {
      type: Array,
      default() {
        return [];
      }
    },
    // banner上显示的tab数量
    tabLimit: {
      type: Number,
      default: 10
    },
    // 最大支持tab数量
    tabMax: {
      type: Number,
      default: 20
    },
    // 关闭tab前回调函数，return false 阻止关闭
    beforeRemoveTab: Function,
    // 关闭Dialog前回调函数，return false 阻止关闭, 支持Promise
    beforeCloseDialog: Function,
    // tab适配函数
    tabAdapter: Function,
    // 开启沉浸式， 隐藏菜单和头部
    immersion: Boolean,
    // 主体内容是否有外边距
    mainMargin: {
      type: Boolean,
      default: true
    },
    // 主体内容是否有内边距
    mainPadding: {
      type: Boolean,
      default: true
    },
    // 皮肤设置
    skin: {
      type: [Boolean, Array],
      default() {
        return [
          { name: '深蓝', theme: 'blue' },
          { name: '深灰', theme: 'grey' }
        ];
      }
    },
    // 默认皮肤
    defaultSkin: {
      type: String,
      default: 'blue'
    },
    sizes: {
      type: [Boolean, Array],
      default() {
        return [
          { name: '标准', size: 'mini', icon: 'el-icon-zoom-in' },
          { name: '紧凑', size: 'tiny', icon: 'el-icon-zoom-out' }
        ];
      }
    },
    defaultSize: {
      type: String,
      default: 'tiny'
    },
    // 菜单查找函数，传入tab ，返回匹配的菜单项,匹配到的菜单项信息合并到tabInfo
    menuFinder: {
      type: Function
    },
    // 弹窗初始化宽度
    dialogWidth: {
      type: Number,
      default: 1000
    },
    // 弹窗初始化高度
    dialogHeight: {
      type: Number,
      default: 600
    },
    // 菜单跳转前回调函数，需要返回Promise，return false阻止跳转
    beforeMenuSelect: {
      type: Function
    },
    // 开启页签弹窗功能
    enableDialog: {
      type: Boolean
    },
    // 开启卸载BeforeUnload提示
    enableBeforeUnload: {
      type: Boolean
    }
  },
  data() {
    return {
      // 侧边栏是否折叠
      collapsed: false,
      // 页签数据 [{name, icon, label, path, closable}]
      tabs: [],
      // 当前激活的页签名称
      currentTabName: this.homeTabName,
      // 视图组件router-view 是否可用，用作刷新视图
      viewEnable: true,
      // 不缓存的组件名称
      exclude: [],
      isTabDragging: false
    };
  },
  computed: {
    // banner背景图
    bannerStyle() {
      return this.banner ? {
        backgroundImage: `url(${this.banner})`
      } : null;
    },
    // 菜单项目数组集合
    flatMenus() {
      if (!this.menus) return [];
      return this.toFlat(this.menus);
    },
    // 当前视图是否开启 keepAlive
    isKeepAlive() {
      const { keepAlive, noCache } = this.$route.meta;
      // 默认开启 KeepAlive
      return !(keepAlive === false || noCache === true);
    },
    // 菜单组件参数
    mergeMenuProps() {
      return Object.freeze({
        router: true,
        defaultActive: this.$route.fullPath,
        uniqueOpened: true,
        theme: this.defaultSkin === 'grey' ? 'grey' : 'dim',
        beforeSelect: this.beforeMenuSelect,
        ...this.menuProps
      });
    },
    dialogs() {
      return this.tabs.filter(n => !!n.dialog).map(n => {
        return Object.freeze({
          ...n,
          _id: n._id,
          title: n.label,
          icon: n.icon,
          center: !!n.center,
          width: this.dialogWidth,
          height: this.dialogHeight,
          left: n.x - this.dialogWidth / 2,
          top: n.y,
          componentInstance: n.componentInstance
        });
      });
    }
  },
  watch: {
    $route: {
      immediate: true,
      handler() {
        this.$nextTick(this.handleRouteChange);
      }
    },
    currentTabName(name) {
      const tab = this.tabs.find(n => n.name === name);
      this.loadByTab(tab);
    },
    tabs() {
      this.$nextTick(this.updateViewCache);
    }
  },
  methods: {
    createTabInfo() {
      const { name, meta, path, fullPath, params, query } = this.$route;
      if (meta.tab === false) {
        return null;
      }
      const tabInfo = {
        name: name || fullPath,
        label: meta.label || meta.title,
        path: fullPath,
        ...meta,
        ...meta.tab,
        route: {
          params: { ...params },
          query: { ...query },
          fullPath,
          path,
          meta,
          name
        }
      };
      const menu = this.menuFinder ? this.flatMenus.find(n => this.menuFinder(n, tabInfo)) : null;
      return Object.assign(tabInfo, menu);
    },
    getTab(path) {
      return this.tabs.find(n => n.path === path || n.name === path);
    },
    /**
     * 获取视图的route对象
     * @method getTabRoute
     * @param {object} vm 视图组件实例
     * @return {object} route 路由信息对象
     */
    getTabRoute(vm) {
      const { cache } = this.getViewCache() || {};
      if (!cache) return this.$route;
      const key = Object.keys(cache).find(name => cache[name]?.componentInstance === vm);
      if (!key) return this.$route;
      return this.getTab(key)?.route || this.$route;
    },
    toFlat(array) {
      let result = [];
      const menuAdapter = this.menuAdapter;
      array.forEach(n => {
        n = menuAdapter ? menuAdapter(n) : n;
        if (!n.children) {
          result.push(n);
        } else {
          result = result.concat(this.toFlat(n.children));
        }
      });
      return result;
    },
    /**
     * 关闭tab
     * @method removeTab
     * @param {object} tab tab对象
     * @param {boolean} [toHome] 关闭的是当前页，是否跳转到首页
     */
    removeTab(tab, toHome) {
      this.tabs = this.tabs.filter(n => n !== tab);
      // 如果关闭的tab是当前显示的，路由切换到下一个tab
      if (toHome && tab.name === this.currentTabName) {
        const len = this.tabs.length;
        this.currentTabName = len ? this.tabs[0].name : this.homeTabName;
      }
    },
    /**
     * 关闭所有tab
     * @method removeAllTabs
     */
    removeAllTabs() {
      this.tabs = [];
      this.currentTabName = this.homeTabName;
    },
    /**
     * 关闭其他tabs
     *  @method removeOtherTabs
     * @param {object} tab tab对象
     */
    removeOtherTabs(tab) {
      this.tabs = this.tabs.filter(n => n === tab);
    },
    /**
     * 关闭当前tab，并跳转到新的路由
     * @method removeCurrentTab
     * @param {string} [toPath] 跳转到新的路由地址
     */
    removeCurrentTab(toPath) {
      const { currentTabName, homeTabName, tabs } = this;
      const currentTab = tabs.find(n => n.name === currentTabName);
      // 首页不能关闭
      if (currentTab && currentTabName !== homeTabName) {
        // 如果需要跳转到新页面，就不需要先到首页
        this.removeTab(currentTab, !toPath);
      }
      if (toPath) {
        this.$nextTick(() => {
          this.$router.push(toPath).catch(e => e);
        });
      }
    },
    /**
     * 更新当前tab信息
     * @method updateCurrentTab
     * @param {object} props 需要更新的属性
     */
    updateCurrentTab(props) {
      const { currentTabName, tabs } = this;
      const index = tabs.findIndex(n => n.name === currentTabName);
      const currentTab = tabs[index];
      if (!currentTab) return;
      const tab = Object.freeze(Object.assign({}, currentTab, props));
      tabs.splice(index, 1, tab);
    },
    /**
     * 新增tab
     * @method addTab
     * @param {object} tab tab对象
     * @param {string} [tab.home] 是否首页
     * @param {string} [tab.name] tab标识名称，已经存在相同名称的进行替换处理
     * @param {string} [tab.label] tab显示文本
     * @param {string|object} [tab.icon] 图标
     * @param {string} [tab.path] 路由路径
     */
    addTab(tab) {
      if (tab.home) {
        this.currentTabName = this.homeTabName;
        return;
      }
      // tab 数据必须要有name， label 和 icon 必须要有一个
      if (!tab.name || (!tab.label && !tab.icon) || !tab.path) {
        return;
      }
      // 截取最大数量
      const tabs = this.tabs = this.tabs.slice(0, this.tabMax);
      // 检测是否存在相同name的tab
      const index = tabs.findIndex(n => n.name === tab.name);
      const defaults = {
        _id: Date.now(),
        closable: true,
        icon: 'el-icon-document'
      };
      // 已经有相同name的tab
      if (index > -1) {
        const oldTab = tabs[index];
        const showTabs = this.$refs.navTabs?.showTabs || [];
        // 相同name的tab是否在展示的区域
        const isVisible = !!showTabs.find(n => n.name === tab.name);
        const tabInfo = Object.freeze(Object.assign(defaults, oldTab, tab));
        // 在展示区，原位置替换tab
        if (isVisible) {
          tabs.splice(index, 1, tabInfo);
        } else {
          // 不在展示区，移动到展示区首位
          tabs.splice(index, 1);
          tabs.unshift(tabInfo);
        }
      } else {
        // 没有相同的tab，在首位添加
        tabs.unshift(Object.freeze(Object.assign(defaults, tab)));
      }
      // 激活新增的tab
      this.currentTabName = tab.name;
    },
    loadByTab(tab) {
      const path = (!tab || tab.home) ? this.homePath : tab.path;
      clearTimeout(this._loadTabTimer);
      // 提高tab切换流畅度
      this._loadTabTimer = setTimeout(() => {
        // 防止重复跳转
        if (path === this.$route.fullPath) return;
        this.$router.push(path).catch(e => e);
      }, 100);
    },
    handleMenuSelect(item) {
      /**
       * 点击菜单项时触发
       * @event menu-select
       * @param {object} item 菜单项对象
       */
      this.$emit('menu-select', item);
    },
    handleLogoClick() {
      this.$router.push(this.homePath).catch(e => e);
      this.currentTabName = this.homeTabName;
    },
    async handleRouteChange() {
      const view = this.$refs.view;
      if (!view) return;
      const tab = this.createTabInfo();
      if (!tab) return;
      if (this.tabAdapter) {
        Object.assign(tab, await this.tabAdapter(tab));
      }
      // 路由组件实现 tabAdapter
      const tabAdapter = view.$options.tabAdapter;
      if (tabAdapter) {
        Object.assign(tab, await tabAdapter.call(view, tab));
      }
      if (!this.isKeepAlive) {
        this.addExclude(view);
      }
      this.addTab(tab);
    },
    addExclude(view) {
      if (!view) return;
      const name = view.$options?.name;
      if (name) {
        const set = new Set(this.exclude);
        set.add(name);
        this.exclude = Array.from(set);
      }
    },
    getViewCache() {
      const vnode = this.$refs.view?.$vnode;
      if (!vnode || !vnode.data?.keepAlive) return;
      return vnode.parent?.componentInstance;
    },
    getViewCacheInstance(key) {
      const { cache } = this.getViewCache() || {};
      if (!cache) return null;
      return cache[key]?.componentInstance;
    },
    updateViewCache() {
      const { tabs, homePath } = this;
      const { cache, keys } = this.getViewCache() || {};
      if (!keys || !cache) return;
      const notExistKeys = [];
      keys.forEach(key => {
        if (key === homePath) return;
        const exist = tabs.find(n => key === n.path);
        if (!exist) {
          notExistKeys.push(key);
          const vm = cache[key].componentInstance;
          vm?.$destroy();
          delete cache[key];
        }
      });
      notExistKeys.forEach(key => {
        const index = keys.findIndex(n => key === n);
        keys.splice(index, 1);
      });
    },
    /**
     * 删除指定路由的视图缓存
     * @method removeViewCache
     * @param {string} key 缓存key，即页面路由fullPath
     */
    removeViewCache(key) {
      const { cache, keys } = this.getViewCache() || {};
      if (!keys || !cache) return;
      const index = keys.findIndex(n => n === key);
      if (index > -1) {
        keys.splice(index, 1);
        const vm = cache[key]?.componentInstance;
        vm?.$destroy();
        delete cache[key];
      }
    },
    /**
     * 刷新当前tab对应的视图
     * @param {object} tab
     */
    reload(tab) {
      if (!tab) return;
      const view = this.$refs.view;
      if (view && view.$options.refresh) {
        view.$options.refresh(tab);
        return;
      }
      this.viewEnable = false;
      this.removeViewCache(tab.path);
      this.$nextTick(() => {
        this.viewEnable = true;
      });
      /**
       * 属性tab view时触发
       * @event refresh
       * @param {object} tab 数据对象
       */
      this.$emit('refresh', tab);
    },
    handleDrop(e) {
      this.isTabDragging = false;
      const tabId = e.dataTransfer.getData('tabId');
      const index = this.tabs.findIndex(n => String(n._id) === String(tabId));
      const tab = this.tabs[index];
      this.openDialog(tab, e);
    },
    setDialogTab(tab, position) {
      const { homePath } = this;
      // 防止错误把首页弹出
      if (tab.path === homePath) return;
      const vm = this.getViewCacheInstance(tab.path);
      if (!vm) {
        this.$notify.warning('该页签暂未开放弹出功能');
        return;
      }
      const tabInfo = { ...tab };
      tabInfo.dialog = true;
      tabInfo.dialogId = tab._id;
      tabInfo.componentInstance = vm;
      if (position) {
        tabInfo.x = position.x;
        tabInfo.y = position.y;
      } else {
        tabInfo.center = true;
      }
      const index = this.tabs.findIndex(n => n._id === tab._id);
      this.tabs.splice(index, 1, Object.freeze(tabInfo));
      this.$nextTick(() => {
        // 页面视图组件实现接口
        if (vm && vm.$options.dialogShow) {
          vm.$options.dialogShow.call(vm, tabInfo);
        }
      });
    },
    openDialog(tab, position) {
      if (!tab || !this.enableDialog) return;
      const { homeTabName, currentTabName } = this;
      // 不能拖出首页
      if (tab.name === homeTabName) {
        return;
      }
      // 如何拖出的是当前路由页面，先跳回首页，再拖出
      if (tab.name === currentTabName) {
        this.currentTabName = homeTabName;
        setTimeout(() => {
          this.setDialogTab(tab, position);
        }, 300);
      } else {
        this.setDialogTab(tab, position);
      }
    },
    removeDialog(item) {
      const index = this.tabs.findIndex(n => n._id === item._id);
      const tab = this.tabs[index];
      if (!tab) return;
      if (tab.componentInstance) {
        tab.componentInstance.$destroy();
      }
      this.tabs.splice(index, 1);
    },
    dialogMinimized(item) {
      const index = this.tabs.findIndex(n => n._id === item._id);
      const tab = this.tabs[index];
      if (!tab) return;
      const tabInfo = { ...tab };
      const vm = tabInfo.componentInstance;
      // 视图组件实现接口
      if (vm && vm.$options.dialogHide) {
        vm.$options.dialogHide.call(vm, tabInfo);
      }
      tabInfo.dialog = false;
      tabInfo.componentInstance = null;
      this.tabs.splice(index, 1, Object.freeze(tabInfo));
    },
    dialogModeChange(mode, item) {
      const index = this.tabs.findIndex(n => n._id === item._id);
      const tab = this.tabs[index];
      if (!tab) return;
      const vm = tab.componentInstance;
      // 视图组件实现接口
      if (vm && vm.$options.dialogModeChange) {
        vm.$options.dialogModeChange.call(vm, mode, tab);
      }
    },
    beforeEach() {
      this.$router.beforeEach((to, from, next) => {
        const index = this.dialogs.findIndex(n => (n.path === to.fullPath || n.name === to.name));
        const dialog = this.dialogs[index];
        if (!dialog) {
          next();
          return;
        }
        const dialogVm = this.dialogInstances[dialog._id];
        if (!dialogVm) return next();
        dialogVm.close();
        this.$nextTick(next);
        // dialogVm.setTopLevel();
        // const componentInstance = dialog.componentInstance;
        // // 路由组件实现 tabRoute
        // const tabRoute = componentInstance?.$options?.tabRoute;
        // if (tabRoute) {
        //   tabRoute.call(componentInstance, to, from);
        // }
        // next(false);
      });
    },
    handleDragOver(e) {
      e.preventDefault();
      return false;
    },
    /**
     * 获取视图的XDialog实例
     * @method getDialog
     * @param {object} vm 视图组件实例
     * @return {object} dialog
     */
    getDialog(vm) {
      const dialog = this.dialogs.find(n => (n.componentInstance === vm || vm.$parent === n.componentInstance));
      const id = dialog?._id;
      return id ? this.dialogInstances[id] : null;
    },
    /**
     * 销毁视图的XDialog
     * @method closeDialog
     * @param {object} vm 视图组件实例
     * @return {boolean} 是否成功关闭
     */
    closeDialog(vm) {
      const dialog = this.getDialog(vm);
      if (dialog) {
        dialog.close();
      }
      return !!dialog;
    },
    /**
     * 关闭页签tab或弹窗
     * @method closeTabOrDialog
     * @param {object} vm 视图组件实例
     * @param {string} [toPath] 打开新的页面路由
     */
    closeTabOrDialog(vm, toPath) {
      const isDialog = this.closeDialog(vm);
      if (isDialog) {
        if (toPath) {
          this.$nextTick(() => {
            this.$router.push(toPath).catch(e => e);
          });
        }
      } else {
        this.removeCurrentTab(toPath);
      }
    },
    bindF5Event(e) {
      if ((e.which || e.keyCode) === 116) {
        e.preventDefault();
        const { currentTabName, tabs } = this;
        const currentTab = tabs.find(n => n.name === currentTabName);
        if (currentTab) {
          this.reload(currentTab);
        }
      }
    },
    handleBeforeUnload(e) {
      const message = '确定退出该系统？';
      e.returnValue = message;
      return message;
    }
  },
  created() {
    if (this.enableDialog) {
      this.dialogInstances = {};
      this.beforeEach();
      on(document.body, 'dragover', this.handleDragOver);
      on(document.body, 'drop', this.handleDrop);
    }
    if (this.enableBeforeUnload) {
      window.addEventListener('beforeunload', this.handleBeforeUnload);
    }
    on(document, 'keydown', this.bindF5Event);
  },
  beforeDestroy() {
    if (this.enableDialog) {
      off(document.body, 'dragover', this.handleDragOver);
      off(document.body, 'drop', this.handleDrop);
      this.dialogInstances = null;
    }
    if (this.enableBeforeUnload) {
      window.removeEventListener('beforeunload', this.handleBeforeUnload);
    }
    off(document, 'keydown', this.bindF5Event);
  }
};
/**
 * 收藏菜单时触发
 * @event add-favorite
 * @param {object} item 收藏菜单项
 */

/**
 * 删除收藏时触发
 * @event remove-favorite
 * @param {object} item 收藏菜单项
 */

/**
 * 点击图标工具按钮时触发
 * @event tool-click
 * @param {object} item 按钮项对象
 */

/**
 * 点击用户名称下拉菜单时触发
 * @event user-tool-click
 * @param {string} command 下拉项名称
 */

/**
 * 主题改变时触发
 * @event skin-change
 * @param {object} skin 主题配置对象
 */
</script>
