feat(product): 新增产品管理模块与字典组件功能

- 新增产品管理相关路由和页面(dashboard、list、requirement、setting)
- 实现产品基础信息编辑弹窗组件(base-info-dialog.vue)
- 添加运行时字典功能(dict-select、dict-text、dict-tag组件)
- 集成字典管理store和API调用
- 规范ID类型定义为string避免精度丢失问题
- 完善国际化资源文件支持中英文对照
- 新增对象上下文业务域入口页导航实现说明
- 添加Vue DevTools浮动入口注释说明
- 统一权限控制支持全局和对象作用域区分
- 规范分页查询参数类型定义与使用方式
This commit is contained in:
2026-04-23 09:05:55 +08:00
parent c5911ea34b
commit 4122dfa50d
95 changed files with 9581 additions and 801 deletions

View File

@@ -47,6 +47,10 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
plugin_tables_vtable: () => import("@/views/plugin/tables/vtable/index.vue"),
plugin_typeit: () => import("@/views/plugin/typeit/index.vue"),
plugin_video: () => import("@/views/plugin/video/index.vue"),
product_dashboard: () => import("@/views/product/dashboard/index.vue"),
product_list: () => import("@/views/product/list/index.vue"),
product_requirement: () => import("@/views/product/requirement/index.vue"),
product_setting: () => import("@/views/product/setting/index.vue"),
system_dict: () => import("@/views/system/dict/index.vue"),
system_menu: () => import("@/views/system/menu/index.vue"),
system_post: () => import("@/views/system/post/index.vue"),

View File

@@ -430,6 +430,64 @@ export const generatedRoutes: GeneratedRoute[] = [
}
]
},
{
name: 'product',
path: '/product',
component: 'layout.base',
meta: {
title: 'product',
i18nKey: 'route.product',
icon: 'carbon:product',
order: 4
},
children: [
{
name: 'product_dashboard',
path: '/product/dashboard',
component: 'view.product_dashboard',
meta: {
title: 'product_dashboard',
i18nKey: 'route.product_dashboard',
hideInMenu: true,
activeMenu: 'product_list'
}
},
{
name: 'product_list',
path: '/product/list',
component: 'view.product_list',
meta: {
title: 'product_list',
i18nKey: 'route.product_list',
icon: 'material-symbols:view-list-outline-rounded',
order: 1,
keepAlive: true
}
},
{
name: 'product_requirement',
path: '/product/requirement',
component: 'view.product_requirement',
meta: {
title: 'product_requirement',
i18nKey: 'route.product_requirement',
hideInMenu: true,
activeMenu: 'product_list'
}
},
{
name: 'product_setting',
path: '/product/setting',
component: 'view.product_setting',
meta: {
title: 'product_setting',
i18nKey: 'route.product_setting',
hideInMenu: true,
activeMenu: 'product_list'
}
}
]
},
{
name: 'system',
path: '/system',

View File

@@ -206,6 +206,11 @@ const routeMap: RouteMap = {
"plugin_tables_vtable": "/plugin/tables/vtable",
"plugin_typeit": "/plugin/typeit",
"plugin_video": "/plugin/video",
"product": "/product",
"product_dashboard": "/product/dashboard",
"product_list": "/product/list",
"product_requirement": "/product/requirement",
"product_setting": "/product/setting",
"system": "/system",
"system_dict": "/system/dict",
"system_menu": "/system/menu",

View File

@@ -7,6 +7,7 @@ import type {
} from 'vue-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { useAuthStore } from '@/store/modules/auth';
import { useObjectContextStore } from '@/store/modules/object-context';
import { useRouteStore } from '@/store/modules/route';
import { localStg } from '@/utils/storage';
import { getRouteName } from '@/router/elegant/transform';
@@ -51,6 +52,13 @@ export function createRouteGuard(router: Router) {
// 不需要登录的路由允许直接访问
if (!needLogin) {
const objectContextLocation = await handleObjectContextSwitch(to);
if (objectContextLocation) {
next(objectContextLocation);
return;
}
handleRouteSwitch(to, from, next);
return;
}
@@ -68,6 +76,13 @@ export function createRouteGuard(router: Router) {
}
// 正常放行
const objectContextLocation = await handleObjectContextSwitch(to);
if (objectContextLocation) {
next(objectContextLocation);
return;
}
handleRouteSwitch(to, from, next);
});
}
@@ -176,6 +191,12 @@ function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNorma
next();
}
async function handleObjectContextSwitch(to: RouteLocationNormalized) {
const objectContextStore = useObjectContextStore();
return objectContextStore.ensureContextByRoute(to);
}
function getRouteQueryOfLoginRoute(to: RouteLocationNormalized, routeHome: RouteKey) {
const loginRoute: RouteKey = 'login';
const redirect = to.fullPath;

View File

@@ -8,6 +8,7 @@ import {
} from 'vue-router';
import { createBuiltinVueRoutes } from './routes/builtin';
import { createRouterGuard } from './guard';
import { setGlobalRouter } from './instance';
const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env;
@@ -22,6 +23,8 @@ export const router = createRouter({
routes: createBuiltinVueRoutes()
});
setGlobalRouter(router);
/** 挂载并初始化 Vue Router */
export async function setupRouter(app: App) {
app.use(router);

15
src/router/instance.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { Router } from 'vue-router';
let globalRouter: Router | null = null;
export function setGlobalRouter(router: Router) {
globalRouter = router;
}
export function getGlobalRouter() {
if (!globalRouter) {
throw new Error('Global router is not initialized');
}
return globalRouter;
}