初始化提交

master
李神峰 2025-06-27 17:15:19 +08:00
commit e798267abc
164 changed files with 37714 additions and 0 deletions

1
.env.development Normal file
View File

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

3
.env.production Normal file
View File

@ -0,0 +1,3 @@
GENERATE_SOURCEMAP=false
# PUBLIC_URL=http://local.gunshiiot.com:19080/recieve
PUBLIC_URL=/xyt/recieve

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

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

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/decst.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
public/assets/icons2/bz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

BIN
public/assets/icons2/hz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

BIN
public/assets/icons2/sx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/assets/icons2/wx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/assets/rzbw/bw1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/assets/rzbw/bw10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/assets/rzbw/bw2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
public/assets/rzbw/bw3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/assets/rzbw/bw4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/assets/rzbw/bw5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/assets/rzbw/bw6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/assets/rzbw/bw7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
public/assets/rzbw/bw8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/assets/rzbw/bw9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/assets/u945.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

6
public/demodata/1.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"水位雨量站","管理单位":"","测站编码":"ZH201509"}],
"total": 10,
"pages": 1
}

6
public/demodata/2.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 17:06:08","数据(m)":"191.147"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-3117:00:00","数据(m)":"191.147"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 17:00:00","数据(m)":"191.138"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 16:05:22","数据(m)":"191.138"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 16:00:00","数据(m)":"191.138"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 16:00:00","数据(m)":"191.120"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 15:05:18","数据(m)":"191.120"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 15:00:00","数据(m)":"191.120"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 15:00:00","数据(m)":"191.108"},{"站名":"水位雨量站","测站编码":"ZH201509","时间":"2023-07-31 14:05:25","数据(m)":"191.108"}],
"total": 10,
"pages": 1
}

6
public/demodata/3.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"输水管流量站","管理单位":"","测站编码":"4211221029"},{"站名":"供水管流量站","管理单位":"","测站编码":"4211221030"},{"站名":"生态流量站","管理单位":"","测站编码":"4208820100"}],
"total": 10,
"pages": 1
}

6
public/demodata/4.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 17:06:08","通道":"1","数据(m³/s)":"5.958"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 17:00:00","通道":"1","数据(m³/s)":"6.155"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 17:00:00","通道":"1","数据(m³/s)":"5.831"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-3116;05;22","通道":"1","数据(m³/s)":"6.022"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 16:00:00","通道":"1","数据(m³/s)":"1. 088"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 16:00:00","通道":"1","数据(m³/s)":"0"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 15:05:18","通道":"1","数据(m³/s)":"0"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-3115;0000","通道":"1","数据(m³/s)":"0"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 15:00:00","通道":"1","数据(m³/s)":"0"},{"站名":"输水管流量站","测站编码":"4208820100","时间":"2023-07-31 14:05:25","通道":"1","数据(m³/s)":"0"}],
"total": 10,
"pages": 1
}

136
public/demodata/4_1.json Normal file
View File

@ -0,0 +1,136 @@
{
"records": [
{
"序号": "1",
"站名": "水位雨量站",
"管理单位": "",
"测站编码": "0020230509",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "0",
"修正值": "-0.01",
"站类": "水库水文站",
"归属协议": "水资源协议",
"创建日期#": "2023-05-15"
},
{
"序号": "2",
"站名": "输水管流量站",
"管理单位": "",
"测站编码": "0020230430",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "",
"修正值": "",
"站类": "流量站",
"归属协议": "水库供水",
"创建日期#": "2023-05-05"
},
{
"序号": "3",
"站名": "供水管流量站",
"管理单位": "",
"测站编码": "0020230429",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "",
"修正值": "",
"站类": "流量站",
"归属协议": "水库供水",
"创建日期#": "2023-05-04"
},
{
"序号": "4",
"站名": "生态流量站",
"管理单位": "",
"测站编码": "4220230428",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "",
"修正值": "",
"站类": "流量站",
"归属协议": "水库供水",
"创建日期#": "2023-04-28"
},
{
"序号": "5",
"站名": "SY01",
"管理单位": "",
"测站编码": "0020230426",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "",
"修正值": "",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-04-26"
},
{
"序号": "6",
"站名": "SY02",
"管理单位": "",
"测站编码": "000000036",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "",
"修正值": "",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-04-17"
},
{
"序号": "7",
"站名": "SY03",
"管理单位": "",
"测站编码": "0020230414",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "0",
"修正值": "",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-04-14"
},
{
"序号": "8",
"站名": "SY04",
"管理单位": "",
"测站编码": "4208820200",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "108.487",
"修正值": "0.06",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-04-04"
},
{
"序号": "9",
"站名": "SY05",
"管理单位": "",
"测站编码": "0020230325",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "0",
"修正值": "",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-03-25"
},
{
"序号": "10",
"站名": "SY06",
"管理单位": "",
"测站编码": "4202023102",
"经度": "112.107041",
"纬度": "30.961038",
"基础准高程": "108.42",
"修正值": "",
"站类": "渗压站",
"归属协议": "水资源协议",
"创建日期#": "2023-03-14"
}
],
"total": 10,
"pages": 1
}

6
public/demodata/5.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 17:06:08","通道":"1","数据(m³/s)":"5.958"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 17:00:00","通道":"1","数据(m³/s)":"6.155"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 17:00:00","通道":"1","数据(m³/s)":"5.831"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-3116;05;22","通道":"1","数据(m³/s)":"6.022"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 16:00:00","通道":"1","数据(m³/s)":"1. 088"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 16:00:00","通道":"1","数据(m³/s)":"0"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 15:05:18","通道":"1","数据(m³/s)":"0"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-3115;0000","通道":"1","数据(m³/s)":"0"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 15:00:00","通道":"1","数据(m³/s)":"0"},{"站名":"站名1","测站编码":"4208820100","时间":"2023-07-31 14:05:25","通道":"1","数据(m³/s)":"0"}],
"total": 10,
"pages": 1
}

6
public/demodata/6.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 17:00:00","通道号":"1","数据(m)":"0.09"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 17:00:00","通道号":"1","数据(m)":"0.00"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 17:00:00","通道号":"1","数据(m)":"0.63"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-3117;00:00","通道号":"1","数据(m)":"0.00"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 16:0000","通道号":"1","数据(m)":"0.09"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 16:00:00","通道号":"1","数据(m)":"0.00"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 16:00:00","通道号":"1","数据(m)":"0.63"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 16:00:00","通道号":"1","数据(m)":"0.00"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 15:00:00","通道号":"1","数据(m)":"0.09"},{"站名":"闸位1","站码":"4263630189","时间":"2023-07-31 15:00:00","通道号":"1","数据(m)":"0.00"}],
"total": 10,
"pages": 1
}

6
public/demodata/7.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"闸位1","管理单位":"","站码":"4263630189"},{"站名":"闸位2","管理单位":"","站码":"4263630188"},{"站名":"闸位3","管理单位":"","站码":"4265630196"},{"站名":"闸位4","管理单位":"","站码":"4265630197"},{"站名":"闸位5","管理单位":"","站码":"4262630190"},{"站名":"闸位6","管理单位":"","站码":"4262630192"},{"站名":"闸位7","管理单位":"","站码":"2023040001"},{"站名":"闸位8","管理单位":"","站码":"2023040002"},{"站名":"闸位9","管理单位":"","站码":"2023040003"},{"站名":"闸位10","管理单位":"","站码":"2023040004"}],
"total": 10,
"pages": 1
}

6
public/demodata/8.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"水位雨量站","测站编码":"4211221029","采集项":"电压0"},{"站名":"输水管流量站","测站编码":"4211221030","采集项":"电压1"},{"站名":"供水管流量站","测站编码":"4208820100","采集项":"电压_ 1"},{"站名":"生态流量站","测站编码":"4211223302","采集项":"电压0"}],
"total": 10,
"pages": 1
}

6
public/demodata/9.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 17:06:08","数据(v)":"12.08"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 17:00:00","数据(v)":"12.07"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-311700:00","数据(v)":"12.13"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 16:0522","数据(v)":"12.11"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 16:00:00","数据(v)":"12.14"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 16:00:00","数据(v)":"12.13"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 15:05:18","数据(v)":"12.11"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 15:00:00","数据(v)":"12.11"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 15:00:00","数据(v)":"12.09"},{"站名":"水位雨量站","测站编码":"1000000007","时间":"2023-07-31 14:05:25","数据(v)":"12.1"}],
"total": 10,
"pages": 1
}

6
public/demodata/dam.json Normal file
View File

@ -0,0 +1,6 @@
{
"records":
[{"序号":"1","站名":"水位雨量站","测站编码":"0020230509","发送时间":"2023-08-01 10:02.10","接收时间":"2023-08-01 10:02:10","归属协议":"水资源协议"},{"序号":"2","站名":"输水管流量站","测站编码":"0020230430","发送时间":"2023-08-01 10:02:10","接收时间":"2023-08-01 10:02:10","归属协议":"水库供水"},{"序号":"3","站名":"供水管流量站","测站编码":"0020230429","发送时间":"2023-08-01 1004:25","接收时间":"2023-08-01 10:02:08","归属协议":"水库供水"},{"序号":"4","站名":"生态流量站","测站编码":"4220230428","发送时间":"2023-08-01 10:02:05","接收时间":"2023-08-01 10:02.05","归属协议":"水库供水"},{"序号":"5","站名":"SY01","测站编码":"0020230426","发送时间":"2023-08-01 10:02:04","接收时间":"2023-08-01 10:02;04","归属协议":"水资源协议"},{"序号":"6","站名":"SY02","测站编码":"0000000036","发送时间":"2023-08-01 10:02:04","接收时间":"2023-08-01 10:02.04","归属协议":"水资源协议"},{"序号":"7","站名":"SY03","测站编码":"0020230414","发送时间":"2023-08-01 10:00:00","接收时间":"2023-08-01 1002:04","归属协议":"水资源协议"},{"序号":"8","站名":"SY04","测站编码":"4208820200","发送时间":"2023-08-01 10:02:04","接收时间":"2023-08-01 10:02:04","归属协议":"水资源协议"},{"序号":"9","站名":"SY05","测站编码":"0020230325","发送时间":"2023-08-01 10:02:00","接收时间":"2023-08-01 10:02:00","归属协议":"水资源协议"},{"序号":"10","站名":"SY06","测站编码":"4202023102","发送时间":"2023-08-01 10:00:00","接收时间":"2023-08-01 10:02:00","归属协议":"水资源协议"}],
"total": 10,
"pages": 1
}

View File

@ -0,0 +1,12 @@
{
"records": [
{
"站名": "水位雨量站",
"管理单位": "",
"测站编码": "DBMH0002"
}
],
"total": 10,
"pages": 1
}

View File

@ -0,0 +1,5 @@
{
"records": [{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1517:06:08","数据(mm)":"0"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1517:00:00","数据(mm)":"0.5"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1517:00:00","数据(mm)":"1"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1516:05:22","数据(mm)":"0"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1516:00:00","数据(mm)":"0"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1516:00:00","数据(mm)":"0"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1515:05:18","数据(mm)":"1.5"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1515:00:00","数据(mm)":"3"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1515:00:00","数据(mm)":"0"},{"站名":"水位雨量站","测站编码":"ZH000117","时间":"2023-07-1514:05:25","数据(mm)":"2"}],
"total": 10,
"pages": 1
}

View File

@ -0,0 +1,5 @@
{
"records": [{"序号":"1","归属协议":"水资源协议","测站编码":"4208020400","站名":"水位雨量站","最后数据时间":"2023-08-2817:1637 .535","数据类型":"雨量_0","测值":"0.5","站点状态":"在线"},{"序号":"2","归属协议":"水库供水","测站编码":"4200000B00","站名":"输水管流量站","最后数据时间":"2023-08-28 17:16;37 535","数据类型":"流量_1","测值":"1.51","站点状态":"在线"},{"序号":"3","归属协议":"水库供水","测站编码":"4200000500","站名":"供水管流量站","最后数据时间":"2023-08-28 17:1637535","数据类型":"流量_1","测值":"0.213","站点状态":"在线"},{"序号":"4","归属协议":"水库供水","测站编码":"4200000300","站名":"生态流量站","最后数据时间":"2023-08-28 17:16:37. 535","数据类型":"流量_1","测值":"0","站点状态":"在线"},{"序号":"5","归属协议":"水资源协议","测站编码":"4208040100","站名":"SY01","最后数据时间":"2023-08-28 17;16;37 : 535","数据类型":"渗压_0","测值":"173.17","站点状态":"在线"},{"序号":"6","归属协议":"水资源协议","测站编码":"4208020100","站名":"SY02","最后数据时间":"2023-08-28 17:16:37.535","数据类型":"渗压_0","测值":"173.51","站点状态":"离线"},{"序号":"7","归属协议":"水资源协议","测站编码":"4208020800","站名":"SY03","最后数据时间":"2023-08-28 17:1637.535","数据类型":"渗压_0","测值":"174.23","站点状态":"在线"},{"序号":"8","归属协议":"水资源协议","测站编码":"4208020200","站名":"SY04","最后数据时间":"2023-08-28 17:16;37,535","数据类型":"渗压_0","测值":"175.23","站点状态":"在线"},{"序号":"9","归属协议":"水资源协议","测站编码":"4208020700","站名":"SY05","最后数据时间":"2023-08-28 17:16;37.535","数据类型":"渗压_0","测值":"171.23","站点状态":"离线"},{"序号":"10","归属协议":"水资源协议","测站编码":"0019082002","站名":"SY06","最后数据时间":"2023-08-28 17:16;37 535","数据类型":"渗压_0","测值":"176.23","站点状态":"在线"}],
"total": 10,
"pages": 1
}

View File

@ -0,0 +1,45 @@
{
"status": {
"online": 19,
"offline": 2
},
"regions": [
{
"name": "闸门控制系统",
"id": "zh_tcp_4",
"count": 4
},
{
"name": "水资源协议",
"id": "zh_tcp_3",
"count": 14
},
{
"name": "水库供水",
"id": "zh_tcp_5",
"count": 3
}
],
"types": [
{
"name": "水库水文站",
"code": "RR",
"count": 1
},
{
"name": "渗流站",
"code": "",
"count": 1
},
{
"name": "渗压站",
"code": "",
"count": 16
},
{
"name": "流量站",
"code": "QQ",
"count": 3
}
]
}

View File

@ -0,0 +1,6 @@
{
"records":
[{"站名":"站点1","管理单位":"","测站编码":"0020230509","监测项目":"流量_1累计流量_1闸位_0","归属协议":"水资源协议"},{"站名":"站点2","管理单位":"","测站编码":"0020230430","监测项目":"图像_1图像_2电压_1水位_11小时降雨_1时段雨量_1累计雨量_1","归属协议":"水资源协议"},{"站名":"站点3","管理单位":"","测站编码":"0020230429","监测项目":"时段流量_1累计流量_1图像_1","归属协议":"水资源协议"},{"站名":"站点4","管理单位":"","测站编码":"4220230428","监测项目":"图像_0累计流量_1流量_1水位_0","归属协议":"水资源协议"},{"站名":"站点5","管理单位":"","测站编码":"0020230426","监测项目":"图像_1图像_2电压_1水位_1","归属协议":"水资源协议"},{"站名":"站点6","管理单位":"","测站编码":"0000000036","监测项目":"1 小时降雨_1图像_1图像_2电压_1水位_1时段雨量_1累计雨量_1","归属协议":"闸门控制系统"},{"站名":"站点7","管理单位":"湖北水文","测站编码":"0020230414","监测项目":"水位_1图像_1图像_2电压_1","归属协议":"闸门控制系统"},{"站名":"站点8","管理单位":"湖北水文","测站编码":"4208820200","监测项目":"闸位_0","归属协议":"水资源协议"},{"站名":"站点9","管理单位":"湖北水文","测站编码":"0020230325","监测项目":"累计流量_1水位_0流量_2累计流量_2流量_1","归属协议":"水资源协议"},{"站名":"站点10","管理单位":"湖北水文","测站编码":"4202023102","监测项目":"累计流量_1水位_0流量_1","归属协议":"水资源协议"}],
"total": 10,
"pages": 1
}

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: 276 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,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,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 TopMenu: 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/icons2/${o.icon}`} />
{o.title}
</div>
))
}
</div>
)
}
export default React.memo(TopMenu);

View File

@ -0,0 +1,115 @@
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 TopMenu from './TopMenu';
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' >
<img alt="..." className="app-icon" src={Icon1} />
<span className="app-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">
<div className={menuIndexes.length > 1 ? 'content-root' : 'content-root-fs'}>
{
user ? (
<Suspense fallback={null}>
<Outlet />
</Suspense>
) : null
}
</div>
</Content>
</Layout>
</Layout>
)
};
export default DashboardLayout

View File

@ -0,0 +1,134 @@
.app-root {
height: 100vh;
.app-header {
display: flex;
align-items: center;
padding: 0 12px 0 0;
overflow: hidden;
z-index: 20;
color: #fff !important;
background-image: url(../../assets/images/menu.png);
background-size: 100% 100%;
.app-icon-a {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
.app-icon {
width: 32px;
height: 32px;
}
.app-title {
font-size: 18px;
font-weight: 500;
color: #fff;
text-align: left;
padding-left: 10px;
}
margin-right: 56px;
}
/*
.ant-menu {
background: transparent;
}
.ant-menu-item-active {
background-color: #fff;
}
.ant-menu-item-selected {
//background-color: rgba(70, 128, 221, .75);
}
.ant-menu-item-selected::after {
border-bottom: 3px solid #86b9ff;
}
.ant-menu-horizonal>.ant-menu-item::after {
right: 0;
left: 0;
}
*/
.app-top-menu {
display: flex;
align-items: center;
.app-top-menu-item {
height: 64px;
padding: 0 20px;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
img {
width: 18px;
margin-right: 8px;
}
&.active {
background-color: rgba(70, 128, 221, .75);
border-bottom: 3px solid #86b9ff;
}
}
}
.user-menu {
display: flex;
height: 59px;
align-items: center;
padding: 0 14px;
.user-menu-icon {
width: 28px;
margin: 20px 10px 20px 0;
}
&:hover {
background-color: hsla(0, 0%, 100%, .3);
}
}
}
.app-sider {
overflow-y: auto;
overflow-x: hidden;
background-color: #fff !important;
z-index: 10;
}
.app-content {
z-index: 0;
display: flex;
flex-direction: column;
}
.content-root {
padding: 10px;
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
}
.content-root-fs {
padding: 0;
flex-grow: 1;
overflow: hidden;
}
}
.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>
);

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

@ -0,0 +1,31 @@
export type MenuItem = {
id: string;
title: string;
path?: string;
redirect?: string;
icon?: string;
children?: MenuItem[];
}
export type RealItem = {
"归属协议": number,
"测站编码": string,
"站名": string,
"最后数据时间": string,
"数据类型": string,
"测值": string,
"站点状态": string,
}
export type DamItem = {
"大坝代码": string,
"大坝名称": string,
"是否主坝": string,
"经度": string,
"纬度": string,
"所在位置": string,
"最大坝高": string,
"坝顶长度": string,
"坝顶宽度": string,
"创建日期": string,
}

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

@ -0,0 +1,109 @@
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/home', icon: 'xtgl.png'
},
{
id: id(), title: '报文数据', redirect: '/mgr/data', icon: 'xtgl.png', children: [
{ id: id(), title: '报文历史', path: '/mgr/data' },
// { id: id(), title: '测站查询', path: '/mgr/cezhan' },
],
},
{
id: id(), title: '监测历史数据', redirect: '/mgr/history', icon: 'xtgl.png', children: [
{ id: id(), title: '雨量历史', path: '/mgr/history' },
{ id: id(), title: '水位历史', path: '/mgr/swls' },
{ id: id(), title: '流量历史', path: '/mgr/llls' },
// { id: id(), title: '闸阀开度历史', path: '/mgr/Zfkd' },
{ id: id(), title: '图像历史', path: '/mgr/txls' },
{ id: id(), title: '电压历史', path: '/mgr/dyls' },
],
},
{
id: id(), title: '系统管理', redirect: '/mgr/manage', icon: 'xtgl.png', children: [
{ id: id(), title: '站点信息管理', path: '/mgr/manage' }
],
},
].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;
}

78
src/models/dam/_.ts Normal file
View File

@ -0,0 +1,78 @@
import { Moment } from "moment";
export type ListItem_Dam = {
id: number;
stationCode: string;
deviceId: string;
drillingNo: string;
embeddingElevation: string;
latestReportingTime: string;
}
export type Record_Dam = {
id: number;
calibrationCoefficient: number;
channelNum: number;
initialReading: number;
startTemperature: number;
stationCode: string;
temperatureK: number;
drillingNo?: string;
deviceId?: string;
drillingSize?: string;
initialWaterLevel?: number;
steadyWaterLevel?: number;
measurePointNo?: string;
measuringProbeNo?: string;
manufacturer?: string;
sensorCoefficient?: number;
range?: number;
resistance?: string;
cableLength?: number;
cableLengthMark?: string;
embeddingElevation?: number;
columnNo?: string;
damWheelbase?: number;
readings?: number;
draft?: number;
startReadings?: string;
endReadings?: number;
zeroPressureReadings?: number;
embeddingFinishReading?: string;
embeddingDate?: string | Moment;
airTemperature?: number;
airPressure?: number;
weather?: string;
headwaterLevel?: number;
tailwaterLevel?: number;
sketchMap?: string;
technicalDirector?: string;
checkPersonnel?: string;
buriedPersonnel?: string;
createDate?: string | Moment;
supervisor?: string;
remark?: string;
}
export type Record_Gnss = {
id?: number;
stationName?: string;
stationType: '监测站' | '基准站';
b: number; // 纬度
l: number; // 经度
x: number;
y: number;
h: number;
startTime?: string | Moment;
endTime?: string | Moment;
}
export type Record_RobotPoint = {
id: number;
limitId: number;
profileId: number;
name: string;
}

44
src/models/dhis/_.ts Normal file
View File

@ -0,0 +1,44 @@
export type ListItem_DHis = {
channelNum: string;
dataA: string;
dataB: string;
dataC: string;
deviceId: string;
id: number;
osmometer: number;
stationCode: string;
timestamp: string;
}
export type ListItem_GnssHis = {
id: number;
deviceId: number;
DeviceDataTypeId: number;
v1: number; // 表面位移测点X方向值
v2: number; // 表面位移测点Y方向值
v3: number; // 表面位移测点H方向值
v4: number;
v5: number;
dacTime: number; // 测点采集时间
dv1: number;
dv2: number;
dv3: number;
}
export type ListItem_RobotHis = {
id: number;
pointId: number;
tpsSetupId: number;
easting: number; // 东坐标
northing: number; // 北坐标
height: number; // 高程
type: number; // 坐标类型1初始值2参考值3当前值4扫描值
}
export const E_ROBOTHIS_TYPE: { [key: number]: string } = {
1: '初始值',
2: '参考值',
3: '当前值',
4: '扫描值'
}

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;
}

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

@ -0,0 +1,160 @@
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('&')}`);
}

Some files were not shown because too many files have changed in this diff Show More