vue-app-sdk
Version:
349 lines (348 loc) • 11 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");
require("../vendors/nice-fns/dist/compactObject.cjs");
require("../vendors/nice-fns/dist/pascalCase.cjs");
const vue = require("vue");
const vueRouter = require("vue-router");
const SDK = require("./SDK.cjs");
const pick = require("../vendors/lodash-es/pick.cjs");
const isFunction = require("../vendors/lodash-es/isFunction.cjs");
var NavigationDirection = /* @__PURE__ */ ((NavigationDirection2) => {
NavigationDirection2["forward"] = "forward";
NavigationDirection2["backward"] = "backward";
NavigationDirection2["unchanged"] = "unchanged";
return NavigationDirection2;
})(NavigationDirection || {});
const ROUTER_ID = Symbol("router");
const DETAILS_CHANGED_FLAG = Symbol("details changed flag");
const DETAILS_RECORD_KEY = Symbol("details record");
function useRouteDetails() {
const sdk = SDK.useAppSDK();
const detailsRecord = vue.inject(DETAILS_RECORD_KEY);
const { getDetails, makeDetails } = sdk.getPlugin(ROUTER_ID);
const { fullPath } = vueRouter.useRoute();
const details = vue.shallowReactive(makeDetails(getDetails(fullPath)));
vue.watch(detailsRecord, (record) => {
const newDetails = record[fullPath];
if (!newDetails) {
return;
}
if (newDetails[DETAILS_CHANGED_FLAG] !== details[DETAILS_CHANGED_FLAG]) {
index.assign(details, newDetails);
}
});
return vue.shallowReadonly(details);
}
function initDetailsWatcher(onCleanup) {
const routeDetails = useRouteDetails();
let watcher = /* @__PURE__ */ new Map();
const unwatch = vue.watch(
() => routeDetails,
(details) => {
var _a, _b, _c;
if (watcher && details.from) {
(_a = watcher.get(details.from.path)) == null ? void 0 : _a.forEach((fn) => fn(details.data, details));
(_b = watcher.get(details.from.fullPath)) == null ? void 0 : _b.forEach((fn) => fn(details.data, details));
(_c = watcher.get(details.from.name)) == null ? void 0 : _c.forEach((fn) => fn(details.data, details));
}
},
{ deep: true }
);
function cleanup() {
if (watcher) {
unwatch();
watcher.clear();
watcher = null;
onCleanup();
}
}
return {
set(key, callback) {
if (watcher && isFunction.default(callback)) {
if (!watcher.has(key)) {
watcher.set(key, []);
}
watcher.get(key).push(callback);
}
},
delete(key, callback) {
if (watcher && watcher.has(key)) {
const callbacks = watcher.get(key);
const index2 = callbacks.indexOf(callback);
if (index2 > -1) {
callbacks.splice(index2, 1);
}
if (!callbacks.length) {
watcher.delete(key);
}
}
if (watcher && !watcher.size) {
cleanup();
}
}
};
}
const DETAILS_WATCHER_KEY = Symbol("details watcher key");
function watchDetails(fromKey, callback) {
const vm = vue.getCurrentInstance();
if (!vm) {
index.logger.error(`watchDetails() 必须在组件 setup() 作用域内调用!`);
}
if (!vm || !vm.proxy) {
return () => {
};
}
const proxy = vm.proxy;
proxy[DETAILS_WATCHER_KEY] || (proxy[DETAILS_WATCHER_KEY] = initDetailsWatcher(() => {
proxy[DETAILS_WATCHER_KEY] = null;
}));
const watcher = proxy[DETAILS_WATCHER_KEY];
watcher.set(fromKey, callback);
const unwatch = () => watcher.delete(fromKey, callback);
vue.onBeforeUnmount(unwatch);
return unwatch;
}
const DEFAULT_DETAILS_DATA = Symbol("details data");
const defaultIdentifyDirection = ({
latestPosition,
currentPosition
}) => {
if (latestPosition == null || currentPosition == null) {
return "forward";
}
if (currentPosition < latestPosition) {
return "backward";
}
if (currentPosition === latestPosition) {
return "unchanged";
}
return "forward";
};
class Router {
constructor(options = {}) {
__publicField(this, "id", ROUTER_ID);
/**
* 最近记录的位置
*/
__publicField(this, "latestPosition", null);
/**
* 路由详情记录
*/
__publicField(this, "_detailsRecord");
/**
* 是否正在导航
*/
__publicField(this, "_isNavigating", vue.ref(false));
/**
* 锁定 `beforeOnce`
*/
__publicField(this, "_onceLocked", false);
/**
* 详情数据
*/
__publicField(this, "_detailsData", DEFAULT_DETAILS_DATA);
/**
* 设置详情数据
* @param data 详情数据
*/
__publicField(this, "_setDetailsData", (data) => {
this._detailsData = data;
});
/**
* 消费详情数据
*/
__publicField(this, "_consumeDetailsData", () => {
const isChanged = this._detailsData !== DEFAULT_DETAILS_DATA;
const data = isChanged ? this._detailsData : void 0;
return { isChanged, data };
});
/**
* 获取路由详情
* @param fullPath 完整路径
* @returns 路由详情
*/
__publicField(this, "getDetails", (fullPath) => {
return this._detailsRecord.value[fullPath];
});
/**
* 清理所有详情记录
*/
__publicField(this, "clearDetailsRecord", () => {
this._updateDetailsRecord({});
});
/**
* 创建路由详情
* @param details 需要合并的路由详情
* @param increment 需要自增路由详情变更次数
*/
__publicField(this, "makeDetails", (details, increment = false) => {
const count = details ? details[DETAILS_CHANGED_FLAG] || 0 : 0;
const result = index.assign(
{ from: void 0, data: void 0, [DETAILS_CHANGED_FLAG]: count },
details
);
if (result.from) {
result.from = Object.freeze(pick.default(result.from, ["path", "name", "fullPath", "hash"]));
}
if (increment) {
result[DETAILS_CHANGED_FLAG]++;
}
return result;
});
/**
* 获取当前导航位置
* @returns 当前导航位置
*/
__publicField(this, "getCurrentPosition", () => {
var _a;
return (_a = history.state) == null ? void 0 : _a.position;
});
__publicField(this, "install", (sdk) => {
const { app, router } = sdk;
app.provide(DETAILS_RECORD_KEY, this._detailsRecord);
router.beforeEach(() => {
this._isNavigating.value = true;
});
router.afterEach(() => {
this._isNavigating.value = false;
});
Object.defineProperty(router, "isNavigating", {
enumerable: true,
get: () => this._isNavigating.value
});
router.afterEach((to, from, failure) => {
if (vueRouter.isNavigationFailure(failure)) {
return;
}
const latestPosition = this.latestPosition ?? this.getCurrentPosition();
const currentPosition = this.getCurrentPosition();
const direction = this.identifyDirection({ to, from, latestPosition, currentPosition });
switch (direction) {
case "backward":
sdk.callHookSync("sdk:router:backward", to, from);
break;
case "unchanged":
sdk.callHookSync("sdk:router:replace", to, from);
break;
default:
sdk.callHookSync("sdk:router:forward", to, from);
break;
}
sdk.callHookSync("sdk:router:navigate", direction, to, from);
this.latestPosition = currentPosition;
});
router.beforeOnce = (guard) => {
return router.beforeEach((...args) => {
if (this._onceLocked) {
return;
}
this._onceLocked = true;
return guard(...args);
});
};
router.afterEach(() => {
this._onceLocked = false;
});
const { _setDetailsData, _consumeDetailsData } = this;
router.pushWithData = function(to, data) {
_setDetailsData(data);
return router.push(to);
};
router.replaceWithData = function(to, data) {
_setDetailsData(data);
return router.replace(to);
};
router.goWithData = function(delta, data) {
_setDetailsData(data);
return router.go(delta);
};
router.forwardWithData = function(data) {
return router.goWithData(1, data);
};
router.backWithData = function(data) {
return router.goWithData(-1, data);
};
router.clearDetailsRecord = this.clearDetailsRecord;
sdk.hook("sdk:router:navigate", (direction, to, from) => {
const detailsRecord = this._detailsRecord.value;
const { isChanged, data } = _consumeDetailsData();
const updateDetails = () => {
detailsRecord[to.fullPath] = this.makeDetails(
index.assign({}, detailsRecord[to.fullPath], { from, data }),
true
);
};
switch (direction) {
case "unchanged":
if (isChanged) {
updateDetails();
}
break;
case "backward":
delete detailsRecord[from.fullPath];
if (isChanged) {
updateDetails();
}
break;
default:
updateDetails();
break;
}
this._updateDetailsRecord(detailsRecord);
_setDetailsData(DEFAULT_DETAILS_DATA);
});
sdk.hook("sdk:cleanup", this.clearDetailsRecord);
const unwatch = vue.watch(this._detailsRecord, (record) => sdk.callHookSync("sdk:router:detailsRecordChange", record));
return () => {
this.latestPosition = null;
this._onceLocked = false;
this._detailsData = DEFAULT_DETAILS_DATA;
unwatch();
};
});
this.options = options;
const {
persistentDetails = true,
storage,
storageKey = "__VUE_APP_SDK__ROUTE_DETAILS_RECORD__",
window
} = options;
this._detailsRecord = index.createPersistentRef(
{
persistent: persistentDetails,
window,
storage,
storageKey,
shallow: true,
value: {}
},
true
);
}
/**
* 识别导航方向
* @readonly
*/
get identifyDirection() {
return this.options.identifyDirection || defaultIdentifyDirection;
}
/**
* 更新详情记录
* @param value 详情记录
*/
_updateDetailsRecord(value) {
this._detailsRecord.value = value;
if (this._detailsRecord.value === value) {
vue.triggerRef(this._detailsRecord);
}
}
}
exports.NavigationDirection = NavigationDirection;
exports.ROUTER_ID = ROUTER_ID;
exports.Router = Router;
exports.useRouteDetails = useRouteDetails;
exports.watchDetails = watchDetails;