咸丰县农村安全饮水智慧平台
|
|
@ -0,0 +1 @@
|
|||
PUBLIC_URL=/xyt_dam
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
GENERATE_SOURCEMAP=false
|
||||
PUBLIC_URL=http://local.gunshiiot.com:19080/dam
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 668 B |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 856 B |
|
After Width: | Height: | Size: 333 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -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 |
|
After Width: | Height: | Size: 6.4 KiB |
|
|
@ -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 |
|
After Width: | Height: | Size: 466 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 325 B |
|
After Width: | Height: | Size: 319 B |
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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 星期五</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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
*/
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const MAIN_TEXT_CLR = '#3B4859'
|
||||
|
|
@ -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
|
||||
|
|
@ -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 },
|
||||
};
|
||||
|
|
@ -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} />
|
||||
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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端',
|
||||
};
|
||||
|
|
@ -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';
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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('&')}`);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
<div >NotFound</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||