vue-app-sdk
Version:
621 lines (620 loc) • 19.7 kB
JavaScript
"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;