Compare commits

..

1 Commits

Author SHA1 Message Date
Soybean
6ac50e7d15 refactor(projects): use new elegant-router plugin 2025-12-25 18:28:11 +08:00
57 changed files with 1634 additions and 2137 deletions

View File

@@ -1,34 +0,0 @@
name: opencode
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
with:
model: google/gemini-3-pro-preview

2
.gitignore vendored
View File

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

View File

@@ -23,6 +23,9 @@
> [!NOTE] > [!NOTE]
> The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out > The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out
> [!WARNING]
> `SoybeanAdmin` is planning to develop a `V2` version, see [plan list](https://github.com/soybeanjs/soybean-admin/issues/767)
## Introduction ## Introduction
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly. [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.

View File

@@ -23,6 +23,9 @@
> [!NOTE] > [!NOTE]
> `SoybeanAdmin` 快速上手系列视频已在 [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) 上线 [点击这里](https://www.bilibili.com/video/BV1YKdRYXELC) 前往查看 > `SoybeanAdmin` 快速上手系列视频已在 [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) 上线 [点击这里](https://www.bilibili.com/video/BV1YKdRYXELC) 前往查看
> [!WARNING]
> `SoybeanAdmin` 正在计划开发 `V2` 版本,详情见[计划清单](https://github.com/soybeanjs/soybean-admin/issues/767)
## 简介 ## 简介
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。

View File

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

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,44 +64,44 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.4", "pinia": "3.0.4",
"tailwind-merge": "3.4.0", "tailwind-merge": "3.4.0",
"vue": "3.5.27", "vue": "3.5.26",
"vue-draggable-plus": "0.6.1", "vue-draggable-plus": "0.6.0",
"vue-i18n": "11.2.8", "vue-i18n": "11.2.7",
"vue-router": "4.6.4" "vue-router": "4.6.4"
}, },
"devDependencies": { "devDependencies": {
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.430", "@iconify/json": "2.2.417",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.5", "@soybeanjs/eslint-config": "1.7.4",
"@types/node": "25.0.9", "@types/node": "25.0.3",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.6.0", "@unocss/eslint-config": "66.5.10",
"@unocss/preset-icons": "66.6.0", "@unocss/preset-icons": "66.5.10",
"@unocss/preset-uno": "66.6.0", "@unocss/preset-uno": "66.5.10",
"@unocss/transformer-directives": "66.6.0", "@unocss/transformer-directives": "66.5.10",
"@unocss/transformer-variant-group": "66.6.0", "@unocss/transformer-variant-group": "66.5.10",
"@unocss/vite": "66.6.0", "@unocss/vite": "66.5.10",
"@vitejs/plugin-vue": "6.0.3", "@vitejs/plugin-vue": "6.0.3",
"@vitejs/plugin-vue-jsx": "5.1.3", "@vitejs/plugin-vue-jsx": "5.1.2",
"consola": "3.4.2", "consola": "3.4.2",
"elegant-router": "1.1.0",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-plugin-vue": "10.7.0", "eslint-plugin-vue": "10.6.2",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"sass": "1.97.2", "sass": "1.97.1",
"simple-git-hooks": "2.13.1", "simple-git-hooks": "2.13.1",
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"unplugin-icons": "23.0.1", "unplugin-icons": "22.5.0",
"unplugin-vue-components": "31.0.0", "unplugin-vue-components": "30.0.0",
"vite": "7.3.1", "vite": "7.3.0",
"vite-plugin-progress": "0.0.7", "vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.5", "vite-plugin-vue-devtools": "8.0.5",
"vite-plugin-vue-transition-root-validator": "^0.0.4",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.2.2" "vue-tsc": "3.2.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",

View File

@@ -13,7 +13,7 @@
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"axios": "1.13.2", "axios": "1.13.2",
"axios-retry": "4.5.0", "axios-retry": "4.5.0",
"qs": "6.14.1" "qs": "6.14.0"
}, },
"devDependencies": { "devDependencies": {
"@types/qs": "6.14.0" "@types/qs": "6.14.0"

View File

@@ -23,7 +23,6 @@ export type TableColumnCheck = {
title: TableColumnCheckTitle; title: TableColumnCheckTitle;
checked: boolean; checked: boolean;
visible: boolean; visible: boolean;
fixed: 'left' | 'right' | 'unFixed';
}; };
export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> { export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> {
@@ -79,14 +78,12 @@ export default function useTable<ResponseData, ApiData, Column, Pagination exten
function reloadColumns() { function reloadColumns() {
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked])); const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
const fixedMap = new Map(columnChecks.value.map(col => [col.key, col.fixed]));
const defaultChecks = getColumnChecks(columns()); const defaultChecks = getColumnChecks(columns());
columnChecks.value = defaultChecks.map(col => ({ columnChecks.value = defaultChecks.map(col => ({
...col, ...col,
checked: checkMap.get(col.key) ?? col.checked, checked: checkMap.get(col.key) ?? col.checked
fixed: (fixedMap.get(col.key) !== 'unFixed' ? fixedMap.get(col.key) : undefined) ?? col.fixed
})); }));
} }

View File

@@ -13,15 +13,15 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.4.3", "@soybeanjs/changelog": "0.3.25",
"bumpp": "10.4.0", "bumpp": "10.3.2",
"c12": "3.3.3", "c12": "3.3.3",
"cac": "6.7.14", "cac": "6.7.14",
"consola": "3.4.2", "consola": "3.4.2",
"enquirer": "2.4.1", "enquirer": "2.4.1",
"execa": "9.6.1", "execa": "9.6.1",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"npm-check-updates": "19.3.1", "npm-check-updates": "19.2.0",
"picomatch": "4.0.3", "picomatch": "4.0.3",
"rimraf": "6.1.2" "rimraf": "6.1.2"
} }

1844
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1 @@
<svg width="100%" height="100%" version="1.1" viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><path d="M81.28 55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1 8.1 0 014-8.61 7.89 7.89 0 019.3 1.23 35.999 35.999 0 015.9 8.83 75.18 75.18 0 018.44 28.58 83.211 83.211 0 01-5.23 36.74 102.983 102.983 0 01-3 7.28 1.2 1.2 0 000 1.41c9.58 13.3 21.76 23 37.85 27.24a54.37 54.37 0 0019.68 1.57 7.72 7.72 0 018.36 6.9 7.903 7.903 0 01-6.7 9 64.744 64.744 0 01-23-1.33 77.68 77.68 0 01-36.93-19.88 93.628 93.628 0 01-11.91-13.71 2.18 2.18 0 00-2.3-1.06 72.744 72.744 0 00-27.38 7.55c-11.6 6-20.67 14.58-26.4 26.45a10.134 10.134 0 01-3.7 4.7 8 8 0 01-9.19-.7 7.86 7.86 0 01-2.36-9.28 60.324 60.324 0 018.72-14.52c12.2-15.43 28.21-24.59 47.32-28.57A85.085 85.085 0 0173.07 87c.524.015 1-.307 1.18-.8a76.06 76.06 0 006.53-22.3c.351-2.652.518-5.325.5-8z" fill="#646cff"/><path d="M136.26 108.34a44.742 44.742 0 01-11.13-2.87 46.108 46.108 0 01-19.66-13.76 8 8 0 015.72-13.22 7.93 7.93 0 016.54 2.93 33.27 33.27 0 0018.87 10.75c1.546.155 3.058.553 4.48 1.18a8.08 8.08 0 013.84 9.21c-.92 3.52-4.13 5.81-8.66 5.78zm-80.6-75.02a7.61 7.61 0 016.64 5 49.139 49.139 0 013.64 17 46.33 46.33 0 01-2.46 17.28c-2 5.77-8.24 7.79-12.89 4.15a8.1 8.1 0 01-2.39-9 31.679 31.679 0 001.68-12.36 35.77 35.77 0 00-2.43-11c-2.1-5.45 1.75-11.07 8.21-11.07zm22.26 93.25a8 8 0 01-6.68 7.86 32.88 32.88 0 00-19.7 12.19 8.13 8.13 0 01-11.21 1.62 8 8 0 01-1.41-11.58A51.043 51.043 0 0154 123.81a45.842 45.842 0 0114-5.1c5.35-1.04 9.91 2.56 9.92 7.86z" fill="#646cff"/></svg>
xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient)" fill-rule="nonzero" opacity="1" stroke="none" />
<path
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient_2)" fill-rule="nonzero" opacity="1" stroke="none" />
<path d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
fill="url(#LinearGradient_3)" fill-rule="nonzero" opacity="1" stroke="none" />
<path d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
fill="url(#LinearGradient_4)" fill-rule="nonzero" opacity="1" stroke="none" />
<path d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
fill="url(#LinearGradient_5)" fill-rule="nonzero" opacity="1" stroke="none" />
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
fill="url(#LinearGradient_6)" fill-rule="nonzero" opacity="1" stroke="none" />
<path
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
fill="url(#LinearGradient_7)" fill-rule="nonzero" opacity="1" stroke="none" />
</g>
<defs>
<linearGradient gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
gradientUnits="userSpaceOnUse" id="LinearGradient" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#373ebf" />
<stop offset="1" stop-color="#5058e6" />
</linearGradient>
<linearGradient gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
gradientUnits="userSpaceOnUse" id="LinearGradient_2" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#c2d6ff" />
<stop offset="1" stop-color="#646cff" />
</linearGradient>
<linearGradient gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
gradientUnits="userSpaceOnUse" id="LinearGradient_3" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#5058e6" />
<stop offset="1" stop-color="#373ebf" />
</linearGradient>
<linearGradient gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
gradientUnits="userSpaceOnUse" id="LinearGradient_4" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#91a7ff" />
<stop offset="1" stop-color="#5058e6" />
</linearGradient>
<linearGradient gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
gradientUnits="userSpaceOnUse" id="LinearGradient_5" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#646cff" />
<stop offset="1" stop-color="#c2d6ff" />
</linearGradient>
<linearGradient gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
gradientUnits="userSpaceOnUse" id="LinearGradient_6" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#c2d6ff" />
<stop offset="1" stop-color="#646cff" />
</linearGradient>
<linearGradient gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
gradientUnits="userSpaceOnUse" id="LinearGradient_7" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#373ebf" />
<stop offset="1" stop-color="#5058e6" />
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +1,4 @@
<script setup lang="ts" generic="T extends Record<string, unknown>, K = never"> <script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
import { computed } from 'vue';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import { $t } from '@/locales'; import { $t } from '@/locales';
@@ -10,53 +9,6 @@ defineOptions({
const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', { const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
required: true required: true
}); });
const tooltipRecord: Record<NaiveUI.TableColumnFixed, App.I18n.I18nKey> = {
left: 'datatable.fixed.right',
right: 'datatable.fixed.unFixed',
unFixed: 'datatable.fixed.left'
};
function handleFixed(column: NaiveUI.TableColumnCheck) {
const fixedOptions: NaiveUI.TableColumnFixed[] = ['left', 'right', 'unFixed'];
const index = fixedOptions.findIndex(item => item === column.fixed);
const nextIndex = index === fixedOptions.length - 1 ? 0 : index + 1;
column.fixed = fixedOptions[nextIndex];
}
const visibleStats = computed(() => {
let total = 0;
let checked = 0;
columns.value.forEach(column => {
if (!column.visible) return;
total += 1;
if (column.checked) checked += 1;
});
return { total, checked };
});
const selectAllChecked = computed(() => {
const { total, checked } = visibleStats.value;
return total > 0 && checked === total;
});
const selectAllIndeterminate = computed(() => {
const { total, checked } = visibleStats.value;
return checked > 0 && checked < total;
});
function toggleSelectAll(checked: boolean) {
columns.value.forEach(column => {
if (!column.visible) return;
column.checked = checked;
});
}
</script> </script>
<template> <template>
@@ -69,49 +21,22 @@ function toggleSelectAll(checked: boolean) {
{{ $t('common.columnSetting') }} {{ $t('common.columnSetting') }}
</NButton> </NButton>
</template> </template>
<div> <VueDraggable v-model="columns" :animation="150" filter=".none_draggable">
<div class="h-36px flex-y-center rd-4px pl-26px hover:(bg-primary bg-opacity-20)"> <div
<NCheckbox v-for="item in columns"
:checked="selectAllChecked" :key="item.key"
:indeterminate="selectAllIndeterminate" class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)"
:disabled="visibleStats.total === 0" :class="{ hidden: !item.visible }"
class="flex-1" >
@update:checked="toggleSelectAll" <icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
> <NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
{{ $t('common.selectAll') }} <template v-if="typeof item.title === 'function'">
<component :is="item.title" />
</template>
<template v-else>{{ item.title }}</template>
</NCheckbox> </NCheckbox>
</div> </div>
<NDivider class="!my-4px" /> </VueDraggable>
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable" class="max-h-[200px] overflow-y-auto">
<div
v-for="item in columns"
:key="item.key"
class="h-36px flex-y-center justify-between gap-6px"
:class="{ hidden: !item.visible }"
>
<div class="h-full flex-y-center flex-1 rd-4px hover:(bg-primary bg-opacity-20)">
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
<template v-if="typeof item.title === 'function'">
<component :is="item.title" />
</template>
<template v-else>{{ item.title }}</template>
</NCheckbox>
</div>
<ButtonIcon
:disabled="!item.checked"
text
:focusable="false"
:tooltip-content="$t(tooltipRecord[item.fixed!])"
@click="handleFixed(item)"
>
<icon-octicon-pin-16 v-if="item.fixed === 'unFixed'" />
<icon-octicon-pin-16 v-else-if="item.fixed === 'left'" class="rotate-270" />
<icon-octicon-pin-slash-16 v-else />
</ButtonIcon>
</div>
</VueDraggable>
</div>
</NPopover> </NPopover>
</template> </template>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useRouterPush } from '@/hooks/common/router'; import { useRouter } from 'vue-router';
import { $t } from '@/locales'; import { $t } from '@/locales';
defineOptions({ name: 'ExceptionBase' }); defineOptions({ name: 'ExceptionBase' });
@@ -20,7 +20,7 @@ interface Props {
const props = defineProps<Props>(); const props = defineProps<Props>();
const { routerPushByKey } = useRouterPush(); const router = useRouter();
const iconMap: Record<ExceptionType, string> = { const iconMap: Record<ExceptionType, string> = {
'403': 'no-permission', '403': 'no-permission',
@@ -36,7 +36,7 @@ const icon = computed(() => iconMap[props.type]);
<div class="flex text-400px text-primary"> <div class="flex text-400px text-primary">
<SvgIcon :local-icon="icon" /> <SvgIcon :local-icon="icon" />
</div> </div>
<NButton type="primary" @click="routerPushByKey('root')">{{ $t('common.backToHome') }}</NButton> <NButton type="primary" @click="router.push('/')">{{ $t('common.backToHome') }}</NButton>
</div> </div>
</template> </template>

View File

@@ -1,160 +1,9 @@
<script lang="ts" setup>
defineOptions({ name: 'SystemLogo' });
</script>
<template> <template>
<div class="app-logo"> <icon-local-logo />
<svg
width="100%"
height="100%"
version="1.1"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g>
<path
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient_2)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
fill="url(#LinearGradient_3)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
fill="url(#LinearGradient_4)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
fill="url(#LinearGradient_5)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
fill="url(#LinearGradient_6)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
fill="url(#LinearGradient_7)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
</g>
<defs>
<linearGradient
id="LinearGradient"
gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_2"
gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_3"
gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-600)" />
<stop offset="1" stop-color="var(--logo-color-700)" />
</linearGradient>
<linearGradient
id="LinearGradient_4"
gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-400)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_5"
gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-500)" />
<stop offset="1" stop-color="var(--logo-color-300)" />
</linearGradient>
<linearGradient
id="LinearGradient_6"
gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_7"
gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
</defs>
</svg>
</div>
</template> </template>
<style scoped> <style scoped></style>
.app-logo {
--logo-color-300: rgb(var(--primary-300-color));
--logo-color-400: rgb(var(--primary-400-color));
--logo-color-500: rgb(var(--primary-500-color));
--logo-color-600: rgb(var(--primary-600-color));
--logo-color-700: rgb(var(--primary-700-color));
}
</style>

View File

@@ -266,7 +266,6 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: column.key as string, key: column.key as string,
title: column.title!, title: column.title!,
checked: true, checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? true visible: getColumnVisible?.(column) ?? true
}); });
} else if (column.type === 'selection') { } else if (column.type === 'selection') {
@@ -274,7 +273,6 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: SELECTION_KEY, key: SELECTION_KEY,
title: $t('common.check'), title: $t('common.check'),
checked: true, checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? false visible: getColumnVisible?.(column) ?? false
}); });
} else if (column.type === 'expand') { } else if (column.type === 'expand') {
@@ -282,7 +280,6 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: EXPAND_KEY, key: EXPAND_KEY,
title: $t('common.expandColumn'), title: $t('common.expandColumn'),
checked: true, checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? false visible: getColumnVisible?.(column) ?? false
}); });
} }
@@ -304,14 +301,7 @@ function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], che
} }
}); });
const filteredColumns = checks const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column);
.filter(item => item.checked)
.map(check => {
return {
...columnMap.get(check.key),
fixed: check.fixed
} as Column;
});
return filteredColumns; return filteredColumns;
} }

View File

@@ -17,7 +17,7 @@ withDefaults(defineProps<Props>(), {
<template> <template>
<RouterLink to="/" class="w-full flex-center nowrap-hidden"> <RouterLink to="/" class="w-full flex-center nowrap-hidden">
<SystemLogo class="size-32px" /> <SystemLogo class="text-32px text-primary" />
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold transition duration-300 ease-in-out"> <h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold transition duration-300 ease-in-out">
{{ $t('system.title') }} {{ $t('system.title') }}
</h2> </h2>

View File

@@ -24,7 +24,3 @@ export const $t = i18n.global.t as App.I18n.$T;
export function setLocale(locale: App.I18n.LangType) { export function setLocale(locale: App.I18n.LangType) {
i18n.global.locale.value = locale; i18n.global.locale.value = locale;
} }
export function getLocale(): App.I18n.LangType {
return i18n.global.locale.value as App.I18n.LangType;
}

View File

@@ -15,7 +15,6 @@ const local: App.I18n.Schema = {
cancel: 'Cancel', cancel: 'Cancel',
close: 'Close', close: 'Close',
check: 'Check', check: 'Check',
selectAll: 'Select All',
expandColumn: 'Expand Column', expandColumn: 'Expand Column',
columnSetting: 'Column Setting', columnSetting: 'Column Setting',
config: 'Config', config: 'Config',
@@ -357,12 +356,7 @@ const local: App.I18n.Schema = {
unpin: 'Unpin' unpin: 'Unpin'
}, },
datatable: { datatable: {
itemCount: 'Total {total} items', itemCount: 'Total {total} items'
fixed: {
left: 'Left Fixed',
right: 'Right Fixed',
unFixed: 'Unfixed'
}
} }
}; };

View File

@@ -15,7 +15,6 @@ const local: App.I18n.Schema = {
cancel: '取消', cancel: '取消',
close: '关闭', close: '关闭',
check: '勾选', check: '勾选',
selectAll: '全选',
expandColumn: '展开列', expandColumn: '展开列',
columnSetting: '列设置', columnSetting: '列设置',
config: '配置', config: '配置',
@@ -353,12 +352,7 @@ const local: App.I18n.Schema = {
unpin: '取消固定' unpin: '取消固定'
}, },
datatable: { datatable: {
itemCount: '共 {total} 条', itemCount: '共 {total} 条'
fixed: {
left: '左固定',
right: '右固定',
unFixed: '取消固定'
}
} }
}; };

View File

@@ -1,10 +1,9 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import './plugins/assets'; import './plugins/assets';
import { setupVueRootValidator } from 'vite-plugin-vue-transition-root-validator/client';
import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins'; import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
import { setupStore } from './store'; import { setupStore } from './store';
import { setupRouter } from './router'; import { setupRouter } from './router';
import { getLocale, setupI18n } from './locales'; import { setupI18n } from './locales';
import App from './App.vue'; import App from './App.vue';
async function setupApp() { async function setupApp() {
@@ -26,10 +25,6 @@ async function setupApp() {
setupAppVersionNotification(); setupAppVersionNotification();
setupVueRootValidator(app, {
lang: getLocale() === 'zh-CN' ? 'zh' : 'en'
});
app.mount('#app'); app.mount('#app');
} }

View File

@@ -1,25 +1,18 @@
// @unocss-include // @unocss-include
import { getColorPalette, getRgb } from '@sa/color'; import { getRgb } from '@sa/color';
import { DARK_CLASS } from '@/constants/app'; import { DARK_CLASS } from '@/constants/app';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import { toggleHtmlClass } from '@/utils/common'; import { toggleHtmlClass } from '@/utils/common';
import systemLogo from '@/assets/svg-icon/logo.svg?raw';
import { $t } from '@/locales'; import { $t } from '@/locales';
export function setupLoading() { export function setupLoading() {
const themeColor = localStg.get('themeColor') || '#646cff'; const themeColor = localStg.get('themeColor') || '#646cff';
const darkMode = localStg.get('darkMode') || false; const darkMode = localStg.get('darkMode') || false;
const palette = getColorPalette(themeColor);
const { r, g, b } = getRgb(themeColor); const { r, g, b } = getRgb(themeColor);
const primaryColor = `--primary-color: ${r} ${g} ${b}`; const primaryColor = `--primary-color: ${r} ${g} ${b}`;
const svgCssVars = Array.from(palette.entries())
.map(([key, value]) => `--logo-color-${key}: ${value}`)
.join(';');
const cssVars = `${primaryColor}; ${svgCssVars}`;
if (darkMode) { if (darkMode) {
toggleHtmlClass(DARK_CLASS).add(); toggleHtmlClass(DARK_CLASS).add();
} }
@@ -31,6 +24,8 @@ export function setupLoading() {
'right-0 bottom-0 animate-delay-1500' 'right-0 bottom-0 animate-delay-1500'
]; ];
const logoWithClass = systemLogo.replace('<svg', `<svg class="size-128px text-primary"`);
const dot = loadingClasses const dot = loadingClasses
.map(item => { .map(item => {
return `<div class="absolute w-16px h-16px bg-primary rounded-8px animate-pulse ${item}"></div>`; return `<div class="absolute w-16px h-16px bg-primary rounded-8px animate-pulse ${item}"></div>`;
@@ -38,10 +33,8 @@ export function setupLoading() {
.join('\n'); .join('\n');
const loading = ` const loading = `
<div class="fixed-center flex-col bg-layout" style="${cssVars}"> <div class="fixed-center flex-col bg-layout" style="${primaryColor}">
<div class="w-128px h-128px"> ${logoWithClass}
${getLogoSvg()}
</div>
<div class="w-56px h-56px my-36px"> <div class="w-56px h-56px my-36px">
<div class="relative h-full animate-spin"> <div class="relative h-full animate-spin">
${dot} ${dot}
@@ -56,155 +49,3 @@ export function setupLoading() {
app.innerHTML = loading; app.innerHTML = loading;
} }
} }
function getLogoSvg() {
const logoSvg = `<svg
width="100%"
height="100%"
version="1.1"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g>
<path
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient_2)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
fill="url(#LinearGradient_3)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
fill="url(#LinearGradient_4)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
fill="url(#LinearGradient_5)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
fill="url(#LinearGradient_6)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
fill="url(#LinearGradient_7)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
</g>
<defs>
<linearGradient
id="LinearGradient"
gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_2"
gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_3"
gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-600)" />
<stop offset="1" stop-color="var(--logo-color-700)" />
</linearGradient>
<linearGradient
id="LinearGradient_4"
gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-400)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_5"
gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-500)" />
<stop offset="1" stop-color="var(--logo-color-300)" />
</linearGradient>
<linearGradient
id="LinearGradient_6"
gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_7"
gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
</defs>
</svg>
`;
return logoSvg;
}

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,113 @@
/* 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: '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,11 +1,6 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
type RouterHistory, import type { RouterHistory } from 'vue-router';
createMemoryHistory,
createRouter,
createWebHashHistory,
createWebHistory
} from 'vue-router';
import { createBuiltinVueRoutes } from './routes/builtin'; import { createBuiltinVueRoutes } from './routes/builtin';
import { createRouterGuard } from './guard'; import { createRouterGuard } from './guard';
@@ -25,6 +20,6 @@ export const router = createRouter({
/** Setup Vue Router */ /** Setup Vue Router */
export async function setupRouter(app: App) { export async function setupRouter(app: App) {
app.use(router); app.use(router);
createRouterGuard(router); // createRouterGuard(router);
await router.isReady(); await router.isReady();
} }

View File

@@ -1,31 +1,39 @@
import type { CustomRoute } from '@elegant-router/types'; import type { AutoRouterRedirect, AutoRouterRoute, AutoRouterSingleView } from '@elegant-router/types';
import { layouts, views } from '../elegant/imports'; import { layouts, views } from '../_generated/imports';
import { getRoutePath, transformElegantRoutesToVueRoutes } from '../elegant/transform'; import { transformToVueRoutes } from '../_generated/transformer';
import { routes } from '../_generated/routes';
export const ROOT_ROUTE: CustomRoute = { /**
name: 'root', * 根路由
*/
export const ROOT_ROUTE: AutoRouterRedirect = {
name: 'Root',
path: '/', path: '/',
redirect: getRoutePath(import.meta.env.VITE_ROUTE_HOME) || '/home', redirect: import.meta.env.VITE_ROUTE_HOME_PATH || '/home',
meta: { meta: {
title: 'root', title: 'Root',
constant: true constant: true
} }
}; };
const NOT_FOUND_ROUTE: CustomRoute = { /**
name: 'not-found', * 404 路由
*/
export const NOT_FOUND_ROUTE: AutoRouterSingleView = {
name: 'NotFound',
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
component: 'layout.blank$view.404', component: '404',
layout: 'blank',
meta: { meta: {
title: 'not-found', title: 'NotFound',
constant: true constant: true
} }
}; };
/** builtin routes, it must be constant and setup in vue-router */ /** 内置路由,必须是常量路由,且必须在 vue-router 中设置 */
const builtinRoutes: CustomRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE]; const builtinRoutes: AutoRouterRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE, ...routes];
/** create builtin vue routes */ /** 创建内置 vue 路由 */
export function createBuiltinVueRoutes() { export function createBuiltinVueRoutes() {
return transformElegantRoutesToVueRoutes(builtinRoutes, layouts, views); return transformToVueRoutes(builtinRoutes, layouts, views);
} }

View File

@@ -1,22 +1,14 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types'; import type { AutoRouterRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes'; import { routes } from '../_generated/routes';
import { layouts, views } from '../elegant/imports'; import { layouts, views } from '../_generated/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform'; import { transformToVueRoutes } from '../_generated/transformer';
/** /** 创建静态路由 */
* custom routes
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [];
/** create routes when the auth route mode is static */
export function createStaticRoutes() { export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = []; const constantRoutes: AutoRouterRoute[] = [];
const authRoutes: AutoRouterRoute[] = [];
const authRoutes: ElegantRoute[] = []; routes.forEach(item => {
[...customRoutes, ...generatedRoutes].forEach(item => {
if (item.meta?.constant) { if (item.meta?.constant) {
constantRoutes.push(item); constantRoutes.push(item);
} else { } else {
@@ -31,10 +23,10 @@ export function createStaticRoutes() {
} }
/** /**
* Get auth vue routes * 获取认证路由
* *
* @param routes Elegant routes * @param authRoutes 认证路由
*/ */
export function getAuthVueRoutes(routes: ElegantConstRoute[]) { export function getAuthVueRoutes(authRoutes: AutoRouterRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views); return transformToVueRoutes(authRoutes, layouts, views);
} }

View File

@@ -5,46 +5,38 @@
*/ */
declare namespace Api { declare namespace Api {
namespace Common { namespace Common {
/** common params of paginating */ interface CreatedUpdatedDTO {
interface PaginatingCommonParams { /** 创建人 */
/** current page number */ createdBy: string;
current: number; /** 创建时间 */
/** page size */ createdTime: string;
size: number; /** 更新人 */
/** total count */ 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; 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 { declare namespace Api {
/**
* namespace Route
*
* backend api module: "route"
*/
namespace Route { namespace Route {
type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute; interface RouteDTO extends Common.CommonRecordDTO {
/** 路由路径 */
interface MenuRoute extends ElegantConstRoute { path: string;
id: 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 { interface RouteListDTO extends Common.PaginationDTO {
routes: MenuRoute[]; list: RouteDTO[];
home: import('@elegant-router/types').LastLevelRouteKey;
} }
} }
} }

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';
}
}

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

@@ -188,9 +188,9 @@ declare namespace App {
type VNode = import('vue').VNode; type VNode = import('vue').VNode;
type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded; type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded;
type RouteKey = import('@elegant-router/types').RouteKey; 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 RoutePath = import('@elegant-router/types').RoutePath;
type LastLevelRouteKey = import('@elegant-router/types').LastLevelRouteKey; type RouteFileKey = import('@elegant-router/types').RouteFileKey;
/** The router push options */ /** The router push options */
type RouterPushOptions = { type RouterPushOptions = {
@@ -208,77 +208,25 @@ declare namespace App {
showMenu?: boolean; showMenu?: boolean;
} }
type MenuType = 'page' | 'directory' | 'menu' | 'iframe' | 'link' | 'button' | 'other';
/** The global menu */ /** The global menu */
type Menu = { interface Menu {
/** id: number;
* The menu key name: string;
* menuType: MenuType;
* Equal to the route key description?: string | null;
*/ icon?: string | null;
key: string; iconType?: string | null;
/** The menu label */ parentId?: number | null;
label: string; order?: number | null;
/** The menu i18n key */ isHidden?: boolean | null;
i18nKey?: I18n.I18nKey | null; iframeUrl?: string | null;
/** The route key */ href?: string | null;
routeKey: RouteKey; routeName?: RouteKey | null;
/** The route path */ routeQueries?: Record<string, string | number | boolean> | null;
routePath: RoutePath; routeParams?: Record<string, string | number | boolean> | null;
/** 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;
};
/** Form rule */ /** Form rule */
type FormRule = import('naive-ui').FormItemRule; type FormRule = import('naive-ui').FormItemRule;
@@ -326,7 +274,6 @@ declare namespace App {
cancel: string; cancel: string;
close: string; close: string;
check: string; check: string;
selectAll: string;
expandColumn: string; expandColumn: string;
columnSetting: string; columnSetting: string;
config: string; config: string;
@@ -579,11 +526,6 @@ declare namespace App {
}; };
datatable: { datatable: {
itemCount: string; itemCount: string;
fixed: {
left: string;
right: string;
unFixed: string;
};
}; };
}; };

View File

@@ -1,240 +1,100 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router // Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router // Read more: https://github.com/soybeanjs/elegant-router
declare module "@elegant-router/types" { 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 = { export type RoutePathMap = {
"root": "/"; "Root": "/";
"not-found": "/:pathMatch(.*)*"; "NotFound": "/:pathMatch(.*)*";
"403": "/403"; "403": "/403";
"404": "/404"; "404": "/404";
"500": "/500"; "500": "/500";
"home": "/home"; "Home": "/home";
"iframe-page": "/iframe-page/:url"; "IframeUrl": "/iframe/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; "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 * route key
*/ */
export type RouteKey = keyof RouteMap; export type RouteKey = keyof RoutePathMap;
/** /**
* route path * route path
*/ */
export type RoutePath = RouteMap[RouteKey]; export type RoutePath = RoutePathMap[RouteKey];
/** /**
* custom route key * root route key
*/ */
export type CustomRouteKey = Extract< export type RootRouteKey = 'Root';
RouteKey,
| "root"
| "not-found"
>;
/** /**
* 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< export type BuiltinRouteKey = RootRouteKey | NotFoundRouteKey;
RouteKey,
| "403"
| "404"
| "500"
| "home"
| "iframe-page"
| "login"
>;
/** /**
* the custom first level route key * reuse route key
*/ */
export type CustomFirstLevelRouteKey = Extract< export type ReuseRouteKey = never;
CustomRouteKey,
| "root"
| "not-found"
>;
/** /**
* the last level route key, which has the page file * the route file key, which has it's own file
*/ */
export type LastLevelRouteKey = Extract< export type RouteFileKey = Exclude<RouteKey, BuiltinRouteKey | ReuseRouteKey>;
RouteKey,
| "403"
| "404"
| "500"
| "iframe-page"
| "login"
| "home"
>;
/** /**
* the custom last level route key * mapped name and path
*/ */
export type CustomLastLevelRouteKey = Extract< type MappedNamePath = {
CustomRouteKey, [K in RouteKey]: { name: K; path: RoutePathMap[K] };
| "root" }[RouteKey];
| "not-found"
>;
/** /**
* 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 * auto router route
*/
export type FirstLevelRouteNotSingleKey = Exclude<FirstLevelRouteKey, SingleLevelRouteKey>;
/**
* the custom first level route key, but not the single level
*/ */
export type CustomFirstLevelRouteNotSingleKey = Exclude<CustomFirstLevelRouteKey, CustomSingleLevelRouteKey>; export type AutoRouterRoute = AutoRouterSingleView | AutoRouterRedirect;
/**
* 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;
} }

View File

@@ -7,7 +7,6 @@ declare namespace NaiveUI {
type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>; type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>; type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
type TableColumnCheck = import('@sa/hooks').TableColumnCheck; type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
type TableColumnFixed = import('@sa/hooks').TableColumnCheck['fixed'];
type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) }; type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) };

View File

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

@@ -56,7 +56,7 @@ const bgColor = computed(() => {
<NCard :bordered="false" class="relative z-4 w-auto rd-12px"> <NCard :bordered="false" class="relative z-4 w-auto rd-12px">
<div class="w-400px lt-sm:w-300px"> <div class="w-400px lt-sm:w-300px">
<header class="flex-y-center justify-between"> <header class="flex-y-center justify-between">
<SystemLogo class="size-64px lt-sm:size-48px" /> <SystemLogo class="text-64px text-primary lt-sm:text-48px" />
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3> <h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
<div class="i-flex-col"> <div class="i-flex-col">
<ThemeSchemaSwitch <ThemeSchemaSwitch

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>