Commit f169352e authored by zhhshen's avatar zhhshen

feat: 打包

parent c331ddd9
Pipeline #199 canceled with stages
# CPAS-Cloud 前端框架
## 相关技术
# 相关技术
| 技术 | 官网 | 描述 |
| -------------------------------- | --------------------------------- | ---------------- |
| Vite 4.X | https://cn.vitejs.dev/ | 基础脚手架 |
| React Router 6.X | https://reactrouter.com/en/main | react 路由管理 |
| ant-design 5.X | https://ant.design/index-cn | ui 组件 |
| @ant-design/pro-components 2.4.0 | https://procomponents.ant.design/ | 中后台高阶组件 |
| mobx 6.8.0 | https://mobx.js.org/README.html | 轻量级状态管理 |
| Vite 4.X | <https://cn.vitejs.dev/> | 基础脚手架 |
| React Router 6.X | <https://reactrouter.com/en/main> | react 路由管理 |
| ant-design 5.X | <https://ant.design/index-cn> | ui 组件 |
| @ant-design/pro-components 2.4.0 | <https://procomponents.ant.design/> | 中后台高阶组件 |
| mobx 6.8.0 | <https://mobx.js.org/README.html> | 轻量级状态管理 |
| typescript | - | 代码类型规范 |
| axios | - | 数据请求 |
| prettier | - | 代码美化,格式化 |
......@@ -17,154 +17,6 @@
| husky | - | git commit 检验 |
| lint-staged | - | git commit 检验 |
## 项目简介
基于 vite-mobx-TypeScript-react 开发的后台管理系统
[个人博客中,Problem 记录了该项目开发中遇到的相关问题](http://blog.jinxinapp.cn/#/problem/vite4-react-admin)
![项目截图](./src/assets/show.png)
## 主要功能
- 登录功能
- token 存储
- 后端 jwt
- 权限相关控制
- 全屏显示
- i18n 国际化
- 使用 react-intl-hooks 进行国际化处理
- 单元测试
- 菜单管理
- 表结构,级联操作
- 用户管理
- 强校验
- 文件上传功能(图片上传)
- 角色管理
- 角色联动菜单进行权限控制
- 前端埋点
- 统计 pv,uv 且展示
- echart,高德 api 地图数据展示
- 通过 AMapUI 取 geoJSON 进行地图渲染
- 持续更新中。。。
## 安装依赖
推荐使用 pnpm
```bash
pnpm install
npx husky install
```
## 脚本描述
### 开发启动
```bash
npm run start
# mock模式启动
npm run start:mock
```
### 打包
```bash
npm run build
```
### 检查代码样式
```bash
npm run lint
```
### 测试代码
```bash
npm run test
```
# CPAS-Cloud 前端框架
前端框架基于 [umi v3.5](https://umijs.org/zh-CN/docs),集成了 Ant Design、DevExpress 和 Amis 的组件。
## 环境准备
Install `node_modules`:
```bash
npm install
```
或者
```
yarn
```
### 启动项目
```bash
npm start
```
或者
```
yarn start
```
### 打包项目
- 默认打包命令(打包速度较快,不支持 IE11 及以下版本浏览器)
```bash
npm run build
```
或者
```bash
yarn build
```
- 打包支持 IE11 浏览器的包(打包速度较慢)
```bash
npm run build:ie11
```
或者
```bash
yarn build:ie11
```
### Lint 代码检测
```bash
npm run lint
```
或者
```bash
yarn lint
```
### Test code
```bash
npm test
```
或者
```bash
yarn test
```
## git 提交规范
[参考地址](https://nitayneeman.com/posts/understanding-semantic-commit-messages-using-git-and-angular/#test) 前缀 + 半角冒号 + 半角空格 + 说明
......@@ -228,7 +80,6 @@ Audit Paper 审计底稿
Audit Phase 审计阶段
```
## Freamwork更新规则
1. 在目录下创建 UpdateConfig.json
......@@ -238,6 +89,7 @@ Audit Phase 审计阶段
5. 每次需要将 (更新新增)/(删除)的文件文件夹地址配置到 UpdateConfig.json中.
## Feamwork ejs模板渲染规则
1. 在要被动态渲染的模板下,建立同名称ejs文件。例如package.json -> package.json.ejs
2. 在ejs文件中编写要动态渲染的部分
......@@ -248,14 +100,19 @@ Audit Phase 审计阶段
- [ ] 国际化支持
- [ ] antd的国际化
- [ ] devexpress的国际化
- [ ]
- [ ]
- [ ] icon图标替换
- [ ]
- [ ]
- [ ] cpas-business&cpas-ui问题
- [ ] 环境变量
- [ ] 打包测试
### Vite 打包
- [问题汇总] <https://github.com/vitejs/vite/discussions/8232>
- [jsx-runtime] <https://stackoverflow.com/questions/75234915/why-is-vite-ts-bundling-both-production-and-development-versions-of-react-jsx-ru>
### 参考项目
- https://github.com/KinXpeng/react-admin-vite/blob/main/vite.config.ts
\ No newline at end of file
- [react-admin-vite] <https://github.com/KinXpeng/react-admin-vite/blob/main/vite.config.ts>
- [react-vite-admin] <https://github.com/ychengcloud/react-vite-admin/blob/main/vite.config.ts>
import path from 'path';
import glob from 'glob';
import { defineConfig } from 'umi';
import CompressionPlugin from 'compression-webpack-plugin';
import { PurgeCSSPlugin } from 'purgecss-webpack-plugin';
import defaultSettings from './defaultSettings';
import proxy from './proxy';
const { REACT_APP_ENV } = process.env;
const PATHS = {
src: path.join(__dirname, 'src'),
};
export default defineConfig({
history: {
type: 'hash', // 为了解决在非根目录下部署问题,采用hash路由:#/xxx的形式
},
// base: process.env.NODE_ENV === 'production' ? './' : '/', 这个加了之后,会在路由上多出一个 './' 如:/#/./welcome
publicPath: process.env.NODE_ENV === 'production' ? './' : '/', // nginx非根目录下部署
hash: true, // 是否开启 hash 文件后缀
antd: {},
dva: {
hmr: true,
},
layout: {
locale: true,
siderWidth: 208,
...defaultSettings,
},
locale: {
default: 'zh-CN',
antd: true,
baseNavigator: true,
},
dynamicImport: {
loading: '@ant-design/pro-layout/es/PageLoading',
},
targets: {
ie: 11,
},
theme: {
'primary-color': defaultSettings.primaryColor,
},
title: false,
ignoreMomentLocale: true,
proxy: proxy[REACT_APP_ENV || 'dev'],
manifest: {
basePath: '/',
},
nodeModulesTransform: { type: 'none' },
exportStatic: {},
// mfsu: {},
webpack5: {
// lazyCompilation: {}, // 基于路由的按需编译
},
alias: {
'~': path.resolve(__dirname, '../'),
'@': path.resolve(__dirname, '../src'),
},
extraPostCSSPlugins: [require('tailwindcss'), require('autoprefixer')],
// chunks:
// process.env.NODE_ENV === 'production'
// ? ['common', 'vendors', 'antdesign', 'devextreme', 'umi']
// : ['umi'],
chainWebpack(config, { env, webpack }) {
// config.cache({
// type: 'filesystem',
// });
if (env === 'production') {
config.merge({
optimization: {
minimize: true,
splitChunks: {
chunks: 'all', // 有效值为 `all`,`async` 和 `initial`
minSize: 20000, // 生成 chunk 的最小体积(≈ 20kb)
minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。
maxAsyncRequests: 30, // 最大的按需(异步)加载次数
maxInitialRequests: 30, // 打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件)
enforceSizeThreshold: 50000,
cacheGroups: {
vendors: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10,
enforce: true,
},
common: {
test: /[\\/]node_modules[\\/](react|react-router-dom|react-router)[\\/]/,
name: 'common',
priority: 11,
enforce: true,
},
antdesign: {
name: 'antdesign',
test: /[\\/]node_modules[\\/](@ant-design|antd|@antv)[\\/]/,
priority: 12,
enforce: true,
},
devextreme: {
name: 'devextreme',
test: /[\\/]node_modules[\\/](devextreme|devextreme-react)[\\/]/,
priority: 13,
},
},
},
},
});
config.plugin('purgecss-webpack-plugin').use(
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
safelist: [],
blocklist: [],
}),
);
config.plugin('compression-webpack-plugin').use(
new CompressionPlugin({
test: /.js$|.html$|.css$/, // 压缩js,html,css文件
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 不删除源文件
algorithm: 'gzip', // 压缩算法
}),
);
// 过滤掉momnet的那些不使用的国际化文件
config
.plugin('replace')
.use(require('webpack').ContextReplacementPlugin)
.tap(() => {
return [/moment[/\\]locale$/, /zh-cn|en/];
});
console.log("🚀 ~ file: config.ts:133 ~ chainWebpack ~ config:", config.plugins)
}
},
});
import mainroutes from './routes-main';
export default [
{
path: '/user',
layout: false,
routes: [
{
path: '/user',
routes: [
{
name: 'login',
path: '/user/login',
component: './user/Login',
},
],
},
],
},
{
flatMenu: true,
access: 'main',
routes: mainroutes,
},
{
name: '404',
component: './404',
},
];
export default [
// {
// path: '/home',
// name: '首页',
// icon: 'Home',
// closable: false,
// component: './home',
// },
{
path: '/changePassword',
name: 'changePassword',
icon: 'smile',
hideInMenu: true,
component: './user/ChangePd',
},
{
path: '/',
redirect: './draft_manage',
},
];
......@@ -8,7 +8,7 @@
"start:debug": "vite --debug",
"start:mock": "vite --mode mock",
"start:production": "vite --mode production",
"build": "vite build",
"build": "NODE_OPTIONS=--max_old_space_size=8192 vite build",
"preview": "vite preview",
"lint:script": "eslint --ext .js,.jsx,.ts,.tsx --fix ./",
"lint:style": "stylelint --fix **/*.{css,less,scss}"
......@@ -28,16 +28,10 @@
"dependencies": {
"@ant-design/charts": "^1.1.16",
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-card": "^1.13.2",
"@ant-design/pro-components": "^2.4.0",
"@ant-design/pro-descriptions": "^1.6.8",
"@ant-design/pro-form": "^2.18.4",
"@ant-design/pro-layout": "^7.16.11",
"@ant-design/pro-list": "^1.13.5",
"@ant-design/pro-table": "^2.30.8",
"@ant-design/pro-form": "^2.18.6",
"@ant-design/pro-layout": "^6.26.5",
"@handsontable/react": "^12.1.2",
"@types/qs": "^6.9.7",
"@umijs/route-utils": "^1.0.36",
"@websee/core": "^4.0.2",
"@websee/performance": "^4.0.2",
"@websee/recordscreen": "^4.0.2",
......@@ -60,6 +54,7 @@
"file-saver": "^2.0.5",
"guid-factory": "^1.2.0",
"handsontable": "^12.1.2",
"history": "^5.3.0",
"html2canvas": "^1.4.1",
"hyperformula": "^2.1.0",
"i18next": "^23.4.9",
......@@ -89,6 +84,7 @@
"react-intl": "^6.4.4",
"react-lifecycles-compat": "^3.0.4",
"react-resizable": "^3.0.4",
"react-router": "^6.15.0",
"react-router-dom": "^6.8.2",
"react-split-pane": "^2.0.3",
"react-style-proptype": "^3.2.2",
......
This diff is collapsed.
[
{
"path": "/home",
"label": "基本信息维护",
"name": "Home",
"name": "基本信息维护",
"element": "Home",
"type": "component",
"icon": "icon-jibenxinxiweihu",
"closable": false
},
{
"path": "/groupStructure",
"name": "GroupStructure",
"label": "集团架构",
"name": "集团架构",
"element": "groupStructure",
"type": "component",
"icon": "icon-jituanjiagou",
"closable": false
},
{
"path": "ReportSys",
"name": "ReportSys",
"label": "报告系统",
"path": "/ReportSys",
"name": "报告系统",
"type": "component",
"closable": false,
"hideInMenu": true
},
{
"path": "ReportSys/StatementData",
"label": "报表项目基础数据",
"path": "/ReportSys/StatementData",
"name": "报表项目基础数据",
"type": "component",
"hideInMenu": true
},
{
"path": "/ReportSys/unFinancialStatements",
"label": "未审财务报表",
"name": "未审财务报表",
"type": "component",
"hideInMenu": true
},
{
"label": "TB汇总",
"name": "TB汇总",
"path": "/ReportSys/trialSys/trialBalance",
"type": "component",
"hideInMenu": true
},
{
"label": "TB-调整抵消",
"name": "TB-调整抵消",
"path": "/ReportSys/trialSys/AuditAdjust",
"type": "component",
"hideInMenu": true
},
{
"label": "TB-模拟调整",
"name": "TB-模拟调整",
"path": "/ReportSys/UnFinancialSys/accountAdjust",
"type": "component",
"hideInMenu": true
},
{
"label": "附注与分析",
"name": "附注与分析",
"path": "/ReportSys/reportAnalysis",
"type": "component",
"hideInMenu": true
......@@ -62,46 +61,46 @@
{
"path": "/ReportSys/approvedFinance",
"label": "审定报表",
"name": "审定报表",
"type": "component",
"hideInMenu": true
},
{
"label": "财务报表核对",
"name": "财务报表核对",
"path": "/ReportSys/UnFinancialSys/financialStatementsCheck",
"type": "component",
"hideInMenu": true
},
{
"label": "长投及关联交易—内部往来统计",
"name": "长投及关联交易—内部往来统计",
"path": "/ReportSys/trialSys/internalTran",
"type": "component",
"hideInMenu": true
},
{
"label": "报告生成",
"name": "报告生成",
"path": "/ReportSys/reportBuilder",
"type": "component",
"hideInMenu": true
},
{
"label": "集团层统计",
"name": "集团层统计",
"path": "/groupLevelStatistics",
"icon": "icon-jituancengtongji",
"children": [
{
"type": "component",
"label": "单体分录汇总",
"name": "单体分录汇总",
"path": "/groupLevelStatistics/entriesSummary"
},
{
"type": "component",
"label": "往来明细表汇总",
"name": "往来明细表汇总",
"path": "/groupLevelStatistics/detailsSummary"
},
{
"type": "component",
"label": "函证统计",
"name": "函证统计",
"path": "/groupLevelStatistics/correspondenceStatistics"
}
]
......
......@@ -6,9 +6,9 @@ import NotFound from '@/components/NotFound'
const Auth = props => {
const { children, permission = true, path } = props
const { curUser } = createSessionStorage.get('@@initialState')
console.log("🚀 ~ file: index.tsx:10 ~ Auth ~ curUser:", curUser)
if (curUser) {
const cpasUserInfo = localStorage.getItem('cpasUserInfo')
console.log('🚀 ~ file: index.tsx:10 ~ Auth ~ curUser:', cpasUserInfo)
if (cpasUserInfo) {
// 有权限返回页面
if (permission) {
return <>{children}</>
......
import { ProSettings } from '@ant-design/pro-components'
import { Settings as LayoutSettings } from '@ant-design/pro-layout'
/** prolayput 设置 */
const Settings: ProSettings | undefined = {
const Settings: LayoutSettings & {
pwa?: boolean
logo?: string
} = {
navTheme: 'light',
// primaryColor: '#1890ff',
// 拂晓蓝
primaryColor: '#1890ff',
layout: 'mix',
contentWidth: 'Fluid',
fixedHeader: false,
......@@ -12,7 +15,7 @@ const Settings: ProSettings | undefined = {
title: 'CPAS',
pwa: false,
logo: './logo.png',
iconfontUrl: '',
iconfontUrl: ''
}
export default Settings
import React from 'react'
import { storage } from '@/utils/Storage'
import { PageContainer, ProLayout } from '@ant-design/pro-components'
// import router, { RouteType } from '@config/routes'
import { useAsyncEffect } from 'ahooks'
import { Button, Dropdown } from 'antd'
import { useEffect, useState } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
import Settings from '@config/defaultSettings'
import PageTab, { AudTabPage } from '@/components/PageTab'
import MicroPage from '@/components/MicroPage'
import { isRunInFrame, isOpenFromPlatform } from '@/utils/env'
import { InitState, SysName } from './index'
import { getIsHideHeader, getIsMicroPage } from '@/utils/pub'
import RightContent from '@/components/RightContent'
import getIcon from '@/utils/icon'
import { CheckCircleOutlined } from '@ant-design/icons'
import { getCurProjectInfo } from '@/services/csService'
import { showSelectProjectInfoDialog } from '@/components/csDialog'
import useGlobalStore from '@/store/global'
import { reduceRoute } from '@/router'
import type { MenuProps } from 'antd'
type MenuItem = Required<MenuProps>['items'][number]
export function layoutLinks(initialState: InitState): React.ReactNode[] {
/* if (process.env.NODE_ENV === 'development') {
return [<></>];
}
return [<></>]; */
//项目信息
const projectInfo = getCurProjectInfo()
return [
//项目编号显示
<Button
id="btn_left_projectCode"
key={'btn_left_projectCode'}
icon={<CheckCircleOutlined style={{ marginLeft: 0 }} />}
type="primary"
style={{ width: '100%', borderRadius: 20 }}
onClick={() => {
showSelectProjectInfoDialog(initialState) ////保存项目信息 start add by csb 2023-02-13 通过documnet.title来判断,不会重复执行
}}
>
<span id="div_left_projectCode" style={{ color: 'white', marginLeft: 0 }}>
{' '}
{projectInfo.projectCode || '请选择项目'}
</span>
</Button>
]
}
export function getSysName(pathname: string): SysName {
return pathname.startsWith('/audit/') ? 'audit' : 'main'
}
function parseMenu(data: any[]): MenuItem[] {
const parseMenuJson = (menuData: any): any[] => {
const res: MenuItem[] = []
if (Array.isArray(menuData) && menuData.length > 0) {
for (const menu of menuData) {
const current: {
label: React.ReactNode
key: React.Key
icon?: React.ReactNode
children?: MenuItem[]
type?: 'group'
} = {}
if (!menu.hideInMenu) {
if (menu.icon) {
current.icon = getIcon(menu.icon)
} else {
current.icon = (
<div
style={{
width: '10px',
height: '10px'
}}
/>
)
}
current.key = menu.path
current.label = menu.label
if (menu.children) {
current.children = parseMenuJson(menu.children)
}
res.push(current)
}
}
}
return res
}
return parseMenuJson(data)
}
function getRoutesItemByPath(mjson: any, path: any) {
let matchItem = null
function findNodeByKeyIteration(matchStr: any, data: string | any[]) {
for (let i = 0; i < data.length; i++) {
const dataItem = data[i]
if (dataItem.children) {
findNodeByKeyIteration(matchStr, dataItem.children)
}
if (dataItem.path === path) {
matchItem = dataItem
break
}
}
}
findNodeByKeyIteration(path, mjson)
return matchItem
}
const LayoutConfig: React.FC = () => {
const navigate = useNavigate() // 路由跳转
const { initialState } = useGlobalStore()
console.log('🚀 ~ file: LayoutConfig.tsx:103 ~ initialState:', initialState)
const { menuJson = [] } = initialState
AudTabPage.getInstance().initPages(initialState?.sysName)
const inFrameLayout = isRunInFrame ? { hide: true, headerHeight: 0 } : {}
const isHideHeader = getIsHideHeader()
//通过配置url加参数,可以隐藏logo的头 如 "url": "http://localhost:8000?hideHeader=1", //不配置的知表示为显示
const inPlatformLayout = isHideHeader ? { hide: false, headerHeight: 0 } : {}
// const inPlatformLayout = isOpenFromPlatform ? { hide: true, headerHeight: 0 } : {};
// 是否作为微应用使用,在本项目中即是否嵌套在iframe中
const isMicroPage = getIsMicroPage()
const isMicroPageLayout = isMicroPage
? {
hide: true,
headerHeight: 0,
childrenRender: initialState && initialState?.curUser ? MicroPage : undefined
}
: {}
const menuItems: MenuItem[] = parseMenu(reduceRoute(menuJson))
console.log('🚀 ~ file: LayoutConfig.tsx:125 ~ getLayoutSetting ~ menuItems:', menuItems)
const layoutSetting = {
...inFrameLayout,
layout: 'side', // side | top
// contentStyle: { background: '#eee' },
disableContentMargin: true, // 内容展示区域的 Margin
// logo 单击事件
onMenuHeaderClick: () => {
if (initialState?.sysName === 'audit') {
navigate('/audit/home')
} else {
navigate('/home')
}
},
// onCollapse,
onPageChange: () => {
// onLayoutPC(initialState)
// 路由切换
},
links: layoutLinks(initialState),
waterMarkProps: {
content: initialState?.curUser?.usercode
},
menuProps: {
items: menuItems,
onClick: (menu: any) => {
console.log('🚀 ~ file: LayoutConfig.tsx:162 ~ getLayoutSetting ~ menu:', menu)
const path = menu.item.props.path
const matchRoutes: any = getRoutesItemByPath(menuJson, path)
if (matchRoutes.type === 'exe') {
// console.log(matchRoutes.page);
// UserCode={1} UserPwd={c4ca4238a0b923820dcc509a6f75849b} ProjectCode={202202-AUD-2020-001} ZcbfID={202202-AUD-2020-001}
if (window.electronComponents) {
const userInfoStr = window.localStorage.cpasUserInfo
const userInfo = JSON.parse(userInfoStr)
const userCode = userInfo.usercode
// const password = window.localStorage.password;
const sessionParams = JSON.parse(window.sessionStorage.electronParams)
const { sjxmbh, ZcbfID } = sessionParams.params
const password = window.main.cryptoUtil(userInfo.aesPassword)
console.log(
`cpas://OpenLocalFile/?fileName=${path}&params=UserCode=${userCode} UserPwd=${password} ProjectCode=${sjxmbh} ZcbfID=${ZcbfID}&workDir=./SysBPL`
)
window.location.href = `cpas://OpenLocalFile/?fileName=${path}&params=UserCode=${userCode} UserPwd=${password} ProjectCode=${sjxmbh} ZcbfID=${ZcbfID}&workDir=./SysBPL`
} else {
console.error('window["electronComponents"] false')
}
} else {
navigate(menu.key)
}
}
},
// siderWidth: typeof SIDER_WIDTH !== 'undefined' ? SIDER_WIDTH : 208,
//siderWidth: initialState && initialState.siderWidth === 0 ? 0 : 208,
siderWidth: 180,
// headerHeight: 43, // 48->43解决最外层有滚动条的问题 add by csb 20230221
headerHeight: 0, // 去掉logo,这里直接设置为0. add by szh 20230905
// siderWidth: 0,
childrenRender: initialState && initialState?.curUser ? PageTab : undefined,
rightContentRender: () => <RightContent />, //不要皮肤\登录\等信息
// footerRender: () => <Footer />,
// links: layoutLinks(),
menuHeaderRender: undefined,
// 自定义 403 页面
unAccessible: <div>unAccessible</div>,
...initialState?.settings,
...inPlatformLayout,
...isMicroPageLayout
}
console.log('🚀 ~ file: LayoutConfig.tsx:197 ~ layoutSetting:', layoutSetting)
return (
<>
<ProLayout {...layoutSetting}>
<PageContainer>
<Outlet />
</PageContainer>
</ProLayout>
</>
)
}
export default LayoutConfig
This diff is collapsed.
import React, { FC } from 'react'
import { Spin } from 'antd'
const SuspenceFallbackLoading: FC = () => {
return (
<div
style={{
width: '100vw',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyItems: 'center'
}}
>
<Spin tip="加载中..."></Spin>
</div>
)
}
export default SuspenceFallbackLoading
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom/client'
import { HashRouter } from 'react-router-dom'
import App from './App'
import SuspenceFallbackLoading from '@/layout/suspenceFallback'
import Initail from './initial'
import './app.less'
// async function getInitialState() {
// let menuJsonData: any[] = []
// let rts = {}
// try {
// rts = await getRTS()
// menuJsonData = await getRTSMenus()
// } catch (error) {
// console.log('🚀 ~ file: main.tsx:16 ~ getInitialState ~ error:', error)
// }
// let curUser = null
// let sessionCur = {
// id: '',
// zcbfid: '', //组成部分编号 zcbfid
// zcbfName: '', //单位名称 customerName
// ZcbfID: '', // 组成部分编号 (报告系统使用非驼峰写法字段)
// sjxmbh: '', // 审计项目编号 getCurProjectInfo().projectCode
// sjnd: '', // 审计年度
// userCode: '', //登录用户
// userName: '',
// dbname: '',
// dbName: '',
// DBName: ''
// }
// try {
// curUser = JSON.parse(window.localStorage?.getItem('cpasUserInfo') ?? '')
// const data = JSON.parse(window.sessionStorage.electronParams).params
// if (data) {
// sessionCur = data
// }
// } catch (error) {
// console.log('error 获取storage用户信息', error)
// }
// const curDataPart = getCurProjectInfo() || {}
// const curPart = {
// ...sessionCur,
// ...curDataPart,
// // 历史原因,报告系统迁移过来页面不统一,兼容使用字段
// // dbname: sessionCur.dbname, // 202205188806-C00014
// // DBName: sessionCur.DBName,
// // sjxmbh: curDataPart.projectCode,
// XMType: '',
// // 测试
// dbname: '202205188806-C00014', // 202205188806-C00014
// DBName: '202205188806-C00014',
// sjxmbh: curDataPart.projectCode,
// zcbfid: '202205188806-C00014', //组成部分编号 zcbfid
// ZcbfID: '202205188806-C00014' // 组成部分编号 (报告系统使用非驼峰写法字段)
// }
// // 修改本地session的存值
// const electronSession = {
// params: curPart,
// session: 'persist:local'
// }
// window.sessionStorage.setItem('electronParams', JSON.stringify(electronSession))
// const initialState = {
// settings: rts,
// menuJson: menuJsonData,
// curUser: curUser,
// sysName: 'main',
// curPart
// }
// // 存储到session
// createSessionStorage.set('@@initialState', initialState)
// // 状态管理里面存
// useGlobalStore.setState({ initialState })
// console.log('🚀 ~ file: main.tsx:69 ~ 初始化state成功:', initialState)
// }
const root = document.getElementById('root') as HTMLElement
ReactDOM.createRoot(root).render(
// <React.StrictMode>
<Suspense fallback={<p>Loading...</p>}>
<Suspense fallback={<SuspenceFallbackLoading></SuspenceFallbackLoading>}>
<Initail>
<HashRouter>
<App />
......
import { intl } from '@/utils/index'
import { Button, Result } from 'antd'
import React from 'react'
import { history } from 'umi'
const UnAccessible: React.FC = () => (
<Result
status="403"
title="403"
subTitle={intl('unAccessible.subTitle', '您没有权限访问.')}
extra={
<Button type="primary" onClick={() => history.goBack()}>
{intl('goBack', '返回')}
</Button>
}
/>
)
export default UnAccessible
import { intl } from '@/utils/index'
import { Button, Result } from 'antd'
import React from 'react'
import { history } from 'umi'
import { Button, Result } from 'antd'
import { intl } from '@/utils/index'
import { useNavigate } from 'react-router-dom'
const NoFoundPage: React.FC = () => (
<Result
status="404"
title="404"
subTitle={intl('noFoundPage.subTitle', '页面未找到.')}
extra={
<Button type="primary" onClick={() => history.goBack()}>
{intl('goBack', '返回')}
</Button>
}
/>
)
const NoFoundPage: React.FC = () => {
const navigate = useNavigate()
return (
<Result
status="404"
title="404"
subTitle={intl('noFoundPage.subTitle', '页面未找到.')}
extra={
<Button type="primary" onClick={() => navigate(-1)}>
{intl('goBack', '返回')}
</Button>
}
/>
)
}
export default NoFoundPage
......@@ -6,7 +6,7 @@ import { businessRenderer } from '@/utils/electronUtil'
// import { uuid } from 'uuidv4';
import type { IGroupTreeItem } from '@/pages/groupStructure/type'
import { getDetailMenus } from '@/pages/ReportSys'
import { mergeTypeText, mergeTypeValue, yesNoText, yesNoValue } from './GroupStructure/constant'
import { mergeTypeText, mergeTypeValue, yesNoText, yesNoValue } from './groupStructure/constant'
import { APP_NAME } from '@/utils/index'
import { createMicroPageUrl } from '@/utils/pub'
import { getCurProjectInfo } from '@/services/csService'
......
......@@ -223,6 +223,10 @@ const Login: React.FC = () => {
if (hasAlreadyLogin) {
return
}
const cpasUserInfo = localStorage.getItem('cpasUserInfo')
if (cpasUserInfo) {
navigage('/')
}
createCode()
// const localLoginInfo = localStorage.getItem('loginInfo');
// let loginInfo = null;
......
import React, { FC } from 'react'
import { Route, RouteProps } from 'react-router-dom'
import PrivateRoute from './privateRoute'
export interface WrapperRouteProps extends RouteProps {
/** authorization? */
auth?: boolean
}
const WrapperRouteComponent: FC<React.PropsWithChildren<WrapperRouteProps>> = ({
auth,
children
}) => {
if (auth) {
return <PrivateRoute>{children}</PrivateRoute>
}
return <>{children}</>
}
export default WrapperRouteComponent
import React from 'react'
import Auth from '@/components/permissions/Auth'
import { MenuDataItem } from '@ant-design/pro-components'
import { createHashRouter, RouteObject } from 'react-router-dom'
import { routers } from './routes'
export type RouteType = {
/** 是否需要菜单布局 */
layout?: string | 'hide'
/** 在菜单栏是否显示 */
hideInMenu?: boolean
/** 权限控制 true 则都控制 */
permissionObj?: {
/** 是否进行页面权限控制,控制取后端数据 */
isPagePermission?: boolean
/** 判断token是否存在控制 */
isToken?: boolean
} & true
children?: RouteType[]
} & Partial<MenuDataItem> &
RouteObject
/** 只给最低层级套 Permission 组件 */
const renderElement = (item: RouteType) => {
if (item?.element) {
if (item?.children) {
return item?.element
}
return (
<Auth name={item?.name} permissionObj={item?.permissionObj}>
{item?.element}
</Auth>
)
}
return undefined
}
const reduceRoute: (params: RouteType[]) => RouteType[] = (routesParams: RouteType[]) => {
return routesParams?.map(item => {
let curRouter = item
if (item?.permissionObj) {
curRouter = {
...curRouter,
element: renderElement(item)
}
}
if (item?.children) {
curRouter = {
...curRouter,
children: reduceRoute(item?.children) as any
}
}
return curRouter
})
}
const relRouters = reduceRoute(routers)
console.log('🚀 ~ file: index.tsx:58 ~ relRouters:', relRouters)
const router = createHashRouter(relRouters) as any
export default router
import React, { FC, useEffect, useState } from 'react'
import { Route, useNavigate, RouteProps, Navigate, useLocation } from 'react-router-dom'
const PrivateRoute: FC<RouteProps> = ({ children }) => {
const cpasUserInfo = localStorage.getItem('cpasUserInfo')
if (!cpasUserInfo) {
return <Navigate to="/login" />
}
return <>{children}</>
}
export default PrivateRoute
import React, { lazy, Suspense } from 'react'
import { Navigate, useRoutes } from 'react-router-dom'
import NotFoundPage from '@/404'
import React, { lazy, ReactElement } from 'react'
import { Navigate, useRoutes, RouteProps, Link, Outlet } from 'react-router-dom'
import ErrorPage from '@/ErrorPage'
import Home from '@/pages/Home'
import Login from '@/pages/user/Login'
import LayoutConfig from '@/layout/LayoutConfig'
import Auth from '@/components/permissions/Auth'
import { MenuDataItem } from '@ant-design/pro-components'
import { createHashRouter, RouteObject } from 'react-router-dom'
import WebFrame from '@/pages/web_frame'
import LayoutPage from '@/layout'
import { SmileFilled } from '@ant-design/icons'
import { createSessionStorage } from '@/utils/Storage'
import Loading from '@/components/loading'
const ChangePassword = lazy(() => import('../pages/user/ChangePd'))
import WrapperRouteComponent from './config'
import { Space } from 'antd'
export type RouteType = {
/** 是否需要菜单布局 */
layout?: string | 'hide'
/** 在菜单栏是否显示 */
hideInMenu?: boolean
/** 权限控制 true 则都控制 */
permission: true
children?: RouteType[]
} & Partial<MenuDataItem> &
RouteObject
const NotFound = lazy(() => import('@/pages/404'))
const ChangePassword = lazy(() => import('../pages/user/ChangePd'))
/** 只给最低层级套 Auth 组件 */
const renderElement = (item: RouteType) => {
const { type, path, name } = item
if (item?.element) {
const Element = lazy(() => import(`@/pages/${item.element}/index.tsx`))
return <Element></Element>
}
if (name) {
const Element = lazy(() => import(`@/pages/${name}/index.tsx`))
return (
<Auth {...item}>
{type === 'web' && <WebFrame url={path}></WebFrame>}
{type === 'component' && <Element></Element>}
</Auth>
)
}
const app = item.componentname || item.path.substring(item.path.lastIndexOf('/') + 1)
const Element = lazy(() => import(`@/pages/${app}/index.tsx`))
return (
<Auth {...item}>
{type === 'web' && <WebFrame url={path}></WebFrame>}
{type === 'component' && <Element></Element>}
</Auth>
)
export type RouteItemType = RouteProps & {
type?: string
name?: string
icon?: string | ReactElement
auth?: true
redirect?: string
children?: RouteItemType[]
element: RouteProps['element'] | string
}
export const reduceRoute: (params: RouteType[]) => RouteType[] = (routesParams: RouteType[]) => {
const ret: any[] = []
for (const item of routesParams) {
let curRouter = item
const { path } = curRouter
if (path) {
curRouter = {
...curRouter,
element: <Suspense>{renderElement(item)}</Suspense>
}
if (item?.children) {
curRouter = {
...curRouter,
children: reduceRoute(item?.children) as any
// 路由样例
// {
// "path": "/groupStructure",
// "name": "集团架构",
// "element": "",
// "meta": {
// "icon": ""
// "title: '",
// "type": "component",
// "closable": false,
// "hideInMenu": true
// }
// }
export const reduceRoute: (params: any[]) => any[] = (routes: RouteItemType[]) => {
return routes.map(item => {
const route = item
if (item.children) {
route.children = reduceRoute(item.children)
}
if (item?.element) {
// 判断是否是ReactElement
if (React.isValidElement(item.element)) {
route.element = item.element
} else {
// element 为字符串的情况,例如element: "Home", 首字母需要大写
// 推荐大写,但有可能传入的大小写都有,这里尝试都加载。
let Element = null
try {
const LowercaseName =
(item.element as string).slice(0, 1).toLowerCase() + (item.element as string).slice(1)
Element = lazy(() => import(`@/pages/${LowercaseName}/index.tsx`))
} catch (error) {
try {
const UppercaseName =
(item.element as string).slice(0, 1).toUpperCase() + (item.element as string).slice(1)
Element = lazy(() => import(`@/pages/${UppercaseName}/index.tsx`))
} catch (error) {}
}
route.element = Element && <Element />
}
ret.push(curRouter)
} else {
// element不存在的情况
route.element = <></>
}
}
return ret
if (item?.redirect) {
route.element = <Navigate to={item.redirect}></Navigate>
}
return route
})
}
export const routes = [
{
path: '/',
/** 重定向 */
element: <Navigate replace to="/home" />
},
export const routeList: RouteItemType[] = [
{
path: '/',
element: <LayoutConfig />,
element: (
<WrapperRouteComponent auth={true}>
<LayoutPage />
</WrapperRouteComponent>
),
errorElement: <ErrorPage />,
icon: <SmileFilled />,
children: []
......@@ -93,19 +91,19 @@ export const routes = [
},
{
path: '*',
element: <NotFoundPage />
element: <NotFound />
}
] as RouteType[]
]
// 获取主路由
export function getAllRoutes() {
export function getAllRoutes(routes: any) {
const { menuJson = [] } = createSessionStorage.get('@@initialState') || {
menuJson: []
}
const dynamicRoutes = reduceRoute(menuJson) || []
routes.forEach(route => {
if (route.path === '/' && route.children?.length === 0) {
route.children = reduceRoute(menuJson)
route.children = route.children.concat([
route.children = dynamicRoutes.concat([
{
path: '/changePassword',
name: 'changePassword',
......@@ -114,11 +112,12 @@ export function getAllRoutes() {
])
}
})
console.log('🚀 ~ file: routes.tsx:247 ~ getAllRoutes ~ routes:', routes)
return routes
}
export default function MainRouter() {
const routes = getAllRoutes()
return useRoutes(routes)
const allRoutes = getAllRoutes(routeList)
console.log("🚀 ~ file: routes.tsx:121 ~ MainRouter ~ allRoutes:", allRoutes)
const element = useRoutes(allRoutes)
return element
}
// fork from umi
import { createIntl, IntlShape, MessageDescriptor } from 'react-intl'
import deepmerge from 'deepmerge'
// import EventEmitter from 'events'
// import { plugin } from '../core/plugin'
import { createSessionStorage } from '@/utils/Storage'
import { useLocation } from 'react-router-dom'
import { createBrowserHistory } from 'history'
// export const event = new EventEmitter()
// event.setMaxListeners(5)
......@@ -284,83 +285,6 @@ export function useModel(name = '@@initialState') {
setInitialState
}
}
abstract class Location {
abstract pathname: string
abstract search: string
abstract hash: string
abstract push(options: any): void
abstract replace(options: any): void
abstract go(options: any): void
}
class CreateLocation extends Location {
public pathname: string
public search: string
public hash: string
constructor() {
super()
const params = new URL(window.location.href)
this.pathname = params.pathname
this.search = params.search
this.hash = params.hash
}
push(options) {
if (typeof options === 'string') {
//
}
if (typeof options === 'object' && options !== null) {
}
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
replace(options) {
if (typeof options === 'string') {
//
}
if (typeof options === 'object' && options !== null) {
}
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
go(options) {
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
}
export const location = new CreateLocation()
class CreateHistory {
location = new CreateLocation()
constructor() {}
push(options) {
if (typeof options === 'string') {
//
}
if (typeof options === 'object' && options !== null) {
}
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
replace(options) {
if (typeof options === 'string') {
//
}
if (typeof options === 'object' && options !== null) {
}
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
go(options) {
console.warn(
'功能需要升级,请使用react-router-route的Navigate组件或者在函数组件中使用useNavigate hook替换'
)
}
}
export const history = new CreateHistory()
// export const location = useLocation()
export const history = createBrowserHistory()
......@@ -2,7 +2,11 @@
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": false,
......@@ -17,15 +21,33 @@
"noUnusedLocals": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@config/*": ["./config/*"]
"@/*": [
"./src/*"
],
"@components/*": [
"./src/components/*"
],
"@config/*": [
"src/config/*"
]
}
},
"include": ["src"],
"exclude": ["node_modules", "build", "dist", "scripts", "webpack", "jest"],
"references": [{ "path": "./tsconfig.node.json" }]
}
"include": [
"src",
],
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"webpack",
"jest"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
\ No newline at end of file
......@@ -8,7 +8,7 @@
},
"include": [
"vite.config.ts",
"config",
"src/router/routes.ts"
"src/config",
"mock"
]
}
\ No newline at end of file
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// import eslintPlugin from 'vite-plugin-eslint'
// import commonjs from '@rollup/plugin-commonjs'
import { viteMockServe } from 'vite-plugin-mock'
import fs from 'fs/promises'
import * as esbuild from 'esbuild'
import path from 'path'
import proxy from './config/proxy'
import proxy from './src/config/proxy'
// answer from https://stackoverflow.com/questions/74620427/how-to-configure-vite-to-allow-jsx-syntax-in-js-files
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
plugins: [
react({
jsxImportSource: 'react',
jsxRuntime: 'automatic'
}),
react({}),
// eslintPlugin({
// include: ['src/**/*.js', 'src/**/*.ts', 'src/**/*.tsx', 'src/*.js', 'src/*.ts', 'src/*.tsx']
// }),
viteMockServe({
// default
localEnabled: mode === 'mock',
mockPath: './config/mock'
mockPath: './mock'
})
],
esbuild: {
loader: 'tsx',
include: /src\/.*\.[t|j]sx?$/,
exclude: []
},
optimizeDeps: {
force: true, //to make sure that the cache is updated
disabled: false
// force: true, //to make sure that the cache is updated
// disabled: false
},
// esbuild: {
// loader: 'tsx',
// include: [
// 'src/**/*.js',
// 'node_modules/**/*.js',
// // Add these lines to allow all .ts files to contain JSX
// 'src/**/*.ts',
// 'node_modules/**/*.ts'
// ],
// exclude: []
// // [@vitejs/plugin-react] This plugin imports React for you automatically,
// // so you can stop using `esbuild.jsxInject` for that purpose.
// // jsxInject: "import React from 'react'"
// },
resolve: {
extensions: ['.mjs', '.js', '.jsx', '.ts', '.tsx', '.json', '.sass', '.scss'], // 忽略输入的扩展名
alias: [
......@@ -84,12 +62,9 @@ export default defineConfig(({ mode }) => ({
},
build: {
// 打包出map文件
sourcemap: true
// rollupOptions: {
// plugins: [rollupPlugin([sourceJSPattern])]
// },
// commonjsOptions: {
// transformMixedEsModules: true
// }
sourcemap: true,
commonjsOptions: {
transformMixedEsModules: true
}
}
}))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment