feat: deliver first finance preview with setup wizard
Release / release (push) Failing after 18s

This commit is contained in:
Qiufeng
2026-08-17 14:08:40 +08:00
parent 47403fd337
commit c8b0151769
558 changed files with 140774 additions and 3 deletions
+37
View File
@@ -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;
+317
View File
@@ -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: '个人中心',
},
},
],
},
];
+38
View File
@@ -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[];
+49
View File
@@ -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' },
];
+37
View File
@@ -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,
};
}