befly-admin 3.3.0 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/plugins/router.ts +1 -1
- package/vite.config.ts +2 -2
- package/libs/auto-routes-template.js +0 -104
- package/libs/autoRouter.ts +0 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-admin",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"description": "Befly Admin - 基于 Vue3 + OpenTiny Vue 的后台管理系统",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@opentiny/vue": "3.26.0",
|
|
19
19
|
"axios": "1.12.2",
|
|
20
|
+
"befly-auto-routes": "^1.0.2",
|
|
20
21
|
"lucide-vue-next": "0.546.0",
|
|
21
22
|
"pinia": "3.0.3",
|
|
22
23
|
"vue": "3.5.22",
|
|
@@ -38,5 +39,5 @@
|
|
|
38
39
|
"node": ">=22.0.0",
|
|
39
40
|
"pnpm": ">=9.0.0"
|
|
40
41
|
},
|
|
41
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "851fbebc2d12dc550ebe06d6fa28b8aa769ed3ce"
|
|
42
43
|
}
|
package/src/plugins/router.ts
CHANGED
package/vite.config.ts
CHANGED
|
@@ -5,7 +5,7 @@ import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
|
|
|
5
5
|
import AutoImport from 'unplugin-auto-import/vite';
|
|
6
6
|
import Components from 'unplugin-vue-components/vite';
|
|
7
7
|
import { TinyVueSingleResolver } from '@opentiny/unplugin-tiny-vue';
|
|
8
|
-
import
|
|
8
|
+
import autoRoutes from 'befly-auto-routes';
|
|
9
9
|
|
|
10
10
|
// https://vite.dev/config/
|
|
11
11
|
export default defineConfig({
|
|
@@ -14,7 +14,7 @@ export default defineConfig({
|
|
|
14
14
|
// Vue 响应式语法糖
|
|
15
15
|
ReactivityTransform(),
|
|
16
16
|
// 自动路由插件
|
|
17
|
-
|
|
17
|
+
autoRoutes({ debug: true }),
|
|
18
18
|
// 自动导入 Vue3 API 和组合式函数
|
|
19
19
|
AutoImport({
|
|
20
20
|
imports: [
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// 模板文件:自动路由生成结果基模板
|
|
2
|
-
// 说明:
|
|
3
|
-
// 目录固定:src/views ;布局目录固定:src/layouts
|
|
4
|
-
// 仅识别 views/<name>/<name>.vue 结构;index 为根路径
|
|
5
|
-
// 固定排除子目录:components
|
|
6
|
-
// 不区分公开/私有路由,全部挂到对应布局(文件名后缀 *_n.vue 指定布局 n,默认 0)
|
|
7
|
-
|
|
8
|
-
// 在虚拟模块环境中,统一使用绝对形式 '/src/...'
|
|
9
|
-
// 支持多级目录:新规则
|
|
10
|
-
// 1. 任意目录下的 index.vue 代表该目录的“默认路由”,不再附加文件段:/news/index.vue -> /news
|
|
11
|
-
// 2. 非 index.vue 页面,路径 = 所有目录段 + 文件名:/news/detail/detail.vue -> /news/detail/detail
|
|
12
|
-
// 即使文件名与末级目录同名亦需保留(区别于旧规则会省略重复)
|
|
13
|
-
// 3. 根目录 index.vue -> '/'
|
|
14
|
-
// 4. 布局后缀规则:只允许出现在“页面文件名”末尾:<name>_n.vue;目录名不再解析布局编号
|
|
15
|
-
// 5. 任何层级的 components 子目录下文件忽略
|
|
16
|
-
// 6. 大小写驼峰与下划线会被 kebab 化
|
|
17
|
-
// 示例:
|
|
18
|
-
// /src/views/news/index.vue => /news
|
|
19
|
-
// /src/views/news/detail/detail.vue => /news/detail/detail
|
|
20
|
-
// /src/views/user/profile/index.vue => /user/profile
|
|
21
|
-
// /src/views/user/profile/edit.vue => /user/profile/edit
|
|
22
|
-
// /src/views/index.vue => /
|
|
23
|
-
// /src/views/dashboard_2/index.vue => /dashboard (布局2)
|
|
24
|
-
// /src/views/dashboard_2/analysis.vue => /dashboard/analysis (布局2)
|
|
25
|
-
const viewFiles = import.meta.glob('/src/views/**/*.vue');
|
|
26
|
-
const layoutFiles = import.meta.glob('/src/layouts/*.vue');
|
|
27
|
-
|
|
28
|
-
const layoutRoutes = {};
|
|
29
|
-
|
|
30
|
-
function normalizePath(p) {
|
|
31
|
-
return p.replace(/\\/g, '/');
|
|
32
|
-
}
|
|
33
|
-
function extractLayoutFromFile(baseName) {
|
|
34
|
-
const m = baseName.match(/_(\d+)$/);
|
|
35
|
-
if (m) return { name: baseName.replace(/_(\d+)$/, ''), layout: m[1] };
|
|
36
|
-
return { name: baseName, layout: '0' };
|
|
37
|
-
}
|
|
38
|
-
function toKebab(s) {
|
|
39
|
-
return s
|
|
40
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase -> camel-Case
|
|
41
|
-
.replace(/[\s_]+/g, '-') // 空白或下划线 -> -
|
|
42
|
-
.toLowerCase()
|
|
43
|
-
.replace(/-+/g, '-') // 合并多余 -
|
|
44
|
-
.replace(/^|-$/g, ''); // 去首尾 -
|
|
45
|
-
}
|
|
46
|
-
function buildPath(name) {
|
|
47
|
-
return name === 'index' ? '/' : '/' + toKebab(name);
|
|
48
|
-
}
|
|
49
|
-
function buildName(name) {
|
|
50
|
-
return toKebab(name);
|
|
51
|
-
}
|
|
52
|
-
// 不再区分公开路由,全部放入对应布局
|
|
53
|
-
|
|
54
|
-
for (const fp in viewFiles) {
|
|
55
|
-
const normPath = normalizePath(fp);
|
|
56
|
-
if (!normPath.endsWith('.vue')) continue;
|
|
57
|
-
if (normPath.includes('/components/')) continue;
|
|
58
|
-
const rootMarker = '/src/views/';
|
|
59
|
-
const idx = normPath.indexOf(rootMarker);
|
|
60
|
-
if (idx === -1) continue;
|
|
61
|
-
const rel = normPath.slice(idx + rootMarker.length); // 相对 views 的路径
|
|
62
|
-
const parts = rel.split('/');
|
|
63
|
-
const fileName = parts.pop(); // <name>.vue 或 index.vue
|
|
64
|
-
if (!fileName) continue;
|
|
65
|
-
const rawBase = fileName.replace(/\.vue$/, '');
|
|
66
|
-
const { name: logicalBase, layout } = extractLayoutFromFile(rawBase);
|
|
67
|
-
const dirChainOriginal = parts; // 目录数组(可能为空)
|
|
68
|
-
if (dirChainOriginal.some((d) => d === 'components')) continue;
|
|
69
|
-
// 目录链直接使用原目录(不再剥离布局后缀)
|
|
70
|
-
const dirChainForPath = dirChainOriginal;
|
|
71
|
-
// index / index_n 作为目录默认:不追加文件名;非 index 保留
|
|
72
|
-
const pathSegments = [...dirChainForPath];
|
|
73
|
-
if (logicalBase !== 'index') pathSegments.push(logicalBase);
|
|
74
|
-
// root index 情况:views/index.vue => []
|
|
75
|
-
if (pathSegments.length === 1 && pathSegments[0] === 'index') pathSegments.pop();
|
|
76
|
-
// kebab 化
|
|
77
|
-
const kebabSegments = pathSegments.map(toKebab).filter(Boolean);
|
|
78
|
-
const routePath = '/' + kebabSegments.join('/');
|
|
79
|
-
const routeName = kebabSegments.length ? kebabSegments.join('-') : 'index';
|
|
80
|
-
const route = { path: routePath === '/' ? '/' : routePath, name: routeName, component: viewFiles[fp], meta: { title: routeName, file: fp } };
|
|
81
|
-
if (!layoutRoutes[layout]) layoutRoutes[layout] = [];
|
|
82
|
-
if (!layoutRoutes[layout].some((r) => r.path === route.path)) layoutRoutes[layout].push(route);
|
|
83
|
-
}
|
|
84
|
-
const finalRoutes = [];
|
|
85
|
-
for (const k in layoutRoutes) {
|
|
86
|
-
const lp = '/src/layouts/' + k + '.vue';
|
|
87
|
-
const comp = layoutFiles[lp];
|
|
88
|
-
if (comp) {
|
|
89
|
-
finalRoutes.push({ path: '/', name: 'layout' + k, component: comp, children: layoutRoutes[k] });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// 开发环境下在浏览器控制台打印一次路由结果
|
|
93
|
-
if (typeof window !== 'undefined' && import.meta && import.meta.env && import.meta.env.DEV) {
|
|
94
|
-
// 使用 setTimeout 避免与其他插件初始化竞争
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
// eslint-disable-next-line no-console
|
|
97
|
-
console.log(
|
|
98
|
-
'[auto-routes] 当前生成路由:',
|
|
99
|
-
finalRoutes.map((r) => ({ layout: r.name, children: r.children?.map((c) => ({ path: c.path, name: c.name, file: c.meta?.file })) }))
|
|
100
|
-
);
|
|
101
|
-
}, 0);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export default finalRoutes;
|
package/libs/autoRouter.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { Plugin, HmrContext } from 'vite';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { dirname, resolve } from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 自动路由生成插件
|
|
8
|
-
* 逻辑放在外部模板文件 `auto-routes-template.js` 中,通过占位符替换实现:
|
|
9
|
-
*/
|
|
10
|
-
// 固定:视图目录 src/views;布局目录 src/layouts;排除目录 ['components'];不区分公开路由;模板已为最终可执行代码,不再做占位符替换。
|
|
11
|
-
export default function autoRouter(): Plugin {
|
|
12
|
-
const virtualModuleId = 'virtual:auto-routes';
|
|
13
|
-
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
|
14
|
-
// 目录固定,无需配置
|
|
15
|
-
|
|
16
|
-
// 模板路径:基于当前文件所在目录,避免 process.cwd() 导致嵌套 workspace 时的重复路径问题
|
|
17
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const templatePath = resolve(thisDir, 'auto-routes-template.js');
|
|
19
|
-
|
|
20
|
-
// 缓存与状态
|
|
21
|
-
let cached = '';
|
|
22
|
-
let loadError = false;
|
|
23
|
-
|
|
24
|
-
function readTemplate() {
|
|
25
|
-
try {
|
|
26
|
-
const content = readFileSync(templatePath, 'utf-8');
|
|
27
|
-
cached = content;
|
|
28
|
-
loadError = false;
|
|
29
|
-
} catch (e) {
|
|
30
|
-
loadError = true;
|
|
31
|
-
cached = `console.error('[auto-routes] 模板读取失败: ${templatePath}');export default [];`;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 初次读取
|
|
36
|
-
readTemplate();
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
name: 'auto-router',
|
|
40
|
-
enforce: 'pre',
|
|
41
|
-
resolveId(id) {
|
|
42
|
-
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
|
43
|
-
},
|
|
44
|
-
buildStart() {
|
|
45
|
-
// 监听模板文件
|
|
46
|
-
try {
|
|
47
|
-
this.addWatchFile(templatePath);
|
|
48
|
-
} catch {}
|
|
49
|
-
},
|
|
50
|
-
handleHotUpdate(ctx: HmrContext) {
|
|
51
|
-
if (ctx.file === templatePath) {
|
|
52
|
-
const before = cached;
|
|
53
|
-
readTemplate();
|
|
54
|
-
if (cached !== before) {
|
|
55
|
-
this.invalidate(resolvedVirtualModuleId);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
load(id) {
|
|
60
|
-
if (id !== resolvedVirtualModuleId) return;
|
|
61
|
-
if (loadError || !cached) {
|
|
62
|
-
readTemplate();
|
|
63
|
-
}
|
|
64
|
-
return cached;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|