chore: 递增版本号并标记停更版本

- 版本号: 中段递增、末段归零

- README: laravel 与 spring-boot-3 更新状态改为 ❌️
This commit is contained in:
Ray.Hao
2026-09-25 19:56:52 +08:00
parent dc1436e3e5
commit 3ccbdb7c06
13 changed files with 1235 additions and 687 deletions

View File

@@ -6,9 +6,9 @@ import { useTenantStoreHook } from "@/stores/tenant";
import { isTenantEnabled } from "@/utils/tenant";
/**
* 路由权限守卫
* 路由守卫
*
* 处理登录验证、动态路由生成、404检测等
* 处理登录验证、动态路由生成、404 检测、页面标题与进度条
*/
export function setupPermissionGuard() {
// 白名单支持前缀匹配:/f 命中所有公开表单分享页 /f/:formKey
@@ -84,7 +84,9 @@ export function setupPermissionGuard() {
});
}
/** 初始化多租户上下文,未启用或失败时静默跳过 */
/**
* 初始化多租户上下文,未启用或失败时静默跳过
*/
async function initTenantContext(): Promise<void> {
if (!isTenantEnabled()) return;

133
src/router/views.ts Normal file
View File

@@ -0,0 +1,133 @@
/**
* 视图组件注册表
*
* 维护「菜单 component 字符串 → src/views 视图文件」的映射,路由注册与菜单表单共用。
* 表单的「页面路径」选项树也由此派生:跳过错误页与页面内部组件,index.vue 归并到目录。
*/
/** 全部视图模块,键形如 /src/views/system/user/index.vue */
export const viewModules = import.meta.glob("/src/views/**/*.vue");
const VIEW_PREFIX = "/src/views/";
/** 视图文件路径集合,元素形如 system/user/index */
export const viewPaths = new Set(
Object.keys(viewModules).map((key) => key.slice(VIEW_PREFIX.length).replace(/\.vue$/, ""))
);
/**
* 规整组件路径:去首尾空白、开头斜杠与 .vue 后缀
*/
export function normalizeViewPath(componentPath: string): string {
return componentPath
.trim()
.replace(/^\/+/, "")
.replace(/\.vue$/i, "");
}
/**
* 解析组件:支持 system/user 与 system/user/index 两种写法,未命中回退 404
*/
export function resolveComponent(componentPath: string) {
const path = normalizeViewPath(componentPath);
return (
viewModules[`${VIEW_PREFIX}${path}.vue`] ||
viewModules[`${VIEW_PREFIX}${path}/index.vue`] ||
viewModules[`${VIEW_PREFIX}error/404.vue`]
);
}
/**
* 判断组件路径是否存在对应视图文件,目录写法命中其 index.vue
*/
export function isViewComponent(componentPath?: string): boolean {
if (!componentPath) return false;
const path = normalizeViewPath(componentPath);
return viewPaths.has(path) || viewPaths.has(`${path}/index`);
}
/** 选项树节点 */
export interface PageComponentNode {
/** 表单取值:真实文件路径,目录无 index.vue 时不可选 */
value?: string;
label: string;
children?: PageComponentNode[];
disabled?: boolean;
}
function isSelectable(path: string): boolean {
const segments = path.split("/");
if (segments.includes("components")) return false;
return segments[0] !== "error" && path !== "redirect";
}
function buildTree(): PageComponentNode[] {
const tree: PageComponentNode[] = [];
[...viewPaths].filter(isSelectable).forEach((path) => insertPath(tree, path));
sortTree(tree);
markDisabled(tree);
return tree;
}
/**
* 将视图路径插入选项树,index.vue 归并到所在目录
*/
function insertPath(tree: PageComponentNode[], path: string): void {
const segments = path.split("/");
if (segments[segments.length - 1] === "index") segments.pop();
let level = tree;
segments.forEach((segment, index) => {
const isLeaf = index === segments.length - 1;
let node = level.find((item) => item.label === segment);
if (!node) {
node = { label: segment };
level.push(node);
}
if (isLeaf) {
// 取值始终是真实文件路径,与历史数据和列表展示一致
node.value = path;
return;
}
node.children ??= [];
level = node.children;
});
}
/**
* 目录下没有 index.vue 时不可选,只用于展开
*/
function markDisabled(nodes: PageComponentNode[]): void {
nodes.forEach((node) => {
if (!node.value) node.disabled = true;
if (node.children) markDisabled(node.children);
});
}
function sortTree(nodes: PageComponentNode[]): void {
nodes.sort((a, b) => a.label.localeCompare(b.label));
nodes.forEach((node) => node.children && sortTree(node.children));
}
/** 页面组件选项树 */
export const pageComponentTree = buildTree();
/**
* 节点筛选:名称与文件路径都参与匹配,输入 index 可筛出所有主页
*/
export function filterPageComponent(
query: string,
node: { label?: string; value?: string }
): boolean {
if (!query) return true;
const keyword = query.toLowerCase();
return (
(node.label ?? "").toLowerCase().includes(keyword) ||
(node.value ?? "").toLowerCase().includes(keyword)
);
}