mirror of
https://github.com/imdap/ruoyi-plus-vben5.git
synced 2026-05-11 13:22:09 +08:00
* docs: readme * fix: missing formPath * chore: 去除锁定的esbuild版本 * perf: 去除debug组件 * perf: 参数键值 自动高度 * refactor: 代码生成配置页面重构 去除步骤条 * perf: 移除文件 * docs: 文件夹说明 * chore: 移除一些配置项 * chore: 注释优化 * refactor: 移除ele和naive目录 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: request support to set how to return response (#5436) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 登录超时的i18n * fix: requestClient缺失i18n内容 * refactor: 优化oss下载进度提示 * feat: 下载进度loading * fix: antd button icon style (#5421) * feat: oss下载进度(已下载的KB 无法作为进度显示 total返回为null) * fix: 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 新增后跳转到未发布流程 * fix: same name route * chore: 调整为部署json类型 * fix: mouse events ignored on modal loading (#5409) * docs: update docs (#5408) * refactor: 移除已经弃用的方法 * refactor: follow官方handleRangeTimeValue更新 * chore: 删除文件夹(前端路由需要的) * chore: 修改本地路由写法(新版)/新增本地菜单图标 * fix: form update state error before form mounted (#5406) * fix: demos route fixed (#5405) * chore: 不使用基础布局(仅在顶级生效) * feat: modal state locked on submitting (#5401) * chore: 修改zIndex * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: fix popup component zIndex (#5397) * style: element plus loading style fixed (#5393) * perf: improve fieldMappingTime to support format function (#5392) * fix: hide root route in breadcrumb * feat: support set default props for drawer and modal (#5390) * fix: root router config fixed (#5389) * fix: 修改Vxe默认zIndex为995 解决右上角全屏后modal/drawer(zIndex: 1000)被遮挡 * feat: add `noBasicLayout` in route meta (#5386) * chore: wechat image * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 改为全局参数配置 去除局部参数 * fix: spinner may stop playing animation after dismiss (#5365) * style: popover bgColor is too close to common (#5364) * docs: version update * docs: changelog * chore: 文件上传 描述 * ci: retry deploy while faild * feat: 文件上传 进度条+提示文字 * feat: 文件上传 进度条 * feat: 上传文件格式说明 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * fix: useEcharts return invalid instance (#5360) * feat: popup component support overlay blur effect (#5359) * feat: improve `tippy` demo (#5357) * feat: integrate new component `Tippy` with demo (#5355) * chore: 优化表格图片显示 * perf: add nested modal demo (#5353) * chore: 默认显示右边的滚动条 防止出现滚动条被挤压 * perf: modal and drawer api support chain calls (#5351) * feat: allow close tab when mouse middle button click (#5347) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * refactor: 重构显示total的逻辑 * chore: 调整高度自适应代码 * chore: vxe升级4.10.0版本(锁定) * fix: 添加失效的option * fix: 需要为数组 * fix: locale switching logic correction (#5344) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * chore: 导入类型优化 解决eslint报红 * refactor: type/注释优化 去除大量any * fix: vxeGrid init without search form (#5342) * chore: 锁屏默认false 关闭该功能 * chore: 调整接口 * chore: update deps * fix: primaryColor calculation (#5337) * fix: form valid-error style in naive (#5336) * fix: form `fieldMappingTime` improve and `modelPropName` support (#5335) * fix: code lint * fix: form `fieldMappingTime` is not working (#5333) * chore: 选人组件样式 * fix: download from url triggered twice sometimes (#5319) * chore: 优化代码 * chore: 动态类名(无效)改为style * refactor: 字典相关功能重构 采用一个Map储存字典(之前为两个Map) * feat: 字典支持number类型存储 * chore: 调整样式 * chore: 修改选中border为1px * chore: 字段 * chore: 改为新窗口打开(适用于pdf/图片)而非直接下载 * chore: 更新样式 * chore: 更新字段 * chore: 改为computed * chore: 跳转到未发布流程tab * chore: 优化样式 * docs: readme * fix: name重复导致的404 * Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into warmflow * chore: 使用legacy来保证copy的兼容性 * chore: 去除log 添加说明 * chore: 优化代码 * feat: 节点关联/节点独立的切换逻辑 * chore: remove logic * chore: vxe可编辑表格demo * chore: 不允许在按钮下添加数据 * docs: changelog * fix: wrong code * chore: 移除测试菜单 * chore: 优化代码 * refactor: 租户套餐菜单替换为新版 * refactor: 使用新版菜单勾选 * chore: 点行会勾选/取消全部权限 点权限不会勾选行 * chore: 全屏引导+样式优化 * chore: 调整间距 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: useEcharts exports echarts instance#5294 (#5299) * chore: update quick-start.md (#5303) * chore: updateCheckedNumber * refactor: 优化代码 * chore: 优化代码 * chore: 优化样式 * chore: keys依赖于menu 需要先加载menu * chore: 菜单加载完毕再显示 * feat: 新的菜单选择组件(beta) * chore: $t * chore: 测试菜单页面 * chore: 优化代码 * feat: 对ossId回显的支持 * chore: 只获取一次默认密码而非每次打开modal都获取 * fix: vben select placeholder color (#5286) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: format code with better style (#5283) * chore: 工作流演示站 * fix: sidebar preferences fixed (#5276) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: breadcrumb setting not valid for `header-sidebar-nav` layout (#5275) * fix: header logo may not be collapsed in `header-sidebar-nav` layout (#5274) * feat: new layout `sidebar nav with full header` (#5270) * feat: drawer close icon placement (#5269) * docs: update dialog and drawer docs * feat: drawer support destroy on close * feat: drawer support `onOpened` & `onClosed` * feat: modal support destroy on close * fix: wrong boolean * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 调整tab位置 * chore: 删除历史流程 改为tab切换 * fix: header-mixed layout side-menu active (#5265) * feat: header mixed layout (#5263) * chore: release 5.5.2 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: downgrade vue-tsc version * feat: header menu align support (#5256) * chore: update deps * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: add apiSelect remote search demo (#5246) * chore: 审批改为description而非disabled的表单 * chore: 改为ts * chore: 错误的conetnt * refactor: 终止/转办/委托支持填写意见 * chore: 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid form submit button locale switch (#5205) * chore: 调整驳回 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: build error (#5199) * fix: esbuild自动升级导致运行/打包报错 * fix: esbuild自动升级导致运行/打包报错 * chore: 流程定义 激活改为switch * chore: 流程申请支持上传文件 * chore: title 审批通过 * fix: vxeGrid top padding (#5193) * fix: 表格排序翻页会丢失排序参数 * chore: 去除log打印 * chore: 流程监控 待办任务 * chore: 我发起的 * chore: 去除已经移除的菜单页面 * chore: 我的已办 * chore: 页面优化 * chore: 重置tooltip * feat: 我的抄送搜索/优化重复触发的接口 * feat: 流程定义 历史 * chore: 修改分类 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid tools in toolbar config not working as expected (#5190) * feat: add `resizable` and `ColPage` component (#5188) * chore: 条件 * chore: break-all * feat: 流程分类 搜索 * chore: 弹窗关闭后仍然显示表单浮层 * chore: 选人组件的样式 * chore: 搜索的样式 * chore: 漏掉的导入 * chore: 最大显示的头像数量 超过显示为省略号头像 * fix: 选人的一些问题 * Merge branch 'warmflow' of https://gitee.com/dapppp/ruoyi-plus-vben5 i… * chore: 没有更多数据了 * fix: sidebar header height (#5183) * chore: 搜索表单布局+申请人 * fix: remove the overlap caused by border-b (#5160) * docs: fix typos (#5169) * fix: resolve eslint errors as well as TS type errors (#5172) * chore: enter提交表单 * chore: 修改文案 * chore: 默认全部展开 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: page content class override (#5179) * fix: sidebar style on focus (#5178) * fix: 抄送选人 最右侧已选中删除item无效 * feat: 复制 * chore: 昵称过长的显示 * chore: 默认选中第一个 * chore: 修改relative位置 * chore: 搜索 * feat: 我的待办 - 搜索条件 * chore: 流程监控 - 待办任务页面的id不唯一 改为前端处理 * feat: 修改办理人 * chore: 流程干预 - 加签/减签 * chore: avatar大小 * chore: 抄送需要手动添加createByName * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 审批通过 抄送 * feat: 流程实例-流程预览 * chore: spell * chore: clientid * chore: 分类条件 * chore: 修改办理人 * chore: 更改postMessage参数 * chore: 内嵌iframe高度根据表单高度调整 * chore: 流程详情 * feat: 抄送选择 * chore: 调整分类树 * fix: user homePath no effect sometimes (#5166) * feat: form compact mode support (#5165) * fix: form auto submit no effect when showDefaultActions is false (#5163) * chore: 修改width * feat: 待办任务 * feat: 我的抄送 * chore: 流程定义 样式 * chore: 退回后重新申请 * chore: 请假申请布局 * chore: 请假申请-并行会签网关 * chore: 分类去除根目录 * chore: 详情modal(未完成) * chore: 请假申请根据不同状态显示按钮 * chore: 流程删除/撤销 * chore: 审批完成后刷新当前页 * feat: 选人组件(未完成) 加签减签 * docs: fix docs-link and add `EllipsisText` docs (#5158) * chore: 新窗口打开文件 * chore: 审批通过 * chore: 使用useEventListener替换原生 * chore: 字段错误 * chore: iframe通信 加载完毕后才显示表单 解决卡顿问题 * chore: 审批终止/驳回 * chore: 附件图标 * chore: process_running显示按钮 * chore: label错误 * chore: 保存的事件 * chore: 需要加上clientId * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: form `colon` support (#5156) * chore: 完善请假申请 * feat: improve code login demo (#5154) * chore: 客户端管理 行高自适应 * chore: 内嵌表单的路径 * chore: 修改avatar背景色 * chore: 注释 * chore: activePath * chore: leave表单 * chore: 修改请假demo路径 * chore: categoryId * chore: 我的已办 * chore: 我发起的 * chore: loading * chore: 历史版本 * chore: 完善task api * chore: 隐藏'菜单加载中' * chore: missing import * feat: add demo for modify menu badge data * chore: 流程实例 * chore: 审批附件 * chore: 我的待办 提取公共组件 * chore: 流程部署 * chore: 新增/编辑/导出xml * chore: 流程定义(除历史版本) * feat: `autoActivateChild` support more layout mode (#5148) * feat: auto activate subMenu on select root menu (#5147) * fix: `disabledOnChangeListener` not work in form (#5146) * fix: login expired modal z-index (#5145) * feat: user-dropdown support `hover` trigger (#5143) * fix: pinInput value synchronous update (#5142) * fix: vxeGrid default sort data no effect in first query (#5141) * fix: vscode debug profile (#5140) * fix: form component events bind (#5137) * chore: 在线用户样式 开启虚拟滚动 * chore: 去掉个人中心 在线设备的分页 * chore: 去掉在线用户的分页 * chore: changelog * refactor: 获取字典的方法 提取公共函数 减少冗余代码 * fix: element plus validate failed style (#5130) * chore: 使用私有桶的提示 * feat: tabbar support mouse wheel vertical (#5129) * fix: form support `disabledOnInputListener` (#5127) * fix: form submission not appropriate (#5126) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: release 5.5.1 * feat: table search form visible control (#5121) * chore: 需要隐藏菜单 * chore: 我的待办 & 请假 * chore: 流程定义(未完成) * chore: 流程定义(开发中) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: version * chore: 锁定vxe-table版本 4.9.8版本存在样式问题 * chore: 暂时锁定cspell版本 * refactor: 由于不能输入 需要使用watch监听 * chore: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB7ANL * chore: 移除冗余代码 * chore: 组件卸载时移除emitter * fix: the route path did not synchronize with the page (#4947) * style: typo (#4948) * chore: 替换为commonDownloadExcel * fix: 左边部门树错误emit导致会调用两次列表api * chore: label样式 * chore: 改为Textarea * chore: 滚动条宽度 * chore: 审批样式 * chore: 部门及以下或本人数据权限 * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: 个人中心强退设备接口路径
557 lines
14 KiB
TypeScript
557 lines
14 KiB
TypeScript
import type { TabDefinition } from '@vben-core/typings';
|
||
import type { Router, RouteRecordNormalized } from 'vue-router';
|
||
|
||
import {
|
||
openRouteInNewWindow,
|
||
startProgress,
|
||
stopProgress,
|
||
} from '@vben-core/shared/utils';
|
||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||
import { toRaw } from 'vue';
|
||
|
||
interface TabbarState {
|
||
/**
|
||
* @zh_CN 当前打开的标签页列表缓存
|
||
*/
|
||
cachedTabs: Set<string>;
|
||
/**
|
||
* @zh_CN 拖拽结束的索引
|
||
*/
|
||
dragEndIndex: number;
|
||
/**
|
||
* @zh_CN 需要排除缓存的标签页
|
||
*/
|
||
excludeCachedTabs: Set<string>;
|
||
/**
|
||
* @zh_CN 是否刷新
|
||
*/
|
||
renderRouteView?: boolean;
|
||
/**
|
||
* @zh_CN 当前打开的标签页列表
|
||
*/
|
||
tabs: TabDefinition[];
|
||
/**
|
||
* @zh_CN 更新时间,用于一些更新场景,使用watch深度监听的话,会损耗性能
|
||
*/
|
||
updateTime?: number;
|
||
}
|
||
|
||
/**
|
||
* @zh_CN 访问权限相关
|
||
*/
|
||
export const useTabbarStore = defineStore('core-tabbar', {
|
||
actions: {
|
||
/**
|
||
* Close tabs in bulk
|
||
*/
|
||
async _bulkCloseByPaths(paths: string[]) {
|
||
this.tabs = this.tabs.filter((item) => {
|
||
return !paths.includes(getTabPath(item));
|
||
});
|
||
|
||
this.updateCacheTabs();
|
||
},
|
||
/**
|
||
* @zh_CN 关闭标签页
|
||
* @param tab
|
||
*/
|
||
_close(tab: TabDefinition) {
|
||
const { fullPath } = tab;
|
||
if (isAffixTab(tab)) {
|
||
return;
|
||
}
|
||
const index = this.tabs.findIndex((item) => item.fullPath === fullPath);
|
||
index !== -1 && this.tabs.splice(index, 1);
|
||
},
|
||
/**
|
||
* @zh_CN 跳转到默认标签页
|
||
*/
|
||
async _goToDefaultTab(router: Router) {
|
||
if (this.getTabs.length <= 0) {
|
||
return;
|
||
}
|
||
const firstTab = this.getTabs[0];
|
||
if (firstTab) {
|
||
await this._goToTab(firstTab, router);
|
||
}
|
||
},
|
||
/**
|
||
* @zh_CN 跳转到标签页
|
||
* @param tab
|
||
* @param router
|
||
*/
|
||
async _goToTab(tab: TabDefinition, router: Router) {
|
||
const { params, path, query } = tab;
|
||
const toParams = {
|
||
params: params || {},
|
||
path,
|
||
query: query || {},
|
||
};
|
||
await router.replace(toParams);
|
||
},
|
||
/**
|
||
* @zh_CN 添加标签页
|
||
* @param routeTab
|
||
*/
|
||
addTab(routeTab: TabDefinition) {
|
||
const tab = cloneTab(routeTab);
|
||
if (!isTabShown(tab)) {
|
||
return;
|
||
}
|
||
|
||
const tabIndex = this.tabs.findIndex((tab) => {
|
||
return getTabPath(tab) === getTabPath(routeTab);
|
||
});
|
||
|
||
if (tabIndex === -1) {
|
||
// 获取动态路由打开数,超过 0 即代表需要控制打开数
|
||
const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ??
|
||
-1) as number;
|
||
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
|
||
// 获取到已经打开的动态路由数, 判断是否大于某一个值
|
||
if (
|
||
maxNumOfOpenTab > 0 &&
|
||
this.tabs.filter((tab) => tab.name === routeTab.name).length >=
|
||
maxNumOfOpenTab
|
||
) {
|
||
// 关闭第一个
|
||
const index = this.tabs.findIndex(
|
||
(item) => item.name === routeTab.name,
|
||
);
|
||
index !== -1 && this.tabs.splice(index, 1);
|
||
}
|
||
|
||
this.tabs.push(tab);
|
||
} else {
|
||
// 页面已经存在,不重复添加选项卡,只更新选项卡参数
|
||
const currentTab = toRaw(this.tabs)[tabIndex];
|
||
const mergedTab = {
|
||
...currentTab,
|
||
...tab,
|
||
meta: { ...currentTab?.meta, ...tab.meta },
|
||
};
|
||
if (currentTab) {
|
||
const curMeta = currentTab.meta;
|
||
if (Reflect.has(curMeta, 'affixTab')) {
|
||
mergedTab.meta.affixTab = curMeta.affixTab;
|
||
}
|
||
if (Reflect.has(curMeta, 'newTabTitle')) {
|
||
mergedTab.meta.newTabTitle = curMeta.newTabTitle;
|
||
}
|
||
}
|
||
|
||
this.tabs.splice(tabIndex, 1, mergedTab);
|
||
}
|
||
this.updateCacheTabs();
|
||
},
|
||
/**
|
||
* @zh_CN 关闭所有标签页
|
||
*/
|
||
async closeAllTabs(router: Router) {
|
||
const newTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||
this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);
|
||
await this._goToDefaultTab(router);
|
||
this.updateCacheTabs();
|
||
},
|
||
/**
|
||
* @zh_CN 关闭左侧标签页
|
||
* @param tab
|
||
*/
|
||
async closeLeftTabs(tab: TabDefinition) {
|
||
const index = this.tabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
|
||
if (index < 1) {
|
||
return;
|
||
}
|
||
|
||
const leftTabs = this.tabs.slice(0, index);
|
||
const paths: string[] = [];
|
||
|
||
for (const item of leftTabs) {
|
||
if (!isAffixTab(item)) {
|
||
paths.push(getTabPath(item));
|
||
}
|
||
}
|
||
await this._bulkCloseByPaths(paths);
|
||
},
|
||
/**
|
||
* @zh_CN 关闭其他标签页
|
||
* @param tab
|
||
*/
|
||
async closeOtherTabs(tab: TabDefinition) {
|
||
const closePaths = this.tabs.map((item) => getTabPath(item));
|
||
|
||
const paths: string[] = [];
|
||
|
||
for (const path of closePaths) {
|
||
if (path !== tab.fullPath) {
|
||
const closeTab = this.tabs.find((item) => getTabPath(item) === path);
|
||
if (!closeTab) {
|
||
continue;
|
||
}
|
||
if (!isAffixTab(closeTab)) {
|
||
paths.push(getTabPath(closeTab));
|
||
}
|
||
}
|
||
}
|
||
await this._bulkCloseByPaths(paths);
|
||
},
|
||
/**
|
||
* @zh_CN 关闭右侧标签页
|
||
* @param tab
|
||
*/
|
||
async closeRightTabs(tab: TabDefinition) {
|
||
const index = this.tabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
|
||
if (index !== -1 && index < this.tabs.length - 1) {
|
||
const rightTabs = this.tabs.slice(index + 1);
|
||
|
||
const paths: string[] = [];
|
||
for (const item of rightTabs) {
|
||
if (!isAffixTab(item)) {
|
||
paths.push(getTabPath(item));
|
||
}
|
||
}
|
||
await this._bulkCloseByPaths(paths);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 关闭标签页
|
||
* @param tab
|
||
* @param router
|
||
*/
|
||
async closeTab(tab: TabDefinition, router: Router) {
|
||
const { currentRoute } = router;
|
||
|
||
// 关闭不是激活选项卡
|
||
if (getTabPath(currentRoute.value) !== getTabPath(tab)) {
|
||
this._close(tab);
|
||
this.updateCacheTabs();
|
||
return;
|
||
}
|
||
const index = this.getTabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(currentRoute.value),
|
||
);
|
||
|
||
const before = this.getTabs[index - 1];
|
||
const after = this.getTabs[index + 1];
|
||
|
||
// 下一个tab存在,跳转到下一个
|
||
if (after) {
|
||
this._close(tab);
|
||
await this._goToTab(after, router);
|
||
// 上一个tab存在,跳转到上一个
|
||
} else if (before) {
|
||
this._close(tab);
|
||
await this._goToTab(before, router);
|
||
} else {
|
||
console.error('Failed to close the tab; only one tab remains open.');
|
||
}
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 通过key关闭标签页
|
||
* @param key
|
||
* @param router
|
||
*/
|
||
async closeTabByKey(key: string, router: Router) {
|
||
const originKey = decodeURIComponent(key);
|
||
const index = this.tabs.findIndex(
|
||
(item) => getTabPath(item) === originKey,
|
||
);
|
||
if (index === -1) {
|
||
return;
|
||
}
|
||
|
||
const tab = this.tabs[index];
|
||
if (tab) {
|
||
await this.closeTab(tab, router);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 根据路径获取标签页
|
||
* @param path
|
||
*/
|
||
getTabByPath(path: string) {
|
||
return this.getTabs.find(
|
||
(item) => getTabPath(item) === path,
|
||
) as TabDefinition;
|
||
},
|
||
/**
|
||
* @zh_CN 新窗口打开标签页
|
||
* @param tab
|
||
*/
|
||
async openTabInNewWindow(tab: TabDefinition) {
|
||
openRouteInNewWindow(tab.fullPath || tab.path);
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 固定标签页
|
||
* @param tab
|
||
*/
|
||
async pinTab(tab: TabDefinition) {
|
||
const index = this.tabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
if (index !== -1) {
|
||
const oldTab = this.tabs[index];
|
||
tab.meta.affixTab = true;
|
||
tab.meta.title = oldTab?.meta?.title as string;
|
||
// this.addTab(tab);
|
||
this.tabs.splice(index, 1, tab);
|
||
}
|
||
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||
// 获得固定tabs的index
|
||
const newIndex = affixTabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
// 交换位置重新排序
|
||
await this.sortTabs(index, newIndex);
|
||
},
|
||
|
||
/**
|
||
* 刷新标签页
|
||
*/
|
||
async refresh(router: Router) {
|
||
const { currentRoute } = router;
|
||
const { name } = currentRoute.value;
|
||
|
||
this.excludeCachedTabs.add(name as string);
|
||
this.renderRouteView = false;
|
||
startProgress();
|
||
|
||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||
|
||
this.excludeCachedTabs.delete(name as string);
|
||
this.renderRouteView = true;
|
||
stopProgress();
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 重置标签页标题
|
||
*/
|
||
async resetTabTitle(tab: TabDefinition) {
|
||
if (tab?.meta?.newTabTitle) {
|
||
return;
|
||
}
|
||
const findTab = this.tabs.find(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
if (findTab) {
|
||
findTab.meta.newTabTitle = undefined;
|
||
await this.updateCacheTabs();
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 设置固定标签页
|
||
* @param tabs
|
||
*/
|
||
setAffixTabs(tabs: RouteRecordNormalized[]) {
|
||
for (const tab of tabs) {
|
||
tab.meta.affixTab = true;
|
||
this.addTab(routeToTab(tab));
|
||
}
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 设置标签页标题
|
||
* @param tab
|
||
* @param title
|
||
*/
|
||
async setTabTitle(tab: TabDefinition, title: string) {
|
||
const findTab = this.tabs.find(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
|
||
if (findTab) {
|
||
findTab.meta.newTabTitle = title;
|
||
|
||
await this.updateCacheTabs();
|
||
}
|
||
},
|
||
|
||
setUpdateTime() {
|
||
this.updateTime = Date.now();
|
||
},
|
||
/**
|
||
* @zh_CN 设置标签页顺序
|
||
* @param oldIndex
|
||
* @param newIndex
|
||
*/
|
||
async sortTabs(oldIndex: number, newIndex: number) {
|
||
const currentTab = this.tabs[oldIndex];
|
||
if (!currentTab) {
|
||
return;
|
||
}
|
||
this.tabs.splice(oldIndex, 1);
|
||
this.tabs.splice(newIndex, 0, currentTab);
|
||
this.dragEndIndex = this.dragEndIndex + 1;
|
||
},
|
||
/**
|
||
* @zh_CN 切换固定标签页
|
||
* @param tab
|
||
*/
|
||
async toggleTabPin(tab: TabDefinition) {
|
||
const affixTab = tab?.meta?.affixTab ?? false;
|
||
|
||
await (affixTab ? this.unpinTab(tab) : this.pinTab(tab));
|
||
},
|
||
|
||
/**
|
||
* @zh_CN 取消固定标签页
|
||
* @param tab
|
||
*/
|
||
async unpinTab(tab: TabDefinition) {
|
||
const index = this.tabs.findIndex(
|
||
(item) => getTabPath(item) === getTabPath(tab),
|
||
);
|
||
|
||
if (index !== -1) {
|
||
const oldTab = this.tabs[index];
|
||
tab.meta.affixTab = false;
|
||
tab.meta.title = oldTab?.meta?.title as string;
|
||
// this.addTab(tab);
|
||
this.tabs.splice(index, 1, tab);
|
||
}
|
||
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||
// 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置
|
||
const newIndex = affixTabs.length;
|
||
// 交换位置重新排序
|
||
await this.sortTabs(index, newIndex);
|
||
},
|
||
|
||
/**
|
||
* 根据当前打开的选项卡更新缓存
|
||
*/
|
||
async updateCacheTabs() {
|
||
const cacheMap = new Set<string>();
|
||
|
||
for (const tab of this.tabs) {
|
||
// 跳过不需要持久化的标签页
|
||
const keepAlive = tab.meta?.keepAlive;
|
||
if (!keepAlive) {
|
||
continue;
|
||
}
|
||
(tab.matched || []).forEach((t, i) => {
|
||
if (i > 0) {
|
||
cacheMap.add(t.name as string);
|
||
}
|
||
});
|
||
|
||
const name = tab.name as string;
|
||
cacheMap.add(name);
|
||
}
|
||
this.cachedTabs = cacheMap;
|
||
},
|
||
},
|
||
getters: {
|
||
affixTabs(): TabDefinition[] {
|
||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||
|
||
return affixTabs.sort((a, b) => {
|
||
const orderA = (a.meta?.affixTabOrder ?? 0) as number;
|
||
const orderB = (b.meta?.affixTabOrder ?? 0) as number;
|
||
return orderA - orderB;
|
||
});
|
||
},
|
||
getCachedTabs(): string[] {
|
||
return [...this.cachedTabs];
|
||
},
|
||
getExcludeCachedTabs(): string[] {
|
||
return [...this.excludeCachedTabs];
|
||
},
|
||
getTabs(): TabDefinition[] {
|
||
const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
|
||
return [...this.affixTabs, ...normalTabs].filter(Boolean);
|
||
},
|
||
},
|
||
persist: [
|
||
// tabs不需要保存在localStorage
|
||
{
|
||
pick: ['tabs'],
|
||
storage: sessionStorage,
|
||
},
|
||
],
|
||
state: (): TabbarState => ({
|
||
cachedTabs: new Set(),
|
||
dragEndIndex: 0,
|
||
excludeCachedTabs: new Set(),
|
||
renderRouteView: true,
|
||
tabs: [],
|
||
updateTime: Date.now(),
|
||
}),
|
||
});
|
||
|
||
// 解决热更新问题
|
||
const hot = import.meta.hot;
|
||
if (hot) {
|
||
hot.accept(acceptHMRUpdate(useTabbarStore, hot));
|
||
}
|
||
|
||
/**
|
||
* @zh_CN 克隆路由,防止路由被修改
|
||
* @param route
|
||
*/
|
||
function cloneTab(route: TabDefinition): TabDefinition {
|
||
if (!route) {
|
||
return route;
|
||
}
|
||
const { matched, meta, ...opt } = route;
|
||
return {
|
||
...opt,
|
||
matched: (matched
|
||
? matched.map((item) => ({
|
||
meta: item.meta,
|
||
name: item.name,
|
||
path: item.path,
|
||
}))
|
||
: undefined) as RouteRecordNormalized[],
|
||
meta: {
|
||
...meta,
|
||
newTabTitle: meta.newTabTitle,
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @zh_CN 是否是固定标签页
|
||
* @param tab
|
||
*/
|
||
function isAffixTab(tab: TabDefinition) {
|
||
return tab?.meta?.affixTab ?? false;
|
||
}
|
||
|
||
/**
|
||
* @zh_CN 是否显示标签
|
||
* @param tab
|
||
*/
|
||
function isTabShown(tab: TabDefinition) {
|
||
const matched = tab?.matched ?? [];
|
||
return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);
|
||
}
|
||
|
||
/**
|
||
* @zh_CN 获取标签页路径
|
||
* @param tab
|
||
*/
|
||
function getTabPath(tab: RouteRecordNormalized | TabDefinition) {
|
||
return decodeURIComponent((tab as TabDefinition).fullPath || tab.path);
|
||
}
|
||
|
||
function routeToTab(route: RouteRecordNormalized) {
|
||
return {
|
||
meta: route.meta,
|
||
name: route.name,
|
||
path: route.path,
|
||
} as TabDefinition;
|
||
}
|