@ithinkdt/core
Version:
iThinkDT Core
1,521 lines (1,396 loc) • 138 kB
JavaScript
import { inject, ref, shallowRef, reactive, markRaw, h, defineComponent, watch, computed, toRef, isRef, shallowReactive, nextTick, unref, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, getCurrentInstance, provide, toRaw, onMounted, Transition, KeepAlive, Suspense, effectScope, isReactive, readonly, onScopeDispose, toRefs, toValue } from 'vue';
import { request, HttpError, walkTree, debounce, omitProps, copy, createI18n, lang as lang$1, useI18n, array2Tree, measureText, merge, message } from '@ithinkdt/common';
export { lang, useI18n } from '@ithinkdt/common';
import { promiseTimeout, useEventListener, until, syncRef, tryOnUnmounted, usePreferredColorScheme, watchDebounced, toReactive, useUrlSearchParams } from '@vueuse/core';
import { defineStore, getActivePinia, createPinia } from 'pinia';
import { createRouter, createWebHashHistory, createWebHistory, useRouter, onBeforeRouteLeave, RouterView } from 'vue-router';
import { hyphenate, isObject } from '@vue/shared';
import { format } from 'date-fns';
const $FETCH = Symbol();
let $fetch = request;
function initHttp(init) {
$fetch = request.extend(init);
return {
install(app) {
app.provide($FETCH, $fetch);
},
}
}
function useFetch(api, options = {}) {
let fn,
options2 = {};
if (!api) {
api = 'get: ';
}
if (typeof api === 'function') {
fn = api;
} else {
const $fetch = options?.$fetch ?? inject($FETCH, request);
let _api;
if (typeof api === 'string') {
const index = api.indexOf(':');
const [method, reqType] = api.slice(0, Math.max(0, index)).split('|');
_api = {
url: api.slice(Math.max(0, index + 1)),
method,
reqType,
};
} else {
_api = api;
}
options.method = _api.method ?? options?.method;
const {
url,
method = options.method ?? 'get',
before,
after,
shallow,
resetOnExecute,
initResult,
...requestOptions
} = Object.assign({}, _api, options);
options2 = { before, after, shallow, resetOnExecute, initResult };
const isGet = method.toLowerCase() === 'get';
fn = (params, options3 = {}) => {
let req;
if (params instanceof Request) {
req = params;
} else {
req = {
...requestOptions,
...options3,
};
if (isGet) {
req.params = params;
} else {
req.body = params;
}
}
return $fetch(method, url, req)
};
}
const loading = ref(false);
const result = (options.shallow === false ? ref : shallowRef)(options?.initResult);
const state = ref('inited');
const _before = [options.before, options2.before];
const _after = [options2.after, options.after];
const _fn = (params, options3) => {
if (options2.resetOnExecute || options.resetOnExecute) result.value = options?.initResult;
loading.value = true;
state.value = 'loading';
// eslint-disable-next-line unicorn/no-array-reduce
params = _before.reduce((params, before) => (before ? before(params) ?? params : params), params);
const req = Promise.resolve(fn(params, options3));
req.then(
(res) => {
state.value = 'success';
// eslint-disable-next-line unicorn/no-array-reduce
result.value = _after.reduce((res, after) => (after ? after(res) ?? res : res), res);
},
(error) => {
state.value = error?.name === 'AbortError' ? 'abort' : 'error';
if (state.value !== 'abort') {
console[error instanceof HttpError ? 'debugger' : 'error'](`[use-fetch]: `, error);
}
},
).finally(() => {
loading.value = false;
});
return req
};
_fn.loading = loading;
_fn.state = state;
_fn.result = result;
_fn.exec = _fn;
return _fn
}
function initAuthInfo({ login, logout, loadAuthInfo, changePwd, router, locale }) {
const fn = defineStore(`core:auth`, {
state: () => ({
error: false,
token: undefined,
app: undefined,
user: undefined,
modules: [],
fromModule: undefined,
}),
getters: {
logged(state) {
return !!state.token
},
moduleMap(state) {
const map = {};
walkTree(state.modules ?? [], (m, _i, parent) => {
m.parent = parent;
if (m.type !== 'action') {
map[m.key] = m;
if (m.path) {
map[m.path] = m;
}
}
});
return map
},
menus: (() => {
function _walk(m) {
if (m?.hidden === true) return
if (m?.type === 'group' && m?.children?.length) {
const children = m.children.map((c) => _walk(c)).filter((m) => !!m);
return children.length > 0
? {
...m,
children: children,
}
: undefined
}
return m?.type === 'action' ? undefined : { ...m, children: undefined }
}
return (state) => {
const menus = state.modules?.map(_walk).filter((m) => !!m);
console.debug('[auth] menus', menus);
return menus
}
})(),
menuMap() {
const map = {};
walkTree(this.menus, (m) => {
map[m.key] = m;
if (m.path) {
map[m.path] = m;
}
});
return map
},
currentRouteInModule() {
const route = router.currentRoute.value;
return !route?.meta?.activedModule?.trim() && !route?.meta?.activedModuleName?.trim()
},
currentModule() {
const route = router.currentRoute.value;
if (!route) return
if (this.currentRouteInModule) {
return this.moduleMap[route.path] ? route.path : route.matched.at(-1)?.path ?? ''
}
return (
route.meta.activedModule?.trim() ||
router.resolve({ name: route.meta.activedModuleName?.trim() })?.path
)
},
activedModulePath() {
const route = router.currentRoute.value;
if (!route) return []
let menu = this.moduleMap[this.currentModule] || this.moduleMap[this.fromModule];
const paths = [];
if (
!this.currentRouteInModule ||
!(this.moduleMap[route.path] || this.moduleMap[route.matched.at(-1)?.path ?? ''])
) {
paths.push(
reactive({
key: route.path,
parentKey: menu?.key,
parent: menu,
label: route.path,
type: 'module',
path: route.path,
}),
);
}
while (menu) {
paths.unshift(menu);
menu = menu.parent;
}
console.debug('[auth] activedModulePath', paths);
return paths
},
activedMenuPath() {
const paths = this.activedModulePath.map((m) => this.menuMap[m.key]).filter((m) => !!m);
console.debug('[auth] activedMenuPath', paths);
return paths
},
permissions(state) {
const permissions = {};
walkTree(state.modules, (m) => {
m.path && (permissions[m.path] = true);
if (m.actions) for (const r of m.actions) permissions[r] = true;
});
console.debug('[auth] permissions', permissions);
return permissions
},
},
actions: (() => {
let _reject;
let $load;
const loginFn = useFetch(login);
const logoutFn = logout && useFetch(logout);
return {
async login(user) {
console.debug(`[login]: user [ ${user.username} ].`);
try {
const res = await loginFn(user);
console.debug('[login]: login success.', res);
return res
} catch (error) {
console.debug('[login]: login error.', error);
throw error
}
},
async load(force = false) {
if (!this.token) {
console.debug('[auth] load not set token!');
$load = undefined;
throw new Error('未设置用户 token')
}
if ($load) {
if (!force) {
console.debug('[auth] load having $load, reuse.');
return $load
}
console.debug('[auth] load having $load, but force it.');
_reject(new Error('重新 load'));
}
const req = ($load = new Promise((resolve, reject) => {
_reject = reject;
loadAuthInfo(this.token, locale.value).then(
(authInfo) => {
if (this.token === undefined || req !== $load) {
console.debug('[auth] load fetched auth info, but token or request expired.');
return
}
for (const key of Object.keys(authInfo)) {
this[key] = markRaw(authInfo[key]);
}
console.debug('[auth] load fetched auth info.', authInfo);
resolve();
},
(error) => {
if (req === $load) {
if (error?.message === 'forbidden' || error?.message === 'timeout') {
this.error = error;
resolve();
return
}
console.debug('[auth] load fetch auth error.', error);
this.error = error;
$load = undefined;
}
reject(error);
},
);
}));
return $load
},
async logout() {
console.debug('[auth] logout.');
const { token, user, app } = this.$state;
await logoutFn?.({ username: user?.username, appCode: app?.appCode, token: token });
this.$reset();
this.$clear();
await promiseTimeout(300);
},
changePwd,
}
})(),
persist: {
storage: 'local',
version: 1,
excludes: ['app', 'modules'],
},
});
return fn
}
function getFirstPageModule(modules) {
for (const m of modules ?? []) {
if (['module', 'lowcode', 'federate', 'external'].includes(m.type)) {
return m
}
if (m.type === 'group') {
const child = getFirstPageModule(m.children ?? []);
if (child) {
return child
}
}
}
return
}
let $msg;
let $dialog;
let $notice;
function initFeedback({ messageApi, dialogApi, notificationApi }) {
$msg = messageApi;
$dialog = dialogApi;
$notice = notificationApi;
return {
install() {
// nothing
},
}
}
const Route = {
INDEX: '__$-app-index',
LOGIN: '__$-app-login',
LOGOUT: '__$-app-logout',
NOT_FOUND: '__$-app-404',
};
function initRouter({
router,
base = '',
routes,
index,
logged,
Login,
Logout,
Error,
sso = false,
setToken,
getToken,
logout,
type,
getAuthErrorType,
}) {
const getIndexRoute = typeof index === 'function' ? index : () => index;
console.debug(`[router] initing...`, routes);
const warn = debounce(
() => {
$notice({
type: 'warning',
title: '请求的资源不存在',
content: `可能的原因:\n· 应用配置错误\n· 新版本发布`,
meta: '若页面正常请忽略此报告!',
action: () =>
h('span', [
h('span', { style: 'font-size: 12px' }, ['您可以 ']),
h('a', { href: `javascript:location.reload()` }, ['刷新页面']),
]),
});
},
30_000,
{ leading: true },
);
walkTree(routes, (route) => {
if (typeof route.component === 'function') {
if (!route.meta) {
route.meta = {};
}
const comp = route.component;
route.component = () =>
comp()
.then((m) => {
route.meta.__DT_ROUTE_LOAD_FAILURED = false;
return m
})
.catch((error) => {
console.error(`[router] load route component error!`, route, error);
route.meta.__DT_ROUTE_LOAD_FAILURED = error;
warn();
return Error
});
}
});
function redirectOrIndex(redirect) {
console.debug(`[login]: logged, redirect to ${redirect ? `'${redirect}'` : 'index'}.`);
if (redirect) {
location.replace(redirect);
}
return { path: '/', replace: true }
}
console.debug(
sso ? `[router] sso enabled, self login ${sso.selfLogin ? 'enabled' : 'disabled'}.` : `[router] sso disabled`,
);
if (router) {
router.addRoute({
path: base,
name: Route.INDEX + '/' + base,
childrens: routes,
beforeEnter: () => {
const index = getIndexRoute() ?? '/403';
console.debug(`[router] goto page '${index}'.`);
return { path: index, replace: true }
},
});
} else {
let _index,
_routes = [];
for (const r of routes) {
if (r.path === '/') {
_index = r;
} else {
_routes.push(r);
}
}
const toIndex = () => {
const index = getIndexRoute();
if (index) {
console.debug(`[router] goto page '${index}'.`);
return { path: index, replace: true }
}
};
router = createRouter({
history: (type === 'hash' ? createWebHashHistory : createWebHistory)(base),
routes: [
_index
? {
beforeEnter: (to) => {
if (
to.name !== Route.INDEX ||
_index.component ||
_index.children?.find((r) => r.path === '')
)
return
return toIndex()
},
..._index,
name: Route.INDEX,
}
: {
path: '/',
name: Route.INDEX,
component: defineComponent({
render() {
return h(Error, { type: getAuthErrorType() })
},
}),
beforeEnter: toIndex,
},
{
path: '/login',
alias: sso && sso.selfLogin ? [typeof sso.selfLogin === 'string' ? sso.selfLogin : '/login!'] : [],
name: Route.LOGIN,
component: Login,
meta: { requiresLogin: false, keepAlive: false },
async beforeEnter(to) {
console.debug('[login]: before each...');
if (logged.value) {
await promiseTimeout(301);
let redirect = to.query?.redirect;
if (to.query?.redirect_with_token) {
redirect += redirect.includes('?') ? (redirect.endsWith('?') ? '' : '&') : '?';
redirect += to.query?.redirect_with_token + '=' + getToken();
}
return redirectOrIndex(redirect)
}
if (!sso || to.path !== '/login') {
console.debug('[login]: enter self login!');
return
}
const token = await sso.parseToken(location.origin + (to.href ?? ''));
console.debug(
`[login]: sso paresd result {\n\ttoken: ${token},\n\tredirect: ${to.query?.redirect}\n}.`,
);
if (token) {
return setToken(token).then(() => redirectOrIndex(to.query?.redirect))
}
console.debug(`[login]: redirect to sso login page...`);
window.open(await sso.login(location.origin + (type === 'hash' ? base : '') + to.href), '_self');
return false
},
},
{
path: '/logout',
name: Route.LOGOUT,
component: Logout,
meta: { requiresLogin: false, keepAlive: false },
async beforeEnter(to) {
if (Logout) {
return
}
await logout();
if (!sso) {
if (to.query?.redirect) {
window.open(to.query.redirect, '_self');
return
}
return { name: Route.LOGIN }
}
console.debug(`[login]: redirect to sso logout page...`);
window.open(
await sso.logout(`${location.origin}${base}`, { ...to.query, token: getToken() }),
'_self',
);
return false
},
},
..._routes,
{
path: '/:path(.*)',
name: Route.NOT_FOUND,
component: Error,
meta: { requiresLogin: false, keepAlive: 'path&query' },
beforeEnter() {
if (!logged.value) {
console.debug(`[router] error view redirect to login...`);
return {
name: Route.LOGIN,
replace: true,
}
}
},
},
],
});
}
router.base = base && base[0] !== '.' ? base : '/';
return router
}
let STORE_PREFIX = '';
function persistPlugin({ prefix: _prefix, getUsername }) {
STORE_PREFIX = _prefix ?? STORE_PREFIX;
return ({ options, store }) => {
if (!options.persist) {
return {
$clear() {
console.debug(`[store] the store '${store.$id}' not set persist, force it.`);
},
}
}
const {
prefix = _prefix,
userIsolate = false,
storage = 'session',
version = 1,
validate,
excludes,
saveOnlyChanged,
onStore,
onRestore,
} = options.persist;
const getKey = () => `${prefix || ''}${store.$id}${userIsolate && getUsername() ? `[${getUsername()}]` : ''}`;
const _state = saveOnlyChanged ? omitProps({ ...store.$state }, ...(excludes || [])) : undefined;
let force;
const parse = () => {
let data = parseData(storage, getKey(), version, validate);
if (saveOnlyChanged) data = Object.assign({}, _state, data);
if (onRestore) data = onRestore(data);
if (data) {
force = true;
Object.assign(store.$state, data);
}
};
parse();
useEventListener('storage', (e) => {
if (e.key !== getKey() || e.newValue === e.oldValue) {
return
}
setTimeout(parse, 0);
});
store.$subscribe((_, state) => {
if (force) {
force = false;
return
}
let s = {};
for (const key of Object.keys(state)) {
if (
typeof key !== 'symbol' &&
!excludes?.includes(key) &&
(!saveOnlyChanged || _state[key] !== state[key])
) {
s[key] = state[key];
}
}
s = onStore ? onStore(s) : s;
saveData(storage, getKey(), s, version);
});
return {
$clear() {
saveData(storage, getKey(), undefined, version);
},
}
}
}
function parseData(storage, key, version, validate) {
const $storage = storage === 'local' ? localStorage : sessionStorage;
const data = $storage.getItem(key);
if (data) {
const { s, v, t } = JSON.parse(data);
console.debug(`[store] parsed '${key}' data from ${storage}, version: ${v}, date: ${t}, data:`, s);
if (v !== version || validate?.(s, v, t) === false) {
console.debug(`[store] '${key}' data not valid, remove it.`);
$storage.removeItem(key);
} else {
return s
}
}
return
}
function saveData(storage, key, data, version) {
const $storage = storage === 'local' ? localStorage : sessionStorage;
const date = Date.now();
const s = JSON.stringify({
t: date,
v: version,
s: data,
});
let old = $storage.getItem(key) ?? '';
old = old.slice(Math.max(0, old.indexOf('"v":')));
let n = s.slice(Math.max(0, s.indexOf('"v":')));
if (old === n) return
$storage.setItem(key, s);
console.debug(`[store] save '${key}' data to ${storage}, version: ${version}, date: ${date}, data:`, copy(data));
}
function initStore({ plugin = [], pinia, ...persist }) {
pinia ||= getActivePinia() || createPinia();
pinia.use(persistPlugin(persist));
for (const p of plugin) pinia.use(p);
return pinia
}
function jwt(
router,
getToken,
setToken,
{
headerField,
cookieField,
cookieDomain,
cookiePath = '/',
tokenParser,
tokenFormatter,
unauthCodes,
sameSite = 'Lax',
},
) {
let _token, resolve;
const setted = new Promise((_resolve) => (resolve = _resolve));
const interceptor = async (req, next) => {
if (_token) {
req.headers.set(headerField, tokenFormatter(_token));
} else if (req.url.includes('/cli/') || req.url.includes('/cer/')) {
await setted;
}
try {
return await next({
...req,
credentials: 'omit',
})
} catch (error) {
if (
error instanceof HttpError &&
error.code &&
unauthCodes.includes(error.code) &&
![Route.NOT_FOUND, Route.LOGOUT, Route.LOGIN].includes(router.currentRoute.value.name)
) {
console.debug('[auth] token expired');
setToken();
}
throw error
}
};
interceptor.setCookieByToken = (token) => {
if (cookieField) {
// eslint-disable-next-line unicorn/no-document-cookie
document.cookie = `${cookieField}=${_token ? encodeURIComponent(tokenFormatter(token)) : ''}; ${
cookieDomain ? `domain=${cookieDomain}; ` : ''
}path=${cookiePath}; ${sameSite}`;
}
};
const readPathCookie = (path, field) => {
try {
const url = location.href.slice(location.origin.length);
// eslint-disable-next-line unicorn/no-null
history.replaceState(null, '', path);
const reg = new RegExp(`(^| )${field}=([^;]*)(;|$)`);
const v = reg.exec(document.cookie)?.[2];
// eslint-disable-next-line unicorn/no-null
history.replaceState(null, '', url);
return v
} catch {
return ''
}
};
interceptor.getTokenFromCookie = () => {
const str = readPathCookie(cookiePath, cookieField);
return str && tokenParser(decodeURIComponent(str))
};
const str = interceptor.getTokenFromCookie();
interceptor._init = () => {
setted.then((token) => {
if (str && str !== token) {
console.debug('[jwt]: 从 cookie 读取到的 token 不同于当前值,将替换');
setToken(str);
}
});
watch(
getToken,
(token) => {
_token = token;
interceptor.setCookieByToken(token);
token && resolve(token);
},
{ immediate: true, flush: 'sync' },
);
};
return interceptor
}
let useAuth = () => {
throw new Error('未初始化 Auth 模块,不能调用 useAuth')
};
let msgHandler = {
loadMessages: () =>
Promise.resolve({
total: 0,
records: [],
}),
};
let auth;
/**
* @type {import('vue-router').Router}
*/
let router;
let $can;
function initAuth({
jwt: jwtInit,
domain,
router: routerInit,
loadMessages,
readMessages,
deleteMessages,
locale,
loadAppInfo,
sso,
logout,
...authInfoInit
}) {
msgHandler = {
loadMessages,
readMessages,
deleteMessages,
};
const logged = ref(false);
function setToken(token) {
if (auth.token === token) return
if (token) {
auth.token = token;
} else {
const href = router.resolve({
name: Route.LOGOUT,
query: auth.user ? { username: auth.user.username, appCode: auth.app.appCode } : {},
}).href;
auth.$reset();
setTimeout(() => {
location.replace(href);
}, 300);
}
return auth.load(true)
}
const getToken = () => {
return auth?.token
};
router = initRouter({
...routerInit,
index:
routerInit.index ??
(() => {
return getFirstPageModule(auth.modules ?? [])?.path ?? '/index'
}),
logged: logged,
getToken,
setToken,
logout: () => auth.logout(),
sso,
getAuthErrorType: () => auth.error,
});
const lastRoutes = [];
let $init;
useAuth = initAuthInfo({
...authInfoInit,
loadAuthInfo: (...params) => {
return ($init = authInfoInit.loadAuthInfo(...params).then((result) => {
for (const r of lastRoutes) {
router.removeRoute(r);
}
lastRoutes.length = 0;
const remoteUrls = new Set();
walkTree(result.modules, (m) => {
if (m.type === 'federate') {
remoteUrls.add(m.remote);
} else if (m.type === 'lowcode') {
lastRoutes.push('/lowcode' + m.path);
router.addRoute({
name: '/lowcode' + m.path,
path: m.path,
component: routerInit.Lowcode,
props: (route) => ({
...route.params,
ithinkdtLcPageKey: m.lcKey,
ithinkdtLcAppCode: m.remote,
}),
});
}
});
return Promise.allSettled(
[...remoteUrls].map(async (url) => {
try {
const m = await import(/* @vite-ignore */ url);
for (const r of m.default) router.addRoute(r);
} catch (error) {
console.error(`[auth] load federate remote app routes error!`, error);
}
}),
).then(() => result)
}))
},
logout: sso
? undefined
: async (...params) => {
const ret = await logout?.(...params);
Promise.resolve(loadAppInfo(locale.value)).then((app) => {
auth.app = app;
});
return ret
},
router,
locale,
});
const _can = (action) => {
return auth.permissions[action] || false
};
function can(...actions) {
if (actions?.length > 1) {
return actions.some((act) => _can(act))
}
return _can(actions[0])
}
function checkLogin(to) {
if (auth.logged) {
return
}
console.debug('[router] requires user login, redirect to login page.');
const redirect = to.matched[0]?.name === Route.INDEX ? undefined : to.href;
return {
name: Route.LOGIN,
query: redirect
? {
redirect: encodeURIComponent(redirect),
}
: undefined,
}
}
let fromModule;
router.beforeEach((to) => {
if ($init) {
return $init.then(() => {
$init = undefined;
return to.fullPath
})
}
console.debug(`[router] route to '${to.fullPath}'...`, to);
fromModule = auth.currentModule || auth.fromModule;
if (to.meta?.requiresLogin === false) {
console.debug('[router] not requires user login.');
return auth.logged ? auth.load() : true
}
const check = checkLogin(to);
if (check) return check
return auth.load().catch((error) => {
console.error('[router] error when loading user info!', error);
const check = checkLogin(to);
if (check) return check
// TODO 调整方式
return {
path: '/500',
query: {
msg: error?.message ?? error,
},
}
})
});
const findAuth = (auths) => {
return auths.find((s) => auth.permissions[s.trim()] || auth.moduleMap[s.trim()])
};
router.afterEach(async (to, _from, failure) => {
auth.fromModule = fromModule;
if (failure) return
if (to.meta?.requiresLogin === false || to.meta?.requiresAuth === false) {
console.debug(`[auth] not requires auth access, path: ${to.fullPath}`);
} else {
let requires = to.meta?.requiresAuth;
if (requires && typeof requires !== 'boolean')
requires = typeof requires === 'string' ? requires.split(',') : requires;
if (
to.path !== '/' &&
(requires?.length
? !findAuth(requires)
: !auth.moduleMap[to.path] && !auth.moduleMap[to.matched.at(-1)?.path ?? ''])
) {
console.debug(`[auth] not found module '${to.fullPath}' in user auth access.`);
to.__DT_ROUTE_ERROR = '403';
} else if (to.matched?.find((route) => route?.meta.__DT_ROUTE_LOAD_FAILURED)) {
to.__DT_ROUTE_ERROR = 'LOAD_FAILURED';
}
}
});
const jwt$1 = jwtInit ? jwt(router, getToken, setToken, jwtInit) : undefined;
function _init() {
auth = useAuth();
jwt$1?._init();
if (domain) {
let timer;
watch(
() => auth.user?.username,
(name) => {
if (name) {
// eslint-disable-next-line unicorn/no-document-cookie
document.cookie = `${STORE_PREFIX}auth=${encodeURIComponent(btoa(name))}; expires=${
Date.now() + 86_400_000
}; domain=${domain}; path=/; Strict`;
} else {
timer !== undefined && clearInterval(timer);
// eslint-disable-next-line unicorn/no-document-cookie
document.cookie = `${STORE_PREFIX}auth=; expires=${
Date.now() - 60_000
}; domain=${domain}; path=/; Strict`;
}
},
{ immediate: true, flush: 'sync' },
);
const reg = new RegExp(`(^| )${STORE_PREFIX}auth=([^;]*)(;|$)`);
const checkUser = () => {
const cookieUser = reg.exec(document.cookie)?.[2];
if (!cookieUser || window.atob(decodeURIComponent(cookieUser)) !== auth.user.username) {
console.debug(
`user is expired, user: ${auth.user.username}, cookie user: ${cookieUser}, origin: ${location.origin}, referrer: ${document.referrer}`,
);
return false
}
};
until(computed(() => auth.user?.username))
.toBeTruthy()
.then(() => {
if (auth.user?.username && checkUser() === false) {
auth.$reset();
promiseTimeout(30).then(() => {
location.reload();
});
} else {
timer = setInterval(() => {
if (auth.user?.username && checkUser() !== false) return
clearInterval(timer);
timer = undefined;
$dialog({
type: 'error',
title: '账号冲突',
content: '您的账号可能被其他窗口登出,即将刷新此页面。。',
cancelText: false,
okText: '刷 新',
onOk() {
auth.$reset();
return promiseTimeout(30).then(() => {
location.reload();
})
},
});
}, 300);
}
});
}
syncRef(toRef(auth, 'logged'), logged, { direction: 'ltr' });
watch(
locale,
async () => {
console.debug('[auth] locale changed, reload user & app info.');
auth.token &&
auth.load(true).catch(() => {
// no empty
});
auth.app = (await loadAppInfo(locale.value)) ?? auth.app;
},
{ immediate: true },
);
}
return {
router,
jwt: jwt$1,
install(app) {
if (!auth) _init();
app.config.globalProperties.$can = $can = can;
app.provide('__dt_access__', can);
app.use(router);
},
}
}
function useLogin() {
const auth = useAuth();
const loging = ref(false);
async function login(user, load = true) {
loging.value = true;
try {
auth.token = await auth.login(user);
load && (await auth.load(true));
} finally {
loging.value = false;
}
}
const router = useRouter();
return {
user: auth.user,
loging,
login,
logout: () => {
const { user, app } = auth.$state;
const href = router.resolve({
name: Route.LOGOUT,
query: user ? { username: user.username, appCode: app.appCode } : {},
}).href;
location.replace(href);
},
next() {
return location.reload()
},
}
}
function useAccess(prefix, actions) {
const can = (...params) => $can(...params);
can.permissions = auth.permissions;
can.can = prefix ? (...actions) => can(...actions.map((action) => prefix + action)) : can;
can.cans = actions?.map(prefix ? (action) => can(prefix + action) : can);
return can
}
function lang({ locale, headerField, localeFormatter }) {
let _locale;
watch(
locale,
() => {
_locale = locale.value;
},
{ immediate: true, flush: 'sync' },
);
return (req, next) => {
if (_locale) {
req.headers.set(headerField, localeFormatter(_locale));
}
return next(req)
}
}
let i18n;
function initI18n(options) {
const { global, install } = createI18n({
locale: options.defaultLocale,
messages: options.globalMessage,
});
i18n = global;
const lang$2 =
typeof options.http === 'object'
? lang({
...options.http,
locale: global.locale,
})
: undefined;
const key = `${STORE_PREFIX}core:i18n`;
const version = 1;
const parse = () => {
const data = parseData('local', key, version, (locale) => locale && Object.values(lang$1).includes(locale));
if (data) global.locale.value = data;
};
parse();
watch(
global.locale,
(locale, from) => {
if (from === locale) return
document.querySelector('html').setAttribute('lang', locale);
console.debug(`[i18n] locale changed ${from || '[undefined]'} -> ${locale}.`);
saveData('local', key, locale === options.defaultLocale ? undefined : locale, version);
},
{ immediate: true },
);
return {
global,
lang: lang$2,
install(app) {
app.use(install);
},
}
}
let init;
function setInit(init0) {
init = init0;
}
const layzePool = [];
function batchFetchDicts() {
const _layzePool = [...layzePool];
layzePool.length = 0;
init.fetchDicts(_layzePool.map(([dictType]) => dictType)).then(
(res) => {
for (const [dictType, cb] of _layzePool) {
cb(res?.[dictType] ?? []);
}
},
(error) => {
console.error('[dict]: 请求字典项数据失败:', error);
for (const [_dictType, cb] of _layzePool) {
cb([]);
}
},
);
}
async function _getDict(dictType, forceCache) {
if (forceCache || !hasDictType(dictType)) {
if (typeof dictType !== 'string' || !dictType?.trim()) {
console.error('[dict]: 字典类型错误:', dictType);
return []
}
removeDict(dictType);
const $fetch = new Promise((resolve) => {
layzePool.push([dictType, resolve]);
});
if (layzePool.length === 1) {
nextTick(batchFetchDicts);
}
registerDict(dictType, $fetch, {
label: init.labelGetter,
value: init.valueGetter,
disabled: init.disabledGetter,
});
await $fetch;
}
await dictMap[dictType]?.$;
return unref(dictMap[dictType]?.data) ?? []
}
const dictMap = {};
const dictValueMap = {};
function registerDict(dictType, dicts, { autoClean = false, cleanUseless = true, label, value, disabled } = {}) {
let _dicts;
let $;
if (typeof dicts === 'function') {
dicts = Promise.resolve(dicts(_getDict));
}
if (dicts instanceof Promise) {
_dicts = shallowRef([]);
$ = dicts.then((dicts) => {
_dicts.value = dicts;
});
} else {
_dicts = isRef(dicts) ? dicts : shallowRef(dicts);
}
const _label = typeof label === 'function' ? label : (d) => d[label ?? 'label'];
const _value = typeof value === 'function' ? value : (d) => d[value ?? 'value'];
const _disabled = typeof disabled === 'function' ? disabled : (d) => d[disabled ?? 'disabled'];
const { locale } = useI18n();
const $dicts = computed(() => {
return _dicts.value?.map((dict) => {
return {
label: _label(dict, locale.value),
value: _value(dict),
disabled: _disabled(dict),
orign: dict,
}
})
});
dictMap[dictType] = {
date: cleanUseless ? Date.now() : undefined,
data: $dicts,
$,
};
const unregister = () => removeDict(dictType);
if (autoClean) {
tryOnUnmounted(unregister);
}
return {
dictType,
dicts: getDicts(dictType),
unregister,
}
}
function getDicts(dictType, hideDisabled = true, valueType = 'string') {
const cache = dictMap[dictType];
cache.date = Date.now();
const dicts = shallowReactive([]);
watch(
cache.data,
(data = []) => {
dicts.length = 0;
let array = valueType === 'number' ? data.map((it) => ({ ...it, value: Number(it.value) })) : data;
if (hideDisabled) {
array = array.filter((it) => it.disabled !== true);
}
dicts.push(...array);
},
{ immediate: true },
);
return dicts
}
function getDictMap(dictType, hideDisabled = false, valueType = 'string') {
if (!dictValueMap[dictType + hideDisabled]) {
const map = shallowReactive(new Map());
watch(
getDicts(dictType, hideDisabled, valueType),
(dicts) => {
map.clear();
for (const dict of dicts) {
map.set(dict.value, dict);
}
},
{ immediate: true },
);
dictValueMap[dictType + hideDisabled] = map;
}
return dictValueMap[dictType + hideDisabled]
}
function hasDictType(dictType) {
return !!dictMap[dictType]
}
function removeDict(dictType) {
delete dictMap[dictType];
delete dictValueMap[dictType];
}
function cleanUseless(delay = 1 * 60 * 1000) {
const now = Date.now();
for (const key of Object.keys(dictMap)) {
const date = dictMap[key]?.date;
if (date && now - date > delay) {
removeDict(key);
}
}
}
function getDict(dictType, getType, valueType, forceCache, hideDisabled, onGet) {
const $fetch = _getDict(dictType, forceCache);
onGet && $fetch.then(onGet);
const isGetMap = getType === 'map';
let $ret;
if (hasDictType(dictType)) {
$ret = isGetMap ? getDictMap(dictType, hideDisabled, valueType) : getDicts(dictType, hideDisabled, valueType);
} else {
$ret = shallowReactive(isGetMap ? new Map() : []);
}
$ret.$ = $fetch;
return $ret
}
/* eslint-disable unicorn/no-thenable */
function _useDict(dictType, getType, valueTypeOrOptions) {
const valueType =
typeof valueTypeOrOptions === 'string' ? valueTypeOrOptions : valueTypeOrOptions?.valueType ?? 'string';
const {
forceCache = false,
onGet,
hideDisabled = getType === 'list',
} = typeof valueTypeOrOptions === 'object' ? valueTypeOrOptions : {};
let $returns;
if (isRef(dictType)) {
const isGetMap = getType === 'map';
$returns = shallowReactive(isGetMap ? new Map() : []);
$returns.__on = [];
$returns.loading = false;
watch(
dictType,
() => {
$returns.loading = true;
isGetMap ? $returns.clear() : ($returns.length = 0);
if (dictType.value) {
const $ret = getDict(dictType.value, getType, valueType, forceCache, hideDisabled, (dicts) => {
nextTick(() => {
if (isGetMap) {
for (const [k, v] of $ret.entries) $returns.set(k, v);
} else {
$returns.push(...$ret);
}
onGet?.(dicts);
$returns.loading = false;
for (const on of $returns.__on) {
try {
on?.($ret);
} catch (error) {
console.error(error);
}
}
});
});
}
},
{ immediate: true },
);
$returns.then = (on) => {
$returns.__on.push(on);
};
} else {
$returns = getDict(dictType, getType, valueType, forceCache, hideDisabled, (dicts) => {
nextTick(() => {
$returns.loading = false;
onGet?.(dicts);
});
});
$returns.loading ??= true;
$returns.then = (on) => {
$returns.$.then(() => {
on?.($returns);
});
};
}
$returns.dicts = $returns;
return $returns
}
function useDict(dictType, valueTypeOrOptions = 'string') {
return _useDict(dictType, 'list', valueTypeOrOptions)
}
function useDictMap(dictType, valueTypeOrOptions = 'string') {
return _useDict(dictType, 'map', valueTypeOrOptions)
}
function initDict({ clean, ...useDictInit }) {
setInit(useDictInit);
if (clean?.interval !== 0) {
setInterval(
() => {
cleanUseless(clean?.delay);
},
clean?.interval ?? 2 * 60 * 10_000,
);
}
return {
install() {
// nothing
},
}
}
function getRouteKey(key = 'path', route) {
if (key === false || key === 'route') {
return route.name
}
if (key === 'path') {
return route.path
}
if (key === 'path&query' || key === 'route&query') {
let search = route.fullPath.split('?')[1] || '';
search = search.split('#')[0];
if (key === 'path&query') {
return `${route.path}?${search}`
}
if (key === 'route&query') {
return `${route.name}?${search}`
}
}
return key
}
const preferredColor = usePreferredColorScheme();
function _isDark(mode) {
return mode === 'dark' || (mode === 'auto' && preferredColor.value === 'dark')
}
function initThemeInfo({ persistExcludesKeys = [], version = 1, Error, router, ...theme }) {
return defineStore(`core:theme`, {
state: () => ({
multiTab: true,
mode: theme.showAppearence ? 'auto' : 'light',
menuPlacement: 'sidebar',
collpaseBtnPlacement: 'sidebar',
logoPlacement: 'sidebar',
isFixedSidebar: true,
topbarDark: false,
sidebarDark: false,
showI18n: true,
showMessage: true,
showChangePwd: true,
showFullscreen: true,
showAppearence: true,
hasSidebar: true,
hasTopbar: true,
hasBreadcrumb: true,
hasFooter: true,
floatFooter: false,
footer: undefined,
fullTab: false,
msgInterval: 10_000,
...theme,
Error: markRaw(Error),
pages: [],
transition: Promise.resolve(),
}),
getters: {
isDark(state) {
return _isDark(state.mode)