Compare commits

...

7 Commits

Author SHA1 Message Date
Soybean
858d7625a4 refactor(projects): use new elegant-router plugin 2025-10-13 16:51:54 +08:00
Soybean
d5a3a25d3d fix(types): fix ts type error 2025-10-13 16:48:33 +08:00
Soybean
d567c057a8 chore(deps): update deps 2025-10-13 16:39:36 +08:00
HongxuanG
91fae50260 typo(other): Correct the typos in the Chinese translation and change "局右" to "居右"。 2025-10-04 00:08:25 +08:00
Lijun Shen
61fa4b7f3b feat(packages): materials support slider-tab. closed #823 2025-10-02 22:09:59 +08:00
Azir-11
ef7acc626f feat(projects): optimize tabs cache cleaning strategy. close #820. 2025-10-02 11:17:44 +08:00
恕瑞玛的皇帝
2a0c9f1b41 docs(projects): add github trendshift info. 2025-09-27 21:18:05 +08:00
62 changed files with 1575 additions and 1598 deletions

2
.gitignore vendored
View File

@@ -33,3 +33,5 @@ package-lock.json
yarn.lock
.VSCodeCounter
.temp

View File

@@ -13,8 +13,10 @@
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<div style="display: flex; gap: 12px; align-items: center;">
<a href="https://trendshift.io/repositories/7963" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7963" alt="soybeanjs%2Fsoybean-admin | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
> [!NOTE]
> If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a ⭐️ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support!

View File

@@ -13,7 +13,10 @@
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<div style="display: flex; gap: 12px; align-items: center;">
<a href="https://trendshift.io/repositories/7963" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7963" alt="soybeanjs%2Fsoybean-admin | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
> [!NOTE]
> 如果您觉得 `SoybeanAdmin`对您有所帮助,或者您喜欢我们的项目,请在 GitHub 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!

View File

@@ -2,7 +2,7 @@ import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import elegantRouter from 'elegant-router/vite';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html';
@@ -13,7 +13,7 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
vue(),
vueJsx(),
setupDevtoolsPlugin(viteEnv),
setupElegantRouter(),
elegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress(),

View File

@@ -1,41 +0,0 @@
import type { RouteMeta } from 'vue-router';
import ElegantVueRouter from '@elegant-router/vue/vite';
import type { RouteKey } from '@elegant-router/types';
export function setupElegantRouter() {
return ElegantVueRouter({
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;
if (key === 'login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return routePath;
},
onRouteMetaGen(routeName) {
const key = routeName as RouteKey;
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
const meta: Partial<RouteMeta> = {
title: key,
i18nKey: `route.${key}` as App.I18n.I18nKey
};
if (constantRoutes.includes(key)) {
meta.constant = true;
}
return meta;
}
});
}

37
er.config.ts Normal file
View File

@@ -0,0 +1,37 @@
import type { RouteMeta } from 'vue-router';
import { defineConfig } from 'elegant-router';
import type { RouteKey } from '@elegant-router/types';
export default defineConfig({
pageDir: ['src/views'],
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
getRoutePath: node => {
if (node.name === 'Login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return node.path;
},
getRouteMeta: node => {
const constantRoutes: RouteKey[] = ['Login', '403', '404', '500'];
const name = node.name as RouteKey;
const meta: Partial<RouteMeta> = {
title: name
};
if (constantRoutes.includes(name)) {
meta.constant = true;
}
return meta;
}
});

View File

@@ -64,43 +64,44 @@
"nprogress": "0.2.0",
"pinia": "3.0.3",
"tailwind-merge": "3.3.1",
"vue": "3.5.21",
"vue": "3.5.22",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.12",
"vue-router": "4.5.1"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.385",
"@iconify/json": "2.2.395",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.5.1",
"@types/node": "24.7.2",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.5.1",
"@unocss/preset-icons": "66.5.1",
"@unocss/preset-uno": "66.5.1",
"@unocss/transformer-directives": "66.5.1",
"@unocss/transformer-variant-group": "66.5.1",
"@unocss/vite": "66.5.1",
"@unocss/eslint-config": "66.5.3",
"@unocss/preset-icons": "66.5.3",
"@unocss/preset-uno": "66.5.3",
"@unocss/transformer-directives": "66.5.3",
"@unocss/transformer-variant-group": "66.5.3",
"@unocss/vite": "66.5.3",
"@vitejs/plugin-vue": "6.0.1",
"@vitejs/plugin-vue-jsx": "5.1.1",
"consola": "3.4.2",
"eslint": "9.35.0",
"eslint-plugin-vue": "10.4.0",
"elegant-router": "1.0.4",
"eslint": "9.37.0",
"eslint-plugin-vue": "10.5.0",
"kolorist": "1.8.0",
"sass": "1.92.1",
"sass": "1.93.2",
"simple-git-hooks": "2.13.1",
"tsx": "4.20.5",
"typescript": "5.9.2",
"unplugin-icons": "22.3.0",
"unplugin-vue-components": "29.0.0",
"vite": "7.1.5",
"tsx": "4.20.6",
"typescript": "5.9.3",
"unplugin-icons": "22.4.2",
"unplugin-vue-components": "29.1.0",
"vite": "7.1.9",
"vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.2",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.7"
"vue-tsc": "3.1.1"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",

View File

@@ -95,3 +95,27 @@
.chrome-tab_dark .chrome-tab-divider {
background-color: rgba(255, 255, 255, 0.9);
}
.slider-tab {
background-color: transparent;
height: 100%;
border-bottom: 2px solid transparent;
}
.slider-tab_dark {
background-color: transparent;
}
.slider-tab:hover {
color: var(--soy-primary-color);
}
.slider-tab_active {
color: var(--soy-primary-color);
background-color: var(--soy-primary-color-opacity1);
border-bottom-color: var(--soy-primary-color);
}
.slider-tab_active_dark {
background-color: var(--soy-primary-color-opacity2);
}

View File

@@ -10,6 +10,10 @@ declare const styles: {
readonly 'chrome-tab_dark': string;
readonly 'chrome-tab-divider': string;
readonly 'svg-close': string;
readonly 'slider-tab': string;
readonly 'slider-tab_active': string;
readonly 'slider-tab_active_dark': string;
readonly 'slider-tab_dark': string;
};
export default styles;

View File

@@ -5,6 +5,7 @@ import type { PageTabMode, PageTabProps } from '../../types';
import { ACTIVE_COLOR, createTabCssVars } from './shared';
import ChromeTab from './chrome-tab.vue';
import ButtonTab from './button-tab.vue';
import SliderTab from './slider-tab.vue';
import SvgClose from './svg-close.vue';
import style from './index.module.css';
@@ -26,7 +27,7 @@ interface Emits {
const emit = defineEmits<Emits>();
const activeTabComponent = computed(() => {
const { mode, chromeClass, buttonClass } = props;
const { mode, chromeClass, buttonClass, sliderClass } = props;
const tabComponentMap = {
chrome: {
@@ -36,6 +37,10 @@ const activeTabComponent = computed(() => {
button: {
component: ButtonTab,
class: buttonClass
},
slider: {
component: SliderTab,
class: sliderClass
}
} satisfies Record<PageTabMode, { component: Component; class?: string }>;
@@ -45,7 +50,7 @@ const activeTabComponent = computed(() => {
const cssVars = computed(() => createTabCssVars(props.activeColor));
const bindProps = computed(() => {
const { chromeClass: _chromeCls, buttonClass: _btnCls, ...rest } = props;
const { chromeClass: _chromeCls, buttonClass: _btnCls, sliderClass: _sliderCls, ...rest } = props;
return rest;
});

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import type { PageTabProps } from '../../types';
import style from './index.module.css';
defineOptions({
name: 'SliderTab'
});
defineProps<PageTabProps>();
type SlotFn = (props?: Record<string, unknown>) => any;
type Slots = {
/**
* Slot
*
* The center content of the tab
*/
default?: SlotFn;
/**
* Slot
*
* The left content of the tab
*/
prefix?: SlotFn;
/**
* Slot
*
* The right content of the tab
*/
suffix?: SlotFn;
};
defineSlots<Slots>();
</script>
<template>
<div
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-6px whitespace-nowrap px-12px py-4px"
:class="[
style['slider-tab'],
{ [style['slider-tab_dark']]: darkMode },
{ [style['slider-tab_active']]: active },
{ [style['slider-tab_active_dark']]: active && darkMode }
]"
>
<slot name="prefix"></slot>
<slot></slot>
<slot name="suffix"></slot>
</div>
</template>
<style scoped></style>

View File

@@ -239,7 +239,7 @@ export type LayoutCssVars = {
*
* @default chrome
*/
export type PageTabMode = 'button' | 'chrome';
export type PageTabMode = 'button' | 'chrome' | 'slider';
export interface PageTabProps {
/** Whether is dark mode */
@@ -262,6 +262,8 @@ export interface PageTabProps {
buttonClass?: string;
/** The class of the chrome tab */
chromeClass?: string;
/** The class of the title tab */
sliderClass?: string;
/** Whether the tab is active */
active?: boolean;
/** The color of the active tab */

View File

@@ -14,14 +14,14 @@
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.25",
"bumpp": "10.2.3",
"bumpp": "10.3.1",
"c12": "3.3.0",
"cac": "6.7.14",
"consola": "3.4.2",
"enquirer": "2.4.1",
"execa": "9.6.0",
"kolorist": "1.8.0",
"npm-check-updates": "18.1.1",
"npm-check-updates": "19.0.0",
"picomatch": "4.0.3",
"rimraf": "6.0.1"
}

View File

@@ -14,7 +14,7 @@
"crypto-js": "4.2.0",
"klona": "2.0.6",
"localforage": "1.10.0",
"nanoid": "5.1.5"
"nanoid": "5.1.6"
},
"devDependencies": {
"@types/crypto-js": "4.2.2"

1659
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,8 @@ export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRec
export const themeTabModeRecord: Record<UnionKey.ThemeTabMode, App.I18n.I18nKey> = {
chrome: 'theme.layout.tab.mode.chrome',
button: 'theme.layout.tab.mode.button'
button: 'theme.layout.tab.mode.button',
slider: 'theme.layout.tab.mode.slider'
};
export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord);
@@ -57,13 +58,6 @@ export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode,
export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I18n.I18nKey> = {
refresh: 'theme.layout.resetCacheStrategy.refresh',
close: 'theme.layout.resetCacheStrategy.close'
};
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
export const DARK_CLASS = 'dark';
export const watermarkTimeFormatOptions = [

View File

@@ -169,7 +169,9 @@ init();
<div
ref="tabRef"
class="h-full flex pr-18px"
:class="[themeStore.tab.mode === 'chrome' ? 'items-end' : 'items-center gap-12px']"
:class="[
themeStore.tab.mode === 'chrome' || themeStore.tab.mode === 'slider' ? 'items-end' : 'items-center gap-12px'
]"
>
<PageTab
v-for="tab in tabStore.tabs"

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { resetCacheStrategyOptions, themeTabModeOptions } from '@/constants/app';
import { themeTabModeOptions } from '@/constants/app';
import { useThemeStore } from '@/store/modules/theme';
import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
@@ -15,14 +15,6 @@ const themeStore = useThemeStore();
<template>
<NDivider>{{ $t('theme.layout.tab.title') }}</NDivider>
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
<SettingItem key="0" :label="$t('theme.layout.resetCacheStrategy.title')">
<NSelect
v-model:value="themeStore.resetCacheStrategy"
:options="translateOptions(resetCacheStrategyOptions)"
size="small"
class="w-120px"
/>
</SettingItem>
<SettingItem key="1" :label="$t('theme.layout.tab.visible')">
<NSwitch v-model:value="themeStore.tab.visible" />
</SettingItem>

View File

@@ -16,7 +16,6 @@ type ThemePreset = Pick<
| 'themeColor'
| 'otherColor'
| 'isInfoFollowPrimary'
| 'resetCacheStrategy'
| 'layout'
| 'page'
| 'header'

View File

@@ -135,6 +135,7 @@ const local: App.I18n.Schema = {
height: 'Tab Height',
mode: {
title: 'Tab Mode',
slider: 'Slider',
chrome: 'Chrome',
button: 'Button'
}
@@ -185,11 +186,6 @@ const local: App.I18n.Schema = {
}
},
fixedHeaderAndTab: 'Fixed Header And Tab'
},
resetCacheStrategy: {
title: 'Reset Cache Strategy',
close: 'Close Page',
refresh: 'Refresh Page'
}
},
general: {

View File

@@ -132,6 +132,7 @@ const local: App.I18n.Schema = {
height: '标签栏高度',
mode: {
title: '标签栏风格',
slider: '滑块风格',
chrome: '谷歌风格',
button: '按钮风格'
}
@@ -158,7 +159,7 @@ const local: App.I18n.Schema = {
visible: '显示底部',
fixed: '固定底部',
height: '底部高度',
right: '底部右'
right: '底部右'
},
content: {
title: '内容区域设置',
@@ -182,11 +183,6 @@ const local: App.I18n.Schema = {
}
},
fixedHeaderAndTab: '固定头部和标签栏'
},
resetCacheStrategy: {
title: '重置缓存策略',
close: '关闭页面',
refresh: '刷新页面'
}
},
general: {

View File

@@ -0,0 +1,27 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteFileKey, RouteLayoutKey, RawRouteComponent } from "@elegant-router/types";
export const layouts: Record<RouteLayoutKey, RawRouteComponent> = {
base: () => import("@/layouts/base-layout/index.vue"),
blank: () => import("@/layouts/blank-layout/index.vue"),
};
export const views: Record<RouteFileKey, RawRouteComponent> = {
403: () => import("@/views/(builtin)/403/index.vue"),
404: () => import("@/views/(builtin)/404/index.vue"),
500: () => import("@/views/(builtin)/500/index.vue"),
Home: () => import("@/views/home/index.vue"),
IframeUrl: () => import("@/views/(builtin)/iframe/[url].vue"),
Login: () => import("@/views/(builtin)/login/index.vue"),
ManageMenu: () => import("@/views/manage/menu/index.vue"),
ManageRole: () => import("@/views/manage/role/index.vue"),
ManageRoute: () => import("@/views/manage/route/index.vue"),
ManageUser: () => import("@/views/manage/user/index.vue"),
Wip: () => import("@/views/(builtin)/wip/index.vue"),
};

View File

@@ -0,0 +1,130 @@
/* eslint-disable */
/* prettier-ignore */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { AutoRouterRoute } from '@elegant-router/types';
export const routes: AutoRouterRoute[] = [
{
name: 'Root',
path: '/',
redirect: '/home',
meta: {
title: "Root"
},
},
{
name: 'NotFound',
path: '/:pathMatch(.*)*',
layout: 'base',
component: '404',
meta: {
title: "NotFound"
},
},
{
name: '403',
path: '/403',
layout: 'base',
component: '403',
meta: {
title: "403",
constant: true
},
},
{
name: '404',
path: '/404',
layout: 'base',
component: '404',
meta: {
title: "404",
constant: true
},
},
{
name: '500',
path: '/500',
layout: 'base',
component: '500',
meta: {
title: "500",
constant: true
},
},
{
name: 'Home',
path: '/home',
layout: 'base',
component: 'Home',
meta: {
title: "Home"
},
},
{
name: 'IframeUrl',
path: '/iframe/:url',
layout: 'base',
component: 'IframeUrl',
meta: {
title: "IframeUrl"
}
},
{
name: 'Login',
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
layout: 'base',
component: 'Login',
meta: {
title: "Login",
constant: true
},
},
{
name: 'ManageMenu',
path: '/manage/menu',
layout: 'base',
component: 'ManageMenu',
meta: {
title: "ManageMenu"
},
},
{
name: 'ManageRole',
path: '/manage/role',
layout: 'base',
component: 'ManageRole',
meta: {
title: "ManageRole"
},
},
{
name: 'ManageRoute',
path: '/manage/route',
layout: 'base',
component: 'ManageRoute',
meta: {
title: "ManageRoute"
}
},
{
name: 'ManageUser',
path: '/manage/user',
layout: 'base',
component: 'ManageUser',
meta: {
title: "ManageUser"
},
},
{
name: 'Wip',
path: '/wip',
layout: 'base',
component: 'Wip',
meta: {
title: "Wip"
}
}
];

View File

@@ -0,0 +1,28 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteKey, RoutePathMap } from '@elegant-router/types';
const routePathMap: RoutePathMap = {
"Root": "/",
"NotFound": "/:pathMatch(.*)*",
"403": "/403",
"404": "/404",
"500": "/500",
"Home": "/home",
"IframeUrl": "/iframe/:url",
"Login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"ManageMenu": "/manage/menu",
"ManageRole": "/manage/role",
"ManageRoute": "/manage/route",
"ManageUser": "/manage/user",
"Wip": "/wip",
};
export function getRoutePath(key: RouteKey) {
return routePathMap[key];
}

View File

@@ -0,0 +1,70 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteRecordRaw } from 'vue-router';
import type {
AutoRouterRedirect,
AutoRouterRoute,
AutoRouterSingleView,
RawRouteComponent,
RouteFileKey,
RouteLayoutKey
} from '@elegant-router/types';
export function transformToVueRoutes(
routes: AutoRouterRoute[],
layouts: Record<RouteLayoutKey, RawRouteComponent>,
views: Record<RouteFileKey, RawRouteComponent>
) {
const { redirects, groupedRoutes } = getFormattedRoutes(routes);
const vueRoutes: RouteRecordRaw[] = [...redirects];
groupedRoutes.forEach((items, layout) => {
const layoutRoute: RouteRecordRaw = {
path: `/${layout}-layout`,
component: layouts[layout],
children: items.map(item => {
const { layout: _, component, ...rest } = item;
return {
component: views[component],
...rest
};
})
};
vueRoutes.push(layoutRoute);
});
return vueRoutes;
}
function getFormattedRoutes(routes: AutoRouterRoute[]) {
const groupedRoutes = new Map<RouteLayoutKey, AutoRouterSingleView[]>();
const redirects: AutoRouterRedirect[] = [];
routes.forEach(route => {
if (isAutoRouterRedirect(route)) {
redirects.push(route);
return;
}
const items = groupedRoutes.get(route.layout) || [];
items.push(route);
groupedRoutes.set(route.layout, items);
});
return {
redirects,
groupedRoutes
};
}
function isAutoRouterRedirect(route: AutoRouterRoute): route is AutoRouterRedirect {
return 'redirect' in route;
}

View File

@@ -1,24 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteComponent } from "vue-router";
import type { LastLevelRouteKey, RouteLayout } from "@elegant-router/types";
import BaseLayout from "@/layouts/base-layout/index.vue";
import BlankLayout from "@/layouts/blank-layout/index.vue";
export const layouts: Record<RouteLayout, RouteComponent | (() => Promise<RouteComponent>)> = {
base: BaseLayout,
blank: BlankLayout,
};
export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
403: () => import("@/views/_builtin/403/index.vue"),
404: () => import("@/views/_builtin/404/index.vue"),
500: () => import("@/views/_builtin/500/index.vue"),
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
login: () => import("@/views/_builtin/login/index.vue"),
home: () => import("@/views/home/index.vue"),
};

View File

@@ -1,78 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { GeneratedRoute } from '@elegant-router/types';
export const generatedRoutes: GeneratedRoute[] = [
{
name: '403',
path: '/403',
component: 'layout.blank$view.403',
meta: {
title: '403',
i18nKey: 'route.403',
constant: true,
hideInMenu: true
}
},
{
name: '404',
path: '/404',
component: 'layout.blank$view.404',
meta: {
title: '404',
i18nKey: 'route.404',
constant: true,
hideInMenu: true
}
},
{
name: '500',
path: '/500',
component: 'layout.blank$view.500',
meta: {
title: '500',
i18nKey: 'route.500',
constant: true,
hideInMenu: true
}
},
{
name: 'home',
path: '/home',
component: 'layout.base$view.home',
meta: {
title: 'home',
i18nKey: 'route.home',
icon: 'mdi:monitor-dashboard',
order: 1
}
},
{
name: 'iframe-page',
path: '/iframe-page/:url',
component: 'layout.base$view.iframe-page',
props: true,
meta: {
title: 'iframe-page',
i18nKey: 'route.iframe-page',
constant: true,
hideInMenu: true,
keepAlive: true
}
},
{
name: 'login',
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
component: 'layout.blank$view.login',
props: true,
meta: {
title: 'login',
i18nKey: 'route.login',
constant: true,
hideInMenu: true
}
}
];

View File

@@ -1,192 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteRecordRaw, RouteComponent } from 'vue-router';
import type { ElegantConstRoute } from '@elegant-router/vue';
import type { RouteMap, RouteKey, RoutePath } from '@elegant-router/types';
/**
* transform elegant const routes to vue routes
* @param routes elegant const routes
* @param layouts layout components
* @param views view components
*/
export function transformElegantRoutesToVueRoutes(
routes: ElegantConstRoute[],
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
) {
return routes.flatMap(route => transformElegantRouteToVueRoute(route, layouts, views));
}
/**
* transform elegant route to vue route
* @param route elegant const route
* @param layouts layout components
* @param views view components
*/
function transformElegantRouteToVueRoute(
route: ElegantConstRoute,
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
) {
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
const ROUTE_DEGREE_SPLITTER = '_';
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
function isLayout(component: string) {
return component.startsWith(LAYOUT_PREFIX);
}
function getLayoutName(component: string) {
const layout = component.replace(LAYOUT_PREFIX, '');
if(!layouts[layout]) {
throw new Error(`Layout component "${layout}" not found`);
}
return layout;
}
function isView(component: string) {
return component.startsWith(VIEW_PREFIX);
}
function getViewName(component: string) {
const view = component.replace(VIEW_PREFIX, '');
if(!views[view]) {
throw new Error(`View component "${view}" not found`);
}
return view;
}
function isFirstLevelRoute(item: ElegantConstRoute) {
return !item.name.includes(ROUTE_DEGREE_SPLITTER);
}
function isSingleLevelRoute(item: ElegantConstRoute) {
return isFirstLevelRoute(item) && !item.children?.length;
}
function getSingleLevelRouteComponent(component: string) {
const [layout, view] = component.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT);
return {
layout: getLayoutName(layout),
view: getViewName(view)
};
}
const vueRoutes: RouteRecordRaw[] = [];
// add props: true to route
if (route.path.includes(':') && !route.props) {
route.props = true;
}
const { name, path, component, children, ...rest } = route;
const vueRoute = { name, path, ...rest } as RouteRecordRaw;
try {
if (component) {
if (isSingleLevelRoute(route)) {
const { layout, view } = getSingleLevelRouteComponent(component);
const singleLevelRoute: RouteRecordRaw = {
path,
component: layouts[layout],
meta: {
title: route.meta?.title || ''
},
children: [
{
name,
path: '',
component: views[view],
...rest
} as RouteRecordRaw
]
};
return [singleLevelRoute];
}
if (isLayout(component)) {
const layoutName = getLayoutName(component);
vueRoute.component = layouts[layoutName];
}
if (isView(component)) {
const viewName = getViewName(component);
vueRoute.component = views[viewName];
}
}
} catch (error: any) {
console.error(`Error transforming route "${route.name}": ${error.toString()}`);
return [];
}
// add redirect to child
if (children?.length && !vueRoute.redirect) {
vueRoute.redirect = {
name: children[0].name
};
}
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
if(isFirstLevelRoute(route)) {
vueRoute.children = childRoutes;
} else {
vueRoutes.push(...childRoutes);
}
}
vueRoutes.unshift(vueRoute);
return vueRoutes;
}
/**
* map of route name and route path
*/
const routeMap: RouteMap = {
"root": "/",
"not-found": "/:pathMatch(.*)*",
"403": "/403",
"404": "/404",
"500": "/500",
"home": "/home",
"iframe-page": "/iframe-page/:url",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
};
/**
* get route path by route name
* @param name route name
*/
export function getRoutePath<T extends RouteKey>(name: T) {
return routeMap[name];
}
/**
* get route name by route path
* @param path route path
*/
export function getRouteName(path: RoutePath) {
const routeEntries = Object.entries(routeMap) as [RouteKey, RoutePath][];
const routeName: RouteKey | null = routeEntries.find(([, routePath]) => routePath === path)?.[0] || null;
return routeName;
}

View File

@@ -1,12 +1,6 @@
import type { App } from 'vue';
import {
type RouterHistory,
createMemoryHistory,
createRouter,
createWebHashHistory,
createWebHistory
} from 'vue-router';
import { createBuiltinVueRoutes } from './routes/builtin';
import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
import type { RouterHistory } from 'vue-router';
import { createRouterGuard } from './guard';
const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env;
@@ -19,7 +13,7 @@ const historyCreatorMap: Record<Env.RouterHistoryMode, (base?: string) => Router
export const router = createRouter({
history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL),
routes: createBuiltinVueRoutes()
routes: []
});
/** Setup Vue Router */

View File

@@ -1,31 +0,0 @@
import type { CustomRoute } from '@elegant-router/types';
import { layouts, views } from '../elegant/imports';
import { getRoutePath, transformElegantRoutesToVueRoutes } from '../elegant/transform';
export const ROOT_ROUTE: CustomRoute = {
name: 'root',
path: '/',
redirect: getRoutePath(import.meta.env.VITE_ROUTE_HOME) || '/home',
meta: {
title: 'root',
constant: true
}
};
const NOT_FOUND_ROUTE: CustomRoute = {
name: 'not-found',
path: '/:pathMatch(.*)*',
component: 'layout.blank$view.404',
meta: {
title: 'not-found',
constant: true
}
};
/** builtin routes, it must be constant and setup in vue-router */
const builtinRoutes: CustomRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE];
/** create builtin vue routes */
export function createBuiltinVueRoutes() {
return transformElegantRoutesToVueRoutes(builtinRoutes, layouts, views);
}

View File

@@ -1,40 +1,39 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes';
import { layouts, views } from '../elegant/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
/**
* custom routes
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [];
import type { AutoRouterRedirect, AutoRouterRoute } from '@elegant-router/types';
import { routes } from '../_generated/routes';
import { layouts, views } from '../_generated/imports';
import { transformToVueRoutes } from '../_generated/transformer';
/** create routes when the auth route mode is static */
export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = [];
const constantRoutes: AutoRouterRoute[] = [];
const authRoutes: AutoRouterRoute[] = [];
const authRoutes: ElegantRoute[] = [];
let rootRoute: AutoRouterRedirect | undefined;
[...customRoutes, ...generatedRoutes].forEach(item => {
routes.forEach(item => {
if (item.meta?.constant) {
constantRoutes.push(item);
} else {
authRoutes.push(item);
}
if (item.name === 'Root') {
rootRoute = item as AutoRouterRedirect;
}
});
return {
constantRoutes,
authRoutes
authRoutes,
rootRoute: rootRoute as AutoRouterRedirect
};
}
/**
* Get auth vue routes
*
* @param routes Elegant routes
* @param authRoutes Elegant routes
*/
export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views);
export function getAuthVueRoutes(authRoutes: AutoRouterRoute[]) {
return transformToVueRoutes(authRoutes, layouts, views);
}

View File

@@ -46,10 +46,7 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
});
setReloadFlag(true);
if (themeStore.resetCacheStrategy === 'refresh') {
routeStore.resetRouteCache();
}
routeStore.resetRouteCache();
}
const locale = ref<App.I18n.LangType>(localStg.get('lang') || 'zh-CN');

View File

@@ -112,10 +112,8 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
await switchRouteByTab(nextTab);
}
// reset route cache if cache strategy is close
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(removedTabRouteKey);
}
// reset route cache
routeStore.resetRouteCache(removedTabRouteKey);
}
/** remove active tab */
@@ -147,10 +145,8 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
const routeKeysToReset: RouteKey[] = [];
if (themeStore.resetCacheStrategy === 'close') {
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
}
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
}
const removedTabsIds = tabsToRemove.map(tab => tab.id);

View File

@@ -15,7 +15,6 @@
"error": "#c49a9a"
},
"isInfoFollowPrimary": true,
"resetCacheStrategy": "refresh",
"layout": {
"mode": "vertical-mix",
"scrollMode": "wrapper"

View File

@@ -15,7 +15,6 @@
"error": "#f5222d"
},
"isInfoFollowPrimary": true,
"resetCacheStrategy": "close",
"layout": {
"mode": "vertical",
"scrollMode": "content"

View File

@@ -15,7 +15,6 @@
"error": "#f5222d"
},
"isInfoFollowPrimary": true,
"resetCacheStrategy": "close",
"layout": {
"mode": "vertical",
"scrollMode": "content"

View File

@@ -15,7 +15,6 @@
"error": "#f5222d"
},
"isInfoFollowPrimary": true,
"resetCacheStrategy": "close",
"layout": {
"mode": "vertical",
"scrollMode": "content"

View File

@@ -12,7 +12,6 @@ export const themeSettings: App.Theme.ThemeSetting = {
error: '#f5222d'
},
isInfoFollowPrimary: true,
resetCacheStrategy: 'refresh',
layout: {
mode: 'vertical',
scrollMode: 'content'

View File

@@ -5,46 +5,38 @@
*/
declare namespace Api {
namespace Common {
/** common params of paginating */
interface PaginatingCommonParams {
/** current page number */
current: number;
/** page size */
size: number;
/** total count */
interface CreatedUpdatedDTO {
/** 创建人 */
createdBy: string;
/** 创建时间 */
createdTime: string;
/** 更新人 */
updatedBy: string;
/** 更新时间 */
updatedTime: string;
}
interface CommonRecordDTO extends CreatedUpdatedDTO {
/** 主键 */
id: number;
/**
* 启用状态 Enable status
*
* - "1": 启用
* - "2": 禁用
*/
status: Union.EnableStatus | null;
}
interface PaginationQueryDTO {
page: number;
pageSize: number;
sort: string;
}
interface PaginationDTO extends Pick<PaginationQueryDTO, 'page' | 'pageSize'> {
/** 总条数 */
total: number;
}
/** common params of paginating query list data */
interface PaginatingQueryRecord<T = any> extends PaginatingCommonParams {
records: T[];
}
/** common search params of table */
type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
/**
* enable status
*
* - "1": enabled
* - "2": disabled
*/
type EnableStatus = '1' | '2';
/** common record */
type CommonRecord<T = any> = {
/** record id */
id: number;
/** record creator */
createBy: string;
/** record create time */
createTime: string;
/** record updater */
updateBy: string;
/** record update time */
updateTime: string;
/** record status */
status: EnableStatus | null;
} & T;
}
}

42
src/typings/api/menu.d.ts vendored Normal file
View File

@@ -0,0 +1,42 @@
declare namespace Api {
namespace Menu {
type RouteKey = import('@elegant-router/types').RouteKey;
interface Menu extends Common.CommonRecordDTO {
/** 菜单名称 */
name: string;
/** 菜单类型 */
menuType: Union.MenuType;
/** 菜单描述 */
description?: string | null;
/** 菜单图标 */
icon?: string | null;
/** 菜单图标类型 */
iconType?: string | null;
/** 父级菜单ID */
parentId?: number | null;
/** 菜单排序 */
order?: number | null;
/** 是否隐藏 */
isHidden?: boolean | null;
/** iframe 页面URL */
iframeUrl?: string | null;
/** 链接 */
href?: string | null;
/** 路由名称 */
routeName?: RouteKey | null;
/** 路由查询参数 */
routeQueries?: Record<string, string | number | boolean> | null;
/** 路由路径参数 */
routeParams?: Record<string, string | number | boolean> | null;
}
interface MenuListDTO extends Common.PaginationDTO {
list: Menu[];
}
interface MenuTreeDTO extends Menu {
children?: MenuTreeDTO[];
}
}
}

View File

@@ -1,19 +1,34 @@
declare namespace Api {
/**
* namespace Route
*
* backend api module: "route"
*/
namespace Route {
type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute;
interface MenuRoute extends ElegantConstRoute {
id: string;
interface RouteDTO extends Common.CommonRecordDTO {
/** 路由路径 */
path: string;
/** 路由名称 */
name: string;
/** 路由布局 */
layout: string;
/** 路由组件 */
component: string;
/** 重定向路径 */
redirect?: string | null;
/** 路由图标 */
icon?: string | null;
/** 路由图标类型 */
iconType?: string | null;
/** 是否缓存 */
isCache?: boolean | null;
/** 是否支持多开 */
isMultiple?: boolean | null;
/** 固定索引 */
fixedIndex?: number | null;
/** 是否需要权限 */
requireAuth?: boolean | null;
/** 路由属性 */
props?: Record<string, string | number | boolean> | null;
}
interface UserRoute {
routes: MenuRoute[];
home: import('@elegant-router/types').LastLevelRouteKey;
interface RouteListDTO extends Common.PaginationDTO {
list: RouteDTO[];
}
}
}

31
src/typings/api/union.d.ts vendored Normal file
View File

@@ -0,0 +1,31 @@
declare namespace Api {
namespace Union {
/**
* 是否 Yes or No
*
* - "Y": yes
* - "N": no
*/
type YesOrNo = 'Y' | 'N';
/**
* 启用状态 Enable status
*
* - "1": 启用
* - "2": 禁用
*/
type EnableStatus = '1' | '2';
/**
* 菜单类型
*
* - "directory": 目录
* - "menu": 菜单
* - "iframe": iframe 页面iframe 嵌入页面)
* - "link": 链接(跳转外部链接)
* - "page": 页面(不展示在菜单中,可用于作为菜单类型的子菜单展示在面包屑中)
* - "other": 其他(作用自行定义)
*/
type MenuType = 'page' | 'directory' | 'menu' | 'iframe' | 'link' | 'other';
}
}

97
src/typings/app.d.ts vendored
View File

@@ -20,8 +20,6 @@ declare namespace App {
otherColor: OtherColor;
/** Whether info color is followed by the primary color */
isInfoFollowPrimary: boolean;
/** Reset cache strategy */
resetCacheStrategy: UnionKey.ResetCacheStrategy;
/** Layout */
layout: {
/** Layout mode */
@@ -181,9 +179,9 @@ declare namespace App {
type VNode = import('vue').VNode;
type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded;
type RouteKey = import('@elegant-router/types').RouteKey;
type RouteMap = import('@elegant-router/types').RouteMap;
type RoutePathMap = import('@elegant-router/types').RoutePathMap;
type RoutePath = import('@elegant-router/types').RoutePath;
type LastLevelRouteKey = import('@elegant-router/types').LastLevelRouteKey;
type RouteFileKey = import('@elegant-router/types').RouteFileKey;
/** The router push options */
type RouterPushOptions = {
@@ -201,77 +199,25 @@ declare namespace App {
showMenu?: boolean;
}
type MenuType = 'page' | 'directory' | 'menu' | 'iframe' | 'link' | 'button' | 'other';
/** The global menu */
type Menu = {
/**
* The menu key
*
* Equal to the route key
*/
key: string;
/** The menu label */
label: string;
/** The menu i18n key */
i18nKey?: I18n.I18nKey | null;
/** The route key */
routeKey: RouteKey;
/** The route path */
routePath: RoutePath;
/** The menu icon */
icon?: () => VNode;
/** The menu children */
children?: Menu[];
};
type Breadcrumb = Omit<Menu, 'children'> & {
options?: Breadcrumb[];
};
/** Tab route */
type TabRoute = Pick<RouteLocationNormalizedLoaded, 'name' | 'path' | 'meta'> &
Partial<Pick<RouteLocationNormalizedLoaded, 'fullPath' | 'query' | 'matched'>>;
/** The global tab */
type Tab = {
/** The tab id */
id: string;
/** The tab label */
label: string;
/**
* The new tab label
*
* If set, the tab label will be replaced by this value
*/
newLabel?: string;
/**
* The old tab label
*
* when reset the tab label, the tab label will be replaced by this value
*/
oldLabel?: string;
/** The tab route key */
routeKey: LastLevelRouteKey;
/** The tab route path */
routePath: RouteMap[LastLevelRouteKey];
/** The tab route full path */
fullPath: string;
/** The tab fixed index */
fixedIndex?: number | null;
/**
* Tab icon
*
* Iconify icon
*/
icon?: string;
/**
* Tab local icon
*
* Local icon
*/
localIcon?: string;
/** I18n key */
i18nKey?: I18n.I18nKey | null;
};
interface Menu {
id: number;
name: string;
menuType: MenuType;
description?: string | null;
icon?: string | null;
iconType?: string | null;
parentId?: number | null;
order?: number | null;
isHidden?: boolean | null;
iframeUrl?: string | null;
href?: string | null;
routeName?: RouteKey | null;
routeQueries?: Record<string, string | number | boolean> | null;
routeParams?: Record<string, string | number | boolean> | null;
}
/** Form rule */
type FormRule = import('naive-ui').FormItemRule;
@@ -376,7 +322,7 @@ declare namespace App {
themeColor: {
title: string;
followPrimary: string;
} & Theme.ThemeColor;
} & Record<Theme.ThemeColorKey, string>;
recommendColor: string;
recommendColorDesc: string;
preset: {
@@ -436,7 +382,6 @@ declare namespace App {
};
fixedHeaderAndTab: string;
};
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
};
general: {
title: string;

View File

@@ -1,240 +1,100 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
declare module "@elegant-router/types" {
type ElegantConstRoute = import('@elegant-router/vue').ElegantConstRoute;
type RouteRecordSingleView = import("vue-router").RouteRecordSingleView;
type RouteRecordRedirect = import("vue-router").RouteRecordRedirect;
type RouteComponent = import("vue-router").RouteComponent;
type Lazy<T> = () => Promise<T>;
export type RawRouteComponent = RouteComponent | Lazy<RouteComponent>;
/**
* route layout
* route layout key
*/
export type RouteLayout = "base" | "blank";
export type RouteLayoutKey = "base" | "blank";
/**
* route map
* route path map
*/
export type RouteMap = {
"root": "/";
"not-found": "/:pathMatch(.*)*";
export type RoutePathMap = {
"Root": "/";
"NotFound": "/:pathMatch(.*)*";
"403": "/403";
"404": "/404";
"500": "/500";
"home": "/home";
"iframe-page": "/iframe-page/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"Home": "/home";
"IframeUrl": "/iframe/:url";
"Login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"ManageMenu": "/manage/menu";
"ManageRole": "/manage/role";
"ManageRoute": "/manage/route";
"ManageUser": "/manage/user";
"Wip": "/wip";
};
/**
* route key
*/
export type RouteKey = keyof RouteMap;
export type RouteKey = keyof RoutePathMap;
/**
* route path
*/
export type RoutePath = RouteMap[RouteKey];
export type RoutePath = RoutePathMap[RouteKey];
/**
* custom route key
* root route key
*/
export type CustomRouteKey = Extract<
RouteKey,
| "root"
| "not-found"
>;
export type RootRouteKey = 'Root';
/**
* the generated route key
* not found route key
*/
export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
export type NotFoundRouteKey = 'NotFound';
/**
* the first level route key, which contain the layout of the route
* builtin route key
*/
export type FirstLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "home"
| "iframe-page"
| "login"
>;
export type BuiltinRouteKey = RootRouteKey | NotFoundRouteKey;
/**
* the custom first level route key
* reuse route key
*/
export type CustomFirstLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
>;
export type ReuseRouteKey = never;
/**
* the last level route key, which has the page file
* the route file key, which has it's own file
*/
export type LastLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "iframe-page"
| "login"
| "home"
>;
export type RouteFileKey = Exclude<RouteKey, BuiltinRouteKey | ReuseRouteKey>;
/**
* the custom last level route key
* mapped name and path
*/
export type CustomLastLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
>;
type MappedNamePath = {
[K in RouteKey]: { name: K; path: RoutePathMap[K] };
}[RouteKey];
/**
* the single level route key
* auto router single view
*/
export type SingleLevelRouteKey = FirstLevelRouteKey & LastLevelRouteKey;
export type AutoRouterSingleView = Omit<RouteRecordSingleView, 'component' | 'name' | 'path'> & {
component: RouteFileKey;
layout: RouteLayoutKey;
} & MappedNamePath;
/**
* the custom single level route key
* auto router redirect
*/
export type CustomSingleLevelRouteKey = CustomFirstLevelRouteKey & CustomLastLevelRouteKey;
export type AutoRouterRedirect = Omit<RouteRecordRedirect, 'children' | 'name' | 'path'> & MappedNamePath;
/**
* the first level route key, but not the single level
*/
export type FirstLevelRouteNotSingleKey = Exclude<FirstLevelRouteKey, SingleLevelRouteKey>;
/**
* the custom first level route key, but not the single level
* auto router route
*/
export type CustomFirstLevelRouteNotSingleKey = Exclude<CustomFirstLevelRouteKey, CustomSingleLevelRouteKey>;
/**
* the center level route key
*/
export type CenterLevelRouteKey = Exclude<GeneratedRouteKey, FirstLevelRouteKey | LastLevelRouteKey>;
/**
* the custom center level route key
*/
export type CustomCenterLevelRouteKey = Exclude<CustomRouteKey, CustomFirstLevelRouteKey | CustomLastLevelRouteKey>;
/**
* the center level route key
*/
type GetChildRouteKey<K extends RouteKey, T extends RouteKey = RouteKey> = T extends `${K}_${infer R}`
? R extends `${string}_${string}`
? never
: T
: never;
/**
* the single level route
*/
type SingleLevelRoute<K extends SingleLevelRouteKey = SingleLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}$view.${K}`;
}
: never;
/**
* the last level route
*/
type LastLevelRoute<K extends GeneratedRouteKey> = K extends LastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component: `view.${K}`;
}
: never;
/**
* the center level route
*/
type CenterLevelRoute<K extends GeneratedRouteKey> = K extends CenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the multi level route
*/
type MultiLevelRoute<K extends FirstLevelRouteNotSingleKey = FirstLevelRouteNotSingleKey> = K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}`;
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom first level route
*/
type CustomSingleLevelRoute<K extends CustomFirstLevelRouteKey = CustomFirstLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component?: `layout.${RouteLayout}$view.${LastLevelRouteKey}`;
}
: never;
/**
* the custom last level route
*/
type CustomLastLevelRoute<K extends CustomRouteKey> = K extends CustomLastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component?: `view.${LastLevelRouteKey}`;
}
: never;
/**
* the custom center level route
*/
type CustomCenterLevelRoute<K extends CustomRouteKey> = K extends CustomCenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom multi level route
*/
type CustomMultiLevelRoute<K extends CustomFirstLevelRouteNotSingleKey = CustomFirstLevelRouteNotSingleKey> =
K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}`;
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom route
*/
type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute;
/**
* the generated route
*/
type GeneratedRoute = SingleLevelRoute | MultiLevelRoute;
/**
* the elegant route
*/
type ElegantRoute = GeneratedRoute | CustomRoute;
export type AutoRouterRoute = AutoRouterSingleView | AutoRouterRedirect;
}

View File

@@ -1,7 +1,7 @@
import 'vue-router';
export {};
declare module 'vue-router' {
interface RouteMeta {
export interface RouteMeta {
/**
* Title of the route
*

40
src/typings/typed-router.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
/* eslint-disable */
// @ts-nocheck
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
export {}
declare module "vue-router" {
type RouteNamedMap = import("vue-router/auto-routes").RouteNamedMap;
export interface TypesConfig {
RouteNamedMap: RouteNamedMap;
}
}
declare module "vue-router/auto-routes" {
import type { RouteParamsRawGeneric, RouteParamsGeneric, RouteMeta, RouteRecordInfo, ParamValue, ParamValueZeroOrOne } from "vue-router";
/**
* route named map
*/
export interface RouteNamedMap {
"Root": RouteRecordInfo<"Root", "/", Record<never, never>, Record<never, never>>;
"NotFound": RouteRecordInfo<"NotFound", "/:pathMatch(.*)*", Record<never, never>, Record<never, never>>;
"403": RouteRecordInfo<"403", "/403", Record<never, never>, Record<never, never>>;
"404": RouteRecordInfo<"404", "/404", Record<never, never>, Record<never, never>>;
"500": RouteRecordInfo<"500", "/500", Record<never, never>, Record<never, never>>;
"Home": RouteRecordInfo<"Home", "/home", Record<never, never>, Record<never, never>>;
"IframeUrl": RouteRecordInfo<"IframeUrl", "/iframe/:url", { url: ParamValue<true> }, { url: ParamValue<false> }>;
"Login": RouteRecordInfo<"Login", "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", { module?: ParamValueZeroOrOne<true> }, { module?: ParamValueZeroOrOne<false> }>;
"ManageMenu": RouteRecordInfo<"ManageMenu", "/manage/menu", Record<never, never>, Record<never, never>>;
"ManageRole": RouteRecordInfo<"ManageRole", "/manage/role", Record<never, never>, Record<never, never>>;
"ManageRoute": RouteRecordInfo<"ManageRoute", "/manage/route", Record<never, never>, Record<never, never>>;
"ManageUser": RouteRecordInfo<"ManageUser", "/manage/user", Record<never, never>, Record<never, never>>;
"Wip": RouteRecordInfo<"Wip", "/wip", Record<never, never>, Record<never, never>>
}
}

View File

@@ -14,14 +14,6 @@ declare namespace UnionKey {
/** Theme scheme */
type ThemeScheme = 'light' | 'dark' | 'auto';
/**
* Reset cache strategy
*
* - close: re-cache when close page
* - refresh: re-cache when refresh page
*/
type ResetCacheStrategy = 'close' | 'refresh';
/**
* The layout mode
*

View File

@@ -23,7 +23,7 @@ const appStore = useAppStore();
const themeStore = useThemeStore();
interface LoginModule {
label: string;
label: App.I18n.I18nKey;
component: Component;
}

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageMenu</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageRole</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageRoute</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageUser</div>
</template>