咸丰县农村安全饮水智慧平台

main
weiying 2023-12-20 17:29:11 +08:00
parent ea39883c81
commit 17e37362bb
88 changed files with 33729 additions and 0 deletions

1
.env.development Normal file
View File

@ -0,0 +1 @@
PUBLIC_URL=/xyt_dam

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
GENERATE_SOURCEMAP=false
PUBLIC_URL=http://local.gunshiiot.com:19080/dam

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
build.7z

28
craco.config.js Normal file
View File

@ -0,0 +1,28 @@
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {
'@primary-color': '#3B7CFF',
'@error-color': '#F55E55',
'@text-color': '#3B4859',
'@menu-dark-bg': '#3773C5',
'@layout-body-background': '#eeeeee',
'@form-vertical-label-padding': '0 0 10px',
'@normal-color': '#EFF1F5',
'@label-color': '#3B4859',
'@card-background': '#fbfbfb',
'@heading-color': '#3B4859',
},
javascriptEnabled: true,
},
},
},
},
],
};

30947
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "zhzmkz-web",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@craco/craco": "^7.0.0-alpha.7",
"@react-hook/resize-observer": "^1.2.6",
"@rematch/core": "^2.2.0",
"@types/geojson": "^7946.0.8",
"@types/node": "^16.11.45",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"antd": "^4.21.7",
"axios": "^0.27.2",
"craco-less": "2.1.0-alpha.0",
"echarts": "^5.3.3",
"echarts-for-react": "^3.0.2",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.2",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"redux": "^4.2.0",
"typescript": "^4.7.4"
},
"scripts": {
"start": "craco start",
"build": "craco build"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
public/assets/dw-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

BIN
public/assets/index-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
public/assets/left-line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/assets/menu-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
public/assets/top-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
public/assets/yhfw-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

BIN
public/assets/yun-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/assets/zhgw-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

BIN
public/assets/zhsc-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

BIN
public/assets/zhsy-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

44
public/index.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>小玉潭水库系统管理</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

Binary file not shown.

BIN
src/assets/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
src/assets/icons/logo64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
src/assets/icons/switch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/assets/icons/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1660979818572" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1211" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M0 0m81.92 0l860.16 0q81.92 0 81.92 81.92l0 860.16q0 81.92-81.92 81.92l-860.16 0q-81.92 0-81.92-81.92l0-860.16q0-81.92 81.92-81.92Z" fill="#29BEFC" p-id="1212"></path><path d="M282.95168 623.616l10.48576 3.52256V324.89472h434.176v307.93728l11.30496-5.36576c14.90944-7.04512 27.40224-6.02112 43.8272-4.79232l8.51968 0.65536V261.16096h-98.54976V184.32h-364.544v76.84096H229.62176v364.87168l9.37984-1.76128c16.01536-2.94912 28.50816-5.77536 43.95008-0.65536z m109.1584-375.52128h236.83072v13.1072H392.11008z" fill="#FFFFFF" p-id="1213"></path><path d="M602.112 623.0016a93.96224 93.96224 0 0 1 52.18304 15.9744l12.288 8.192v-191.6928h-72.21248v167.5264zM363.84768 648.92928l9.13408-6.88128a93.88032 93.88032 0 0 1 46.4896-19.08736l7.00416-0.8192v-166.7072h-72.4992v189.27616zM477.02016 635.65824a92.44672 92.44672 0 0 1 9.87136 6.47168l29.4912 22.36416 29.12256-22.03648v-209.67424h-72.54016v200.704zM853.44256 768.73728l-26.05056-19.49696a84.86912 84.86912 0 0 0-101.62176 0l-25.96864 19.53792a16.384 16.384 0 0 1-19.74272 0l-26.0096-19.53792a84.95104 84.95104 0 0 0-101.70368 0l-25.88672 19.53792a16.67072 16.67072 0 0 1-19.82464 0l-25.96864-19.53792a84.95104 84.95104 0 0 0-101.66272 0l-25.96864 19.53792a16.5888 16.5888 0 0 1-19.78368 0L307.2 749.24032a84.91008 84.91008 0 0 0-101.66272 0l-33.9968 25.76384A33.95584 33.95584 0 0 0 212.992 828.86656l33.66912-25.43616a16.5888 16.5888 0 0 1 19.78368 0l25.92768 19.49696a84.74624 84.74624 0 0 0 101.62176 0l26.0096-19.49696a16.384 16.384 0 0 1 19.70176 0l25.35424 19.2512a86.50752 86.50752 0 0 0 76.47232 13.35296 83.92704 83.92704 0 0 0 25.88672-13.1072l26.0096-19.53792a16.384 16.384 0 0 1 19.74272 0l25.96864 19.49696a84.91008 84.91008 0 0 0 101.66272 0l25.96864-19.53792a16.384 16.384 0 0 1 19.74272 0l25.68192 19.29216q1.06496 0.90112 2.21184 1.67936a33.95584 33.95584 0 0 0 52.8384-33.792 33.54624 33.54624 0 0 0-13.80352-21.79072z" fill="#FFFFFF" p-id="1214"></path><path d="M863.88736 674.4064l-26.8288-20.15232a87.61344 87.61344 0 0 0-105.02144 0l-26.78784 20.15232a17.16224 17.16224 0 0 1-20.48 0l-26.78784-20.15232a87.6544 87.6544 0 0 0-105.02144 0l-26.8288 20.15232a17.08032 17.08032 0 0 1-20.48 0l-26.86976-20.15232a87.53152 87.53152 0 0 0-104.98048 0l-26.8288 20.15232a17.08032 17.08032 0 0 1-20.48 0l-26.78784-20.15232a87.57248 87.57248 0 0 0-104.98048 0l-34.77504 26.33728a35.06176 35.06176 0 0 0 42.3936 55.82848l34.65216-26.29632a17.08032 17.08032 0 0 1 20.48 0l26.86976 20.15232a87.69536 87.69536 0 0 0 104.98048 0l26.8288-20.15232a17.08032 17.08032 0 0 1 20.48 0l26.8288 20.11136a86.95808 86.95808 0 0 0 52.26496 17.57184h0.36864v-8.192 8.192a87.40864 87.40864 0 0 0 52.38784-17.57184l26.74688-20.15232a17.08032 17.08032 0 0 1 20.48 0l26.8288 20.15232a87.73632 87.73632 0 0 0 104.98048 0l26.8288-20.15232a17.08032 17.08032 0 0 1 20.48 0l26.8288 20.11136a35.06176 35.06176 0 1 0 42.35264-55.82848z" fill="#FFFFFF" p-id="1215"></path></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

BIN
src/assets/images/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
src/assets/images/menu1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

View File

@ -0,0 +1,42 @@
import { Breadcrumb } from 'antd';
import React, { useMemo } from 'react'
import { Link } from 'react-router-dom';
import { MenuItem } from '../../models/_/defs';
const AppBreadcrumb: React.FC<{
menu: MenuItem[];
menuIndexes: string[];
}> = ({ menu, menuIndexes }) => {
const menulist = useMemo(() => {
const ret: MenuItem[] = [];
let o: MenuItem[] = menu;
for (const id of menuIndexes) {
const hit = o.find((m: any) => m.id === id);
if (hit) {
ret.push(hit);
o = hit.children || [];
}
}
return ret;
}, [menu, menuIndexes])
return (
<div className="app-breadcrumb">
<Breadcrumb >
{
menulist.map((m: any) => (
<Breadcrumb.Item key={m.id}>
{
m.path ? <Link to={m.path}>{m.title}</Link> : m.title
}
</Breadcrumb.Item>
))
}
</Breadcrumb>
</div>
)
}
export default AppBreadcrumb

View File

@ -0,0 +1,64 @@
import React from 'react';
import { useNavigate } from 'react-router';
import { MenuItem } from '../../models/_/defs';
function getMenuUrl(menuItem: MenuItem | undefined): string | null {
if (!menuItem) {
return null;
}
const url = menuItem.path || menuItem.redirect;
if (url) {
return url;
}
if (menuItem.children && menuItem.children.length) {
for (const m of menuItem.children) {
const url = getMenuUrl(m);
if (url) {
return url;
}
}
}
return null;
}
const BottomMenu: React.FC<{
menu: MenuItem[];
menuIndexes: string[];
}> = ({ menu, menuIndexes }) => {
const navigate = useNavigate();
const menuClicked = (id: string) => {
const menuItem = menu.find(m => m.id == id);
const url = getMenuUrl(menuItem);
if (url) {
navigate(url);
}
}
return (
<div className='app-top-menu'>
{
menu?.map(o => (
<div
key={o.id}
className={`app-top-menu-item${menuIndexes[0] == o.id ? ' active' : ''}`}
onClick={() => menuClicked(o.id)}
>
<img src={`${process.env.PUBLIC_URL}/assets/${o.icon}`} />
{o.title}
</div>
))
}
</div>
)
}
export default React.memo(BottomMenu);

View File

@ -0,0 +1,50 @@
import { LogoutOutlined } from '@ant-design/icons';
import { Dropdown, Menu } from 'antd';
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router";
import User from '../../assets/icons/user.png';
import { LoginUser } from "../../models/auth/_";
import { Dispatch } from "../../models/store";
const HeaderUser: React.FC<{
user: LoginUser | null;
}> = ({ user }) => {
const navigate = useNavigate();
const dispatch = useDispatch<Dispatch>();
const hasLogout = sessionStorage.getItem('__useToken') === '0';
const logout = () => {
dispatch.auth.logout();
navigate('/login');
};
if (hasLogout) {
return (
<Dropdown
overlay={(
<Menu>
<Menu.Item icon={<LogoutOutlined />} key="logout" onClick={logout}>退</Menu.Item>
</Menu>
)}
>
<span className='user-menu'>
<img className='user-menu-icon' src={User} />
{`欢迎您,${user?.name || ''}`}
</span>
</Dropdown>
)
}
return (
<span className='user-menu'>
<img className='user-menu-icon' src={User} />
{`欢迎您,${user?.name || ''}`}
</span>
)
};
export default HeaderUser

View File

@ -0,0 +1,63 @@
import { Menu } from 'antd';
import React, { useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { MenuItem } from '../../models/_/defs';
const { SubMenu } = Menu;
export function selectedMenu(menu: MenuItem[], menuIndexes: string[]) {
if (!menuIndexes || !menuIndexes[0]) {
return [];
}
const menuItem = menu.find((o) => o.id === menuIndexes[0]);
return (menuItem && menuItem.children) || [];
}
const SiderMenu: React.FC<{
menu: MenuItem[],
menuIndexes: string[],
}> = ({ menu, menuIndexes }) => {
const subMenu = useMemo(() => selectedMenu(menu, menuIndexes), [menu, menuIndexes]);
const location = useLocation();
const pathname = location.pathname;
const navigate = useNavigate();
function goto(url: string) {
if (pathname !== url) {
navigate(url);
}
}
return (
<Menu
key={menuIndexes[0] || '0'}
mode="inline"
selectedKeys={[`${menuIndexes[2] || menuIndexes[1]}`]}
defaultOpenKeys={[`${menuIndexes[1]}`]}
>
{
subMenu.map((o: any) => (
o.children && o.children.length > 0 ? (
<SubMenu key={o.id} icon={o.icon ? <img style={{ width: 16, height: 16 }} src={`/assets/icons/${o.icon}.png`} /> : undefined} title={o.title}>
{
o.children.map((oo: any) => (
<Menu.Item
onClick={() => goto(oo.path)} key={oo.id}>{oo.title}</Menu.Item>
))
}
</SubMenu>
) : (
<Menu.Item
icon={o.icon ? <img style={{ width: 16, height: 16 }} src={`/assets/icons/${o.icon}.png`} /> : undefined}
onClick={() => goto(o.path)} key={o.id}>{o.title}</Menu.Item>
)
))
}
</Menu>
);
}
export default React.memo(SiderMenu);

View File

@ -0,0 +1,137 @@
import { BellOutlined } from '@ant-design/icons';
import { Layout } from 'antd';
import React, { Suspense, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Outlet, useNavigate } from 'react-router';
import { useLocation } from 'react-router-dom';
import Icon1 from '../../assets/icons/icon.png';
import { SUBTITLE, TITLE, config } from '../../config';
import { findLeafMenu, findMenu } from '../../models/auth/menu';
import { Dispatch, RootState } from '../../models/store';
import { getParameter } from '../../utils/utils';
import HeaderUser from './HeaderUser';
import SiderMenu from './SiderMenu';
import BottomMenu from './BottomMenu';
import './style.less';
const { Header, Content, Sider } = Layout;
const DashboardLayout: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const pathname = location.pathname;
const dispatch = useDispatch<Dispatch>();
const user = useSelector((state: RootState) => state.auth.user);
const menu = useSelector((state: RootState) => state.auth.menu);
useEffect(() => {
const token = getParameter('token') || 'demo';
if (token) {
dispatch.auth.regByToken(token).then(() => {
dispatch.auth.loadMenu(undefined);
});
} else {
if (user == null) {
navigate('/login')
} else {
dispatch.auth.loadMenu(undefined);
}
}
}, []);
useEffect(() => {
if (user === null) {
}
if (user === 'failed') {
// 跳到主页面
return;
}
}, [user]);
const [menuIndexes, leafMenu] = useMemo(() => [
findMenu(menu, pathname),
findLeafMenu(menu, pathname),
], [menu, pathname]);
useEffect(() => {
if (leafMenu) {
document.title = '小玉潭水库 · ' + leafMenu.title
}
}, [leafMenu]);
if (user === 'failed') {
return null;
}
return (
<Layout className="app-root">
{/*<Header className="app-header">
<a className='app-icon-a' href='http://219.138.108.99:30046/guide/'>
<img alt="..." className="app-icon" src={Icon1} />
<span className="app-title">{TITLE}-{SUBTITLE}</span>
</a>
<TopMenu menu={menu} menuIndexes={menuIndexes} />
<div className='flex-grow-1'></div>
<span className='user-menu' style={{ padding: '0 19px' }}>
<BellOutlined />
</span>
<HeaderUser user={user} />
</Header>*/}
<Layout>
{/*{
menuIndexes.length > 1 ? (
<Sider className="app-sider" width={208}>
<SiderMenu menu={menu} menuIndexes={menuIndexes} />
</Sider>
) : null
}*/}
<Content className="app-content index-box">
<div className="top-menu-box">
<div className="txt left-txt">
<img src={`${process.env.PUBLIC_URL}/assets/dw-icon.png`} />
<div></div>
<img src={`${process.env.PUBLIC_URL}/assets/yun-icon.png`} className="img2"/>
<div>32</div>
</div>
<div className="txt right-txt">
<div>2023-10-13&nbsp;&nbsp;&nbsp;&nbsp;</div>
<div style={{color:"#979797",margin:"4px 12px"}}>|</div>
<div>10:30:00</div>
</div>
</div>
<div className="left-line"></div>
<div className="right-line"></div>
<div className={menuIndexes.length > 1 ? 'content-root content-box' : 'content-root-fs content-box'}>
{
user ? (
<Suspense fallback={null}>
<Outlet />
</Suspense>
) : null
}
</div>
<div className="bottom-menu">
<BottomMenu menu={menu} menuIndexes={menuIndexes} />
</div>
</Content>
</Layout>
</Layout>
)
};
export default DashboardLayout

View File

@ -0,0 +1,119 @@
.app-root{
overflow: hidden auto !important;
}
.index-box {
width: 101vw;
height: 100vh;
background: url("../../../public/assets/index-bg.png") no-repeat !important;
background-size: cover !important;
overflow: hidden auto !important;
.left-line,.right-line{
width: 30px;
height: calc( 100vh - 90px );
position: fixed;
top:60px;
}
.left-line{
background: url("../../../public/assets/left-line.png") 0 0 no-repeat;
background-size: contain;
left:5px;
}
.right-line{
background: url("../../../public/assets/right-line.png") 0 0 no-repeat;
background-size: contain;
right:-11px;
}
.top-menu-box{
width: 100vw;
height: 90px;
background: url("../../../public/assets/top-menu.png") 0 0 no-repeat;
background-size: 100% 100%;
position: absolute;
top:0;
left:0;
display: flex;
justify-content: space-between;
.txt{
display: flex;
color: #fff;
img{
width: 12px;
height: 18px;
margin: 9px 10px 0 20px;
}
.img2{
width: 18px;
margin-top: 7px;
}
div{
margin-top: 4px;
font-size: 14px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 25px;
}
}
.right-txt{
margin-right: 20px;
}
}
.content-box{
width: 100vw;
height: 100vh;
color: #fff;
padding: 90px 30px 20px 30px;
}
.bottom-menu{
width: 45vw;
height: 90px;
position: absolute;
bottom: 10px;
left:calc( 55vw/2 );
background: url("../../../public/assets/menu-shadow.png") 0 50px no-repeat;
background-size: contain;
.app-top-menu{
height: 60px;
display: flex;
align-items: center;
justify-content: center;
.app-top-menu-item{
color: #fff;
margin: 0 5px;
cursor: pointer;
/*width: 159px;
height: 52px;*/
width: 7vw;
height: 30px;
text-align: center;
line-height: 30px;
letter-spacing: 1px;
background: url("../../../public/assets/menu-bg.png") 0 0 no-repeat;
background-size: 100% 100%;
img{
width: 16px;
margin: -3px 4px 0 0;
}
&.active{
background: url("../../../public/assets/menu-bg-active.png") 0 0 no-repeat !important;
background-size: 100% 100% !important;
}
}
}
}
}
.content-body {
background-color: #fff;
padding: 20px;
}

View File

@ -0,0 +1,327 @@
.ant-btn-primary,
.ant-btn-default {
padding: 0 15px;
border-radius: 4px;
}
.ant-form-item-label>label {
font-weight: bold;
padding-right: 8px;
}
.ant-input,
.ant-select-selector {
border-radius: 4px !important;
// box-shadow: 0 0 2px 2px #f5f5f5 !important;
}
.ant-input-search {
.ant-input {
border-radius: 4px 0 0 4px !important;
}
.ant-btn {
border-radius: 0 4px 4px 0 !important;
min-width: 80px;
}
}
.ant-radio-group {
.ant-radio-button-wrapper:first-child {
border-radius: 8px 0 0 8px !important
}
.ant-radio-button-wrapper:last-child {
border-radius: 0 8px 8px 0 !important
}
.ant-radio-button-wrapper {
border: 1px solid @primary-color !important;
&:hover {
background-color: #D9EBFF;
}
}
.ant-radio-button-wrapper {
color: #3B7CFF;
}
}
.common-style {
.ant-table-container {
border: 1px solid #e8e8e8 !important;
}
.ant-table-thead>tr>th,
.ant-table-tbody>tr>td,
.ant-table tfoot>tr>th,
.ant-table tfoot>tr>td {
padding: 12px 8px;
border-right: 1px solid transparent !important;
}
.ant-table-thead>tr>th {
background-color: #e0edff !important;
border-bottom: 1px solid #e8e8e8;
}
.ant-table-tbody>tr:not(.ant-table-placeholder)>td {
border-top: 1px solid #e0edff;
border-bottom: 1px solid #e0edff;
&.ant-table-cell-row-hover {
background: #f4f8ff;
color: #3773c5;
}
}
.ant-table-tbody tr:not(.ant-table-placeholder):nth-child(2n) {
background: #f4f8ff;
}
.ant-pagination {
.ant-pagination-item:not(.ant-pagination-item-active) {
border: none !important;
}
.ant-pagination-item-link {
border: none !important;
}
.ant-pagination-item-active {
background-color: #3773c5 !important;
border-radius: 99px;
a {
color: #fff !important;
}
}
}
}
.ant-modal-content {
border-radius: 4px;
.ant-modal-header {
background: #2a66c2;
border-radius: 4px 4px 0 0;
.ant-modal-title {
color: #fff !important;
}
.ant-modal-close {
color: #fff !important;
}
}
}
.ant-btn-link.ant-btn-sm {
color: #3773c5;
height: 18px;
}
.anticon-close.ant-modal-close-icon {
color: #fff;
}
td.ant-table-column-sort {
background-color: unset;
}
.ant-modal.fullscreen {
width: 100vw !important;
max-width: 100vw !important;
top: 0;
padding-bottom: 0;
.ant-modal-body {
height: calc(100vh - 55px);
overflow-y: auto;
}
}
/*
.ant-table-thead>tr>th {
border-bottom: none !important;
background: @primary-color !important;
color: #fff !important;
font-weight: 600 !important;
&::before {
width: 0 !important;
}
&:first-child {
border-top-left-radius: 16px !important;
}
&:last-child {
border-top-right-radius: 16px !important;
}
}
.ant-table {
background: transparent !important;
}
.ant-table-tbody>tr>td {
border-bottom: none !important;
background: #fff;
box-shadow: 3px 2px 3px 2px #E8ECF1;
padding: 12px 16px !important;
&:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
&:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
}
.ant-table table {
border-spacing: 0 8px !important;
}
.ant-pagination {
.ant-select-selector {
border: none !important;
}
.ant-pagination-item:not(.ant-pagination-item-active) {
border: none !important;
}
.ant-pagination-item-link {
border: none !important;
}
.ant-pagination-item-active {
background-color: @primary-color;
border-radius: 99px;
a {
color: #fff !important;
}
}
.ant-pagination-options-quick-jumper input {
border: none !important;
}
}
.ant-btn-primary,
.ant-btn-default {
min-width: 110px;
border-radius: 99px;
}
.ant-input,
.ant-select-selector {
border-radius: 4px !important;
box-shadow: 0 0 2px 2px #f5f5f5 !important;
}
.ant-input-search {
.ant-input-lg {
border-top-left-radius: 99px !important;
border-bottom-left-radius: 99px !important;
}
.ant-btn-lg {
border-top-right-radius: 99px !important;
border-bottom-right-radius: 99px !important;
min-width: 80px;
}
}
.kai-comp .ant-picker-range {
border-top-right-radius: 4px !important;
border-bottom-right-radius: 4px !important;
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
box-shadow: 0 0 2px 2px #f5f5f5 !important;
}
.kai-comp .ant-picker-status-error.ant-picker {
border-left: none;
}
.tree-select {
.ant-tree {
border: 1px solid #f3f5f8;
background: #f9fafc;
border-radius: 4px !important;
padding: 8px !important;
}
.ant-tree-switcher {
background-color: transparent !important;
}
.ant-tree-switcher {
display: flex;
align-items: center;
justify-content: center;
color: #C7CBD3;
font-size: 12px;
}
}
.ant-radio-group {
.ant-radio-button-wrapper:first-child {
border-radius: 8px 0 0 8px !important
}
.ant-radio-button-wrapper:last-child {
border-radius: 0 8px 8px 0 !important
}
.ant-radio-button-wrapper {
border: 1px solid @primary-color !important;
&:hover {
background-color: #D9EBFF;
}
}
.ant-radio-button-wrapper {
color: #3B7CFF;
}
}
.ant-checkbox-checked {
.ant-checkbox-inner {
background-color: #3b7cff !important;
border-color: #3b7cff !important;
}
&::after {
border: none !important;
}
}
.ant-checkbox-inner {
border-radius: 4px !important;
border-width: 1.4px !important;
border-color: #C7CBD3 !important;
}
*/

View File

@ -0,0 +1,16 @@
import React from 'react'
const CenterHorizontal: React.FC<{
children: any,
maxWidth?: number;
}> = ({ children, maxWidth }) => {
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ maxWidth }}>
{children}
</div>
</div>
)
};
export default CenterHorizontal

View File

@ -0,0 +1,69 @@
import { UploadOutlined } from '@ant-design/icons';
import { Button, Modal, Upload, UploadFile, UploadProps } from 'antd';
import React, { useState } from 'react';
const ImportModal: React.FC<{
visible: boolean;
close: () => void;
doImpt: (file: any) => Promise<void>;
accept: string;
}> = ({ visible, close, doImpt, accept }) => {
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const handleUpload = async () => {
const file = fileList[0];
if (!file || uploading) {
return;
}
setUploading(true);
try {
await doImpt(file);
close();
} catch (e) {
console.log(e)
} finally {
setUploading(false);
}
};
if (!visible) {
return null;
}
return (
<Modal
title="数据导入"
open
okButtonProps={{
disabled: fileList.length === 0,
loading: uploading
}}
onOk={handleUpload}
onCancel={() => close()}
>
<Upload
multiple={false}
beforeUpload={file => {
setFileList([file]);
return false;
}}
onRemove={() => {
setFileList([]);
}}
accept={accept}
maxCount={1}
onChange={(info) => {
console.log(info);
}}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Modal>
)
}
export default ImportModal

View File

@ -0,0 +1,48 @@
import { CloseCircleOutlined, CloseOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
import { Button, Modal, Space, Tabs } from 'antd';
import React, { useEffect, useState } from 'react'
const PopupDialog: React.FC<{
onClose: () => void;
width: number;
title: React.ReactNode;
data: any;
render: (data: any, fullScreen: boolean) => React.ReactNode;
}> = ({ data, width, title, onClose, render }) => {
const [fullscreen, setFullscreen] = useState(false);
const FullBtn = fullscreen ? FullscreenExitOutlined : FullscreenOutlined;
if (!data) {
return null;
}
return (
<Modal
open
title={(
<div style={{ display: 'flex', alignItems: 'center' }}>
{title}
<div style={{ flexGrow: 1 }}></div>
<Space>
<FullBtn style={{ cursor: 'pointer' }} onClick={() => setFullscreen(!fullscreen)} />
<CloseOutlined style={{ cursor: 'pointer' }} onClick={() => { onClose(); setFullscreen(false); }} />
</Space>
</div>
)}
className={fullscreen ? 'fullscreen' : undefined}
width={fullscreen ? undefined : width}
onCancel={onClose}
footer={null}
closable={false}
bodyStyle={{ padding: 0 }}
destroyOnClose
maskClosable={false}
>
{data ? render(data, fullscreen) : null}
</Modal>
)
}
export default PopupDialog

View File

@ -0,0 +1,88 @@
import React from 'react'
import ReactEcharts from 'echarts-for-react';
export const colorLight = ['#00B9EF', '#36CBA9', '#6EDC8E', '#FACC14', '#da13d4', '#F04864', '#8543E0'];
export type StatItem = {
name: string;
percent?: number;
value: number;
data?: any;
}
const TagsPieChart: React.FC<{
data: StatItem[];
total: number;
title: string;
loading?: boolean
}> = ({ data, total, title, loading }) => {
const option = React.useMemo(() => {
return {
tooltip: {
trigger: 'item',
formatter: function (item: any) {
const { data } = item;
return `${data.name}: ${data.value}`
}
},
color: colorLight,
series: [
{
name: '',
type: 'pie',
center: ['50%', '50%'],
radius: ['55%', '80%'],
avoidLabelOverlap: false,
minAngle: 3,
itemStyle: {
borderWidth: 2,
borderColor: '#1f5593'
},
label: {
normal: {
show: false,
position: 'center'
},
emphasis: {
show: false,
textStyle: {
fontSize: '18',
fontWeight: 'bold'
}
}
},
labelLine: {
normal: {
show: false
}
},
data: data.map(item => {
if (item.value == 0) {
return null
} else {
return item
}
})
}
]
};
}, [data]);
return (
<div style={{ height: '100%', width: '100%', position: 'relative' }}>
<ReactEcharts
option={option}
style={{ height: '100%', width: '100%' }}
/>
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center' }}>
<div style={{ fontSize: 16, color: '#eee8' }}>{title}</div>
<div style={{ fontSize: 16, color: '#eee', fontWeight: 'bold' }}>{total}</div>
</div>
</div>
)
}
export default TagsPieChart

View File

@ -0,0 +1 @@
export const MAIN_TEXT_CLR = '#3B4859'

View File

@ -0,0 +1,30 @@
import { Button, Popconfirm } from 'antd';
import React from 'react';
import { CrudContext } from './useCrud';
const CancelCrud: React.FC<{
crudCtx: CrudContext<any>,
confirm?: boolean,
text?: string
}> = ({ crudCtx, confirm, text }) => {
text = text || '返回';
if (confirm) {
return (
<Popconfirm title="是否要放弃表单返回列表"
onConfirm={() => {
crudCtx.goto(null, null);
}}
>
<Button size="small" type="link">{text}</Button>
</Popconfirm>
)
} else {
return (
<Button onClick={() => crudCtx.goto(null, null)} size="small" type="link">{text}</Button>
)
}
};
export default CancelCrud

View File

@ -0,0 +1,11 @@
export const DEF_INPUT_LEN = 464;
export const DEF_INPUT_LEN_QUARTS = 92;
export const DEF_LAYOUT = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
export const DEF_TAIL_LAYOUT = {
wrapperCol: { offset: 8, span: 16 },
};

View File

@ -0,0 +1,48 @@
import { DeleteOutlined, EditOutlined, QuestionCircleOutlined, UndoOutlined, FileSearchOutlined } from '@ant-design/icons';
import { Button, Popconfirm, Tooltip } from 'antd'
import React from 'react'
type IProps = {
icon?: React.ReactNode;
danger?: boolean;
text?: string;
tooltip?: string;
onClick?: () => any;
};
const OpButton: React.FC<IProps> = ({ tooltip, icon, danger, text, onClick }) => {
if (tooltip) {
return (
<Tooltip title={tooltip}>
<Button icon={icon} danger={danger} onClick={onClick} type="link" size='small'>{text}</Button>
</Tooltip>
)
}
return (
<Button icon={icon} danger={danger} onClick={onClick} type="link" size='small'>{text}</Button>
)
}
export default OpButton;
type ITypedProps = {
onClick?: () => void;
}
export const DelOpButton: React.FC<ITypedProps> = ({ onClick }) => (
<Popconfirm title="确定删除?" icon={<QuestionCircleOutlined style={{ color: 'red' }} />} onConfirm={onClick}>
<OpButton text='删除' icon={<DeleteOutlined />} danger />
</Popconfirm>
)
export const EditOpButton: React.FC<ITypedProps> = ({ onClick }) =>
<OpButton text='编辑' icon={<EditOutlined />} onClick={onClick} />
export const ViewOpButton: React.FC<ITypedProps> = ({ onClick }) =>
<OpButton text='查看' icon={<FileSearchOutlined />} onClick={onClick} />
export const RestoreOpButton: React.FC<ITypedProps> = ({ onClick }) =>
<OpButton text='恢复' icon={<UndoOutlined />} onClick={onClick} />

View File

@ -0,0 +1,44 @@
import { useState } from "react";
export type BasicCrudType = 'add' | 'edit' | 'view';
export const CRUD_NAMES = {
add: '新增',
edit: '编辑'
}
export type CrudContext<modes = any> = {
mode: modes | null;
record: any;
goto: (mode: modes | null, r: any) => void;
loading: boolean;
setLoading: (val: boolean) => void;
}
export default function useCrud<modes = any>(params?: {
mode?: modes;
record?: any;
}): CrudContext<modes> {
const [loading, setLoading] = useState(false);
const [state, setState] = useState<{
mode: modes | null;
record: any;
}>({
mode: params?.mode || null,
record: params?.record,
})
const goto = (mode: modes | null, record: any) => {
setState({ mode, record })
};
return {
...state,
goto,
loading,
setLoading,
}
}

View File

@ -0,0 +1,142 @@
import { TablePaginationConfig, TableProps } from 'antd';
import { useEffect, useReducer, useRef } from 'react';
export type PageResult<T> = {
list: T[];
totalRow: number;
}
export type SearchOption = {
pageSize?: number;
pageNumber?: number;
search?: { [key: string]: any };
sortField?: string;
sortOrder?: 'asc' | 'desc';
}
type TableState<T> = {
abort: boolean;
data: T[];
total: number;
loading: boolean;
pageSize: number;
pageNumber: number;
search: { [key: string]: string };
sortField?: string;
sortOrder?: 'asc' | 'desc';
}
export type PageTableContext<T> = {
tableProps: TableProps<T>,
search: (params?: SearchOption) => void,
refresh: () => void,
initialSearch?: { [key: string]: string },
noRender: (index: number) => number;
searchParams: { [key: string]: string; }
}
function usePageTable<T>(
service: (params?: SearchOption) => Promise<PageResult<T>>,
options: SearchOption = {}
): PageTableContext<T> {
const [, forceRender] = useReducer(s => s + 1, 1);
const state = useRef<TableState<T>>({
abort: false,
data: [],
total: 0,
loading: false,
pageSize: options.pageSize ?? 10,
pageNumber: options.pageNumber ?? 1,
sortField: options.sortField,
sortOrder: options.sortOrder,
search: options.search || {},
});
useEffect(() => {
const stateRef = state.current;
stateRef.abort = false;
search();
return () => { stateRef.abort = true; }
}, []);
const search = (opt: SearchOption = {}) => {
const s = state.current;
if (s.loading || s.abort) {
return;
}
s.loading = true;
forceRender();
const pageParams = {
pageNumber: opt?.pageNumber ?? s.pageNumber,
pageSize: opt?.pageSize ?? s.pageSize,
sortField: opt?.sortField ?? s.sortField,
sortOrder: opt?.sortOrder ?? s.sortOrder,
search: opt?.search ?? s.search,
};
service(pageParams).then((data) => {
if (!s.abort) {
s.search = pageParams.search;
s.sortField = pageParams.sortField;
s.sortOrder = pageParams.sortOrder;
s.pageNumber = pageParams.pageNumber;
s.pageSize = pageParams.pageSize;
s.data = data.list;
s.total = data.totalRow;
s.loading = false;
forceRender();
}
});
}
const handleTableChange = (pagination: any, filters: any, sort: any) => {
const { field, order } = sort;
if (field && order) {
const sortOrder: any = order === 'ascend' ? 'asc' : 'desc';
search({ pageNumber: pagination.current, pageSize: pagination.pageSize, sortOrder, sortField: field });
return;
}
search({ pageNumber: pagination.current, pageSize: pagination.pageSize });
}
return {
tableProps: {
bordered: true,
dataSource: state.current.data,
pagination: {
pageSize: state.current.pageSize,
current: state.current.pageNumber,
total: state.current.total,
...DefaultPaginationParam,
},
loading: state.current.loading,
onChange: handleTableChange,
},
search: (params?: SearchOption) => search({ ...params, pageNumber: 1 }),
refresh: search,
initialSearch: options?.search,
noRender: (index) => (state.current.pageNumber - 1) * state.current.pageSize + (index + 1),
searchParams: state.current.search,
}
}
export default usePageTable;
export const DefaultPaginationParam: TablePaginationConfig = {
showTotal: (total: number, range: [number, number]) => `${range[0]}-${range[1]}${total}`,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
size: 'small'
}

171
src/config/chart.ts Normal file
View File

@ -0,0 +1,171 @@
export const timeAxisX = {
type: 'time',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#1187cd',
},
},
axisLabel: {
interval: 0,
fontSize: 14,
},
splitLine: {
show: true,
lineStyle: {
color: '#1187cd44',
type: [5, 5]
},
},
}
export const enumAxisX = (values: string[], formatter?: boolean) => {
return {
data: values,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#1187cd',
},
},
splitLine: {
show: true,
lineStyle: {
color: '#1187cd44',
type: [5, 5]
},
},
axisLabel: {
fontSize: 18,
formatter: formatter === false ? undefined : charLongNameFormatter
},
}
}
export const unitAsNameY = (unit: string, right?: boolean) => {
return {
name: unit,
nameGap: -6,
nameTextStyle: {
padding: right ? [0, 24, 0, 0] : [0, 0, 0, 16],
},
}
}
export const charLongNameFormatter = (value: string) => {
var strs = value.split('');
let ret = '';
for (let i = 0; strs[i]; i += 1) {
if (i > 0) {
ret += '\n';
}
ret += strs[i];
}
return ret;
}
export const valueAxisY = {
type: 'value',
splitLine: {
show: true,
lineStyle: {
color: '#1187cd44',
type: [5, 5]
},
},
axisLabel: {
margin: 20,
fontSize: 12,
},
axisTick: {
show: false
},
nameTextStyle: {
fontSize: 12,
},
}
export const valueAxisYNoLabel = {
type: 'value',
splitLine: {
show: true,
lineStyle: {
color: '#1187cd44',
type: [5, 5]
},
},
axisLabel: {
show: false,
},
axisTick: {
show: false
},
}
export const minmaxMarkPoint = {
label: {
color: '#fff',
},
data: [
{ type: 'max', name: 'Max', label: { fontSize: 18, textBorderColor: '#000', textBorderWidth: 1 } },
{ type: 'min', name: 'Min', label: { fontSize: 18, textBorderColor: '#000', textBorderWidth: 1 } }
]
}
export const labelLegend = {
textStyle: {
fontSize: 16,
},
itemWidth: 24,
itemHeight: 14,
}
export const containLabelGrid = (top?: number) => {
return {
left: 10,
right: 10,
bottom: 10,
top: top || 30,
containLabel: true,
}
}
export const smoothAreaLine = (colorSharpFormat: string) => {
return {
type: 'line',
color: colorSharpFormat,
smooth: true,
symbol: 'none'
}
}
export const smoothAreaLineEx = (colorSharpFormat: string) => {
return {
type: 'line',
color: colorSharpFormat,
smooth: true,
}
}
export const gradientBarWithLabel = {
type: 'bar',
label: {
show: true,
position: 'top',
textBorderWidth: 0,
fontSize: 12,
}
}

45
src/config/consts.tsx Normal file
View File

@ -0,0 +1,45 @@
export type objEnum = {
[key: string]: any,
}
export const systype: objEnum = {
0: '目录',
1: '菜单',
2: '按钮'
};
export const permissionType: objEnum = {
0: 'WEB端',
1: 'APP端'
};
export const hiddenType: objEnum = {
0: '显示',
1: '隐藏'
};
export const userStatus: objEnum = {
0: '启用',
1: '禁用'
};
export const levelType: objEnum = {
1: '一般',
2: '重要',
3: '紧急'
};
export const messageStatus: objEnum = {
1: '已发送',
2: '已读'
};
export const sysLoginLogStatus: objEnum = {
0: '成功',
1: '失败',
};
export const sysLoginLogType: objEnum = {
1: 'WEB端',
2: 'App端',
};

28
src/config/index.ts Normal file
View File

@ -0,0 +1,28 @@
export const TITLE = ''
export const SUBTITLE = '';
const address = 'zhsz';
const extent: [number, number, number, number] = [
111.4151642480001101, 30.2719341291568753, 112.5877217125527068, 31.7067788077957857
];
const ADCD6 = '420802';
const NOAUTH_REDIRECT = 'http://219.138.108.99:30046/user/login?redirect=';
export const config = {
address,
title: '大坝安全监测',
extent,
ADCD6,
transitionDuaration: 500,
NOAUTH_REDIRECT
};
export const CTRL_DLG_WIDTH = 1800;
export const CHAN_DLG_WIDTH = 1200;
export const SM4_KEY = 'zo7v0xb9erg2pwqc';

57
src/index.less Normal file
View File

@ -0,0 +1,57 @@
@import '~antd/dist/antd.less';
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-y: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
#root {
min-width: 1280px;
}
.flex-grow-1 {
flex-grow: 1;
}
@font-face {
font-family: "Password Dots Regular";
src: url('./assets/fonts/password.ttf');
}
*,
:after,
:before {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
::-webkit-scrollbar {
height: 8px;
width: 8px;
}
::-webkit-scrollbar-thumb {
background: #d6dde9;
border-radius: 4px;
border: 1px solid #c4cfe1;
}
::-webkit-scrollbar-track {
background-color: #f6f6f6;
}
::selection {
color: #fff;
background: #3b7cff;
}

25
src/index.tsx Normal file
View File

@ -0,0 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import 'moment/locale/zh-cn';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import { store } from './models/store';
import AppRouters from './views/AppRouters';
import './index.less';
import './components/ant_override.less'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<HashRouter>
<ConfigProvider locale={zhCN}>
<AppRouters />
</ConfigProvider>
</HashRouter>
</Provider>
);

74
src/models/_/defs.ts Normal file
View File

@ -0,0 +1,74 @@
export type MenuItem = {
id: string;
title: string;
path?: string;
redirect?: string;
icon?: string;
children?: MenuItem[];
}
export type SysUserItem = {
: string;
: string;
: string;
: string;
: string;
: string;
}
export type RoleItem = {
: string;
: string;
: string;
}
export type SysPermission = {
id: number;
title: string;
path?: string;
parentId?: string | number;
parentTitle?: string;
parentName?: string;
pType?: number;
type?: number;
perms?: string;
redirect?: string;
name?: string;
level?: number;
children?: SysPermission[];
orderNum?: number;
component?: string;
hidden?: number;
icon?: string;
createBy?: string;
createTime?: Date;
actionEntitySet?: any;
actions?: string;
}
export type DepartmentItem = {
: string;
: string;
: string;
: string;
: string;
: string;
}
export type DictItem = {
"id": number,
"val": string,
"label": string,
"orderNum": number,
"parentId": number,
"parentName": string,
"children": {
"id": 37,
"val": "4",
"label": "其它",
"createBy": string,
"createTime": string
}[],
"createBy": string,
"createTime": string
}

85
src/models/auth/_.ts Normal file
View File

@ -0,0 +1,85 @@
import { message } from "antd";
import { MenuItem } from "../_/defs";
export type LoginUser = {
id: number;
loginName: string;
name: string;
roleList: string[];
tokenInfo: {
tokenValue: string;
};
}
export type AuthState = {
user: LoginUser | null | 'failed';
menu: MenuItem[];
}
export const USER_SESSION_KEY = '__usereinfo__';
export function removeLoginInfo() {
sessionStorage.removeItem(USER_SESSION_KEY);
sessionStorage.removeItem('TOKEN');
}
export function setLoginInfo(userInfo: string, token: string) {
sessionStorage.setItem(USER_SESSION_KEY, userInfo);
sessionStorage.setItem('TOKEN', token);
}
export function getUserFromSession(): LoginUser | null {
const strUser = sessionStorage.getItem(USER_SESSION_KEY);
if (!strUser) {
return null;
}
try {
const obj: LoginUser = JSON.parse(strUser);
if (obj.id && obj.tokenInfo.tokenValue) {
return obj;
}
} catch (e) { }
return null
}
export async function regByToken(token: string): Promise<LoginUser | undefined> {
const result: LoginUser | null = {
id: 1,
loginName: 'admin',
name: '管理员',
roleList: [],
tokenInfo: {
tokenValue: 'demo-token'
},
}
setLoginInfo(JSON.stringify(result), result.tokenInfo?.tokenValue);
return result;
}
export async function login(form: { username: string, password: string }): Promise<LoginUser | undefined> {
message.error('登陆失败');
return;
}
export function loadMenu(user: LoginUser): MenuItem[] {
const id = (() => {
let id = 1;
return () => `${id++}`
})();
return [
{ id: id(), title: '综合首页', path: '/mgr/zhsy', icon:"zhsy-icon.png" },
{ id: id(), title: '水源管理', path: '/mgr/sygl', icon:"szygl-icon.png" },
{ id: id(), title: '智慧水厂 ', path: '/mgr/zhsc', icon:"zhsc-icon.png" },
{ id: id(), title: '智慧管网', path: '/mgr/zhgw', icon:"zhgw-icon.png" },
{ id: id(), title: '用户服务', path: '/mgr/yhfw', icon:"yhfw-icon.png" },
].filter(Boolean);
}
export function defaultHomePage() {
return '/mgr/stinfo';
}

61
src/models/auth/index.ts Normal file
View File

@ -0,0 +1,61 @@
import { createModel } from "@rematch/core";
import { RootModel } from "..";
import { MenuItem } from "../_/defs";
import { AuthState, getUserFromSession, loadMenu, login, LoginUser, regByToken, removeLoginInfo } from "./_";
export const auth = createModel<RootModel>()({
state: {
user: getUserFromSession(),
menu: [],
} as AuthState,
reducers: {
setUser(state, user: LoginUser | null | 'failed'): AuthState {
return { ...state, user };
},
setMenu(state, menu: MenuItem[]): AuthState {
return { ...state, menu };
},
},
effects: {
async regByToken(token: string): Promise<boolean> {
const result = await regByToken(token);
if (result) {
this.setUser(result);
sessionStorage.setItem('__useToken', '1');
return true;
}
this.setUser('failed');
return false;
},
async login(form: { username: string, password: string }): Promise<boolean> {
const result = await login(form);
if (result) {
this.setUser(result);
sessionStorage.setItem('__useToken', '0');
return true;
}
return false;
},
logout() {
removeLoginInfo();
this.setUser(null);
},
async loadMenu(payload, s) {
if (s.auth.user && s.auth.user !== 'failed') {
this.setMenu(loadMenu(s.auth.user));
}
}
}
});

61
src/models/auth/menu.ts Normal file
View File

@ -0,0 +1,61 @@
import { MenuItem } from "../_/defs";
export function findMenu(menus: MenuItem[], pathname: string) {
if (!menus) {
return [];
}
for (const m1 of menus) {
if (m1.path === pathname) {
return [m1.id];
}
if (m1.children && m1.children.length) {
for (const m2 of m1.children) {
if (m2.path === pathname) {
return [m1.id, m2.id];
}
if (m2.children && m2.children.length) {
for (const m3 of m2.children) {
if (m3.path === pathname) {
return [m1.id, m2.id, m3.id];
}
}
}
}
}
}
return [];
}
export function findLeafMenu(menus: MenuItem[], pathname: string) {
if (!menus) {
return null;
}
for (const m1 of menus) {
if (m1.path === pathname) {
return m1;
}
if (m1.children && m1.children.length) {
for (const m2 of m1.children) {
if (m2.path === pathname) {
return m2;
}
if (m2.children && m2.children.length) {
for (const m3 of m2.children) {
if (m3.path === pathname) {
return m3;
}
}
}
}
}
}
return null;
}

10
src/models/index.ts Normal file
View File

@ -0,0 +1,10 @@
import { Models } from "@rematch/core"
import { auth } from "./auth";
export interface RootModel extends Models<RootModel> {
auth: typeof auth;
}
export const models: RootModel = {
auth,
};

10
src/models/store.ts Normal file
View File

@ -0,0 +1,10 @@
import { init, RematchDispatch, RematchRootState } from '@rematch/core'
import { models, RootModel } from './index'
export const store = init({
models,
})
export type Store = typeof store
export type Dispatch = RematchDispatch<RootModel>
export type RootState = RematchRootState<RootModel>

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

34
src/utils/cachepromise.ts Normal file
View File

@ -0,0 +1,34 @@
export default class CachePromise<DataType = any> {
expireTime: number;
curTm: number = 0;
promise: Promise<DataType> | undefined;
pf: () => Promise<DataType>;
constructor(pf: () => Promise<DataType>, expireTime?: number) {
this.pf = pf;
this.expireTime = expireTime || 0;
}
setNull = () => {
this.promise = undefined;
}
get = async () => {
const now = Date.now();
if (this.expireTime && now - this.curTm > this.expireTime) {
this.promise = this.pf();
this.curTm = now;
}
if (!this.promise) {
this.promise = this.pf();
this.curTm = now;
}
const data = await this.promise;
if (!data) {
this.setNull();
}
return data;
}
}

72
src/utils/common.ts Normal file
View File

@ -0,0 +1,72 @@
export function wait(t?: number, data?: any): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, t || 1000);
})
}
export function getBasePath(): string {
if (process.env.PUBLIC_URL.startsWith('http')) {
return process.env.PUBLIC_URL;
} else {
return window.location.origin + process.env.PUBLIC_URL;
}
}
export function parseGeoJSONFeature(data: any): any {
if (typeof data.lgtd === 'number' && typeof data.lttd === 'number') {
return {
type: "Feature",
properties: data,
geometry: {
type: "Point",
coordinates: [data.lgtd, data.lttd]
}
};
} else if (data.geometry) {
let geometryObj = data.geometry;
if (typeof geometryObj === 'string') {
try {
geometryObj = JSON.parse(geometryObj);
} catch (e) {
geometryObj = null;
}
}
if (geometryObj?.type) {
return {
type: "Feature",
properties: data,
geometry: geometryObj
}
}
} else {
return {
type: "Feature",
properties: data,
};
}
}
export function parseGeoJSON(data: any): any {
const ret: any = {
type: "FeatureCollection",
crs: {
type: "name",
properties: {
name: "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
features: []
};
if (Array.isArray(data)) {
ret.features = data.map(parseGeoJSONFeature);
}
return ret;
}

161
src/utils/request.ts Normal file
View File

@ -0,0 +1,161 @@
import { message } from "antd";
import axios, { AxiosRequestConfig } from "axios";
import { SearchOption } from "../components/crud/usePageTable";
// 此文件根据后端接口的实际规则进行调整
export const XINDA_TOKEN = 'XINDA_TOKEN';
let lastLoginErrorMsg = 0;
function loginError(data: any) {
const now = Date.now();
if (now - lastLoginErrorMsg < 3000) {
return;
}
lastLoginErrorMsg = now;
if (
data.msg.startsWith('Token已被顶下线') ||
data.msg.startsWith('Token已被踢下线')
) {
message.error('该账号已在别处登录');
} else if (
data.msg.startsWith('Token已过期')
) {
message.error('登陆已过期,请重新登陆');
} else {
message.error('登陆信息异常,请重新登陆');
}
}
export async function paginate(url: string, params: SearchOption = {}) {
const { search, ...pagerParams } = params;
const reqParams: any = {
...search,
...pagerParams
};
try {
const data = await httpPost(url, reqParams);
if (!data) {
message.error('请求失败')
return { list: [], totalRow: 0 }
}
return { list: data.records || [], totalRow: data.total ?? 0 }
} catch (e) {
return { list: [], totalRow: 0 }
}
}
export async function paginateDemo(fileName: string, params: SearchOption = {}) {
const data = await fetch(`${process.env.PUBLIC_URL}/demodata/${fileName}`).then(resp => resp.json());
if (!data) {
message.error('请求失败')
return { list: [], totalRow: 0 }
}
return { list: data.records || [], totalRow: data.total ?? 0 }
}
export async function fetchListDemo(fileName: string) {
const data = await fetch(`${process.env.PUBLIC_URL}/demodata/${fileName}`).then(resp => resp.json());
if (!data) {
message.error('请求失败')
return []
}
return data;
}
export async function fetchObjectDemo(fileName: string) {
const data = await fetch(`${process.env.PUBLIC_URL}/demodata/${fileName}`).then(resp => resp.json());
if (!data) {
message.error('请求失败')
return {}
}
return data;
}
export async function httpPost(url: string, params: Object | string, config?: AxiosRequestConfig<Object>) {
try {
// const token = sessionStorage.getItem('TOKEN');
const resp = await axios.post(url, params, config);
const data = resp.data;
if (data.code === 200) {
return data.data || true;
}
/*
if (data.code === 401) {
const dispatch: Dispatch | undefined = (window as any).__dispatch__;
if (dispatch) {
dispatch.auth.logout();
}
}
*/
if (data.code === 401 && data.msg) {
loginError(data);
} else if (typeof data.msg === 'string') {
message.error(data.msg);
}
return null;
} catch (e) {
console.log(e);
return null;
}
}
export async function httpGet(url: string, params?: any) {
try {
// const token = sessionStorage.getItem('TOKEN');
const resp = await axios.get(url, {
params,
// headers: { Authorization: `Bearer ${token}` }
});
const data = resp.data;
if (data.code === 200) {
return data.data;
}
if (data.code === 401 && data.msg) {
loginError(data);
} else if (typeof data.msg === 'string') {
message.error(data.msg);
}
/*
if (data.code === 401) {
const dispatch: Dispatch | undefined = (window as any).__dispatch__;
if (dispatch) {
dispatch.auth.logout();
}
}
*/
return null;
} catch (e) {
console.log(e);
return null;
}
}
export const download = async (url: string, params: any) => {
let parts = [];
if (typeof params !== 'string') {
for (const key in params) {
const value = params[key];
parts.push(`${key}=${encodeURIComponent(`${value}`)}`);
}
} else {
parts.push(params);
}
console.log(`${url}?${parts.join('&')}`);
window.open(`${url}?${parts.join('&')}`);
}

24
src/utils/useRefresh.ts Normal file
View File

@ -0,0 +1,24 @@
import { useState, useEffect } from "react";
const useRefresh = (interval: number, params?: any) => {
const [t, setT] = useState(Date.now());
useEffect(() => {
let h: any = null;
if (interval) {
h = setInterval(() => {
setT(Date.now());
}, interval);
}
return () => {
if (interval) {
clearInterval(h);
}
};
}, [interval, params]);
return t;
};
export default useRefresh;

51
src/utils/useRequest.ts Normal file
View File

@ -0,0 +1,51 @@
import { useState, useEffect, useRef, useReducer } from "react";
export type RequestContext<DataType> = {
data?: DataType;
error?: any;
loading: boolean;
refresh: () => void;
}
/**
* @param p
* @returns
* @todo deal refresh when loading
*/
const useRequest = <DataType = any>(p: () => Promise<DataType>, dependences?: any[]): RequestContext<DataType> => {
const [data, setData] = useState<DataType | undefined>()
const [error, setError] = useState<any>();
const [loading, setLoading] = useState(false);
const abort = useRef(false);
const [_, refresh] = useReducer(s => s + 1, 0);
useEffect(() => {
const doFetch = async () => {
setLoading(true);
abort.current = false;
try {
const data = await p();
if (!abort.current) {
setData(data);
}
} catch (e) {
if (!abort.current) {
setError(e);
}
} finally {
if (!abort.current) {
setLoading(false);
}
}
};
doFetch();
return () => {
abort.current = true;
};
}, dependences ? [...dependences, _] : [_]);
return { data, error, loading, refresh };
};
export default useRequest;

13
src/utils/useSize.ts Normal file
View File

@ -0,0 +1,13 @@
import useResizeObserver from '@react-hook/resize-observer';
import React from 'react';
export default function useSize(target: React.RefObject<HTMLElement>) {
const [size, setSize] = React.useState<DOMRect>();
React.useLayoutEffect(() => {
setSize(target.current?.getBoundingClientRect())
}, [target]);
useResizeObserver(target, (entry) => setSize(entry.contentRect))
return size
}

105
src/utils/utils.ts Normal file
View File

@ -0,0 +1,105 @@
import moment from "moment";
export function changeObjectStringToMoment(obj: { [key: string]: any }, fields: string[]): any {
const ret = { ...obj };
for (const f of fields) {
if (f in ret && typeof ret[f] === 'string') {
ret[f] = moment(ret[f]);
}
}
return ret;
}
export async function base64FromFile(file: File): Promise<string | undefined> {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const str = reader.result;
if (typeof str === 'string') {
resolve(str);
}
resolve(undefined);
}
reader.onerror = () => {
resolve(undefined);
}
})
}
export const normFile = (e: any) => {
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
export function randNum(a: number, b: number) {
const ret = a * Math.random() + b;
return parseFloat(ret.toFixed(2));
}
export function getParameter(param: string) {
var query = window.location.hash;
var iLen = param.length;
var iStart = query.indexOf(param);
if (iStart == -1)
return "";
iStart += iLen + 1;
var iEnd = query.indexOf("&", iStart);
if (iEnd == -1)
return query.substring(iStart);
return query.substring(iStart, iEnd);
}
export function toFixed3(val: any) {
if (typeof val !== 'number') {
return '-';
}
return val.toFixed(3);
}
export function renTm(val: any) {
if (!val) {
return '-'
}
return moment(val).format('MM-DD HH:mm:ss')
}
export function dpContainerTransform(containerWidth: number, containerHeight: number, size?: DOMRect): undefined | string {
if (!size) {
return;
}
const sx = size.width / containerWidth;
const sy = size.height / containerHeight;
const scale = sx < sy ? sx : sy;
if (!scale) {
return
}
const offx = containerWidth * (sx - scale) * 0.5;
const offy = containerHeight * (sy - scale) * 0.5;
return `scale(${scale.toFixed(3)}) translate(${offx.toFixed(3)}px, ${offy.toFixed(3)}px)`;
}
export function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
if (value === null || value === undefined) return false;
return true;
}
export function getBasePath(): string {
if (process.env.PUBLIC_URL.startsWith('http')) {
return process.env.PUBLIC_URL;
} else {
return window.location.origin + process.env.PUBLIC_URL;
}
}

42
src/views/AppRouters.tsx Normal file
View File

@ -0,0 +1,42 @@
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Navigate, useRoutes } from 'react-router';
import DashboardLayout from '../components/DashboardLayout';
import { Dispatch } from '../models/store';
import NotFound from './NotFound';
import ZhsyPage from './Zhsy';
import SyglPage from './Sygl';
import ZhscPage from './Zhsc';
import ZhgwPage from './Zhgw';
import YhfwPage from './Yhfw';
const AppRouters: React.FC = () => {
const dispatch = useDispatch<Dispatch>();
useEffect(() => {
(window as any).__dispatch__ = dispatch;
return () => {
delete (window as any).__dispatch__;
}
}, [dispatch]);
let element = useRoutes([
{ path: '/', element: <Navigate to="/mgr/sygl" /> },
{
path: '/mgr', element: <DashboardLayout />, children: [
{ path: 'zhsy', element: <ZhsyPage /> },
{ path: 'sygl', element: <SyglPage /> },
{ path: 'zhsc', element: <ZhscPage /> },
{ path: 'zhgw', element: <ZhgwPage /> },
{ path: 'yhfw', element: <YhfwPage /> },
{ path: '*', element: <NotFound /> },
]
},
{ path: '*', element: <NotFound /> },
]);
return element;
};
export default AppRouters

View File

@ -0,0 +1,8 @@
const NotFound = () => {
return (
<div >NotFound</div>
)
}
export default NotFound

View File

14
src/views/Sygl/index.tsx Normal file
View File

@ -0,0 +1,14 @@
import {Modal, Tabs, Switch} from 'antd'
import React, {useMemo, useState} from 'react';
import "./index.less"
const SyglPage: React.FC = () => {
return (
<div className=''>
</div>
)
}
export default SyglPage

View File

14
src/views/Yhfw/index.tsx Normal file
View File

@ -0,0 +1,14 @@
import {Modal, Tabs, Switch} from 'antd'
import React, {useMemo, useState} from 'react';
import "./index.less"
const SyglPage: React.FC = () => {
return (
<div className=''>
</div>
)
}
export default SyglPage

View File

14
src/views/Zhgw/index.tsx Normal file
View File

@ -0,0 +1,14 @@
import {Modal, Tabs, Switch} from 'antd'
import React, {useMemo, useState} from 'react';
import "./index.less"
const SyglPage: React.FC = () => {
return (
<div className=''>
</div>
)
}
export default SyglPage

View File

14
src/views/Zhsc/index.tsx Normal file
View File

@ -0,0 +1,14 @@
import {Modal, Tabs, Switch} from 'antd'
import React, {useMemo, useState} from 'react';
import "./index.less"
const SyglPage: React.FC = () => {
return (
<div className=''>
</div>
)
}
export default SyglPage

View File

14
src/views/Zhsy/index.tsx Normal file
View File

@ -0,0 +1,14 @@
import {Modal, Tabs, Switch} from 'antd'
import React, {useMemo, useState} from 'react';
import "./index.less"
const SyglPage: React.FC = () => {
return (
<div className=''>
</div>
)
}
export default SyglPage

26
tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}