This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import { financeRoutes } from './modules/finance';
|
||||
import { systemRoutes } from './modules/system';
|
||||
|
||||
const env = import.meta.env.MODE || 'development';
|
||||
|
||||
export const homepageRouterList = financeRoutes;
|
||||
export const fixedRouterList: RouteRecordRaw[] = [];
|
||||
|
||||
export const defaultRouterList: RouteRecordRaw[] = systemRoutes;
|
||||
|
||||
export const allRoutes = [...financeRoutes, ...defaultRouterList];
|
||||
|
||||
export const getActive = (maxLevel = 3): string => {
|
||||
const route = router.currentRoute.value;
|
||||
if (!route.path) return '';
|
||||
return route.path
|
||||
.split('/')
|
||||
.filter((_item: string, index: number) => index <= maxLevel && index > 0)
|
||||
.map((item: string) => `/${item}`)
|
||||
.join('');
|
||||
};
|
||||
|
||||
const router = createRouter({
|
||||
history:
|
||||
typeof window === 'undefined'
|
||||
? createMemoryHistory(env === 'site' ? '/' : import.meta.env.VITE_BASE_URL)
|
||||
: createWebHistory(env === 'site' ? '/' : import.meta.env.VITE_BASE_URL),
|
||||
routes: allRoutes,
|
||||
scrollBehavior() {
|
||||
return { el: '#app', top: 0, behavior: 'smooth' };
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,317 @@
|
||||
import { ChartBubbleIcon, FileAttachmentIcon, FileIcon, FolderOpenIcon, SettingIcon } from 'tdesign-icons-vue-next';
|
||||
import { shallowRef } from 'vue';
|
||||
import type { RouteComponent, RouteRecordRaw, RouteRecordSingleView } from 'vue-router';
|
||||
|
||||
import { LAYOUT } from '@/utils/route/constant';
|
||||
|
||||
import { pageContractMeta } from '../route-contract';
|
||||
|
||||
const projectArchivesPage = () => import('@/pages/archives/ProjectArchivesPage.vue');
|
||||
const archiveFilesPage = () => import('@/pages/archives/ArchiveFilesPage.vue');
|
||||
const masterDataPage = () => import('@/pages/finance/MasterDataPage.vue');
|
||||
const contractCostLedgerPage = () => import('@/pages/finance/ContractCostLedgerPage.vue');
|
||||
const receiptsInvoicesPage = () => import('@/pages/finance/ReceiptsInvoicesPage.vue');
|
||||
const paymentsPage = () => import('@/pages/finance/PaymentsPage.vue');
|
||||
const accountingPage = () => import('@/pages/finance/AccountingPage.vue');
|
||||
const formCenterPage = () => import('@/pages/forms/FormCenterPage.vue');
|
||||
const formEditorPage = () => import('@/pages/forms/FormEditorPage.vue');
|
||||
const taskCenterPage = () => import('@/pages/tasks/TaskCenterPage.vue');
|
||||
const projectListPage = () => import('@/pages/projects/ProjectListPage.vue');
|
||||
const projectDetailPage = () => import('@/pages/projects/ProjectDetailPage.vue');
|
||||
const reportsPage = () => import('@/pages/reports/ReportsPage.vue');
|
||||
const auditLogsPage = () => import('@/pages/governance/AuditLogsPage.vue');
|
||||
const systemSettingsPage = () => import('@/pages/governance/SystemSettingsPage.vue');
|
||||
const systemUpdatePage = () => import('@/pages/governance/SystemUpdatePage.vue');
|
||||
const workbench = (name: 'project' | 'finance' | 'archive') => {
|
||||
if (name === 'project') return () => import('@/pages/workbench/project.vue');
|
||||
if (name === 'finance') return () => import('@/pages/workbench/finance.vue');
|
||||
return () => import('@/pages/workbench/archive.vue');
|
||||
};
|
||||
|
||||
const group = (
|
||||
path: string,
|
||||
name: string,
|
||||
titleText: string,
|
||||
icon: unknown,
|
||||
orderNo: number,
|
||||
children: RouteRecordRaw[],
|
||||
): RouteRecordRaw => ({
|
||||
path,
|
||||
name,
|
||||
component: LAYOUT,
|
||||
redirect: (() => {
|
||||
const first = children.find((item) => !item.meta?.hidden)?.path;
|
||||
if (!first) return undefined;
|
||||
if (first === '') return path;
|
||||
return first.startsWith('/') ? first : `${path}/${first}`;
|
||||
})(),
|
||||
meta: { title: { zh_CN: titleText, en_US: titleText }, titleText, icon: shallowRef(icon as any), orderNo },
|
||||
children: children.map((child) => ({
|
||||
...child,
|
||||
meta: {
|
||||
...child.meta,
|
||||
breadcrumb: child.meta?.breadcrumb || [titleText, child.meta?.titleText || String(child.name || '')],
|
||||
},
|
||||
})),
|
||||
});
|
||||
|
||||
const businessContract = (
|
||||
pageId: string,
|
||||
path: string,
|
||||
name: string,
|
||||
titleText: string,
|
||||
extraMeta: Record<string, unknown>,
|
||||
component: RouteComponent,
|
||||
): RouteRecordSingleView => {
|
||||
return {
|
||||
path,
|
||||
name,
|
||||
component,
|
||||
meta: {
|
||||
...pageContractMeta(pageId),
|
||||
title: { zh_CN: titleText, en_US: titleText },
|
||||
titleText,
|
||||
...extraMeta,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const financeRoutes: RouteRecordRaw[] = [
|
||||
group('/workbench', 'Workbench', '工作台', ChartBubbleIcon, 10, [
|
||||
{
|
||||
path: 'project',
|
||||
name: 'WorkbenchProject',
|
||||
component: workbench('project'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-04'),
|
||||
title: { zh_CN: '项目管理工作台', en_US: 'Project workbench' },
|
||||
titleText: '项目管理工作台',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'finance',
|
||||
name: 'WorkbenchFinance',
|
||||
component: workbench('finance'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-05'),
|
||||
title: { zh_CN: '财务管理工作台', en_US: 'Finance workbench' },
|
||||
titleText: '财务管理工作台',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'archive',
|
||||
name: 'WorkbenchArchive',
|
||||
component: workbench('archive'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-06'),
|
||||
title: { zh_CN: '资料管理工作台', en_US: 'Archive workbench' },
|
||||
titleText: '资料管理工作台',
|
||||
},
|
||||
},
|
||||
]),
|
||||
group('/projects', 'ProjectManagement', '项目管理', FolderOpenIcon, 20, [
|
||||
businessContract('PAGE-07', '', 'ProjectList', '项目列表', {}, projectListPage),
|
||||
businessContract(
|
||||
'PAGE-08',
|
||||
':projectId',
|
||||
'ProjectDetail',
|
||||
'项目详情',
|
||||
{ hidden: true, pageTemplate: 'detail' },
|
||||
projectDetailPage,
|
||||
),
|
||||
]),
|
||||
group('/forms', 'FormWorkflow', '表单审批', FileIcon, 30, [
|
||||
businessContract('PAGE-09', '', 'FormCenter', '表单中心', {}, formCenterPage),
|
||||
{
|
||||
path: ':formType/:documentId?',
|
||||
name: 'FormEditor',
|
||||
component: formEditorPage,
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-10'),
|
||||
title: { zh_CN: '表单填写与导入', en_US: 'Form editor' },
|
||||
titleText: '表单填写与导入',
|
||||
// Viewing an existing form is a read operation; creation/edit actions
|
||||
// remain guarded by the server-side allowedActions and button-level
|
||||
// permissions inside the editor.
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
businessContract('PAGE-11', '/tasks', 'ApprovalTasks', '待办审批', {}, taskCenterPage),
|
||||
]),
|
||||
group('/finance', 'FinanceManagement', '财务管理', ChartBubbleIcon, 40, [
|
||||
businessContract(
|
||||
'PAGE-12',
|
||||
'master-data',
|
||||
'MasterData',
|
||||
'主数据中心',
|
||||
{
|
||||
dataKind: 'master',
|
||||
dataResources: [
|
||||
{ label: '公司', value: 'companies' },
|
||||
{ label: '往来单位', value: 'counterparties' },
|
||||
{ label: '银行账户', value: 'bank-accounts' },
|
||||
{ label: '合同', value: 'contracts' },
|
||||
{ label: '成本类别', value: 'cost-categories' },
|
||||
{ label: '会计科目', value: 'accounts' },
|
||||
|
||||
{ label: '税率', value: 'tax-rates' },
|
||||
],
|
||||
},
|
||||
masterDataPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-13',
|
||||
'contracts-costs',
|
||||
'ContractsCosts',
|
||||
'合同与成本台账',
|
||||
{
|
||||
dataKind: 'finance',
|
||||
dataResources: [
|
||||
{ label: '合同执行', value: 'contracts' },
|
||||
{ label: '成本台账', value: 'costs' },
|
||||
|
||||
{ label: '应付记录', value: 'payables' },
|
||||
],
|
||||
},
|
||||
contractCostLedgerPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-14',
|
||||
'receipts-invoices',
|
||||
'ReceiptsInvoices',
|
||||
'收款与发票',
|
||||
{
|
||||
dataKind: 'finance',
|
||||
dataResources: [
|
||||
{ label: '收款记录', value: 'receipts' },
|
||||
|
||||
{ label: '开票申请', value: 'invoices' },
|
||||
],
|
||||
},
|
||||
receiptsInvoicesPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-15',
|
||||
'payments',
|
||||
'Payments',
|
||||
'进度款与付款',
|
||||
{ dataKind: 'finance', dataResources: [{ label: '付款申请', value: 'payments' }] },
|
||||
paymentsPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-16',
|
||||
'accounting',
|
||||
'Accounting',
|
||||
'记账准备',
|
||||
{
|
||||
dataKind: 'finance',
|
||||
dataResources: [
|
||||
{ label: '财务事件', value: 'accounting-events' },
|
||||
|
||||
{ label: '凭证草稿', value: 'accounting-vouchers' },
|
||||
],
|
||||
},
|
||||
accountingPage,
|
||||
),
|
||||
]),
|
||||
group('/archives', 'Archives', '资料档案', FileAttachmentIcon, 50, [
|
||||
businessContract(
|
||||
'PAGE-17',
|
||||
'projects',
|
||||
'ProjectArchives',
|
||||
'项目归档',
|
||||
{ dataKind: 'archive', dataResources: [{ label: '档案包', value: 'packages' }] },
|
||||
projectArchivesPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-18',
|
||||
'files',
|
||||
'ArchiveFiles',
|
||||
'文件检索与借阅',
|
||||
{
|
||||
dataKind: 'archive',
|
||||
dataResources: [
|
||||
{ label: '文件检索', value: 'files' },
|
||||
|
||||
{ label: '借阅记录', value: 'borrows' },
|
||||
],
|
||||
},
|
||||
archiveFilesPage,
|
||||
),
|
||||
]),
|
||||
group('/reports', 'Reports', '查询分析', ChartBubbleIcon, 60, [
|
||||
businessContract(
|
||||
'PAGE-19',
|
||||
'',
|
||||
'ReportsIndex',
|
||||
'查询与报表',
|
||||
{
|
||||
dataKind: 'report',
|
||||
dataResources: [
|
||||
{ label: '项目台账', value: 'project-ledger' },
|
||||
{ label: '合同执行', value: 'contract-execution' },
|
||||
{ label: '收款与开票', value: 'receipt-invoice' },
|
||||
{ label: '付款进度', value: 'payment-progress' },
|
||||
{ label: '流程时长', value: 'workflow-duration' },
|
||||
|
||||
{ label: '资料完整性', value: 'archive-completeness' },
|
||||
],
|
||||
},
|
||||
reportsPage,
|
||||
),
|
||||
]),
|
||||
group('/governance', 'Governance', '系统治理', SettingIcon, 70, [
|
||||
businessContract(
|
||||
'PAGE-20',
|
||||
'audit',
|
||||
'AuditLogs',
|
||||
'审计日志',
|
||||
{ dataKind: 'audit', dataResources: [{ label: '审计事件', value: 'audit' }] },
|
||||
auditLogsPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-21',
|
||||
'settings',
|
||||
'SystemSettings',
|
||||
'权限与配置',
|
||||
{
|
||||
dataKind: 'admin',
|
||||
dataResources: [
|
||||
{ label: '用户', value: 'users' },
|
||||
{ label: '角色', value: 'roles' },
|
||||
{ label: '数据范围', value: 'scopes' },
|
||||
{ label: '表单模板', value: 'templates' },
|
||||
|
||||
{ label: '参数版本', value: 'parameters' },
|
||||
],
|
||||
},
|
||||
systemSettingsPage,
|
||||
),
|
||||
businessContract(
|
||||
'PAGE-22',
|
||||
'update',
|
||||
'SystemUpdate',
|
||||
'系统更新',
|
||||
{ dataKind: 'admin', dataResources: [{ label: '更新记录', value: 'update' }] },
|
||||
systemUpdatePage,
|
||||
),
|
||||
]),
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'ProfileParent',
|
||||
component: LAYOUT,
|
||||
meta: { title: { zh_CN: '个人中心', en_US: 'Profile' }, hidden: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'Profile',
|
||||
component: () => import('@/pages/profile/index.vue'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-03'),
|
||||
title: { zh_CN: '个人中心', en_US: 'Profile' },
|
||||
titleText: '个人中心',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { LAYOUT } from '@/utils/route/constant';
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/result',
|
||||
name: 'Result',
|
||||
component: LAYOUT,
|
||||
meta: { hidden: true, title: { zh_CN: '系统提示', en_US: 'System result' } },
|
||||
children: [
|
||||
{
|
||||
path: '403',
|
||||
name: 'Result403',
|
||||
component: () => import('@/pages/result/403/index.vue'),
|
||||
meta: { title: { zh_CN: '无权限', en_US: 'Forbidden' }, hidden: true },
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: 'Result404',
|
||||
component: () => import('@/pages/result/404/index.vue'),
|
||||
meta: { title: { zh_CN: '页面不存在', en_US: 'Not found' }, hidden: true },
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
name: 'Result500',
|
||||
component: () => import('@/pages/result/500/index.vue'),
|
||||
meta: { title: { zh_CN: '系统错误', en_US: 'Server error' }, hidden: true },
|
||||
},
|
||||
{
|
||||
path: 'network-error',
|
||||
name: 'ResultNetworkError',
|
||||
component: () => import('@/pages/result/network-error/index.vue'),
|
||||
meta: { title: { zh_CN: '网络异常', en_US: 'Network error' }, hidden: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies RouteRecordRaw[];
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BLANK_LAYOUT } from '@/utils/route/constant';
|
||||
|
||||
import { pageContractMeta } from '../route-contract';
|
||||
import resultRoutes from './result';
|
||||
|
||||
export const systemRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/setup',
|
||||
name: 'SetupWizard',
|
||||
component: BLANK_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'SetupWizardPage',
|
||||
component: () => import('@/pages/setup/index.vue'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-23'),
|
||||
title: { zh_CN: '安装向导', en_US: 'Setup wizard' },
|
||||
titleText: '安装向导',
|
||||
blank: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/login/index.vue'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-01'),
|
||||
title: { zh_CN: '登录', en_US: 'Sign in' },
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/role-select',
|
||||
name: 'RoleSelect',
|
||||
component: () => import('@/pages/role-select/index.vue'),
|
||||
meta: {
|
||||
...pageContractMeta('PAGE-02'),
|
||||
title: { zh_CN: '选择身份', en_US: 'Select role' },
|
||||
blank: true,
|
||||
},
|
||||
},
|
||||
...resultRoutes,
|
||||
{ path: '/', redirect: '/role-select' },
|
||||
{ path: '/:pathMatch(.*)*', redirect: '/result/404' },
|
||||
];
|
||||
@@ -0,0 +1,37 @@
|
||||
import routesContract from '../../contracts/routes.json';
|
||||
|
||||
export interface PageRouteContract {
|
||||
pageId: string;
|
||||
path: string;
|
||||
component: string;
|
||||
pageType: string;
|
||||
pageTemplate: string;
|
||||
requiresAuth: boolean;
|
||||
description: string;
|
||||
breadcrumb: string[];
|
||||
roles: string[];
|
||||
permission: null | string | string[];
|
||||
}
|
||||
|
||||
const pageContracts = new Map((routesContract.routes as PageRouteContract[]).map((route) => [route.pageId, route]));
|
||||
|
||||
export function getPageRouteContract(pageId: string): PageRouteContract {
|
||||
const route = pageContracts.get(pageId);
|
||||
if (!route) throw new Error(`Missing route contract for ${pageId}`);
|
||||
return route;
|
||||
}
|
||||
|
||||
export function pageContractMeta(pageId: string) {
|
||||
const route = getPageRouteContract(pageId);
|
||||
return {
|
||||
pageId: route.pageId,
|
||||
pageType: route.pageType,
|
||||
pageTemplate: route.pageTemplate,
|
||||
requiresAuth: route.requiresAuth,
|
||||
roleCodes: [...route.roles],
|
||||
permission: route.permission,
|
||||
description: route.description,
|
||||
breadcrumb: [...route.breadcrumb],
|
||||
componentPath: route.component,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user