UNPKG

vue-app-sdk

Version:
621 lines (620 loc) 19.7 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const index = require("../../utils/index2.cjs"); const arrayToMap = require("../../vendors/nice-fns/dist/arrayToMap.cjs"); require("../../vendors/nice-fns/dist/compactObject.cjs"); const parseJSON = require("../../vendors/nice-fns/dist/parseJSON.cjs"); require("../../vendors/nice-fns/dist/pascalCase.cjs"); const vue = require("vue"); const KeepAlive = require("../KeepAlive/KeepAlive.cjs"); const isFunction = require("../../vendors/lodash-es/isFunction.cjs"); const isString = require("../../vendors/lodash-es/isString.cjs"); const pick = require("../../vendors/lodash-es/pick.cjs"); const isArray = require("../../vendors/lodash-es/isArray.cjs"); const once = require("../../vendors/lodash-es/once.cjs"); const noop = require("../../vendors/lodash-es/noop.cjs"); var TabsReplaceResult = /* @__PURE__ */ ((TabsReplaceResult2) => { TabsReplaceResult2["replace"] = "replace"; TabsReplaceResult2["open"] = "open"; TabsReplaceResult2["cancel"] = "cancel"; return TabsReplaceResult2; })(TabsReplaceResult || {}); const TABS_ID = Symbol("tabs"); function getMetadata(route) { return route.meta._metadata || {}; } const defaultRawPageToTabPage = (data) => ({ ...data, pageId: data.id }); const defaultIsValidRawPage = () => true; const updatableTabPageProps = [ "isAffix", "isUniq", "isKeepAlive", "title", "icon", "fullPath" ]; const unsafeUpdatableTabPageProps = [...updatableTabPageProps, "fullPath"]; const beforeDefaults = { add: () => true, remove: () => true, replace: () => "replace" /* replace */ }; class Tabs { constructor(options = {}) { __publicField(this, "id", TABS_ID); /** * AppSDK 实例 */ __publicField(this, "_sdk"); /** * 持久化的状态 */ __publicField(this, "_state"); /** * 是否自动模式 */ __publicField(this, "_isAuto"); /** * 切换自动模式 * @param value 状态值 */ __publicField(this, "toggleAuto", (value = !this._isAuto.value) => { this._isAuto.value = value; return value; }); /** * 启用自动模式 */ __publicField(this, "enableAuto", () => { return this.toggleAuto(true); }); /** * 禁用自动模式 */ __publicField(this, "disableAuto", () => { return this.toggleAuto(false); }); /** * 更新 KeepAlive 缓存 */ __publicField(this, "updateKeepAlive", async () => { if (!this._keepAlive) return; const routes = this._routes; const set = /* @__PURE__ */ new Set(); for (const page of this.pages) { if (!this.isKeepAlive(page)) continue; const route = routes.find((r) => getMetadata(r).id === page.pageId); const name = await index.resolveComponentNameByRoute(route); if (name) { set.add(name); if (page.componentName !== name) page.componentName = name; } } this._keepAlive.set([...set]); }); /** * 设置标签页列表 * @param pages 标签页列表 */ __publicField(this, "setTabPages", (pages) => { this.pages = pages; this.updateKeepAlive(); }); /** * 根据页面元数据获取 `fullPath` */ __publicField(this, "_resolveFullPath", (page) => { const { router } = this._sdk; const query = {}; const params = {}; if (page.routeQuery) index.assign(query, isString.default(page.routeQuery) ? parseJSON.parseJSON(page.routeQuery) : page.routeQuery); if (page.routeParams) index.assign(params, isString.default(page.routeParams) ? parseJSON.parseJSON(page.routeParams) : page.routeParams); return router.resolve({ name: page.name, query, params }).fullPath; }); /** * 根据页面元数据列表设置固定标签页列表 * @param data 页面元数据列表 */ __publicField(this, "setAffixTabPagesByRawPages", (data) => { const affixTabPages = []; data.forEach((item) => { if (!this.isValidRawPage(item) || !this.rawPageToTabPage(item).isAffix) return; affixTabPages.push( this.createTabPage({ fullPath: this._resolveFullPath(item), meta: { _metadata: item } }) ); }); this.setTabPages(affixTabPages); }); /** * 更新标签页 * @param target 可获取标签页 ID 的值 * @param data 标签页属性 */ __publicField(this, "updateTabPage", (target, data) => { const id = this.resolveId(target); const tabPage = this.pages.find((p) => p.id === id); if (tabPage) { const needUpdateId = data.fullPath && tabPage.fullPath !== data.fullPath && !tabPage.isUniq; index.assign(tabPage, pick.default(data, unsafeUpdatableTabPageProps)); if (needUpdateId) { index.assign(tabPage, { id: this.generateID(tabPage) }); if (this._keepAlive && tabPage.componentName) this._keepAlive.remove(tabPage.componentName); if (id === this.active) this._setActive(tabPage.id); } } }); /** * 更新标签页的 `fullPath` 属性 * @param data 路由数据 * @param data.query `route.query` * @param data.params `route.params` * @param source 可获取标签页 ID 的值 */ __publicField(this, "updateFullPath", (data = {}, source = this.active) => { const id = this.resolveId(source); const tabPage = this.pages.find((p) => p.id === id); if (!tabPage) return; const router = this._sdk.router; const { fullPath } = router.resolve({ name: tabPage.name, ...data }); this.updateTabPage(tabPage, { fullPath }); }); /** * 根据页面元数据更新标签页列表 * @param data 页面元数据列表或映射 */ __publicField(this, "updateTabPagesByRawPages", (data) => { const map = isArray.default(data) ? arrayToMap.arrayToMap(data, { primaryKey: "id", useMap: false }) : data; this.pages.forEach((page) => { const mapPage = map[page.pageId]; if (!mapPage) return; index.assign(page, pick.default(this.rawPageToTabPage(mapPage), updatableTabPageProps)); }); }); /** * 获取标签页 ID * @param value 可获取标签页 ID 的值 * @returns 标签页 ID */ __publicField(this, "resolveId", (value) => { var _a; if (value == null) return ""; if (typeof value === "number") return ((_a = this.pages[value]) == null ? void 0 : _a.id) || ""; if (typeof value === "string") return value; if ("pageId" in value) return value.id; if ("fullPath" in value && "meta" in value) return this.generateID(value); index.logger.warn("无效参数:", value); return ""; }); /** * 仅设置当前激活的标签页 ID * @param value 可获取标签页 ID 的值 */ __publicField(this, "_setActive", (value) => { const id = this.resolveId(value); if (id === this.active) return false; this.active = id; return true; }); /** * 设置当前激活的标签页 ID * @param value 可获取标签页 ID 的值 */ __publicField(this, "setActive", (value) => { if (!this._setActive(value)) return; if (this.active === "") { const { processEmptyActive = noop.default } = this.options; processEmptyActive(); return; } const router = this._sdk.router; const tabPage = this.activeTabPage; if (tabPage && router.currentRoute.value.fullPath !== tabPage.fullPath) router.push(tabPage.fullPath); else if (!tabPage) index.logger.warn("未知的标签页来源:", value); }); /** * 根据路由创建标签页 * @param route 路由 * @returns 标签页 */ __publicField(this, "createTabPage", (route) => { const metadata = getMetadata(route); return { ...this.rawPageToTabPage(metadata), id: this.generateID(route), fullPath: route.fullPath }; }); /** * 是否可以移除指定标签页 * @param source 可获取标签页 ID 的值 */ __publicField(this, "canRemove", (source = this.active) => { const id = this.resolveId(source); if (!id) return false; const find = this.pages.find((p) => p.id === id); if (find) return !find.isAffix; return true; }); /** * 是否可以移除非指定的其他非固定标签页 * @param source 可获取标签页 ID 的值 */ __publicField(this, "canRemoveOther", (source = this.active) => { const id = this.resolveId(source); return !!id && this.pages.some((p) => !p.isAffix && p.id !== id); }); /** * 是否可以移除指定标签页的指定方向侧非固定标签页 * @param side 方向侧 * @param source 可获取标签页 ID 的值 */ __publicField(this, "canRemoveSide", (side, source = this.active) => { const id = this.resolveId(source); if (!id) return false; const isLeft = side === "left"; const sourceIndex = this.pages.findIndex((p) => p.id === id); const pages = this.pages.slice(isLeft ? 0 : sourceIndex + 1, isLeft ? sourceIndex : void 0).filter((p) => !p.isAffix); return pages.length > 0 && pages.some((p) => !p.isAffix); }); /** * 是否可以移除全部非固定标签页 */ __publicField(this, "canRemoveAll", () => { return this.pages.some((p) => !p.isAffix); }); /** * 尝试移除标签页列表 * @param pages 标签页列表 */ __publicField(this, "tryRemoveTabPages", async (pages) => { let valid = pages.length > 0; const { beforeRemove = beforeDefaults.remove } = this.options; for (const page of pages) { valid = !!await beforeRemove(page); if (!valid) break; } return valid; }); /** * 移除激活的标签页 */ __publicField(this, "removeActive", () => { return this.removeOne(this.active); }); /** * 移除指定标签页 * @param value 可获取标签页 ID 的值 */ __publicField(this, "removeOne", async (value) => { const id = this.resolveId(value); const index2 = this.pages.findIndex((p) => p.id === id); if (index2 === -1 || this.pages[index2].isAffix) return true; const valid = await this.tryRemoveTabPages([this.pages[index2]]); if (!valid) return false; const isCurrent = id === this.active; const nextTabPage = isCurrent ? this.pages[index2 - 1] || this.pages[index2 + 1] : null; if (isCurrent) this.setActive(nextTabPage); const { componentName } = this.pages.splice(index2, 1)[0]; if (this._keepAlive && componentName) this._keepAlive.remove(componentName); return true; }); /** * 移除指定标签页的指定方向侧非固定标签页 * @param side 指定方向侧 * @param source 可获取标签页 ID 的值 */ __publicField(this, "removeSide", async (side, source = this.active) => { const isLeft = side === "left"; const id = this.resolveId(source); let needResetActive = false; if (id && id !== this.active) { const index2 = this.pages.findIndex((p) => p.id === id); const activeIndex = this.pages.findIndex((p) => p.id === this.active); needResetActive = isLeft ? activeIndex < index2 : activeIndex > index2; } const sourceIndex = id ? this.pages.findIndex((p) => p.id === id) : -1; const needRemovePages = this.pages.slice(isLeft ? 0 : sourceIndex + 1, isLeft ? sourceIndex : void 0).filter((p) => !p.isAffix); const valid = await this.tryRemoveTabPages(needRemovePages); if (!valid) return; if (needResetActive) this.setActive(sourceIndex); this.setTabPages( this.pages.filter((p, i) => p.isAffix || (isLeft ? i >= sourceIndex : i <= sourceIndex)) ); }); /** * 移除非指定的其他非固定标签页 * @param source 可获取标签页 ID 的值 */ __publicField(this, "removeOther", async (source = this.active) => { const id = this.resolveId(source); if (!id) return; const needRemovePages = this.pages.filter((p) => !p.isAffix && p.id !== id); const valid = await this.tryRemoveTabPages(needRemovePages); if (!valid) return; if (id !== this.active) this.setActive(id); this.setTabPages(this.pages.filter((p) => p.isAffix || p.id === id)); }); /** * 移除全部非固定标签页 * @param force 是否强制移除,将不会触发 `beforeRemove` 回调 */ __publicField(this, "removeAll", async (force = false) => { if (!force) { const needRemovePages = this.pages.filter((p) => !p.isAffix); const valid = await this.tryRemoveTabPages(needRemovePages); if (!valid) return; } const pages = force ? [] : this.pages.filter((p) => p.isAffix); this.setActive(pages[0]); this.setTabPages(pages); }); /** * 匹配相同标签页 * @param tabPage 标签页 */ __publicField(this, "matchSameTabPage", (tabPage) => { const fuzzy = []; let exact; this.pages.forEach((page) => { if (page.pageId === tabPage.pageId) { fuzzy.push(page); if (!exact && page.fullPath === tabPage.fullPath) exact = page; } }); return { /** * 模糊匹配结果(同 `pageId`) */ fuzzy, /** * 精确匹配(同 `pageId`、`fullPath`) */ exact }; }); /** * 根据路由添加标签页 * @param route 路由 */ __publicField(this, "addOne", async (route) => { const page = getMetadata(route); if (!this.isValidRawPage(page)) return; const tabPage = this.createTabPage(route); const { fuzzy, exact } = this.matchSameTabPage(tabPage); const isExist = tabPage.isUniq ? fuzzy.length > 0 : !!exact; if (!isExist) { const { beforeAdd = beforeDefaults.add } = this.options; if (!await beforeAdd(page)) { return; } const route2 = this._routes.find((r) => getMetadata(r).id === tabPage.pageId); if (route2) { tabPage.componentName = await index.resolveComponentNameByRoute(route2); } this.pages.push(tabPage); } this.setActive(tabPage); if (!isExist && this.isKeepAlive(tabPage) && this._keepAlive) { if (route.matched) { route.matched.forEach(this._keepAlive.add); } else { const matchedRoute = this._routes.find((r) => getMetadata(r).id === tabPage.pageId); if (matchedRoute) this._keepAlive.add(matchedRoute); } } }); __publicField(this, "install", (sdk) => { this._sdk = sdk; const { router } = sdk; const collector = once.default(() => { sdk.hook("sdk:router:backward", (_, from) => { this.removeOne(from); }); router.beforeResolve(async (to) => { if (!this.isAuto || !to.meta._metadata || !this.isValidRawPage(to.meta._metadata)) return; const unhook = sdk.hookOnce("sdk:router:navigate", (_, to2) => { if (this.isAuto) this.addOne(to2); }); if (this.options.mode !== "replace") return; const tabPage = this.createTabPage(to); const { fuzzy, exact } = this.matchSameTabPage(tabPage); if (fuzzy.length === 1 && !exact) { const oldTabPage = fuzzy[0]; const { beforeReplace = beforeDefaults.replace } = this.options; const result = await beforeReplace(tabPage, oldTabPage); switch (result) { case "cancel": unhook(); return false; case "open": return; default: this.updateTabPage(oldTabPage, tabPage); this.setActive(tabPage); unhook(); break; } } }); }); const unwatch = vue.watch( this._isAuto, (value) => { if (!value) return; vue.nextTick(() => unwatch()); collector(); }, { immediate: true } ); sdk.hook("sdk:cleanup", () => this.removeAll(true)); return () => { unwatch(); }; }); this.options = options; const { autoCollect = true, persistent = true, window, storage, storageKey = "__VUE_APP_SDK__TABS__" } = options; this._isAuto = vue.ref(vue.toValue(autoCollect)); if (vue.isRef(autoCollect) || isFunction.default(autoCollect)) vue.watch(autoCollect, this.toggleAuto); this._state = index.createPersistentRef({ persistent, window, storage, storageKey, value: { pages: [], active: "" } }); } /** * 页面元数据转为标签页属性 * @readonly */ get rawPageToTabPage() { return this.options.rawPageToTabPage || defaultRawPageToTabPage; } /** * 是否为有效的页面元数据 * @readonly */ get isValidRawPage() { return this.options.isValidRawPage || defaultIsValidRawPage; } /** * 设置标签页列表 */ set pages(value) { this._state.value.pages = value; } /** * 标签页列表 */ get pages() { return this._state.value.pages; } /** * 设置激活的标签页 ID */ set active(value) { this._state.value.active = value; } /** * 激活的标签页 ID */ get active() { return this._state.value.active; } /** * 激活的标签页 * @readonly */ get activeTabPage() { return this.pages.find((p) => p.id === this.active); } /** * 是否自动模式 * @readonly */ get isAuto() { return this._isAuto.value; } /** * KeepAlive 插件 */ get _keepAlive() { const plugin = this._sdk.getPlugin(KeepAlive.KEEP_ALIVE_ID); plugin && plugin.isAuto && plugin.disableAuto(); return plugin; } /** * 路由列表 */ get _routes() { return this._sdk.router.getRoutes(); } /** * 判断是否需要缓存标签页 */ isKeepAlive(tagPage) { return tagPage.isKeepAlive ?? this.options.keepAlive ?? true; } /** * 生成标签页 ID */ generateID(data) { let pageId, fullPath, isUniq; if ("meta" in data) { const metadata = getMetadata(data); pageId = metadata.id; isUniq = metadata.isUniq; fullPath = data.fullPath; } else { pageId = data.pageId; isUniq = data.isUniq; fullPath = data.fullPath; } let id = `${pageId}`; if (!isUniq) id += `__${fullPath}`; return id; } } exports.TABS_ID = TABS_ID; exports.Tabs = Tabs; exports.TabsReplaceResult = TabsReplaceResult;