├── views
├── .nvmrc
├── config
│ ├── test.env.js
│ ├── prod.env.js
│ ├── dev.env.js
│ └── index.js
├── static
│ └── logo.png
├── postcss.config.js
├── .eslintignore
├── .prettierrc.js
├── src
│ ├── index.js
│ ├── constants.js
│ ├── components
│ │ ├── Lowvol
│ │ │ ├── lowvol.scss
│ │ │ └── index.js
│ │ ├── Voltage
│ │ │ ├── voltage.scss
│ │ │ └── index.js
│ │ ├── Worklog
│ │ │ ├── worklog.scss
│ │ │ └── index.js
│ │ ├── Transformer
│ │ │ ├── transformer.scss
│ │ │ └── index.js
│ │ └── MainLayout
│ │ │ ├── layout.scss
│ │ │ └── index.js
│ ├── pages
│ │ ├── Worklog
│ │ │ ├── worklog.scss
│ │ │ └── index.js
│ │ ├── NotFound
│ │ │ └── index.js
│ │ ├── Lampins
│ │ │ ├── lampins.scss
│ │ │ └── index.js
│ │ ├── Login
│ │ │ ├── login.scss
│ │ │ └── index.js
│ │ ├── Substation
│ │ │ ├── substation.scss
│ │ │ ├── detail.js
│ │ │ ├── index.js
│ │ │ └── edit.js
│ │ └── Transbox
│ │ │ ├── transbox.scss
│ │ │ ├── detail.js
│ │ │ ├── index.js
│ │ │ └── edit.js
│ ├── style
│ │ ├── variables.scss
│ │ └── colors.scss
│ ├── utils
│ │ ├── uploader.js
│ │ └── index.js
│ ├── App.js
│ ├── App.scss
│ ├── services
│ │ └── user.js
│ ├── router
│ │ ├── index.js
│ │ └── routes.js
│ └── api
│ │ └── index.js
├── .editorconfig
├── public
│ └── index.html
├── build
│ ├── utils.js
│ ├── webpack.dev.conf.js
│ ├── build.js
│ ├── check-versions.js
│ ├── webpack.base.conf.js
│ └── webpack.prod.conf.js
├── .eslintrc.js
└── package.json
├── screenshots
├── pems-1.png
├── pems-2.png
├── pems-3.png
├── pems-4.png
└── mongo-path.png
├── server
├── util
│ └── index.js
├── model
│ ├── fields_table.js
│ ├── lampins.js
│ ├── user.js
│ ├── lowvol.js
│ ├── transformer.js
│ ├── voltage.js
│ ├── worklog.js
│ ├── transbox.js
│ ├── substation.js
│ └── index.js
├── controller
│ ├── index.js
│ ├── user.js
│ ├── lampins.js
│ ├── worklog.js
│ ├── lowvol.js
│ ├── voltage.js
│ ├── transbox.js
│ ├── transformer.js
│ └── substation.js
├── config
│ └── index.js
├── app.js
├── middleware
│ └── index.js
├── ecosystem.config.js
├── server.js
├── package.json
└── router
│ └── index.js
├── .gitignore
├── LICENSE
└── README.md
/views/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/carbon
2 |
--------------------------------------------------------------------------------
/views/config/test.env.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/screenshots/pems-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/screenshots/pems-1.png
--------------------------------------------------------------------------------
/screenshots/pems-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/screenshots/pems-2.png
--------------------------------------------------------------------------------
/screenshots/pems-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/screenshots/pems-3.png
--------------------------------------------------------------------------------
/screenshots/pems-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/screenshots/pems-4.png
--------------------------------------------------------------------------------
/server/util/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = class Util {
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/views/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/views/static/logo.png
--------------------------------------------------------------------------------
/views/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require('autoprefixer')],
3 | };
4 |
--------------------------------------------------------------------------------
/screenshots/mongo-path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rannie/pems/HEAD/screenshots/mongo-path.png
--------------------------------------------------------------------------------
/views/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | dist/
4 | test/
5 | index/
6 | config/
7 | postcss.config.js
8 |
--------------------------------------------------------------------------------
/views/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "all"
5 | };
6 |
--------------------------------------------------------------------------------
/server/model/fields_table.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | user: ['name', 'nick_name', 'head_img', 'token']
5 | };
6 |
--------------------------------------------------------------------------------
/views/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | NODE_ENV: JSON.stringify('production'),
5 | SERVICE_URL: JSON.stringify(''),
6 | INDEX_URL: '""',
7 | };
8 |
--------------------------------------------------------------------------------
/views/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDom from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDom.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/views/src/constants.js:
--------------------------------------------------------------------------------
1 | export default {
2 | OSS_REGION: 'oss-cn-beijing',
3 | OSS_ACCESS_KEY_ID: '',
4 | OSS_ACCESS_KEY_SECRET: '',
5 | OSS_BUCKET: '',
6 | HOME_URL: '',
7 | };
8 |
--------------------------------------------------------------------------------
/views/src/components/Lowvol/lowvol.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .lowvol-content {
4 | @extend .main-content;
5 |
6 | .oper-area {
7 | margin: 0 0 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/views/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/views/src/components/Voltage/voltage.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .voltage-content {
4 | @extend .main-content;
5 |
6 | .oper-area {
7 | margin: 0 0 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/views/src/components/Worklog/worklog.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .worklog-content {
4 | @extend .main-content;
5 |
6 | .oper-area {
7 | margin: 0 0 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/views/src/components/Transformer/transformer.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .trans-content {
4 | @extend .main-content;
5 |
6 | .oper-area {
7 | margin: 0 0 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/views/src/pages/Worklog/worklog.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .wl-content {
4 | @extend .main-content;
5 |
6 | .title {
7 | font-size: 20px;
8 | text-align: center;
9 | margin: 15px 0;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/views/src/pages/NotFound/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Exception from 'ant-design-pro/lib/Exception';
3 |
4 | class NotFound extends React.Component {
5 | render() {
6 | return ;
7 | }
8 | }
9 |
10 | export default NotFound;
11 |
--------------------------------------------------------------------------------
/views/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | let SERVICE_URL = process.env.MOCK ? '"MOCK_SERVER_URL"' : '"http://localhost:3700"';
4 |
5 | module.exports = {
6 | NODE_ENV: JSON.stringify('development'),
7 | SERVICE_URL,
8 | INDEX_URL: '"http://localhost:8080"',
9 | };
10 |
--------------------------------------------------------------------------------
/views/src/style/variables.scss:
--------------------------------------------------------------------------------
1 | @import './colors';
2 | $header-height: 64px;
3 | $font-size-normal: 15px;
4 | $border-radius: 4px;
5 |
6 | .main-content {
7 | background-color: white;
8 | padding: 15px;
9 | height: 100%;
10 | width: 100%;
11 | overflow: scroll;
12 | }
13 |
--------------------------------------------------------------------------------
/views/src/pages/Lampins/lampins.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .lampins-content {
4 | @extend .main-content;
5 |
6 | .title {
7 | font-size: 20px;
8 | text-align: center;
9 | margin: 15px 0;
10 | }
11 |
12 | .oper-area {
13 | margin: 0 0 10px;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | /test/unit/coverage/
8 | /test/e2e/reports/
9 | selenium-debug.log
10 | static/mock
11 |
12 | # Editor directories and files
13 | .idea
14 | .vscode
15 | *.suo
16 | *.ntvs*
17 | *.njsproj
18 | *.sln
19 |
--------------------------------------------------------------------------------
/views/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | PEMS
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/views/src/utils/uploader.js:
--------------------------------------------------------------------------------
1 | import OSS from 'ali-oss';
2 | import constants from '../constants';
3 |
4 | const client = new OSS({
5 | region: constants.OSS_REGION,
6 | accessKeyId: constants.OSS_ACCESS_KEY_ID,
7 | accessKeySecret: constants.OSS_ACCESS_KEY_SECRET,
8 | bucket: constants.OSS_BUCKET,
9 | });
10 |
11 | export default client;
12 |
--------------------------------------------------------------------------------
/views/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.scss';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import AppRouter from './router';
5 |
6 | class App extends React.Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/server/controller/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | user: require('./user'),
5 | substation: require('./substation'),
6 | transformer: require('./transformer'),
7 | worklog: require('./worklog'),
8 | transbox: require('./transbox'),
9 | voltage: require('./voltage'),
10 | lowvol: require('./lowvol'),
11 | lampins: require('./lampins')
12 | };
13 |
--------------------------------------------------------------------------------
/views/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | // paths
5 | assetsRoot: path.resolve(__dirname, '../dist'),
6 | assetsSubDirectory: 'static',
7 | assetsPublicPath: '/',
8 | // gzip
9 | productionGzip: false,
10 | productionGzipExtensions: ['js', 'css'],
11 | // report
12 | bundleAnalyzerReport: process.env.npm_config_report,
13 | };
14 |
--------------------------------------------------------------------------------
/server/model/lampins.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const lampinsSchema = new Schema({
7 | date: Date,
8 | road_name: String,
9 | record: String,
10 | resolved: Boolean,
11 | create_at: {
12 | type: Date,
13 | default: Date.now
14 | }
15 | });
16 |
17 | module.exports = mongoose.model('Lampins', lampinsSchema, 'lampins');
18 |
--------------------------------------------------------------------------------
/views/src/pages/Worklog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WorklogComp from '../../components/Worklog';
3 | import './worklog.scss';
4 |
5 | class Worklog extends React.Component {
6 | render() {
7 | return (
8 |
12 | );
13 | }
14 | }
15 |
16 | export default Worklog;
17 |
--------------------------------------------------------------------------------
/views/src/style/colors.scss:
--------------------------------------------------------------------------------
1 | $action-color-blue: #0095dc;
2 | $action-color-green: #00a446;
3 | $action-color-red: #eb5d5d;
4 | $status-color-yellow: #ffbd2e;
5 | $table-bg-color-odd: #f8f8f8;
6 | $global-background-color: #f0f3f7;
7 | $separator-color: #e8e8e8;
8 | $text-color-black: rgba(0, 0, 0, 0.75);
9 | $text-color-black-secondary: rgba(0, 0, 0, 0.65);
10 | $text-color-gray: #ababab;
11 | $text-color-gray-description: rgba(0, 0, 0, 0.45);
12 |
--------------------------------------------------------------------------------
/views/src/App.scss:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 | @import '~ant-design-pro/dist/ant-design-pro.css';
3 | @import './style/variables';
4 |
5 | body {
6 | background-color: $global-background-color;
7 | }
8 |
9 | #root {
10 | height: 100%;
11 | position: relative;
12 | overflow: hidden;
13 | }
14 |
15 | .ant-btn {
16 | min-width: 100px;
17 | }
18 |
19 | img{
20 | width:auto;
21 | height:auto;
22 | max-width:100%;
23 | max-height:100%;
24 | }
25 |
--------------------------------------------------------------------------------
/server/model/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const userSchema = new Schema({
7 | name: String,
8 | password: String,
9 | nick_name: String,
10 | head_img: String,
11 | create_at: {
12 | type: Date,
13 | default: Date.now
14 | }
15 | });
16 |
17 | userSchema.index({ name: 1 }, { unique: true });
18 |
19 | module.exports = mongoose.model('User', userSchema, 'user');
20 |
--------------------------------------------------------------------------------
/server/model/lowvol.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const lowvolSchema = new Schema({
8 | number: String,
9 | range: String,
10 | model: String,
11 | substation: { type: ObjectId, ref: 'Substation' },
12 | transbox: { type: ObjectId, ref: 'Transbox' },
13 | create_at: {
14 | type: Date,
15 | default: Date.now
16 | }
17 | });
18 |
19 | module.exports = mongoose.model('Lowvol', lowvolSchema, 'lowvol');
20 |
--------------------------------------------------------------------------------
/server/model/transformer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const transSchema = new Schema({
8 | serial_number: String,
9 | model: String,
10 | substation: { type: ObjectId, ref: 'Substation' },
11 | transbox: { type: ObjectId, ref: 'Transbox' },
12 | create_at: {
13 | type: Date,
14 | default: Date.now
15 | }
16 | });
17 |
18 | module.exports = mongoose.model('Transformer', transSchema, 'transformer');
19 |
--------------------------------------------------------------------------------
/server/model/voltage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const voltageSchema = new Schema({
8 | number: String,
9 | range: String,
10 | model: String,
11 | substation: { type: ObjectId, ref: 'Substation' },
12 | transbox: { type: ObjectId, ref: 'Transbox' },
13 | create_at: {
14 | type: Date,
15 | default: Date.now
16 | }
17 | });
18 |
19 | module.exports = mongoose.model('Index', voltageSchema, 'voltage');
20 |
--------------------------------------------------------------------------------
/server/model/worklog.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const worklogSchema = new Schema({
8 | date: Date,
9 | content: String,
10 | resolved: Boolean,
11 | notify_user: Boolean,
12 | substation: { type: ObjectId, ref: 'Substation' },
13 | transbox: { type: ObjectId, ref: 'Transbox' },
14 | create_at: {
15 | type: Date,
16 | default: Date.now
17 | }
18 | });
19 |
20 | module.exports = mongoose.model('Worklog', worklogSchema, 'worklog');
21 |
--------------------------------------------------------------------------------
/server/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | let config;
4 |
5 | if (process.env.ENV_PHASE === 'dev') {
6 | config = {
7 | SERVE_PATH: path.resolve(__dirname, '../../views/dist'),
8 | MONGO_PATH: ''
9 | };
10 | }
11 |
12 | if (process.env.ENV_PHASE === 'prod') {
13 | config = {
14 | SERVE_PATH: '/var/www/pems',
15 | MONGO_PATH: ''
16 | };
17 | }
18 |
19 | config.JWT_SECRET = '';
20 | config.JWT_EXPIRE = '15 day';
21 | config.RATE_LIMIT_MAX = 1000;
22 | config.RATE_LIMIT_DURATION = 1000;
23 |
24 | config.DEFAULT_PAGE_SIZE = 8;
25 | config.SMALL_PAGE_SIZE = 5;
26 |
27 | module.exports = config;
28 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const koa = require('koa');
4 | const staticServe = require('koa-static-cache');
5 | const router = require('koa-router');
6 | const views = require('koa-views');
7 | const cors = require('koa2-cors');
8 | const logger = require('koa-logger');
9 | const bodyParser = require('koa-body');
10 | const config = require('./config');
11 |
12 | let app = new koa();
13 | let route = new router();
14 |
15 | app
16 | .use(cors())
17 | .use(bodyParser())
18 | .use(logger())
19 | .use(staticServe(config.SERVE_PATH))
20 | .use(views(config.SERVE_PATH))
21 | .use(route.routes());
22 |
23 | route.get('/*', async ctx => {
24 | await ctx.render('index.html');
25 | });
26 |
27 | app.listen(3010, () => {
28 | console.log('start pems static server, listen 3010');
29 | });
30 |
--------------------------------------------------------------------------------
/server/middleware/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const codeMap = {
4 | '-1': 'fail',
5 | '200': 'success',
6 | '401': 'token expired',
7 | '500': 'server error',
8 | '10001': 'params error'
9 | };
10 |
11 | const utilFn = {
12 | resuccess(data) {
13 | return {
14 | code: 200,
15 | success: true,
16 | message: codeMap['200'],
17 | data: data || null
18 | };
19 | },
20 | refail(message, code, data) {
21 | return {
22 | code: code || -1,
23 | success: false,
24 | message: message || codeMap[code],
25 | data: data || null
26 | };
27 | }
28 | };
29 |
30 | module.exports = class Middleware {
31 | static util(ctx, next) {
32 | ctx.set('X-Request-Id', ctx.req.id);
33 | ctx.util = utilFn;
34 | return next();
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/server/ecosystem.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | apps : [
3 | {
4 | name: 'PEMSStaticServer',
5 | script: './app.js',
6 |
7 | instances: 1,
8 | autorestart: true,
9 | watch: false,
10 | max_memory_restart: '1G',
11 |
12 | output: '~/.pm2/logs/pems-static/ss.out.log',
13 | error: '~/.pm2/logs/pems-static/ss.error.log',
14 | merge_logs: true,
15 | log_date_format: "YYYY-MM-DD hh:mm:ss",
16 | },
17 | {
18 | name: 'PEMSApiServer',
19 | script: './server.js',
20 |
21 | instances: 2,
22 | autorestart: true,
23 | watch: false,
24 | max_memory_restart: '1G',
25 |
26 | output: '~/.pm2/logs/pems-api/ss.out.log',
27 | error: '~/.pm2/logs/pems-api/ss.error.log',
28 | merge_logs: true,
29 | log_date_format: "YYYY-MM-DD hh:mm:ss",
30 | }
31 | ],
32 | };
33 |
--------------------------------------------------------------------------------
/views/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const config = require('../config');
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6 |
7 | exports.assetsPath = (_path) => {
8 | return path.posix.join(config.assetsSubDirectory, _path);
9 | };
10 |
11 | exports.cssLoaders = (_options = {}) => {
12 | let loaders = [];
13 |
14 | // style loader
15 | !_options.extract && loaders.push({loader: 'style-loader'});
16 | // css loader
17 | loaders.push({
18 | loader: 'css-loader',
19 | options: {
20 | importLoaders: _options.importLoaders
21 | }
22 | });
23 | // postcss loader
24 | loaders.push({loader: 'postcss-loader'});
25 | // sass loader
26 | _options.isSass && loaders.push({loader: 'sass-loader'});
27 |
28 | if (_options.extract) {
29 | return [MiniCssExtractPlugin.loader, ...loaders];
30 | }
31 | return loaders;
32 | };
33 |
--------------------------------------------------------------------------------
/views/src/pages/Login/login.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables.scss";
2 |
3 | .login-wrapper {
4 | position: relative;
5 | height: 100%;
6 |
7 | .login-panel {
8 | position: absolute;
9 | width: 500px;
10 | height: 365px;
11 | top: 25%;
12 | left: 50%;
13 | margin-left: -250px;
14 | border-radius: 12px;
15 | background-color: #ffffff;
16 |
17 | .title {
18 | height: 33px;
19 | margin-top: 50px;
20 | text-align: center;
21 | line-height: 33px;
22 | font-size: 27px;
23 | color: $text-color-black;
24 | }
25 |
26 | .divider {
27 | margin-left: 30px;
28 | margin-right: 30px;
29 | margin-top: 24px;
30 | height: 20px;
31 |
32 | .line {
33 | margin-top: 10px;
34 | width: 100%;
35 | height: 1px;
36 | background-color: $separator-color;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/server/model/transbox.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const transboxSchema = new Schema({
8 | name: String,
9 | superior: String,
10 | user_comp: String,
11 | contact_info: String,
12 | appear_pic: String,
13 | location_pic: String,
14 | area: String,
15 | high_vols: [{
16 | type: ObjectId,
17 | ref: 'Voltage',
18 | default: [],
19 | }],
20 | low_vols: [{
21 | type: ObjectId,
22 | ref: 'Lowvol',
23 | default: [],
24 | }],
25 | transformers: [{
26 | type: ObjectId,
27 | ref: 'Transformer',
28 | default: [],
29 | }],
30 | worklogs: [{
31 | type: ObjectId,
32 | ref: 'Worklog',
33 | default: [],
34 | }],
35 | create_at: {
36 | type: Date,
37 | default: Date.now
38 | }
39 | });
40 |
41 | transboxSchema.index({ area: 1 });
42 |
43 | module.exports = mongoose.model('Transbox', transboxSchema, 'transbox');
44 |
--------------------------------------------------------------------------------
/server/model/substation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 |
5 | const Schema = mongoose.Schema;
6 | const ObjectId = Schema.Types.ObjectId;
7 | const substationSchema = new Schema({
8 | name: String,
9 | superior: String,
10 | user_comp: String,
11 | contact_info: String,
12 | number: String,
13 | appear_pic: String,
14 | location_pic: String,
15 | area: String,
16 | high_vols: [{
17 | type: ObjectId,
18 | ref: 'Voltage',
19 | default: [],
20 | }],
21 | low_vols: [{
22 | type: ObjectId,
23 | ref: 'Lowvol',
24 | default: [],
25 | }],
26 | transformers: [{
27 | type: ObjectId,
28 | ref: 'Transformer',
29 | default: [],
30 | }],
31 | worklogs: [{
32 | type: ObjectId,
33 | ref: 'Worklog',
34 | default: [],
35 | }],
36 | create_at: {
37 | type: Date,
38 | default: Date.now
39 | }
40 | });
41 |
42 | substationSchema.index({ area: 1 });
43 |
44 | module.exports = mongoose.model('Substation', substationSchema, 'substation');
45 |
--------------------------------------------------------------------------------
/server/model/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 | const config = require('../config');
5 |
6 | mongoose.connect(config.MONGO_PATH, {
7 | useNewUrlParser: true,
8 | keepAlive: true,
9 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
10 | reconnectInterval: 500, // Reconnect every 500ms
11 | poolSize: 10, // Maintain up to 10 socket connections
12 | // If not connected, return errors immediately rather than waiting for reconnect
13 | bufferMaxEntries: 0,
14 | useFindAndModify: false
15 | }).then(res => {
16 | console.log('database connect success');
17 | }, err => {
18 | console.log('database connect failed');
19 | process.exit(1);
20 | });
21 |
22 | module.exports = {
23 | User: require('./user'),
24 | Substation: require('./substation'),
25 | Voltage: require('./voltage'),
26 | Lowvol: require('./lowvol'),
27 | Transformer: require('./transformer'),
28 | Worklog: require('./worklog'),
29 | Lampins: require('./lampins'),
30 | Transbox: require('./transbox'),
31 | };
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Hanran Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/controller/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const jwt = require('jsonwebtoken');
4 | const _ = require('lodash');
5 | const { User } = require('../model');
6 | const ft = require('../model/fields_table');
7 | const config = require('../config');
8 |
9 | module.exports = class UserController {
10 | static async login(ctx) {
11 | let verifyPassword;
12 | const name = ctx.checkBody('name').notEmpty().value;
13 | const password = ctx.checkBody('password').notEmpty().value;
14 |
15 | if (ctx.errors) {
16 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
17 | return;
18 | }
19 |
20 | let user = await User.findOne({ name: name });
21 |
22 | if (!user) {
23 | ctx.body = ctx.util.refail('用户不存在');
24 | return;
25 | }
26 |
27 | verifyPassword = user.password === password;
28 | if (!verifyPassword) {
29 | ctx.body = ctx.util.refail('用户名或密码错误');
30 | return;
31 | }
32 |
33 | user.token = jwt.sign({ id: user.id, name: user.name }, config.JWT_SECRET, { expiresIn: config.JWT_EXPIRE });
34 |
35 | ctx.body = ctx.util.resuccess(_.pick(user, ft.user));
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const koa = require('koa');
4 | const cors = require('koa2-cors');
5 | const logger = require('koa-logger');
6 | const bodyParser = require('koa-body');
7 | const koaJwt = require('koa-jwt');
8 | const onerror = require('koa-onerror');
9 | const validate = require('koa-validate');
10 | const pathToRegexp = require('path-to-regexp');
11 |
12 | const config = require('./config');
13 | const middleware = require('./middleware');
14 | const router = require('./router');
15 |
16 | const app = new koa();
17 |
18 | onerror(app);
19 | validate(app);
20 |
21 | app
22 | .use(cors({ credentials: true, maxAge: 2592000 })
23 | .use(logger())
24 | .use(middleware.util)
25 | .use(koaJwt({ secret: config.JWT_SECRET }).unless((ctx) => {
26 | if (/^\/api/.test(ctx.path)) {
27 | return pathToRegexp([
28 | '/api/pems/login'
29 | ]).test(ctx.path);
30 | }
31 | return true;
32 | }))
33 | .use(bodyParser({ multipart: true }))
34 | .use(router.routes())
35 | .use(router.allowedMethods());
36 |
37 | app.listen(3700, () => {
38 | console.log('start pems api server, listen 3700');
39 | });
40 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pems-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "author": "Rann",
7 | "license": "ISC",
8 | "scripts": {
9 | "static-debug": "./node_modules/.bin/cross-env ENV_PHASE='dev' node app.js",
10 | "api-debug": "./node_modules/.bin/cross-env ENV_PHASE='dev' node server.js",
11 | "dev": "./node_modules/.bin/cross-env ENV_PHASE='dev' pm2 start ecosystem.config.js",
12 | "start": "./node_modules/.bin/cross-env ENV_PHASE='prod' pm2 start ecosystem.config.js",
13 | "monitor": "pm2 monit"
14 | },
15 | "dependencies": {
16 | "@koa/cors": "^2.2.3",
17 | "cross-env": "^5.2.0",
18 | "jsonwebtoken": "^8.5.1",
19 | "koa": "^2.7.0",
20 | "koa-body": "^4.1.1",
21 | "koa-jwt": "^3.6.0",
22 | "koa-logger": "^3.2.1",
23 | "koa-onerror": "^4.1.0",
24 | "koa-ratelimit": "^4.2.0",
25 | "koa-router": "^7.4.0",
26 | "koa-static-cache": "^5.1.2",
27 | "koa-validate": "^1.0.7",
28 | "koa-views": "^6.2.0",
29 | "koa2-cors": "^2.0.6",
30 | "lodash": "^4.17.15",
31 | "mongoose": "^5.6.11",
32 | "path-to-regexp": "^3.1.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/views/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const devEnv = require('../config/dev.env');
3 | const baseConfig = require('./webpack.base.conf');
4 | const webpack = require('webpack');
5 | const utils = require('./utils');
6 | const CopyWebpackPlugin = require('copy-webpack-plugin');
7 | const config = require('../config');
8 | const path = require('path');
9 |
10 | module.exports = merge(baseConfig, {
11 | mode: 'development',
12 | devtool: 'source-map',
13 | devServer: {
14 | historyApiFallback: true,
15 | },
16 | module: {
17 | rules: [
18 | // sass loader
19 | {
20 | test: /\.(scss|sass)$/,
21 | exclude: /node_modules/,
22 | use: utils.cssLoaders({
23 | isSass: true,
24 | importLoaders: 2
25 | })
26 | },
27 | ]
28 | },
29 | plugins: [
30 | new webpack.DefinePlugin({
31 | 'process.env': devEnv
32 | }),
33 | new webpack.HotModuleReplacementPlugin(),
34 | new CopyWebpackPlugin([
35 | {
36 | from: path.resolve(__dirname, '../static'),
37 | to: config.assetsSubDirectory,
38 | ignore: ['.*']
39 | }
40 | ])
41 | ]
42 | });
43 |
--------------------------------------------------------------------------------
/views/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./check-versions')();
4 | process.env.NODE_ENV = 'production';
5 |
6 | const ora = require('ora');
7 | const rm = require('rimraf');
8 | const path = require('path');
9 | const chalk = require('chalk');
10 | const webpack = require('webpack');
11 | const config = require('../config');
12 | const webpackConfig = require('./webpack.prod.conf');
13 | const _package = require('../package');
14 |
15 | const spinner = ora(`building for ${_package.name}...`);
16 | spinner.start();
17 |
18 | rm(path.join(config.assetsRoot, config.assetsSubDirectory), err => {
19 | if (err) throw err;
20 | webpack(webpackConfig, (err, stats) => {
21 | spinner.stop();
22 | if (err) throw err;
23 | process.stdout.write(stats.toString({
24 | colors: true,
25 | modules: false,
26 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
27 | chunks: false,
28 | chunkModules: false
29 | }) + '\n\n');
30 |
31 | if (stats.hasErrors()) {
32 | console.log(chalk.red(' Build failed with errors.\n'));
33 | process.exit(1);
34 | }
35 |
36 | console.log(chalk.cyan(' Build complete.\n'));
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/views/src/services/user.js:
--------------------------------------------------------------------------------
1 | import md5 from 'crypto-js/md5';
2 | import utils from '../utils';
3 | import * as api from '../api';
4 |
5 | class UserService {
6 | static SharedInstance() {
7 | if (!UserService.instance) {
8 | UserService.instance = new UserService();
9 | }
10 | return UserService.instance;
11 | }
12 |
13 | constructor() {
14 | this.currentUser = utils.getUser();
15 | }
16 |
17 | getLoginUser() {
18 | return this.currentUser;
19 | }
20 |
21 | hasLogin() {
22 | return this.currentUser !== undefined && this.currentUser !== null;
23 | }
24 |
25 | login(values) {
26 | const config = {
27 | data: {
28 | name: values.name,
29 | password: md5(values.password).toString(),
30 | },
31 | };
32 |
33 | return new Promise((resolve, reject) => {
34 | api.user.login(config).then(
35 | res => {
36 | this.currentUser = res.data.data;
37 | utils.saveUser(this.currentUser);
38 | resolve();
39 | },
40 | error => {
41 | reject(error);
42 | },
43 | );
44 | });
45 | }
46 |
47 | logout() {
48 | this.currentUser = null;
49 | utils.removeUser();
50 | }
51 | }
52 |
53 | export default UserService.SharedInstance();
54 |
--------------------------------------------------------------------------------
/views/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": [
3 | "airbnb",
4 | "plugin:prettier/recommended",
5 | "prettier/react"
6 | ],
7 | "parser": "babel-eslint",
8 | "env": {
9 | "browser": true,
10 | "commonjs": true,
11 | "es6": true,
12 | "jest": true,
13 | "node": true
14 | },
15 | "rules": {
16 | "jsx-a11y/href-no-hash": 0,
17 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }],
18 | "react/prefer-stateless-function": 0,
19 | "import/no-unresolved": 0,
20 | "camelcase": 0,
21 | "react/prop-types": 0,
22 | "import/prefer-default-export": 0,
23 | "react/destructuring-assignment": 0,
24 | "no-else-return": 0,
25 | "no-lonely-if": 0,
26 | "react/no-multi-comp": 0,
27 | "class-methods-use-this": 0,
28 | "react/no-access-state-in-setstate": 0,
29 | "react/jsx-no-bind": 0,
30 | "jsx-a11y/no-static-element-interactions": 0,
31 | "jsx-a11y/click-events-have-key-events": 0,
32 | "prefer-destructuring": 0,
33 | "no-param-reassign": 0,
34 | "no-underscore-dangle": 0,
35 | "max-len": [
36 | "warn",
37 | {
38 | "code": 100,
39 | "tabWidth": 2,
40 | "comments": 100,
41 | "ignoreComments": false,
42 | "ignoreTrailingComments": true,
43 | "ignoreUrls": true,
44 | "ignoreStrings": true,
45 | "ignoreTemplateLiterals": true,
46 | "ignoreRegExpLiterals": true
47 | }
48 | ]
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/views/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import { Base64 } from 'js-base64';
2 |
3 | export default class Utils {
4 | static saveUser(user) {
5 | localStorage.setItem('pems_user', JSON.stringify(user));
6 | }
7 |
8 | static getUser() {
9 | const user = localStorage.getItem('pems_user');
10 | return user ? JSON.parse(user) : null;
11 | }
12 |
13 | static removeUser() {
14 | localStorage.removeItem('pems_user');
15 | }
16 |
17 | static getUserIndexPath() {
18 | return '/substation';
19 | }
20 |
21 | static getUserMenuItems() {
22 | return [
23 | {
24 | id: 'substation',
25 | linkTo: '/substation',
26 | index: true,
27 | menuIconType: 'retweet',
28 | menuTitle: '变电所',
29 | },
30 | {
31 | id: 'transbox',
32 | linkTo: '/transbox',
33 | index: false,
34 | menuIconType: 'code-sandbox',
35 | menuTitle: '箱变',
36 | },
37 | {
38 | id: 'lampins',
39 | linkTo: '/lampins',
40 | index: false,
41 | menuIconType: 'bulb',
42 | menuTitle: '路灯',
43 | },
44 | {
45 | id: 'worklog',
46 | linkTo: '/worklog',
47 | index: false,
48 | menuIconType: 'file-text',
49 | menuTitle: '日志',
50 | },
51 | ];
52 | }
53 |
54 | static getAreaList() {
55 | return ['全部', '北疆 110kv 站', '北港 110kv 站', '国际物流 35kv 站', '国际物流 3#kb 站'];
56 | }
57 |
58 | static generateEncodeName(imgName) {
59 | const source = `${this.getUser().name}-${imgName}-${new Date().getTime()}`;
60 | return Base64.encode(source);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/views/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const chalk = require('chalk');
3 | const semver = require('semver');
4 | const packageConfig = require('../package.json');
5 | const shell = require('shelljs');
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ];
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | });
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = [];
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i];
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | );
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('');
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'));
44 | console.log();
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i];
48 | console.log(' ' + warning);
49 | }
50 |
51 | console.log();
52 | process.exit(1);
53 | }
54 |
55 | console.log('Node version check: ' + chalk.cyan('Passed.'));
56 | };
57 |
--------------------------------------------------------------------------------
/views/src/pages/Substation/substation.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .sub-list-content {
4 | @extend .main-content;
5 |
6 | .title {
7 | font-size: 20px;
8 | text-align: center;
9 | margin: 15px 0;
10 | }
11 |
12 | .filter-area {
13 | margin-top: 15px;
14 | margin-bottom: 30px;
15 |
16 | .filter-title {
17 | display: inline-block;
18 | margin-right: 20px;
19 | font-size: 16px;
20 | }
21 |
22 | .query-button {
23 | margin: 0 20px;
24 | }
25 | }
26 | }
27 |
28 | .add-form-content {
29 | @extend .main-content;
30 |
31 | .form-title {
32 | margin: 15px 0;
33 | text-align: center;
34 | font-size: 18px;
35 | }
36 |
37 | .form-button {
38 | width: 140px;
39 | }
40 | }
41 |
42 | .action-button {
43 | cursor: pointer !important;
44 | min-width: 40px !important;
45 | }
46 |
47 | .sub-detail-content {
48 | @extend .main-content;
49 | padding: 15px;
50 |
51 | .title {
52 | margin: 15px 20px;
53 | font-size: 20px;
54 | }
55 |
56 | .detail-wrapper {
57 | padding: 0 20px;
58 | }
59 |
60 | .row-normal {
61 | margin-top: 8px;
62 | height: 30px;
63 | }
64 |
65 | .row-pic {
66 | margin-top: 30px;
67 | height: 320px;
68 | }
69 |
70 | .col-normal {
71 | font-size: 15px;
72 | }
73 |
74 | .col-pic {
75 | position: relative;
76 | width: 100%;
77 | height: 100%;
78 |
79 | .col-pic-label {
80 | display: inline-block;
81 | font-size: 15px;
82 | margin-right: 10px;
83 | margin-bottom: 10px;
84 | }
85 | }
86 |
87 | .ant-tabs-tab {
88 | width: 80px !important;
89 | font-size: 14px;
90 | text-align: center;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/views/src/pages/Transbox/transbox.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/variables";
2 |
3 | .transbox-list-content {
4 | @extend .main-content;
5 |
6 | .title {
7 | font-size: 20px;
8 | text-align: center;
9 | margin: 15px 0;
10 | }
11 |
12 | .filter-area {
13 | margin-top: 15px;
14 | margin-bottom: 30px;
15 |
16 | .filter-title {
17 | display: inline-block;
18 | margin-right: 20px;
19 | font-size: 16px;
20 | }
21 |
22 | .query-button {
23 | margin: 0 20px;
24 | }
25 | }
26 | }
27 |
28 | .add-form-content {
29 | @extend .main-content;
30 |
31 | .form-title {
32 | margin: 15px 0;
33 | text-align: center;
34 | font-size: 18px;
35 | }
36 |
37 | .form-button {
38 | width: 140px;
39 | }
40 |
41 | .remove-button {
42 | color: red;
43 | opacity: 0.7;
44 | }
45 | }
46 |
47 | .action-button {
48 | cursor: pointer !important;
49 | min-width: 40px !important;
50 | }
51 |
52 | .trans-detail-content {
53 | @extend .main-content;
54 | padding: 15px;
55 |
56 | .title {
57 | margin: 15px 20px;
58 | font-size: 20px;
59 | }
60 |
61 | .detail-wrapper {
62 | padding: 0 20px;
63 | }
64 |
65 | .row-normal {
66 | margin-top: 8px;
67 | height: 30px;
68 | }
69 |
70 | .row-pic {
71 | margin-top: 10px;
72 | height: 320px;
73 | }
74 |
75 | .col-normal {
76 | font-size: 15px;
77 | }
78 |
79 | .col-pic {
80 | position: relative;
81 | width: 100%;
82 | height: 100%;
83 |
84 | .col-pic-label {
85 | display: inline-block;
86 | font-size: 15px;
87 | margin-right: 10px;
88 | margin-bottom: 10px;
89 | }
90 | }
91 |
92 | .ant-tabs-tab {
93 | width: 80px !important;
94 | font-size: 14px;
95 | text-align: center;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/views/src/router/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect, Switch } from 'react-router-dom';
3 | import { unAuthRoutes, userRoutes } from './routes';
4 | import MainLayout from '../components/MainLayout';
5 | import userService from '../services/user';
6 | import Utils from '../utils';
7 |
8 | const routeGuard = routerConfig => (
9 | {
14 | const hasLogin = userService.hasLogin();
15 | const { pathname } = props.location;
16 |
17 | if (!hasLogin) {
18 | if (pathname !== '/login') {
19 | return ;
20 | } else {
21 | return ;
22 | }
23 | }
24 |
25 | if (routerConfig.redirectToPath) {
26 | return (
27 |
30 | );
31 | }
32 |
33 | const indexPath = Utils.getUserIndexPath();
34 | if (pathname === '/login' || pathname === '/') {
35 | return ;
36 | }
37 |
38 | return ;
39 | }}
40 | />
41 | );
42 |
43 | class AppRouter extends React.Component {
44 | render() {
45 | return (
46 |
47 | {unAuthRoutes.map(routerConfig => routeGuard(routerConfig))}
48 |
49 | {userRoutes.map(routerConfig => routeGuard(routerConfig))}
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default AppRouter;
57 |
--------------------------------------------------------------------------------
/views/src/components/MainLayout/layout.scss:
--------------------------------------------------------------------------------
1 | @import '../../style/variables';
2 |
3 | .main-layout {
4 | height: 100%;
5 | position: relative;
6 |
7 | .line-wrapper {
8 | position: absolute;
9 | width: 100%;
10 | top: 62px;
11 | height: 3px;
12 | z-index: 10;
13 | }
14 |
15 | .menu-head-wrapper {
16 | height: $header-height;
17 | overflow: hidden;
18 |
19 | .menu-title {
20 | display: block;
21 | text-align: center;
22 | line-height: $header-height;
23 | color: $text-color-black;
24 | font-size: 17px;
25 | height: $header-height;
26 | }
27 | }
28 |
29 | .main-header {
30 | position: relative;
31 | background: #ffffff;
32 | padding: 0;
33 |
34 | .sep-line {
35 | position: absolute;
36 | left: 0;
37 | top: 13px;
38 | bottom: 13px;
39 | width: 1px;
40 | background-color: $separator-color;
41 | }
42 |
43 | .trigger {
44 | font-size: 18px;
45 | line-height: 64px;
46 | padding: 0 24px;
47 | cursor: pointer;
48 | transition: color .3s;
49 | }
50 |
51 | .trigger:hover {
52 | color: #1890ff;
53 | }
54 |
55 | .user {
56 | position: absolute;
57 | top: 0;
58 | right: 40px;
59 |
60 | .user-avatar {
61 | color: #ffffff;
62 | background-color: $action-color-blue;
63 | }
64 |
65 | .user-name {
66 | display: inline-block;
67 | height: $header-height;
68 | line-height: $header-height;
69 | font-size: $font-size-normal;
70 | padding-right: 10px;
71 | }
72 | }
73 |
74 | .sign-out-button {
75 | color: $action-color-blue;
76 | }
77 | }
78 |
79 | .menu-item {
80 | font-size: $font-size-normal;
81 | height: 50px !important;
82 | line-height: 50px !important;
83 |
84 | .icon {
85 | font-size: $font-size-normal;
86 | }
87 | }
88 |
89 | .content {
90 | margin: 24px;
91 | background: $global-background-color;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/views/src/router/routes.js:
--------------------------------------------------------------------------------
1 | import Login from '../pages/Login';
2 | import Substation from '../pages/Substation';
3 | import Transbox from '../pages/Transbox';
4 | import Lampins from '../pages/Lampins';
5 | import Worklog from '../pages/Worklog';
6 | import NotFound from '../pages/NotFound';
7 | import EditSubstation from '../pages/Substation/edit';
8 | import SubstationDetail from '../pages/Substation/detail';
9 | import EditTransbox from '../pages/Transbox/edit';
10 | import TransboxDetail from '../pages/Transbox/detail';
11 |
12 | export const unAuthRoutes = [
13 | {
14 | id: 'login',
15 | path: '/login',
16 | component: Login,
17 | exact: true,
18 | },
19 | ];
20 |
21 | export const userRoutes = [
22 | {
23 | id: 'entry',
24 | path: '/',
25 | exact: true,
26 | },
27 | {
28 | id: 'substation',
29 | path: '/substation',
30 | component: Substation,
31 | exact: true,
32 | },
33 | {
34 | id: 'edit-substation',
35 | path: '/substation/edit',
36 | component: EditSubstation,
37 | exact: true,
38 | },
39 | {
40 | id: 'detail-substation',
41 | path: '/substation/detail',
42 | component: SubstationDetail,
43 | exact: true,
44 | },
45 | {
46 | id: 'transbox',
47 | path: '/transbox',
48 | component: Transbox,
49 | exact: true,
50 | },
51 | {
52 | id: 'edit-transbox',
53 | path: '/transbox/edit',
54 | component: EditTransbox,
55 | exact: true,
56 | },
57 | {
58 | id: 'detail-transbox',
59 | path: '/transbox/detail',
60 | component: TransboxDetail,
61 | exact: true,
62 | },
63 | {
64 | id: 'lampins',
65 | path: '/lampins',
66 | component: Lampins,
67 | exact: true,
68 | },
69 | {
70 | id: 'worklog',
71 | path: '/worklog',
72 | component: Worklog,
73 | exact: true,
74 | },
75 | {
76 | id: '404',
77 | path: '/404',
78 | component: NotFound,
79 | exact: true,
80 | },
81 | {
82 | id: '*',
83 | path: '*',
84 | redirectToPath: '/404',
85 | },
86 | ];
87 |
--------------------------------------------------------------------------------
/server/controller/lampins.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Lampins } = require('../model');
5 |
6 | module.exports = class LampinsController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | if (ctx.errors) {
11 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
12 | return;
13 | }
14 |
15 | const opt = {
16 | skip: (pageIndex - 1) * pageSize,
17 | limit: pageSize,
18 | sort: '-create_at'
19 | };
20 |
21 | const lampins = await Lampins.find(null, null, opt);
22 | const count = await Lampins.count();
23 | ctx.body = ctx.util.resuccess({ count, lampins });
24 | }
25 |
26 | static async create(ctx) {
27 | const {
28 | date,
29 | road_name,
30 | record,
31 | resolved,
32 | } = ctx.request.body;
33 |
34 | if (ctx.errors) {
35 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
36 | return;
37 | }
38 |
39 | const newLampins = new Lampins({
40 | date, road_name, resolved, record
41 | });
42 | newLampins.save();
43 |
44 | ctx.body = ctx.util.resuccess();
45 | }
46 |
47 | static async update(ctx) {
48 | const insId = ctx.checkBody('_id').notEmpty().value;
49 | const {
50 | date,
51 | road_name,
52 | record,
53 | resolved,
54 | } = ctx.request.body;
55 |
56 | if (ctx.errors) {
57 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
58 | return;
59 | }
60 |
61 | await Lampins.findOneAndUpdate( { _id: insId }, {
62 | date, road_name, resolved, record
63 | }, null, (err) => {
64 | if (err) {
65 | ctx.body = ctx.util.refail(err.message, null, err);
66 | } else {
67 | ctx.body = ctx.util.resuccess();
68 | }
69 | });
70 | }
71 |
72 | static async delete(ctx) {
73 | const insId = ctx.checkBody('id').notEmpty().value;
74 |
75 | if (ctx.errors) {
76 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
77 | return;
78 | }
79 |
80 | await Lampins.findByIdAndRemove(insId);
81 |
82 | ctx.body = ctx.util.resuccess();
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/server/router/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const Router = require('koa-router');
5 | const ratelimit = require('koa-ratelimit');
6 | const {
7 | user,
8 | substation,
9 | lampins,
10 | transbox,
11 | transformer,
12 | voltage,
13 | lowvol,
14 | worklog
15 | } = require('../controller');
16 | const util = require('../util');
17 |
18 | const apiRouter = new Router({ prefix: '/api/pems' });
19 | const rate = ratelimit({
20 | db: null,
21 | id: ctx => ctx.url,
22 | max: config.RATE_LIMIT_MAX,
23 | duration: config.RATE_LIMIT_DURATION,
24 | errorMessage: 'Sometimes You Just Have to Slow Down.',
25 | headers: {
26 | remaining: 'Rate-Limit-Remaining',
27 | reset: 'Rate-Limit-Reset',
28 | total: 'Rate-Limit-Total'
29 | }
30 | });
31 |
32 | // user
33 | apiRouter
34 | .post('/login', user.login)
35 | // substation
36 | .get('/substation/list', substation.list)
37 | .post('/substation/create', substation.create)
38 | .post('/substation/update', substation.update)
39 | .post('/substation/delete', substation.delete)
40 | // voltage
41 | .get('/voltage/list', voltage.list)
42 | .post('/voltage/create', voltage.create)
43 | .post('/voltage/update', voltage.update)
44 | .post('/voltage/delete', voltage.delete)
45 | // lowvol
46 | .get('/lowvol/list', lowvol.list)
47 | .post('/lowvol/create', lowvol.create)
48 | .post('/lowvol/update', lowvol.update)
49 | .post('/lowvol/delete', lowvol.delete)
50 | // transformer
51 | .get('/transformer/list', transformer.list)
52 | .post('/transformer/create', transformer.create)
53 | .post('/transformer/update', transformer.update)
54 | .post('/transformer/delete', transformer.delete)
55 | // worklog
56 | .get('/worklog/list', worklog.list)
57 | .post('/worklog/create', worklog.create)
58 | .post('/worklog/update', worklog.update)
59 | .post('/worklog/delete', worklog.delete)
60 | // lampins
61 | .get('/lampins/list', lampins.list)
62 | .post('/lampins/create', lampins.create)
63 | .post('/lampins/update', lampins.update)
64 | .post('/lampins/delete', lampins.delete)
65 | // transbox
66 | .get('/transbox/list', transbox.list)
67 | .post('/transbox/create', transbox.create)
68 | .post('/transbox/update', transbox.update)
69 | .post('/transbox/delete', transbox.delete);
70 |
71 | module.exports = apiRouter;
72 |
--------------------------------------------------------------------------------
/views/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | const utils = require('./utils');
2 | const config = require('../config');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const path = require('path');
5 |
6 | function resolve(dir) {
7 | return path.join(__dirname, '..', dir);
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: './src/index.js'
13 | },
14 | output: {
15 | path: config.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: config.assetsPublicPath
18 | },
19 | resolve: {
20 | alias: {
21 | '@': resolve('src')
22 | }
23 | },
24 | module: {
25 | rules: [
26 | // js loader
27 | {
28 | test: /\.js$/,
29 | exclude: /node_modules/,
30 | use: {
31 | loader: 'babel-loader',
32 | options: {
33 | presets: [
34 | '@babel/preset-env',
35 | '@babel/preset-react'
36 | ],
37 | plugins: [
38 | '@babel/plugin-syntax-dynamic-import',
39 | '@babel/transform-runtime',
40 | '@babel/plugin-proposal-class-properties'
41 | ]
42 | }
43 | }
44 | },
45 | // image loader
46 | {
47 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
48 | loader: 'url-loader',
49 | options: {
50 | limit: 8192,
51 | name: utils.assetsPath('img/[name].[hash:8].[ext]')
52 | }
53 | },
54 | // font loader
55 | {
56 | test: /\.(woff|woff2|eot|ttf|otf)$/,
57 | loader: 'url-loader',
58 | options: {
59 | limit: 8192,
60 | name: utils.assetsPath('fonts/[name].[hash:8].[ext]')
61 | }
62 | },
63 | // player file
64 | {
65 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
66 | loader: 'url-loader',
67 | options: {
68 | limit: 8192,
69 | name: utils.assetsPath('media/[name].[hash:8].[ext]')
70 | }
71 | }
72 | ]
73 | },
74 | plugins: [
75 | new HtmlWebpackPlugin({
76 | template: 'public/index.html',
77 | filename: 'index.html'
78 | })
79 | ],
80 | node: {
81 | setImmediate: false,
82 | dgram: 'empty',
83 | fs: 'empty',
84 | net: 'empty',
85 | tls: 'empty',
86 | child_process: 'empty',
87 | },
88 | performance: false
89 | };
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pems
2 | A javascript full-stack power equipment management system.
3 |
4 | ## Demo
5 |
6 | Demo Address: [Power Equipment Management System](https://pems.rannstudio.com)
7 | Account: admin/admin
8 |
9 | ## Screenshots
10 |
11 | * Login Page
12 |
13 | 
14 |
15 | * List Page
16 |
17 | 
18 |
19 | * Add Page
20 |
21 | 
22 |
23 | * Add Popup
24 |
25 | 
26 |
27 | ## Tech Stack
28 |
29 | ### Frontend
30 |
31 | - [ ] Typescript
32 | - [x] React
33 | - [x] React-Router
34 | - [ ] React-Redux
35 | - [x] Ant Design
36 | - [x] Axios
37 | - [ ] GraphQL
38 | - [x] AliOSS
39 | - [ ] i18n
40 | - [x] Sass
41 | - [x] ESLint
42 | - [x] Babel
43 | - [x] Webpack
44 |
45 | ### Backend (Node)
46 |
47 | - [x] Koa2
48 | - [ ] Express
49 | - [ ] Egg
50 | - [x] MongoDB
51 |
52 | ## Setup
53 |
54 | ### Backend
55 |
56 | * MongoDB
57 |
58 | Add your MongoDB path in *server/config/index.js*.
59 |
60 | 
61 |
62 | * Other Config
63 |
64 | **JWT Secret**, **expire date**, **serve path** also can be modified in *server/config/index.js*.
65 |
66 | ### Frontend
67 |
68 | * Aliyun OSS
69 |
70 | Set **OSS_REGION**, **OSS_ACCESS_KEY_ID**, **OSS_ACCESS_KEY_SECRET**, **OSS_BUCKET** in *views/src/constants.js*.
71 |
72 | * Home URL
73 |
74 | Set **HOME_URL** in *views/src/constants.js*, system will redirect to this address when JWT expires.
75 |
76 | * API Base URL
77 |
78 | Set **SERVICE_URL** in *views/config/prod.env.js*. (Deploy Mode)
79 |
80 | ### Debug
81 |
82 | * build
83 |
84 | ```bash
85 | cd views/
86 | npm run build
87 | ```
88 |
89 | * serve page & start api server
90 |
91 | require [pm2](https://pm2.keymetrics.io/)
92 |
93 | ```bash
94 | cd server/
95 | npm run dev
96 | ```
97 |
98 | Then you can open http://127.0.0.1:3010
99 |
100 | ## Deployment
101 |
102 | Upload website dist & server code to your server, and run ```npm run start``` in server folder.
103 | (default webpage path is */var/www/pems*, you can config it in *server/config/index.js*)
104 |
105 | ## Wechat Official Account
106 |
107 | 
108 |
109 | ## License
110 |
111 | MIT License
112 |
113 | Copyright (c) 2019 Hanran Liu
114 |
--------------------------------------------------------------------------------
/views/src/pages/Login/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './login.scss';
3 | import { Input, Icon, Button, Form, message } from 'antd';
4 | import userService from '../../services/user';
5 |
6 | const FormItem = Form.Item;
7 |
8 | class LoginForm extends React.Component {
9 | state = {
10 | isLoading: false,
11 | };
12 |
13 | setLoadingState = isLoading => {
14 | this.setState({
15 | isLoading,
16 | });
17 | };
18 |
19 | handleSubmit = e => {
20 | e.preventDefault();
21 | this.props.form.validateFields((err, values) => {
22 | if (err) {
23 | return;
24 | }
25 |
26 | this.setLoadingState(true);
27 | const hide = message.loading('正在登录...', 0);
28 | userService.login(values).then(
29 | () => {
30 | this.setLoadingState(false);
31 | hide();
32 | message.success('登录成功');
33 | this.props.history.push('/');
34 | },
35 | error => {
36 | this.setLoadingState(false);
37 | hide();
38 | message.error(error.data.message);
39 | },
40 | );
41 | });
42 | };
43 |
44 | render() {
45 | const { form } = this.props;
46 | const { getFieldDecorator } = form;
47 | const { isLoading } = this.state;
48 | return (
49 |
76 | );
77 | }
78 | }
79 | const PEMSLoginForm = Form.create({})(LoginForm);
80 |
81 | class Login extends React.Component {
82 | render() {
83 | return (
84 |
85 |
86 |
电力设施管理系统
87 |
90 |
91 |
92 |
93 | );
94 | }
95 | }
96 |
97 | export default Login;
98 |
--------------------------------------------------------------------------------
/views/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pems",
3 | "version": "1.0.0",
4 | "author": "Rann ",
5 | "scripts": {
6 | "dev": "webpack-dev-server --hot --inline --config build/webpack.dev.conf.js",
7 | "start": "npm run dev",
8 | "build": "npm run lint && node build/build.js",
9 | "mock": "cross-env MOCK=1 npm run dev",
10 | "report": "npm run build --report",
11 | "lint": "./node_modules/.bin/eslint src"
12 | },
13 | "husky": {
14 | "hooks": {
15 | "pre-commit": "npm run lint"
16 | }
17 | },
18 | "dependencies": {
19 | "ali-oss": "^6.1.1",
20 | "ant-design-pro": "^2.3.2",
21 | "antd": "^3.23.1",
22 | "axios": "^0.19.0",
23 | "crypto-js": "^3.1.9-1",
24 | "history": "^4.9.0",
25 | "js-base64": "^2.5.1",
26 | "lodash": "^4.17.15",
27 | "moment": "^2.24.0",
28 | "react": "^16.8.6",
29 | "react-dom": "^16.8.6",
30 | "react-router-dom": "^5.0.1",
31 | "rimraf": "^3.0.0",
32 | "autoprefixer": "^9.5.1"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "^7.4.4",
36 | "@babel/plugin-proposal-class-properties": "^7.4.4",
37 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
38 | "@babel/plugin-transform-runtime": "^7.4.4",
39 | "@babel/preset-env": "^7.4.4",
40 | "@babel/preset-react": "^7.0.0",
41 | "@babel/runtime": "^7.5.5",
42 | "babel-eslint": "^10.0.1",
43 | "babel-loader": "^8.0.5",
44 | "babel-runtime": "^6.26.0",
45 | "chalk": "^2.4.2",
46 | "child_process": "^1.0.2",
47 | "compression-webpack-plugin": "^2.0.0",
48 | "copy-webpack-plugin": "^5.0.3",
49 | "cross-env": "^5.2.0",
50 | "css-loader": "^2.1.1",
51 | "eslint": "^5.16.0",
52 | "eslint-config-airbnb": "^17.1.0",
53 | "eslint-config-prettier": "^4.2.0",
54 | "eslint-plugin-import": "^2.17.2",
55 | "eslint-plugin-jsx-a11y": "^6.2.1",
56 | "eslint-plugin-prettier": "^3.0.1",
57 | "eslint-plugin-react": "^7.13.0",
58 | "favicons-webpack-plugin": "0.0.9",
59 | "file-loader": "^3.0.1",
60 | "html-webpack-plugin": "^3.2.0",
61 | "husky": "^2.2.0",
62 | "mini-css-extract-plugin": "^0.6.0",
63 | "node-sass": "^4.12.0",
64 | "optimize-css-assets-webpack-plugin": "^5.0.1",
65 | "ora": "^3.4.0",
66 | "postcss-loader": "^3.0.0",
67 | "prettier": "^1.17.0",
68 | "rm": "^0.1.8",
69 | "sass-loader": "^7.1.0",
70 | "semver": "^6.0.0",
71 | "shelljs": "^0.8.3",
72 | "style-loader": "^0.23.1",
73 | "uglifyjs-webpack-plugin": "^2.1.2",
74 | "url-loader": "^1.1.2",
75 | "webpack": "^4.30.0",
76 | "webpack-bundle-analyzer": "^3.3.2",
77 | "webpack-cli": "^3.3.0",
78 | "webpack-dev-server": "^3.3.1",
79 | "webpack-merge": "^4.2.1"
80 | },
81 | "engines": {
82 | "node": ">= 6.0.0",
83 | "npm": ">= 3.0.0"
84 | },
85 | "browserslist": [
86 | "> 1%",
87 | "last 2 versions",
88 | "not ie <= 8"
89 | ]
90 | }
91 |
--------------------------------------------------------------------------------
/server/controller/worklog.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Worklog, Substation, Transbox } = require('../model');
5 |
6 | module.exports = class WorklogController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const subId = ctx.checkQuery('subId').value;
11 | const transId = ctx.checkQuery('transId').value;
12 |
13 | if (ctx.errors) {
14 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
15 | return;
16 | }
17 |
18 | const opt = {
19 | skip: (pageIndex - 1) * pageSize,
20 | limit: pageSize,
21 | sort: '-create_at'
22 | };
23 |
24 | let query;
25 | if (subId) {
26 | query = { substation: subId };
27 | }
28 | if (transId) {
29 | query = { transbox: transId };
30 | }
31 |
32 | const worklogs = await Worklog
33 | .find(query, null, opt)
34 | .populate({ path: 'substation', select: { name: 1 } })
35 | .populate({ path: 'transbox', select: { name: 1 } });
36 | const count = await Worklog.count(query);
37 | ctx.body = ctx.util.resuccess({ count, worklogs });
38 | }
39 |
40 | static async create(ctx) {
41 | const {
42 | date,
43 | content,
44 | resolved,
45 | notify_user,
46 | subId,
47 | transId
48 | } = ctx.request.body;
49 |
50 | if (ctx.errors) {
51 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
52 | return;
53 | }
54 |
55 | const newWorklog = new Worklog({
56 | date, content, resolved, notify_user, substation: subId, transbox: transId,
57 | });
58 | newWorklog.save();
59 |
60 | if (subId) {
61 | const substation = await Substation.findOne({ _id: subId });
62 | substation.worklogs.push(newWorklog);
63 | substation.save();
64 | }
65 | if (transId) {
66 | const transbox = await Transbox.findOne({ _id: transId });
67 | transbox.worklogs.push(newWorklog);
68 | transbox.save();
69 | }
70 |
71 | ctx.body = ctx.util.resuccess();
72 | }
73 |
74 | static async update(ctx) {
75 | const logId = ctx.checkBody('_id').notEmpty().value;
76 | const {
77 | date,
78 | content,
79 | resolved,
80 | notify_user,
81 | } = ctx.request.body;
82 |
83 | if (ctx.errors) {
84 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
85 | return;
86 | }
87 |
88 | await Worklog.findOneAndUpdate( { _id: logId }, {
89 | date, content, resolved, notify_user
90 | }, null, (err) => {
91 | if (err) {
92 | ctx.body = ctx.util.refail(err.message, null, err);
93 | } else {
94 | ctx.body = ctx.util.resuccess();
95 | }
96 | });
97 | }
98 |
99 | static async delete(ctx) {
100 | const logId = ctx.checkBody('id').notEmpty().value;
101 |
102 | if (ctx.errors) {
103 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
104 | return;
105 | }
106 |
107 | await Worklog.findByIdAndRemove(logId);
108 |
109 | ctx.body = ctx.util.resuccess();
110 | }
111 | };
112 |
--------------------------------------------------------------------------------
/server/controller/lowvol.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Lowvol, Substation, Transbox } = require('../model');
5 |
6 | module.exports = class LowvolController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const subId = ctx.checkQuery('subId').value;
11 | const transId = ctx.checkQuery('transId').value;
12 |
13 | if (ctx.errors) {
14 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
15 | return;
16 | }
17 |
18 | const opt = {
19 | skip: (pageIndex - 1) * pageSize,
20 | limit: pageSize,
21 | sort: '-create_at'
22 | };
23 |
24 | let query;
25 | if (subId) {
26 | query = { substation: subId };
27 | }
28 | if (transId) {
29 | query = { transbox: transId };
30 | }
31 |
32 | const voltages = await Lowvol.find(query, null, opt);
33 | const count = await Lowvol.count(query);
34 | ctx.body = ctx.util.resuccess({ count, voltages });
35 | }
36 |
37 | static async create(ctx) {
38 | const number = ctx.checkBody('number').notEmpty().value;
39 | const {
40 | range,
41 | model,
42 | subId,
43 | transId
44 | } = ctx.request.body;
45 |
46 | if (ctx.errors) {
47 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
48 | return;
49 | }
50 |
51 | const voltage = await Lowvol.findOne({ 'number': number });
52 | if (voltage) {
53 | ctx.body = ctx.util.refail(`${number} 调度号的高压室已经存在`);
54 | return;
55 | }
56 |
57 | const newVol = new Lowvol({
58 | number, range, model, substation: subId, transbox: transId,
59 | });
60 | newVol.save();
61 |
62 | if (subId) {
63 | const substation = await Substation.findOne({ _id: subId });
64 | substation.low_vols.push(newVol);
65 | substation.save();
66 | }
67 | if (transId) {
68 | const transbox = await Transbox.findOne({ _id: transId });
69 | transbox.low_vols.push(lowvol);
70 | transbox.save();
71 | }
72 |
73 | ctx.body = ctx.util.resuccess();
74 | }
75 |
76 | static async update(ctx) {
77 | const volId = ctx.checkBody('_id').notEmpty().value;
78 | const number = ctx.checkBody('number').notEmpty().value;
79 | const {
80 | range,
81 | model
82 | } = ctx.request.body;
83 |
84 | if (ctx.errors) {
85 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
86 | return;
87 | }
88 |
89 | const voltage = await Lowvol.findOne({ 'number': number });
90 | if (voltage && voltage._id.toString() !== volId) {
91 | ctx.body = ctx.util.refail(`调度号 ${number} 的低压室已经存在`);
92 | return;
93 | }
94 |
95 | await Lowvol.findOneAndUpdate( { _id: volId }, {
96 | number, range, model
97 | }, null, (err) => {
98 | if (err) {
99 | ctx.body = ctx.util.refail(err.message, null, err);
100 | } else {
101 | ctx.body = ctx.util.resuccess();
102 | }
103 | });
104 | }
105 |
106 | static async delete(ctx) {
107 | const volId = ctx.checkBody('id').notEmpty().value;
108 |
109 | if (ctx.errors) {
110 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
111 | return;
112 | }
113 |
114 | await Lowvol.findByIdAndRemove(volId);
115 |
116 | ctx.body = ctx.util.resuccess();
117 | }
118 | };
119 |
--------------------------------------------------------------------------------
/server/controller/voltage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Voltage, Substation, Transbox } = require('../model');
5 |
6 | module.exports = class VoltageController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const subId = ctx.checkQuery('subId').value;
11 | const transId = ctx.checkQuery('transId').value;
12 |
13 | if (ctx.errors) {
14 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
15 | return;
16 | }
17 |
18 | const opt = {
19 | skip: (pageIndex - 1) * pageSize,
20 | limit: pageSize,
21 | sort: '-create_at'
22 | };
23 |
24 | let query;
25 | if (subId) {
26 | query = { substation: subId };
27 | }
28 | if (transId) {
29 | query = { transbox: transId };
30 | }
31 |
32 | const voltages = await Voltage.find(query, null, opt);
33 | const count = await Voltage.count(query);
34 | ctx.body = ctx.util.resuccess({ count, voltages });
35 | }
36 |
37 | static async create(ctx) {
38 | const number = ctx.checkBody('number').notEmpty().value;
39 | const {
40 | range,
41 | model,
42 | subId,
43 | transId
44 | } = ctx.request.body;
45 |
46 | if (ctx.errors) {
47 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
48 | return;
49 | }
50 |
51 | const voltage = await Voltage.findOne({ 'number': number });
52 | if (voltage) {
53 | ctx.body = ctx.util.refail(`${number} 调度号的高压室已经存在`);
54 | return;
55 | }
56 |
57 | const newVol = new Voltage({
58 | number, range, model, substation: subId, transbox: transId,
59 | });
60 | newVol.save();
61 |
62 | if (subId) {
63 | const substation = await Substation.findOne({ _id: subId });
64 | substation.high_vols.push(newVol);
65 | substation.save();
66 | }
67 | if (transId) {
68 | const transbox = await Transbox.findOne({ _id: transId });
69 | transbox.high_vols.push(newVol);
70 | transbox.save();
71 | }
72 |
73 | ctx.body = ctx.util.resuccess();
74 | }
75 |
76 | static async update(ctx) {
77 | const volId = ctx.checkBody('_id').notEmpty().value;
78 | const number = ctx.checkBody('number').notEmpty().value;
79 | const {
80 | range,
81 | model
82 | } = ctx.request.body;
83 |
84 | if (ctx.errors) {
85 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
86 | return;
87 | }
88 |
89 | const voltage = await Voltage.findOne({ 'number': number });
90 | if (voltage && voltage._id.toString() !== volId) {
91 | ctx.body = ctx.util.refail(`调度号 ${number} 的高压室已经存在`);
92 | return;
93 | }
94 |
95 | await Voltage.findOneAndUpdate( { _id: volId }, {
96 | number, range, model
97 | }, null, (err) => {
98 | if (err) {
99 | ctx.body = ctx.util.refail(err.message, null, err);
100 | } else {
101 | ctx.body = ctx.util.resuccess();
102 | }
103 | });
104 | }
105 |
106 | static async delete(ctx) {
107 | const volId = ctx.checkBody('id').notEmpty().value;
108 |
109 | if (ctx.errors) {
110 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
111 | return;
112 | }
113 |
114 | await Voltage.findByIdAndRemove(volId);
115 |
116 | ctx.body = ctx.util.resuccess();
117 | }
118 | };
119 |
--------------------------------------------------------------------------------
/views/src/components/MainLayout/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout, Menu, Icon, Avatar, Popover, message, Button } from 'antd';
3 | import './layout.scss';
4 | import { withRouter, NavLink } from 'react-router-dom';
5 | import userService from '../../services/user';
6 | import utils from '../../utils';
7 |
8 | const { Header, Sider, Content } = Layout;
9 | const MenuItem = Menu.Item;
10 |
11 | class UserMenu extends React.Component {
12 | render() {
13 | const menuList = utils.getUserMenuItems();
14 | const { pathname } = this.props.location;
15 | const { collapsed } = this.props;
16 | const selectedMenu =
17 | pathname !== '/'
18 | ? menuList.find(_ => pathname.includes(_.linkTo))
19 | : menuList.find(_ => _.index);
20 | const selectedMenuKeys = selectedMenu ? [selectedMenu.id] : ['404'];
21 |
22 | return (
23 |
35 | );
36 | }
37 | }
38 | const SideUserMenu = withRouter(UserMenu);
39 |
40 | class MainLayout extends React.Component {
41 | state = {
42 | collapsed: false,
43 | };
44 |
45 | toggle = () => {
46 | this.setState({
47 | collapsed: !this.state.collapsed,
48 | });
49 | };
50 |
51 | logout = () => {
52 | const hideLoading = message.loading('正在退出登录...', 0);
53 | userService.logout();
54 | hideLoading();
55 | this.props.history.push('/login');
56 | };
57 |
58 | render() {
59 | const { collapsed } = this.state;
60 | const { history } = this.props;
61 | const user = userService.getLoginUser();
62 | if (!user) {
63 | history.push('/login');
64 | return
;
65 | }
66 | const { name } = user;
67 | return (
68 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 |
87 |
90 | 退出登录
91 |
92 | }
93 | trigger="hover"
94 | >
95 |
96 |
{name}
97 |
{name.charAt(0) || 'U'}
98 |
99 |
100 |
101 | {this.props.children}
102 |
103 |
104 | );
105 | }
106 | }
107 |
108 | export default withRouter(MainLayout);
109 |
--------------------------------------------------------------------------------
/server/controller/transbox.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Transbox, Voltage, Lowvol, Transformer, Worklog } = require('../model');
5 |
6 | module.exports = class TransboxController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const fil_area = ctx.checkQuery('fil_area').empty().value;
11 |
12 | if (ctx.errors) {
13 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
14 | return;
15 | }
16 |
17 | const opt = {
18 | skip: (pageIndex - 1) * pageSize,
19 | limit: pageSize,
20 | sort: '-create_at'
21 | };
22 | const where = fil_area ? { area: fil_area } : null;
23 |
24 | const transboxs = await Transbox
25 | .find(where, null, opt)
26 | .populate({ path: 'worklogs', select: { resolved: 1, notify_user: 1 } });
27 | const count = await Transbox.count(where);
28 |
29 | ctx.body = ctx.util.resuccess({ count, transboxs });
30 | }
31 |
32 | static async create(ctx) {
33 | const name = ctx.checkBody('name').notEmpty().value;
34 | const {
35 | superior,
36 | user_comp,
37 | contact_info,
38 | appear_pic,
39 | location_pic,
40 | area
41 | } = ctx.request.body;
42 |
43 | if (ctx.errors) {
44 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
45 | return;
46 | }
47 |
48 | const transbox = await Transbox.findOne({ 'name': name });
49 | if (transbox) {
50 | ctx.body = ctx.util.refail(`${name} 变电箱已存在`);
51 | return;
52 | }
53 |
54 | const newTransbox = new Transbox({
55 | name, superior,user_comp, contact_info, appear_pic, location_pic, area
56 | });
57 | newTransbox.save();
58 |
59 | ctx.body = ctx.util.resuccess();
60 | }
61 |
62 | static async update(ctx) {
63 | const transId = ctx.checkBody('_id').notEmpty().value;
64 | const name = ctx.checkBody('name').notEmpty().value;
65 | const {
66 | superior,
67 | user_comp,
68 | contact_info,
69 | appear_pic,
70 | location_pic,
71 | area
72 | } = ctx.request.body;
73 |
74 | if (ctx.errors) {
75 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
76 | return;
77 | }
78 |
79 | const transbox = await Transbox.findOne({ 'name': name });
80 | if (transbox && transbox._id.toString() !== transId) {
81 | ctx.body = ctx.util.refail(`${name} 变电箱已存在`);
82 | return;
83 | }
84 |
85 | await Transbox.findOneAndUpdate({ _id: transId }, {
86 | name,
87 | superior,
88 | user_comp,
89 | contact_info,
90 | appear_pic,
91 | location_pic,
92 | area
93 | }, null, (err) => {
94 | if (err) {
95 | ctx.body = ctx.util.refail(err.message, null, err);
96 | } else {
97 | ctx.body = ctx.util.resuccess();
98 | }
99 | });
100 | }
101 |
102 | static async delete(ctx) {
103 | const transId = ctx.checkBody('id').notEmpty().value;
104 |
105 | if (ctx.errors) {
106 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
107 | return;
108 | }
109 |
110 | const query = { transbox: transId };
111 | await Voltage.deleteMany(query);
112 | await Lowvol.deleteMany(query);
113 | await Transformer.deleteMany(query);
114 | await Worklog.deleteMany(query);
115 | await Transbox.findByIdAndRemove(transId);
116 |
117 | ctx.body = ctx.util.resuccess();
118 | }
119 | };
120 |
--------------------------------------------------------------------------------
/server/controller/transformer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Transformer, Substation, Transbox } = require('../model');
5 |
6 | module.exports = class TransformerController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const subId = ctx.checkQuery('subId').value;
11 | const transId = ctx.checkQuery('transId').value;
12 |
13 | if (ctx.errors) {
14 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
15 | return;
16 | }
17 |
18 | const opt = {
19 | skip: (pageIndex - 1) * pageSize,
20 | limit: pageSize,
21 | sort: '-create_at'
22 | };
23 |
24 | let query;
25 | if (subId) {
26 | query = { substation: subId };
27 | }
28 | if (transId) {
29 | query = { transbox: transId };
30 | }
31 |
32 | const transformers = await Transformer.find(query, null, opt);
33 | const count = await Transformer.count(query);
34 | ctx.body = ctx.util.resuccess({ count, transformers });
35 | }
36 |
37 | static async create(ctx) {
38 | const serial_number = ctx.checkBody('serial_number').notEmpty().value;
39 | const {
40 | model,
41 | subId,
42 | transId
43 | } = ctx.request.body;
44 |
45 | if (ctx.errors) {
46 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
47 | return;
48 | }
49 |
50 | const transformer = await Transformer.findOne({ 'serial_number': serial_number });
51 | if (transformer) {
52 | ctx.body = ctx.util.refail(`${serial_number} 编号变压器已经存在`);
53 | return;
54 | }
55 |
56 | const newTrans = new Transformer({
57 | serial_number, model, substation: subId, transbox: transId,
58 | });
59 | newTrans.save();
60 |
61 | if (subId) {
62 | const substation = await Substation.findOne({ _id: subId });
63 | substation.transformers.push(newTrans);
64 | substation.save();
65 | }
66 | if (transId) {
67 | const transbox = await Transbox.findOne({ _id: transId });
68 | transbox.transformers.push(newTrans);
69 | transbox.save();
70 | }
71 |
72 | ctx.body = ctx.util.resuccess();
73 | }
74 |
75 | static async update(ctx) {
76 | const transId = ctx.checkBody('_id').notEmpty().value;
77 | const serial_number = ctx.checkBody('serial_number').notEmpty().value;
78 | const {
79 | model
80 | } = ctx.request.body;
81 |
82 | if (ctx.errors) {
83 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
84 | return;
85 | }
86 |
87 | const transformer = await Transformer.findOne({ 'serial_number': serial_number });
88 | if (transformer && transformer._id.toString() !== transId) {
89 | ctx.body = ctx.util.refail(`${serial_number} 编号变压器已经存在`);
90 | return;
91 | }
92 |
93 | await Transformer.findOneAndUpdate( { _id: transId }, {
94 | serial_number, model
95 | }, null, (err) => {
96 | if (err) {
97 | ctx.body = ctx.util.refail(err.message, null, err);
98 | } else {
99 | ctx.body = ctx.util.resuccess();
100 | }
101 | });
102 | }
103 |
104 | static async delete(ctx) {
105 | const transId = ctx.checkBody('id').notEmpty().value;
106 |
107 | if (ctx.errors) {
108 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
109 | return;
110 | }
111 |
112 | await Transformer.findByIdAndRemove(transId);
113 |
114 | ctx.body = ctx.util.resuccess();
115 | }
116 | };
117 |
--------------------------------------------------------------------------------
/server/controller/substation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const config = require('../config');
4 | const { Substation, Voltage, Lowvol, Transformer, Worklog } = require('../model');
5 |
6 | module.exports = class SubstationController {
7 | static async list(ctx) {
8 | const pageIndex = ctx.checkQuery('page_index').empty().toInt().gt(0).default(1).value;
9 | const pageSize = ctx.checkQuery('page_size').empty().toInt().gt(0).default(config.DEFAULT_PAGE_SIZE).value;
10 | const fil_area = ctx.checkQuery('fil_area').empty().value;
11 |
12 | if (ctx.errors) {
13 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
14 | return;
15 | }
16 |
17 | const opt = {
18 | skip: (pageIndex - 1) * pageSize,
19 | limit: pageSize,
20 | sort: '-create_at'
21 | };
22 | const where = fil_area ? { area: fil_area } : null;
23 |
24 | const substations = await Substation
25 | .find(where, null, opt)
26 | .populate({ path: 'worklogs', select: { resolved: 1, notify_user: 1 } });
27 | const count = await Substation.count(where);
28 |
29 | ctx.body = ctx.util.resuccess({ count, substations });
30 | }
31 |
32 | static async create(ctx) {
33 | const name = ctx.checkBody('name').notEmpty().value;
34 | const {
35 | superior,
36 | user_comp,
37 | contact_info,
38 | number,
39 | appear_pic,
40 | location_pic,
41 | area
42 | } = ctx.request.body;
43 |
44 | if (ctx.errors) {
45 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
46 | return;
47 | }
48 |
49 | const substation = await Substation.findOne({ 'name': name });
50 | if (substation) {
51 | ctx.body = ctx.util.refail(`${name} 变电所已存在`);
52 | return;
53 | }
54 |
55 | const newSub = new Substation({
56 | name, superior, user_comp, number, contact_info, appear_pic, location_pic, area
57 | });
58 | newSub.save();
59 |
60 | ctx.body = ctx.util.resuccess();
61 | }
62 |
63 | static async update(ctx) {
64 | const subId = ctx.checkBody('_id').notEmpty().value;
65 | const name = ctx.checkBody('name').notEmpty().value;
66 | const {
67 | superior,
68 | user_comp,
69 | contact_info,
70 | number,
71 | appear_pic,
72 | location_pic,
73 | area
74 | } = ctx.request.body;
75 |
76 | if (ctx.errors) {
77 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
78 | return;
79 | }
80 |
81 | const substation = await Substation.findOne({ 'name': name });
82 | if (substation && substation._id.toString() !== subId) {
83 | ctx.body = ctx.util.refail(`${name} 变电所已存在`);
84 | return;
85 | }
86 |
87 | await Substation.findOneAndUpdate({ _id: subId }, {
88 | name,
89 | superior,
90 | user_comp,
91 | contact_info,
92 | number,
93 | appear_pic,
94 | location_pic,
95 | area
96 | }, null, (err) => {
97 | if (err) {
98 | ctx.body = ctx.util.refail(err.message, null, err);
99 | } else {
100 | ctx.body = ctx.util.resuccess();
101 | }
102 | });
103 | }
104 |
105 | static async delete(ctx) {
106 | const subId = ctx.checkBody('id').notEmpty().value;
107 |
108 | if (ctx.errors) {
109 | ctx.body = ctx.util.refail(null, 10001, ctx.errors);
110 | return;
111 | }
112 |
113 | const query = { substation: subId };
114 | await Voltage.deleteMany(query);
115 | await Lowvol.deleteMany(query);
116 | await Transformer.deleteMany(query);
117 | await Worklog.deleteMany(query);
118 | await Substation.findByIdAndRemove(subId);
119 |
120 | ctx.body = ctx.util.resuccess();
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/views/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { message } from 'antd';
3 | import utils from '../utils';
4 | import constants from '../constants';
5 |
6 | const instance = axios.create({
7 | baseURL: `${process.env.SERVICE_URL}/api/pems`,
8 | timeout: 10000,
9 | });
10 |
11 | instance.interceptors.request.use(
12 | config => {
13 | const user = utils.getUser();
14 | if (user) {
15 | const token = user.token;
16 | if (token) {
17 | config.headers.Authorization = `Bearer ${token}`;
18 | }
19 | }
20 | return config;
21 | },
22 | error => Promise.reject(error),
23 | );
24 |
25 | instance.interceptors.response.use(
26 | res => {
27 | const body = res.data;
28 | if (body.success === false) {
29 | if (body.code === 10001) {
30 | message.error('参数异常');
31 | }
32 | return Promise.reject(res);
33 | }
34 | return res;
35 | },
36 | error => {
37 | const res = error.response;
38 | if (res && res.status === 401 && /authentication/i.test(res.data.error)) {
39 | message.info('登录信息已过期,请重新登录');
40 | utils.removeUser();
41 | // react-redux dispatch
42 | window.location.href = constants.HOME_URL;
43 | } else if (res && res.data && res.data.error) {
44 | message.error(res.data.error);
45 | }
46 | return error;
47 | },
48 | );
49 |
50 | const createAPI = (url, method, config) => {
51 | config = config || {};
52 | return instance({
53 | url,
54 | method,
55 | ...config,
56 | });
57 | };
58 |
59 | const user = {
60 | login: config => createAPI('/login', 'post', config),
61 | };
62 |
63 | const substation = {
64 | getList: config => createAPI('/substation/list', 'get', config),
65 | create: config => createAPI('/substation/create', 'post', config),
66 | delete: config => createAPI('/substation/delete', 'post', config),
67 | update: config => createAPI('/substation/update', 'post', config),
68 | };
69 |
70 | const voltage = {
71 | getList: config => createAPI('/voltage/list', 'get', config),
72 | create: config => createAPI('/voltage/create', 'post', config),
73 | update: config => createAPI('/voltage/update', 'post', config),
74 | delete: config => createAPI('/voltage/delete', 'post', config),
75 | };
76 |
77 | const lowvol = {
78 | getList: config => createAPI('/lowvol/list', 'get', config),
79 | create: config => createAPI('/lowvol/create', 'post', config),
80 | update: config => createAPI('/lowvol/update', 'post', config),
81 | delete: config => createAPI('/lowvol/delete', 'post', config),
82 | };
83 |
84 | const transformer = {
85 | getList: config => createAPI('/transformer/list', 'get', config),
86 | create: config => createAPI('/transformer/create', 'post', config),
87 | update: config => createAPI('/transformer/update', 'post', config),
88 | delete: config => createAPI('/transformer/delete', 'post', config),
89 | };
90 |
91 | const worklog = {
92 | getList: config => createAPI('/worklog/list', 'get', config),
93 | create: config => createAPI('/worklog/create', 'post', config),
94 | update: config => createAPI('/worklog/update', 'post', config),
95 | delete: config => createAPI('/worklog/delete', 'post', config),
96 | };
97 |
98 | const lampins = {
99 | getList: config => createAPI('/lampins/list', 'get', config),
100 | create: config => createAPI('/lampins/create', 'post', config),
101 | update: config => createAPI('/lampins/update', 'post', config),
102 | delete: config => createAPI('/lampins/delete', 'post', config),
103 | };
104 |
105 | const transbox = {
106 | getList: config => createAPI('/transbox/list', 'get', config),
107 | create: config => createAPI('/transbox/create', 'post', config),
108 | update: config => createAPI('/transbox/update', 'post', config),
109 | delete: config => createAPI('/transbox/delete', 'post', config),
110 | };
111 |
112 | export { user, substation, voltage, lowvol, transformer, worklog, lampins, transbox };
113 |
--------------------------------------------------------------------------------
/views/src/pages/Transbox/detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col, Tabs } from 'antd';
3 | import Voltage from '../../components/Voltage';
4 | import Lowvol from '../../components/Lowvol';
5 | import Transformer from '../../components/Transformer';
6 | import Worklog from '../../components/Worklog';
7 | import './transbox.scss';
8 |
9 | const { TabPane } = Tabs;
10 |
11 | class TransboxDetail extends React.Component {
12 | render() {
13 | const { location } = this.props;
14 | const transbox = location.state;
15 |
16 | const appearStyle = transbox.appear_pic ? { maxHeight: '320px' } : { display: 'none' };
17 | const locationStyle = transbox.location_pic ? { maxHeight: '320px' } : { display: 'none' };
18 |
19 | return (
20 |
21 |
{transbox.name}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 箱变名称:
30 |
31 |
32 | {transbox.name}
33 |
34 |
35 | 上级电源:
36 |
37 |
38 | {transbox.superior}
39 |
40 |
41 |
42 |
43 |
44 |
45 | 用户:
46 |
47 |
48 | {transbox.user_comp || ''}
49 |
50 |
51 | 联系人:
52 |
53 |
54 | {transbox.contact_info || ''}
55 |
56 |
57 |
58 |
59 |
60 |
61 | 区域:
62 |
63 |
64 | {transbox.area}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
外观图:
73 |

74 |
75 |
76 |
77 |
78 |
变电所地理位置:
79 |

80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | );
102 | }
103 | }
104 |
105 | export default TransboxDetail;
106 |
--------------------------------------------------------------------------------
/views/src/pages/Substation/detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col, Tabs } from 'antd';
3 | import Voltage from '../../components/Voltage';
4 | import Lowvol from '../../components/Lowvol';
5 | import Transformer from '../../components/Transformer';
6 | import Worklog from '../../components/Worklog';
7 | import './substation.scss';
8 |
9 | const { TabPane } = Tabs;
10 |
11 | class SubstationDetail extends React.Component {
12 | render() {
13 | const { location } = this.props;
14 | const substation = location.state;
15 |
16 | const appearStyle = substation.appear_pic ? { maxHeight: '320px' } : { display: 'none' };
17 | const locationStyle = substation.location_pic ? { maxHeight: '320px' } : { display: 'none' };
18 |
19 | return (
20 |
21 |
{substation.name}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 变电所名称:
30 |
31 |
32 | {substation.name}
33 |
34 |
35 | 上级电源:
36 |
37 |
38 | {substation.superior}
39 |
40 |
41 |
42 |
43 |
44 |
45 | 用户:
46 |
47 |
48 | {substation.user_comp || ''}
49 |
50 |
51 | 联系人:
52 |
53 |
54 | {substation.contact_info || ''}
55 |
56 |
57 |
58 |
59 |
60 |
61 | 计量表号:
62 |
63 |
64 | {substation.number || ''}
65 |
66 |
67 | 区域:
68 |
69 |
70 | {substation.area}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
外观图:
79 |

80 |
81 |
82 |
83 |
84 |
变电所地理位置:
85 |

86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | export default SubstationDetail;
112 |
--------------------------------------------------------------------------------
/views/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const path = require('path');
3 | const baseConfig = require('./webpack.base.conf');
4 | const prodEnv = require('../config/prod.env');
5 | const webpack = require('webpack');
6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
8 | const config = require('../config');
9 | const utils = require('./utils');
10 | const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
11 | const HtmlWebpackPlugin = require('html-webpack-plugin');
12 | const CopyWebpackPlugin = require('copy-webpack-plugin');
13 | const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
14 |
15 | const webpackConfig = merge(baseConfig, {
16 | mode: 'production',
17 | output: {
18 | path: config.assetsRoot,
19 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
20 | chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
21 | },
22 | module: {
23 | rules: [
24 | // sass loader
25 | {
26 | test: /\.(scss|sass)$/,
27 | exclude: /node_modules/,
28 | use: utils.cssLoaders({
29 | isSass: true,
30 | importLoaders: 2,
31 | extract: true
32 | })
33 | },
34 | ]
35 | },
36 | optimization: {
37 | minimize: true,
38 | runtimeChunk: 'single',
39 | splitChunks: {
40 | chunks: 'async',
41 | minSize: 30000,
42 | maxSize: 0,
43 | minChunks: 1,
44 | maxAsyncRequests: 5,
45 | maxInitialRequests: 3,
46 | cacheGroups: {
47 | react: {
48 | name: 'react',
49 | test: (module) => {
50 | return /react|redux/.test(module.context);
51 | },
52 | chunks: 'initial',
53 | priority: 10,
54 | },
55 | vendor: {
56 | name: 'vendor',
57 | test: /[\\/]node_modules[\\/]/,
58 | chunks: 'initial',
59 | priority: 5,
60 | },
61 | common: {
62 | name: 'common',
63 | chunks: 'initial',
64 | priority: 2,
65 | minChunks: 2,
66 | },
67 | }
68 | }
69 | },
70 | plugins: [
71 | new UglifyJsPlugin({
72 | uglifyOptions: {
73 | compress: {
74 | warnings: false
75 | }
76 | },
77 | parallel: true
78 | }),
79 | new MiniCssExtractPlugin({
80 | filename: utils.assetsPath('css/[name].[contenthash].css'),
81 | chunkFilename: utils.assetsPath('css/[name].[contenthash].chunk.css')
82 | }),
83 | new OptimizeCssPlugin({
84 | cssProcessorOptions: {
85 | safe: true
86 | }
87 | }),
88 | new webpack.DefinePlugin({
89 | 'process.env': prodEnv
90 | }),
91 | new HtmlWebpackPlugin({
92 | template: 'public/index.html',
93 | filename: 'index.html',
94 | inject: true,
95 | minify: {
96 | removeComments: true,
97 | collapseWhitespace: true,
98 | removeRedundantAttributes: true,
99 | useShortDoctype: true,
100 | removeEmptyAttributes: true,
101 | removeStyleLinkTypeAttributes: true,
102 | keepClosingSlash: true,
103 | minifyJS: true,
104 | minifyCSS: true,
105 | minifyURLs: true,
106 | },
107 | chunksSortMode: 'dependency'
108 | }),
109 | new CopyWebpackPlugin([
110 | {
111 | from: path.resolve(__dirname, '../static'),
112 | to: config.assetsSubDirectory,
113 | ignore: ['.*']
114 | }
115 | ]),
116 | new webpack.HashedModuleIdsPlugin(),
117 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
118 | new FaviconsWebpackPlugin({
119 | logo: path.resolve(__dirname, '../static/logo.png'),
120 | icons: {
121 | android: false,
122 | appleIcon: false,
123 | appleStartup: false,
124 | coast: false,
125 | favicons: true,
126 | firefox: false,
127 | opengraph: false,
128 | twitter: false,
129 | yandex: false,
130 | windows: false
131 | }
132 | })
133 | ]
134 | });
135 |
136 | if (config.productionGzip) {
137 | const CompressionWebpackPlugin = require('compression-webpack-plugin');
138 |
139 | webpackConfig.plugins.push(
140 | new CompressionWebpackPlugin({
141 | filename: '[path].gz[query]',
142 | algorithm: 'gzip',
143 | test: new RegExp(
144 | '\\.(' +
145 | config.productionGzipExtensions.join('|') +
146 | ')$'
147 | ),
148 | threshold: 10240,
149 | minRatio: 0.8
150 | })
151 | );
152 | }
153 |
154 | if (config.bundleAnalyzerReport) {
155 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
156 | webpackConfig.plugins.push(new BundleAnalyzerPlugin());
157 | }
158 |
159 | module.exports = webpackConfig;
160 |
--------------------------------------------------------------------------------
/views/src/pages/Transbox/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, Button, Select, Divider, Modal, message } from 'antd';
3 | import * as api from '../../api';
4 | import './transbox.scss';
5 | import utils from '../../utils';
6 |
7 | const kPageSize = 8;
8 | const areaList = utils.getAreaList();
9 |
10 | const { Option } = Select;
11 |
12 | class Transbox extends React.Component {
13 | state = {
14 | data: [],
15 | pagination: {},
16 | loading: false,
17 | area: areaList[0],
18 | };
19 |
20 | componentDidMount() {
21 | this.fetchList({ page_size: kPageSize, page_index: 1 });
22 | }
23 |
24 | fetchList = (params = {}) => {
25 | this.setState({ loading: true });
26 | api.transbox
27 | .getList({
28 | params,
29 | })
30 | .then(res => {
31 | const data = res.data.data;
32 | const { pagination } = this.state;
33 | pagination.total = data.count;
34 | pagination.pageSize = kPageSize;
35 | const transboxs = data.transboxs;
36 | this.checkResolveAndNotify(transboxs);
37 | this.setState({
38 | loading: false,
39 | data: transboxs,
40 | pagination,
41 | });
42 | });
43 | };
44 |
45 | checkResolveAndNotify = transboxs => {
46 | transboxs.forEach(item => {
47 | let UnresolvedButNotify = false;
48 | let UnresolvedNotNotify = false;
49 | for (let i = 0; i < item.worklogs.length; i += 1) {
50 | const worklog = item.worklogs[i];
51 | if (!worklog.resolved) {
52 | if (worklog.notify_user) {
53 | UnresolvedButNotify = true;
54 | } else {
55 | UnresolvedNotNotify = true;
56 | }
57 | }
58 | }
59 | item.unresolvedButNotify = UnresolvedButNotify;
60 | item.unresolvedNotNotify = UnresolvedNotNotify;
61 | });
62 | };
63 |
64 | handleTableChange = pagination => {
65 | const pager = { ...this.state.pagination };
66 | const area = this.state.area;
67 | pager.current = pagination.current;
68 | this.setState({
69 | pagination: pager,
70 | });
71 |
72 | const params = { page_size: kPageSize, page_index: pager.current };
73 | if (area && area !== areaList[0]) {
74 | params.fil_area = area;
75 | }
76 | this.fetchList(params);
77 | };
78 |
79 | handleSelectChange = value => {
80 | this.setState({ area: value });
81 | };
82 |
83 | handleQueryClick = () => {
84 | const { pagination, area } = this.state;
85 | const page = pagination.current;
86 | const params = { page_size: kPageSize, page_index: page };
87 | if (area && area !== areaList[0]) {
88 | params.fil_area = area;
89 | }
90 | this.fetchList(params);
91 | };
92 |
93 | handleCreateClick = () => {
94 | const { history } = this.props;
95 | history.push('/transbox/edit');
96 | };
97 |
98 | handleSubstationClick = item => {
99 | const { history } = this.props;
100 | history.push({ pathname: '/transbox/detail', state: item });
101 | };
102 |
103 | handleEditClick = (record, event) => {
104 | event.stopPropagation();
105 | const { history } = this.props;
106 | history.push({ pathname: '/transbox/edit', state: record });
107 | };
108 |
109 | handleDeleteClick = (record, event) => {
110 | event.stopPropagation();
111 | Modal.confirm({
112 | title: '注意',
113 | content: `你确定要删除 ${record.name} 这个变电箱吗?`,
114 | okText: '删除',
115 | okType: 'danger',
116 | cancelText: '取消',
117 | onOk: () => {
118 | api.transbox.delete({ data: { id: record._id } }).then(
119 | () => {
120 | message.success('删除成功');
121 | const pager = { ...this.state.pagination };
122 | const area = this.state.area;
123 | pager.current = 1;
124 | this.setState({
125 | pagination: pager,
126 | });
127 |
128 | const params = { page_size: kPageSize, page_index: 1 };
129 | if (area && area !== areaList[0]) {
130 | params.fil_area = area;
131 | }
132 | this.fetchList(params);
133 | },
134 | err => {
135 | message.error(err.data.message);
136 | },
137 | );
138 | },
139 | });
140 | };
141 |
142 | render() {
143 | const { data, pagination, loading, area } = this.state;
144 | const columns = [
145 | {
146 | title: '箱变名称',
147 | dataIndex: 'name',
148 | width: '20%',
149 | render: (name, record) => {
150 | let appendStyle;
151 | if (record.unresolvedButNotify) {
152 | appendStyle = { color: '#fbc02d' };
153 | }
154 | if (record.unresolvedNotNotify) {
155 | appendStyle = { color: '#ef5350 ' };
156 | }
157 | return {name}
;
158 | },
159 | },
160 | {
161 | title: '上级电源',
162 | dataIndex: 'superior',
163 | width: '20%',
164 | },
165 | {
166 | title: '区域',
167 | dataIndex: 'area',
168 | width: '20%',
169 | },
170 | {
171 | title: '联系方式',
172 | dataIndex: 'contact_info',
173 | width: '20%',
174 | },
175 | {
176 | title: '操作',
177 | key: 'action',
178 | render: record => (
179 |
180 |
187 |
188 |
195 |
196 | ),
197 | },
198 | ];
199 |
200 | return (
201 |
202 |
箱变列表
203 |
204 |
区域查询:
205 |
214 |
217 |
220 |
221 |
record._id}
224 | dataSource={data}
225 | pagination={pagination}
226 | loading={loading}
227 | onChange={this.handleTableChange}
228 | onRow={record => {
229 | return {
230 | onClick: this.handleSubstationClick.bind(this, record),
231 | };
232 | }}
233 | />
234 |
235 | );
236 | }
237 | }
238 |
239 | export default Transbox;
240 |
--------------------------------------------------------------------------------
/views/src/pages/Substation/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, Button, Select, Divider, Modal, message } from 'antd';
3 | import * as api from '../../api';
4 | import './substation.scss';
5 | import utils from '../../utils';
6 |
7 | const kPageSize = 8;
8 | const areaList = utils.getAreaList();
9 |
10 | const { Option } = Select;
11 |
12 | class Substation extends React.Component {
13 | state = {
14 | data: [],
15 | pagination: {},
16 | loading: false,
17 | area: areaList[0],
18 | };
19 |
20 | componentDidMount() {
21 | this.fetchList({ page_size: kPageSize, page_index: 1 });
22 | }
23 |
24 | fetchList = (params = {}) => {
25 | this.setState({ loading: true });
26 | api.substation
27 | .getList({
28 | params,
29 | })
30 | .then(res => {
31 | const data = res.data.data;
32 | const { pagination } = this.state;
33 | pagination.total = data.count;
34 | pagination.pageSize = kPageSize;
35 | const substations = data.substations;
36 | this.checkResolveAndNotify(substations);
37 | this.setState({
38 | loading: false,
39 | data: substations,
40 | pagination,
41 | });
42 | });
43 | };
44 |
45 | checkResolveAndNotify = substations => {
46 | substations.forEach(item => {
47 | let UnresolvedButNotify = false;
48 | let UnresolvedNotNotify = false;
49 | for (let i = 0; i < item.worklogs.length; i += 1) {
50 | const worklog = item.worklogs[i];
51 | if (!worklog.resolved) {
52 | if (worklog.notify_user) {
53 | UnresolvedButNotify = true;
54 | } else {
55 | UnresolvedNotNotify = true;
56 | }
57 | }
58 | }
59 | item.unresolvedButNotify = UnresolvedButNotify;
60 | item.unresolvedNotNotify = UnresolvedNotNotify;
61 | });
62 | };
63 |
64 | handleTableChange = pagination => {
65 | const pager = { ...this.state.pagination };
66 | const area = this.state.area;
67 | pager.current = pagination.current;
68 | this.setState({
69 | pagination: pager,
70 | });
71 |
72 | const params = { page_size: kPageSize, page_index: pager.current };
73 | if (area && area !== areaList[0]) {
74 | params.fil_area = area;
75 | }
76 | this.fetchList(params);
77 | };
78 |
79 | handleSelectChange = value => {
80 | this.setState({ area: value });
81 | };
82 |
83 | handleQueryClick = () => {
84 | const { pagination, area } = this.state;
85 | const page = pagination.current;
86 | const params = { page_size: kPageSize, page_index: page };
87 | if (area && area !== areaList[0]) {
88 | params.fil_area = area;
89 | }
90 | this.fetchList(params);
91 | };
92 |
93 | handleCreateClick = () => {
94 | const { history } = this.props;
95 | history.push('/substation/edit');
96 | };
97 |
98 | handleSubstationClick = item => {
99 | const { history } = this.props;
100 | history.push({ pathname: '/substation/detail', state: item });
101 | };
102 |
103 | handleEditClick = (record, event) => {
104 | event.stopPropagation();
105 | const { history } = this.props;
106 | history.push({ pathname: '/substation/edit', state: record });
107 | };
108 |
109 | handleDeleteClick = (record, event) => {
110 | event.stopPropagation();
111 | Modal.confirm({
112 | title: '注意',
113 | content: `你确定要删除 ${record.name} 这个变电站吗?`,
114 | okText: '删除',
115 | okType: 'danger',
116 | cancelText: '取消',
117 | onOk: () => {
118 | api.substation.delete({ data: { id: record._id } }).then(
119 | () => {
120 | message.success('删除成功');
121 | const pager = { ...this.state.pagination };
122 | const area = this.state.area;
123 | pager.current = 1;
124 | this.setState({
125 | pagination: pager,
126 | });
127 |
128 | const params = { page_size: kPageSize, page_index: 1 };
129 | if (area && area !== areaList[0]) {
130 | params.fil_area = area;
131 | }
132 | this.fetchList(params);
133 | },
134 | err => {
135 | message.error(err.data.message);
136 | },
137 | );
138 | },
139 | });
140 | };
141 |
142 | render() {
143 | const { data, pagination, loading, area } = this.state;
144 | const columns = [
145 | {
146 | title: '变电所名称',
147 | dataIndex: 'name',
148 | width: '20%',
149 | render: (name, record) => {
150 | let appendStyle;
151 | if (record.unresolvedButNotify) {
152 | appendStyle = { color: '#fbc02d' };
153 | }
154 | if (record.unresolvedNotNotify) {
155 | appendStyle = { color: '#ef5350 ' };
156 | }
157 | return {name}
;
158 | },
159 | },
160 | {
161 | title: '上级电源',
162 | dataIndex: 'superior',
163 | width: '20%',
164 | },
165 | {
166 | title: '区域',
167 | dataIndex: 'area',
168 | width: '20%',
169 | },
170 | {
171 | title: '联系方式',
172 | dataIndex: 'contact_info',
173 | width: '20%',
174 | },
175 | {
176 | title: '操作',
177 | key: 'action',
178 | render: record => (
179 |
180 |
187 |
188 |
195 |
196 | ),
197 | },
198 | ];
199 |
200 | return (
201 |
202 |
变电所列表
203 |
204 |
区域查询:
205 |
214 |
217 |
220 |
221 |
record._id}
224 | dataSource={data}
225 | pagination={pagination}
226 | loading={loading}
227 | onChange={this.handleTableChange}
228 | onRow={record => {
229 | return {
230 | onClick: this.handleSubstationClick.bind(this, record),
231 | };
232 | }}
233 | />
234 |
235 | );
236 | }
237 | }
238 |
239 | export default Substation;
240 |
--------------------------------------------------------------------------------
/views/src/components/Transformer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, message, Button, Divider, Modal, Form, Input } from 'antd';
3 | import * as api from '../../api';
4 | import './transformer.scss';
5 |
6 | const kDefaultPageSize = 8;
7 | const FormItem = Form.Item;
8 |
9 | class TransForm extends React.Component {
10 | submitForm = callback => {
11 | this.props.form.validateFields((error, values) => {
12 | if (error) {
13 | return;
14 | }
15 |
16 | callback(values);
17 | });
18 | };
19 |
20 | clearForm = () => {
21 | this.props.form.resetFields();
22 | };
23 |
24 | render() {
25 | const { form } = this.props;
26 | const { getFieldDecorator } = form;
27 | const editTransformer = this.props.editTransformer || {};
28 |
29 | const formItemLayout = {
30 | labelCol: {
31 | xs: { span: 24 },
32 | sm: { span: 5 },
33 | },
34 | wrapperCol: {
35 | xs: { span: 24 },
36 | sm: { span: 15 },
37 | },
38 | };
39 |
40 | return (
41 |
52 | );
53 | }
54 | }
55 | const TraForm = Form.create({})(TransForm);
56 |
57 | class Transformer extends React.Component {
58 | traForm;
59 |
60 | state = {
61 | data: [],
62 | pagination: {},
63 | loading: false,
64 | showModal: false,
65 | curTrans: null,
66 | };
67 |
68 | componentDidMount() {
69 | this.fetchTransList({ page_size: kDefaultPageSize, page_index: 1 });
70 | }
71 |
72 | fetchTransList = (params = {}) => {
73 | const { substation, transbox } = this.props;
74 | if (substation) {
75 | params.subId = substation._id;
76 | }
77 | if (transbox) {
78 | params.transId = transbox._id;
79 | }
80 |
81 | this.setState({ loading: true });
82 | api.transformer.getList({ params }).then(res => {
83 | const data = res.data.data;
84 | const { pagination } = this.state;
85 | pagination.total = data.count;
86 | pagination.pageSize = kDefaultPageSize;
87 | this.setState({
88 | loading: false,
89 | data: data.transformers,
90 | pagination,
91 | });
92 | });
93 | };
94 |
95 | handleCreateClick = () => {
96 | this.setState({ showModal: true });
97 | };
98 |
99 | handleTableChange = pagination => {
100 | const pager = { ...this.state.pagination };
101 | pager.current = pagination.current;
102 | this.setState({
103 | pagination: pager,
104 | });
105 |
106 | const params = { page_size: kDefaultPageSize, page_index: pager.current };
107 | this.fetchTransList(params);
108 | };
109 |
110 | handleEditClick = record => {
111 | this.setState({ curTrans: record, showModal: true });
112 | };
113 |
114 | handleDeleteClick = record => {
115 | Modal.confirm({
116 | title: '注意',
117 | content: '你确定要删除这个变压器吗',
118 | okText: '删除',
119 | okType: 'danger',
120 | cancelText: '取消',
121 | onOk: () => {
122 | const hide = message.loading('正在删除', 0);
123 | api.transformer.delete({ data: { id: record._id } }).then(
124 | () => {
125 | hide();
126 | message.success('删除成功');
127 | const pager = { ...this.state.pagination };
128 | pager.current = 1;
129 | this.setState({
130 | pagination: pager,
131 | });
132 |
133 | const params = { page_size: kDefaultPageSize, page_index: 1 };
134 | this.fetchTransList(params);
135 | },
136 | err => {
137 | hide();
138 | message.error(err.data.message);
139 | },
140 | );
141 | },
142 | });
143 | };
144 |
145 | handleModalSubmit = () => {
146 | const { substation, transbox } = this.props;
147 | const { curTrans } = this.state;
148 | const isAdd = !curTrans;
149 | const msg = isAdd ? '正在添加变压器' : '正在更新变压器';
150 | this.traForm.submitForm(values => {
151 | const hide = message.loading(msg, 0);
152 | if (isAdd) {
153 | let data;
154 | if (substation) {
155 | data = { subId: substation._id, ...values };
156 | }
157 | if (transbox) {
158 | data = { transId: transbox._id, ...values };
159 | }
160 |
161 | api.transformer.create({ data }).then(
162 | () => {
163 | hide();
164 | message.success('添加成功');
165 | this.fetchTransList({ page_size: kDefaultPageSize, page_index: 1 });
166 | this.hideModalAndClear();
167 | },
168 | err => {
169 | hide();
170 | message.error(err.data.message);
171 | },
172 | );
173 | } else {
174 | api.transformer.update({ data: { _id: curTrans._id, ...values } }).then(
175 | () => {
176 | hide();
177 | message.success('更新成功');
178 | this.fetchTransList({ page_size: kDefaultPageSize, page_index: 1 });
179 | this.hideModalAndClear();
180 | },
181 | err => {
182 | hide();
183 | message.error(err.data.message);
184 | },
185 | );
186 | }
187 | });
188 | };
189 |
190 | handleModalCancel = () => {
191 | this.hideModalAndClear();
192 | };
193 |
194 | hideModalAndClear() {
195 | this.setState({ curTrans: null, showModal: false });
196 | this.traForm.clearForm();
197 | }
198 |
199 | render() {
200 | const { data, pagination, loading, showModal, curTrans } = this.state;
201 | const isAdd = !curTrans;
202 | const title = isAdd ? '添加变压器' : '编辑变压器';
203 |
204 | const columns = [
205 | {
206 | title: '变压器编号',
207 | dataIndex: 'serial_number',
208 | width: '30%',
209 | },
210 | {
211 | title: '变压器型号',
212 | dataIndex: 'model',
213 | width: '40%',
214 | },
215 | {
216 | title: '操作',
217 | key: 'action',
218 | width: '30%',
219 | render: record => (
220 |
221 |
228 |
229 |
236 |
237 | ),
238 | },
239 | ];
240 |
241 | return (
242 |
243 |
244 |
247 |
255 | {
258 | this.traForm = ref;
259 | }}
260 | />
261 |
262 |
263 |
record._id}
266 | dataSource={data}
267 | pagination={pagination}
268 | loading={loading}
269 | onChange={this.handleTableChange}
270 | />
271 |
272 | );
273 | }
274 | }
275 |
276 | export default Transformer;
277 |
--------------------------------------------------------------------------------
/views/src/components/Lowvol/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, message, Button, Divider, Modal, Form, Input } from 'antd';
3 | import * as api from '../../api';
4 | import './lowvol.scss';
5 |
6 | const kDefaultPageSize = 8;
7 | const FormItem = Form.Item;
8 |
9 | class LowvolForm extends React.Component {
10 | submitForm = callback => {
11 | this.props.form.validateFields((error, values) => {
12 | if (error) {
13 | return;
14 | }
15 |
16 | callback(values);
17 | });
18 | };
19 |
20 | clearForm = () => {
21 | this.props.form.resetFields();
22 | };
23 |
24 | render() {
25 | const { form } = this.props;
26 | const { getFieldDecorator } = form;
27 | const editLowvol = this.props.editLowvol || {};
28 |
29 | const formItemLayout = {
30 | labelCol: {
31 | xs: { span: 24 },
32 | sm: { span: 5 },
33 | },
34 | wrapperCol: {
35 | xs: { span: 24 },
36 | sm: { span: 15 },
37 | },
38 | };
39 |
40 | return (
41 |
55 | );
56 | }
57 | }
58 | const VolForm = Form.create({})(LowvolForm);
59 |
60 | class Lowvol extends React.Component {
61 | volForm;
62 |
63 | state = {
64 | data: [],
65 | pagination: {},
66 | loading: false,
67 | showModal: false,
68 | curLowvol: null,
69 | };
70 |
71 | componentDidMount() {
72 | this.fetchLowvolList({ page_size: kDefaultPageSize, page_index: 1 });
73 | }
74 |
75 | fetchLowvolList = (params = {}) => {
76 | const { substation, transbox } = this.props;
77 | if (substation) {
78 | params.subId = substation._id;
79 | }
80 | if (transbox) {
81 | params.transId = transbox._id;
82 | }
83 | this.setState({ loading: true });
84 | api.lowvol.getList({ params }).then(res => {
85 | const data = res.data.data;
86 | const { pagination } = this.state;
87 | pagination.total = data.count;
88 | pagination.pageSize = kDefaultPageSize;
89 | this.setState({
90 | loading: false,
91 | data: data.voltages,
92 | pagination,
93 | });
94 | });
95 | };
96 |
97 | handleCreateClick = () => {
98 | this.setState({ showModal: true });
99 | };
100 |
101 | handleTableChange = pagination => {
102 | const pager = { ...this.state.pagination };
103 | pager.current = pagination.current;
104 | this.setState({
105 | pagination: pager,
106 | });
107 |
108 | const params = { page_size: kDefaultPageSize, page_index: pager.current };
109 | this.fetchLowvolList(params);
110 | };
111 |
112 | handleEditClick = record => {
113 | this.setState({ curLowvol: record, showModal: true });
114 | };
115 |
116 | handleDeleteClick = record => {
117 | Modal.confirm({
118 | title: '注意',
119 | content: '你确定要删除这个设备吗',
120 | okText: '删除',
121 | okType: 'danger',
122 | cancelText: '取消',
123 | onOk: () => {
124 | const hide = message.loading('正在删除', 0);
125 | api.lowvol.delete({ data: { id: record._id } }).then(
126 | () => {
127 | hide();
128 | message.success('删除成功');
129 | const pager = { ...this.state.pagination };
130 | pager.current = 1;
131 | this.setState({
132 | pagination: pager,
133 | });
134 |
135 | const params = { page_size: kDefaultPageSize, page_index: 1 };
136 | this.fetchLowvolList(params);
137 | },
138 | err => {
139 | hide();
140 | message.error(err.data.message);
141 | },
142 | );
143 | },
144 | });
145 | };
146 |
147 | handleModalSubmit = () => {
148 | const { substation, transbox } = this.props;
149 | const { curLowvol } = this.state;
150 | const isAdd = !curLowvol;
151 | const msg = isAdd ? '正在添加设备' : '正在更新设备';
152 | this.volForm.submitForm(values => {
153 | const hide = message.loading(msg, 0);
154 | if (isAdd) {
155 | let data;
156 | if (substation) {
157 | data = { subId: substation._id, ...values };
158 | }
159 | if (transbox) {
160 | data = { transId: transbox._id, ...values };
161 | }
162 |
163 | api.lowvol.create({ data }).then(
164 | () => {
165 | hide();
166 | message.success('添加成功');
167 | this.fetchLowvolList({ page_size: kDefaultPageSize, page_index: 1 });
168 | this.hideModalAndClear();
169 | },
170 | err => {
171 | hide();
172 | message.error(err.data.message);
173 | },
174 | );
175 | } else {
176 | api.lowvol.update({ data: { _id: curLowvol._id, ...values } }).then(
177 | () => {
178 | hide();
179 | message.success('更新成功');
180 | this.fetchLowvolList({ page_size: kDefaultPageSize, page_index: 1 });
181 | this.hideModalAndClear();
182 | },
183 | err => {
184 | hide();
185 | message.error(err.data.message);
186 | },
187 | );
188 | }
189 | });
190 | };
191 |
192 | handleModalCancel = () => {
193 | this.hideModalAndClear();
194 | };
195 |
196 | hideModalAndClear() {
197 | this.setState({ curLowvol: null, showModal: false });
198 | this.volForm.clearForm();
199 | }
200 |
201 | render() {
202 | const { data, pagination, loading, showModal, curLowvol } = this.state;
203 | const isAdd = !curLowvol;
204 | const title = isAdd ? '添加设备' : '编辑设备';
205 |
206 | const columns = [
207 | {
208 | title: '调度号',
209 | dataIndex: 'number',
210 | width: '25%',
211 | },
212 | {
213 | title: '供电范围',
214 | dataIndex: 'range',
215 | width: '25%',
216 | },
217 | {
218 | title: '设备型号',
219 | dataIndex: 'model',
220 | width: '25%',
221 | },
222 | {
223 | title: '操作',
224 | key: 'action',
225 | render: record => (
226 |
227 |
234 |
235 |
242 |
243 | ),
244 | },
245 | ];
246 |
247 | return (
248 |
249 |
250 |
253 |
261 | {
264 | this.volForm = ref;
265 | }}
266 | />
267 |
268 |
269 |
record._id}
272 | dataSource={data}
273 | pagination={pagination}
274 | loading={loading}
275 | onChange={this.handleTableChange}
276 | />
277 |
278 | );
279 | }
280 | }
281 |
282 | export default Lowvol;
283 |
--------------------------------------------------------------------------------
/views/src/components/Voltage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, message, Button, Divider, Modal, Form, Input } from 'antd';
3 | import * as api from '../../api';
4 | import './voltage.scss';
5 |
6 | const kDefaultPageSize = 8;
7 | const FormItem = Form.Item;
8 |
9 | class VoltageForm extends React.Component {
10 | submitForm = callback => {
11 | this.props.form.validateFields((error, values) => {
12 | if (error) {
13 | return;
14 | }
15 |
16 | callback(values);
17 | });
18 | };
19 |
20 | clearForm = () => {
21 | this.props.form.resetFields();
22 | };
23 |
24 | render() {
25 | const { form } = this.props;
26 | const { getFieldDecorator } = form;
27 | const editVoltage = this.props.editVoltage || {};
28 |
29 | const formItemLayout = {
30 | labelCol: {
31 | xs: { span: 24 },
32 | sm: { span: 5 },
33 | },
34 | wrapperCol: {
35 | xs: { span: 24 },
36 | sm: { span: 15 },
37 | },
38 | };
39 |
40 | return (
41 |
55 | );
56 | }
57 | }
58 | const VolForm = Form.create({})(VoltageForm);
59 |
60 | class Voltage extends React.Component {
61 | volForm;
62 |
63 | state = {
64 | data: [],
65 | pagination: {},
66 | loading: false,
67 | showModal: false,
68 | curVoltage: null,
69 | };
70 |
71 | componentDidMount() {
72 | this.fetchVoltageList({ page_size: kDefaultPageSize, page_index: 1 });
73 | }
74 |
75 | fetchVoltageList = (params = {}) => {
76 | const { substation, transbox } = this.props;
77 | if (substation) {
78 | params.subId = substation._id;
79 | }
80 | if (transbox) {
81 | params.transId = transbox._id;
82 | }
83 | this.setState({ loading: true });
84 | api.voltage.getList({ params }).then(res => {
85 | const data = res.data.data;
86 | const { pagination } = this.state;
87 | pagination.total = data.count;
88 | pagination.pageSize = kDefaultPageSize;
89 | this.setState({
90 | loading: false,
91 | data: data.voltages,
92 | pagination,
93 | });
94 | });
95 | };
96 |
97 | handleCreateClick = () => {
98 | this.setState({ showModal: true });
99 | };
100 |
101 | handleTableChange = pagination => {
102 | const pager = { ...this.state.pagination };
103 | pager.current = pagination.current;
104 | this.setState({
105 | pagination: pager,
106 | });
107 |
108 | const params = { page_size: kDefaultPageSize, page_index: pager.current };
109 | this.fetchVoltageList(params);
110 | };
111 |
112 | handleEditClick = record => {
113 | this.setState({ curVoltage: record, showModal: true });
114 | };
115 |
116 | handleDeleteClick = record => {
117 | Modal.confirm({
118 | title: '注意',
119 | content: '你确定要删除这个设备吗',
120 | okText: '删除',
121 | okType: 'danger',
122 | cancelText: '取消',
123 | onOk: () => {
124 | const hide = message.loading('正在删除', 0);
125 | api.voltage.delete({ data: { id: record._id } }).then(
126 | () => {
127 | hide();
128 | message.success('删除成功');
129 | const pager = { ...this.state.pagination };
130 | pager.current = 1;
131 | this.setState({
132 | pagination: pager,
133 | });
134 |
135 | const params = { page_size: kDefaultPageSize, page_index: 1 };
136 | this.fetchVoltageList(params);
137 | },
138 | err => {
139 | hide();
140 | message.error(err.data.message);
141 | },
142 | );
143 | },
144 | });
145 | };
146 |
147 | handleModalSubmit = () => {
148 | const { substation, transbox } = this.props;
149 | const { curVoltage } = this.state;
150 | const isAdd = !curVoltage;
151 | const msg = isAdd ? '正在添加设备' : '正在更新设备';
152 | this.volForm.submitForm(values => {
153 | const hide = message.loading(msg, 0);
154 | if (isAdd) {
155 | let data;
156 | if (substation) {
157 | data = { subId: substation._id, ...values };
158 | }
159 | if (transbox) {
160 | data = { transId: transbox._id, ...values };
161 | }
162 |
163 | api.voltage.create({ data }).then(
164 | () => {
165 | hide();
166 | message.success('添加成功');
167 | this.fetchVoltageList({ page_size: kDefaultPageSize, page_index: 1 });
168 | this.hideModalAndClear();
169 | },
170 | err => {
171 | hide();
172 | message.error(err.data.message);
173 | },
174 | );
175 | } else {
176 | api.voltage.update({ data: { _id: curVoltage._id, ...values } }).then(
177 | () => {
178 | hide();
179 | message.success('更新成功');
180 | this.fetchVoltageList({ page_size: kDefaultPageSize, page_index: 1 });
181 | this.hideModalAndClear();
182 | },
183 | err => {
184 | hide();
185 | message.error(err.data.message);
186 | },
187 | );
188 | }
189 | });
190 | };
191 |
192 | handleModalCancel = () => {
193 | this.hideModalAndClear();
194 | };
195 |
196 | hideModalAndClear() {
197 | this.setState({ curVoltage: null, showModal: false });
198 | this.volForm.clearForm();
199 | }
200 |
201 | render() {
202 | const { data, pagination, loading, showModal, curVoltage } = this.state;
203 | const isAdd = !curVoltage;
204 | const title = isAdd ? '添加设备' : '编辑设备';
205 |
206 | const columns = [
207 | {
208 | title: '调度号',
209 | dataIndex: 'number',
210 | width: '25%',
211 | },
212 | {
213 | title: '供电范围',
214 | dataIndex: 'range',
215 | width: '25%',
216 | },
217 | {
218 | title: '设备型号',
219 | dataIndex: 'model',
220 | width: '25%',
221 | },
222 | {
223 | title: '操作',
224 | key: 'action',
225 | render: record => (
226 |
227 |
234 |
235 |
242 |
243 | ),
244 | },
245 | ];
246 |
247 | return (
248 |
249 |
250 |
253 |
261 | {
264 | this.volForm = ref;
265 | }}
266 | />
267 |
268 |
269 |
record._id}
272 | dataSource={data}
273 | pagination={pagination}
274 | loading={loading}
275 | onChange={this.handleTableChange}
276 | />
277 |
278 | );
279 | }
280 | }
281 |
282 | export default Voltage;
283 |
--------------------------------------------------------------------------------
/views/src/pages/Transbox/edit.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input, Select, Button, Form, message, Icon, Upload } from 'antd';
3 | import './transbox.scss';
4 | import * as api from '../../api';
5 | import utils from '../../utils';
6 | import uploader from '../../utils/uploader';
7 |
8 | const FormItem = Form.Item;
9 | const Option = Select.Option;
10 |
11 | class TransForm extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | let appearUrl;
15 | let locationUrl;
16 | const transbox = props.location.state;
17 | if (transbox) {
18 | appearUrl = transbox.appear_pic;
19 | locationUrl = transbox.location_pic;
20 | }
21 | this.state = {
22 | isLoading: false,
23 | appearUrl,
24 | appearLoading: false,
25 | locationUrl,
26 | locationLoading: false,
27 | isAdd: !transbox,
28 | transbox: transbox || {},
29 | };
30 | }
31 |
32 | handleSubmit = e => {
33 | e.preventDefault();
34 | const { history } = this.props;
35 | const { isAdd, transbox, appearUrl, locationUrl } = this.state;
36 | this.props.form.validateFields((error, values) => {
37 | if (error) {
38 | return;
39 | }
40 |
41 | values.appear_pic = appearUrl;
42 | values.location_pic = locationUrl;
43 |
44 | this.setState({ isLoading: true });
45 | const msg = isAdd ? '正在添加箱变...' : '正在更新箱变...';
46 | const hide = message.loading(msg, 0);
47 | if (isAdd) {
48 | api.transbox.create({ data: values }).then(
49 | () => {
50 | hide();
51 | this.setState({ isLoading: false });
52 | const sucMsg = '添加成功';
53 | message.success(sucMsg);
54 | history.goBack();
55 | },
56 | () => {
57 | hide();
58 | this.setState({ isLoading: false });
59 | },
60 | );
61 | } else {
62 | api.transbox.update({ data: { _id: transbox._id, ...values } }).then(
63 | () => {
64 | hide();
65 | this.setState({ isLoading: false });
66 | const sucMsg = '更新成功';
67 | message.success(sucMsg);
68 | history.goBack();
69 | },
70 | () => {
71 | hide();
72 | this.setState({ isLoading: false });
73 | },
74 | );
75 | }
76 | });
77 | };
78 |
79 | handleAppearBeforeUpload = img => {
80 | this.setState({ appearLoading: true });
81 | uploader.multipartUpload(utils.generateEncodeName(img.name), img).then(
82 | res => {
83 | message.success('上传成功');
84 | const url = res.res.requestUrls[0];
85 | const list = url.split('?');
86 | const imgUrl = list[0];
87 | this.setState({ appearLoading: false, appearUrl: imgUrl });
88 | },
89 | () => {
90 | message.error('上传失败');
91 | this.setState({ appearLoading: false });
92 | },
93 | );
94 | return false;
95 | };
96 |
97 | handleLocationBeforeUpload = img => {
98 | this.setState({ appearLoading: true });
99 | uploader.multipartUpload(utils.generateEncodeName(img.name), img).then(
100 | res => {
101 | message.success('上传成功');
102 | const url = res.res.requestUrls[0];
103 | const list = url.split('?');
104 | const imgUrl = list[0];
105 | this.setState({ locationLoading: false, locationUrl: imgUrl });
106 | },
107 | () => {
108 | message.error('上传失败');
109 | this.setState({ locationLoading: false });
110 | },
111 | );
112 | return false;
113 | };
114 |
115 | handleAppearRemove = () => {
116 | this.setState({ appearUrl: null });
117 | };
118 |
119 | handleLocationRemove = () => {
120 | this.setState({ locationUrl: null });
121 | };
122 |
123 | render() {
124 | const {
125 | isLoading,
126 | appearLoading,
127 | locationLoading,
128 | isAdd,
129 | transbox,
130 | appearUrl,
131 | locationUrl,
132 | } = this.state;
133 | const { form } = this.props;
134 | const { getFieldDecorator } = form;
135 | const areaList = utils.getAreaList();
136 | areaList.shift();
137 |
138 | const formItemLayout = {
139 | labelCol: {
140 | xs: { span: 24 },
141 | sm: { span: 5 },
142 | },
143 | wrapperCol: {
144 | xs: { span: 24 },
145 | sm: { span: 15 },
146 | },
147 | };
148 | const tailFormItemLayout = {
149 | wrapperCol: {
150 | xs: {
151 | span: 24,
152 | offset: 0,
153 | },
154 | sm: {
155 | span: 15,
156 | offset: 5,
157 | },
158 | },
159 | };
160 |
161 | const uploadButton = loading => (
162 |
166 | );
167 |
168 | const title = isAdd ? '添加箱变' : '编辑箱变';
169 |
170 | return (
171 |
172 |
{title}
173 |
262 |
263 | );
264 | }
265 | }
266 | const EditTransForm = Form.create({})(TransForm);
267 |
268 | class EditTransbox extends React.Component {
269 | render() {
270 | return (
271 |
272 |
273 |
274 | );
275 | }
276 | }
277 |
278 | export default EditTransbox;
279 |
--------------------------------------------------------------------------------
/views/src/pages/Lampins/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, message, Button, Divider, Modal, Form, Input, DatePicker, Checkbox } from 'antd';
3 | import locale from 'antd/es/date-picker/locale/zh_CN';
4 | import moment from 'moment';
5 | import 'moment/locale/zh-cn';
6 | import * as api from '../../api';
7 | import './lampins.scss';
8 |
9 | const kDefaultPageSize = 8;
10 | const dateFormat = 'YYYY/MM/DD';
11 |
12 | const FormItem = Form.Item;
13 |
14 | class LampinsForm extends React.Component {
15 | submitForm = callback => {
16 | this.props.form.validateFields((error, values) => {
17 | if (error) {
18 | return;
19 | }
20 |
21 | callback(values);
22 | });
23 | };
24 |
25 | clearForm = () => {
26 | this.props.form.resetFields();
27 | };
28 |
29 | render() {
30 | const { form, editLampins } = this.props;
31 | const { getFieldDecorator } = form;
32 | const date = editLampins ? moment(editLampins.date, dateFormat) : null;
33 | const roadname = editLampins ? editLampins.content : null;
34 | const resolved = editLampins ? editLampins.resolved : false;
35 | const record = editLampins ? editLampins.record : null;
36 |
37 | const formItemLayout = {
38 | labelCol: {
39 | xs: { span: 24 },
40 | sm: { span: 8 },
41 | },
42 | wrapperCol: {
43 | xs: { span: 24 },
44 | sm: { span: 14 },
45 | },
46 | };
47 |
48 | return (
49 |
75 | );
76 | }
77 | }
78 | const LIForm = Form.create({})(LampinsForm);
79 |
80 | class Lampins extends React.Component {
81 | insForm;
82 |
83 | state = {
84 | data: [],
85 | pagination: {},
86 | loading: false,
87 | showModal: false,
88 | curIns: null,
89 | };
90 |
91 | componentDidMount() {
92 | this.fetchLampinsList({ page_size: kDefaultPageSize, page_index: 1 });
93 | }
94 |
95 | fetchLampinsList = (params = {}) => {
96 | this.setState({ loading: true });
97 | api.lampins.getList({ params }).then(res => {
98 | const data = res.data.data;
99 | const { pagination } = this.state;
100 | pagination.total = data.count;
101 | pagination.pageSize = kDefaultPageSize;
102 | this.setState({
103 | loading: false,
104 | data: data.lampins,
105 | pagination,
106 | });
107 | });
108 | };
109 |
110 | handleCreateClick = () => {
111 | this.setState({ showModal: true });
112 | };
113 |
114 | handleTableChange = pagination => {
115 | const pager = { ...this.state.pagination };
116 | pager.current = pagination.current;
117 | this.setState({
118 | pagination: pager,
119 | });
120 |
121 | const params = { page_size: kDefaultPageSize, page_index: pager.current };
122 | this.fetchLampinsList(params);
123 | };
124 |
125 | handleEditClick = record => {
126 | this.setState({ curIns: record, showModal: true });
127 | };
128 |
129 | handleDeleteClick = record => {
130 | Modal.confirm({
131 | title: '注意',
132 | content: '你确定要删除这条检查记录吗',
133 | okText: '删除',
134 | okType: 'danger',
135 | cancelText: '取消',
136 | onOk: () => {
137 | const hide = message.loading('正在删除', 0);
138 | api.lampins.delete({ data: { id: record._id } }).then(
139 | () => {
140 | hide();
141 | message.success('删除成功');
142 | const pager = { ...this.state.pagination };
143 | pager.current = 1;
144 | this.setState({
145 | pagination: pager,
146 | });
147 |
148 | const params = { page_size: kDefaultPageSize, page_index: 1 };
149 | this.fetchLampinsList(params);
150 | },
151 | err => {
152 | hide();
153 | message.error(err.data.message);
154 | },
155 | );
156 | },
157 | });
158 | };
159 |
160 | handleModalSubmit = () => {
161 | const { curIns } = this.state;
162 | const isAdd = !curIns;
163 | const msg = isAdd ? '正在添加检查记录' : '正在更新检查记录';
164 | this.insForm.submitForm(values => {
165 | const hide = message.loading(msg, 0);
166 | if (isAdd) {
167 | api.lampins.create({ data: values }).then(
168 | () => {
169 | hide();
170 | message.success('添加成功');
171 | this.fetchLampinsList({ page_size: kDefaultPageSize, page_index: 1 });
172 | this.hideModalAndClear();
173 | },
174 | err => {
175 | hide();
176 | message.error(err.data.message);
177 | },
178 | );
179 | } else {
180 | api.lampins.update({ data: { _id: curIns._id, ...values } }).then(
181 | () => {
182 | hide();
183 | message.success('更新成功');
184 | this.fetchLampinsList({ page_size: kDefaultPageSize, page_index: 1 });
185 | this.hideModalAndClear();
186 | },
187 | err => {
188 | hide();
189 | message.error(err.data.message);
190 | },
191 | );
192 | }
193 | });
194 | };
195 |
196 | handleModalCancel = () => {
197 | this.hideModalAndClear();
198 | };
199 |
200 | hideModalAndClear() {
201 | this.setState({ curIns: null, showModal: false });
202 | this.insForm.clearForm();
203 | }
204 |
205 | render() {
206 | const { data, pagination, loading, showModal, curIns } = this.state;
207 | const isAdd = !curIns;
208 | const title = isAdd ? '添加路灯检查记录' : '编辑路灯检查记录';
209 |
210 | const columns = [
211 | {
212 | title: '日期',
213 | dataIndex: 'date',
214 | width: '15%',
215 | render: date => moment(date).format('LL'),
216 | },
217 | {
218 | title: '道路名称',
219 | dataIndex: 'road_name',
220 | width: '20%',
221 | },
222 | {
223 | title: '故障记录',
224 | dataIndex: 'record',
225 | width: '30%',
226 | },
227 | {
228 | title: '是否解决',
229 | dataIndex: 'resolved',
230 | render: resolved => (resolved ? '是' : '否'),
231 | width: '15%',
232 | },
233 | {
234 | title: '操作',
235 | key: 'action',
236 | width: '20%',
237 | render: record => (
238 |
239 |
246 |
247 |
254 |
255 | ),
256 | },
257 | ];
258 |
259 | return (
260 |
261 |
路灯巡视检查记录
262 |
263 |
266 |
267 |
record._id}
270 | dataSource={data}
271 | pagination={pagination}
272 | loading={loading}
273 | onChange={this.handleTableChange}
274 | />
275 |
283 | {
286 | this.insForm = ref;
287 | }}
288 | />
289 |
290 |
291 | );
292 | }
293 | }
294 |
295 | export default Lampins;
296 |
--------------------------------------------------------------------------------
/views/src/pages/Substation/edit.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input, Select, Button, Form, message, Upload, Icon } from 'antd';
3 | import './substation.scss';
4 | import * as api from '../../api';
5 | import utils from '../../utils';
6 | import uploader from '../../utils/uploader';
7 |
8 | const FormItem = Form.Item;
9 | const Option = Select.Option;
10 |
11 | class SubForm extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | let appearUrl;
15 | let locationUrl;
16 | const substation = props.location.state;
17 | if (substation) {
18 | appearUrl = substation.appear_pic;
19 | locationUrl = substation.location_pic;
20 | }
21 |
22 | this.state = {
23 | isLoading: false,
24 | appearUrl,
25 | appearLoading: false,
26 | locationUrl,
27 | locationLoading: false,
28 | isAdd: !substation,
29 | substation: substation || {},
30 | };
31 | }
32 |
33 | handleSubmit = e => {
34 | e.preventDefault();
35 | const { history } = this.props;
36 | const { isAdd, substation, appearUrl, locationUrl } = this.state;
37 | this.props.form.validateFields((error, values) => {
38 | if (error) {
39 | return;
40 | }
41 |
42 | values.appear_pic = appearUrl;
43 | values.location_pic = locationUrl;
44 |
45 | this.setState({ isLoading: true });
46 | const msg = isAdd ? '正在添加变电站...' : '正在更新变电站...';
47 | const hide = message.loading(msg, 0);
48 | if (isAdd) {
49 | api.substation.create({ data: values }).then(
50 | () => {
51 | hide();
52 | this.setState({ isLoading: false });
53 | const sucMsg = '添加成功';
54 | message.success(sucMsg);
55 | history.goBack();
56 | },
57 | () => {
58 | hide();
59 | this.setState({ isLoading: false });
60 | },
61 | );
62 | } else {
63 | api.substation.update({ data: { _id: substation._id, ...values } }).then(
64 | () => {
65 | hide();
66 | this.setState({ isLoading: false });
67 | const sucMsg = '更新成功';
68 | message.success(sucMsg);
69 | history.goBack();
70 | },
71 | () => {
72 | hide();
73 | this.setState({ isLoading: false });
74 | },
75 | );
76 | }
77 | });
78 | };
79 |
80 | handleAppearBeforeUpload = img => {
81 | this.setState({ appearLoading: true });
82 | uploader.multipartUpload(utils.generateEncodeName(img.name), img).then(
83 | res => {
84 | message.success('上传成功');
85 | const url = res.res.requestUrls[0];
86 | const list = url.split('?');
87 | const imgUrl = list[0];
88 | this.setState({ appearLoading: false, appearUrl: imgUrl });
89 | },
90 | () => {
91 | message.error('上传失败');
92 | this.setState({ appearLoading: false });
93 | },
94 | );
95 | return false;
96 | };
97 |
98 | handleLocationBeforeUpload = img => {
99 | this.setState({ appearLoading: true });
100 | uploader.multipartUpload(utils.generateEncodeName(img.name), img).then(
101 | res => {
102 | message.success('上传成功');
103 | const url = res.res.requestUrls[0];
104 | const list = url.split('?');
105 | const imgUrl = list[0];
106 | this.setState({ locationLoading: false, locationUrl: imgUrl });
107 | },
108 | () => {
109 | message.error('上传失败');
110 | this.setState({ locationLoading: false });
111 | },
112 | );
113 | return false;
114 | };
115 |
116 | handleAppearRemove = () => {
117 | this.setState({ appearUrl: null });
118 | };
119 |
120 | handleLocationRemove = () => {
121 | this.setState({ locationUrl: null });
122 | };
123 |
124 | render() {
125 | const {
126 | isLoading,
127 | isAdd,
128 | substation,
129 | appearLoading,
130 | appearUrl,
131 | locationLoading,
132 | locationUrl,
133 | } = this.state;
134 | const { form } = this.props;
135 | const { getFieldDecorator } = form;
136 | const areaList = utils.getAreaList();
137 | areaList.shift();
138 |
139 | const formItemLayout = {
140 | labelCol: {
141 | xs: { span: 24 },
142 | sm: { span: 5 },
143 | },
144 | wrapperCol: {
145 | xs: { span: 24 },
146 | sm: { span: 15 },
147 | },
148 | };
149 | const tailFormItemLayout = {
150 | wrapperCol: {
151 | xs: {
152 | span: 24,
153 | offset: 0,
154 | },
155 | sm: {
156 | span: 15,
157 | offset: 5,
158 | },
159 | },
160 | };
161 |
162 | const uploadButton = loading => (
163 |
167 | );
168 |
169 | const title = isAdd ? '添加变电所' : '编辑变电所';
170 |
171 | return (
172 |
173 |
{title}
174 |
268 |
269 | );
270 | }
271 | }
272 | const EditSubForm = Form.create({})(SubForm);
273 |
274 | class EditSubstation extends React.Component {
275 | render() {
276 | return (
277 |
278 |
279 |
280 | );
281 | }
282 | }
283 |
284 | export default EditSubstation;
285 |
--------------------------------------------------------------------------------
/views/src/components/Worklog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table, message, Button, Divider, Modal, Form, Input, DatePicker, Checkbox } from 'antd';
3 | import locale from 'antd/es/date-picker/locale/zh_CN';
4 | import moment from 'moment';
5 | import 'moment/locale/zh-cn';
6 | import * as api from '../../api';
7 | import './worklog.scss';
8 |
9 | const kDefaultPageSize = 8;
10 | const dateFormat = 'YYYY/MM/DD';
11 |
12 | const FormItem = Form.Item;
13 | const TextArea = Input.TextArea;
14 |
15 | class WorklogForm extends React.Component {
16 | submitForm = callback => {
17 | this.props.form.validateFields((error, values) => {
18 | if (error) {
19 | return;
20 | }
21 |
22 | callback(values);
23 | });
24 | };
25 |
26 | clearForm = () => {
27 | this.props.form.resetFields();
28 | };
29 |
30 | render() {
31 | const { form, editWorklog } = this.props;
32 | const { getFieldDecorator } = form;
33 | const date = editWorklog ? moment(editWorklog.date, dateFormat) : null;
34 | const content = editWorklog ? editWorklog.content : null;
35 | const resolved = editWorklog ? editWorklog.resolved : false;
36 | const notify_user = editWorklog ? editWorklog.notify_user : false;
37 |
38 | const formItemLayout = {
39 | labelCol: {
40 | xs: { span: 24 },
41 | sm: { span: 8 },
42 | },
43 | wrapperCol: {
44 | xs: { span: 24 },
45 | sm: { span: 14 },
46 | },
47 | };
48 |
49 | return (
50 |
76 | );
77 | }
78 | }
79 | const WLForm = Form.create({})(WorklogForm);
80 |
81 | class Worklog extends React.Component {
82 | logForm;
83 |
84 | state = {
85 | data: [],
86 | pagination: {},
87 | loading: false,
88 | showModal: false,
89 | curLog: null,
90 | };
91 |
92 | componentDidMount() {
93 | this.fetchWorklogList({ page_size: kDefaultPageSize, page_index: 1 });
94 | }
95 |
96 | fetchWorklogList = (params = {}) => {
97 | const { substation, transbox } = this.props;
98 | if (substation) {
99 | params.subId = substation._id;
100 | }
101 | if (transbox) {
102 | params.transId = transbox._id;
103 | }
104 | this.setState({ loading: true });
105 | api.worklog.getList({ params }).then(res => {
106 | const data = res.data.data;
107 | const { pagination } = this.state;
108 | pagination.total = data.count;
109 | pagination.pageSize = kDefaultPageSize;
110 | this.setState({
111 | loading: false,
112 | data: data.worklogs,
113 | pagination,
114 | });
115 | });
116 | };
117 |
118 | handleCreateClick = () => {
119 | this.setState({ showModal: true });
120 | };
121 |
122 | handleTableChange = pagination => {
123 | const pager = { ...this.state.pagination };
124 | pager.current = pagination.current;
125 | this.setState({
126 | pagination: pager,
127 | });
128 |
129 | const params = { page_size: kDefaultPageSize, page_index: pager.current };
130 | this.fetchWorklogList(params);
131 | };
132 |
133 | handleEditClick = record => {
134 | this.setState({ curLog: record, showModal: true });
135 | };
136 |
137 | handleDeleteClick = record => {
138 | Modal.confirm({
139 | title: '注意',
140 | content: '你确定要删除这条日志吗',
141 | okText: '删除',
142 | okType: 'danger',
143 | cancelText: '取消',
144 | onOk: () => {
145 | const hide = message.loading('正在删除', 0);
146 | api.worklog.delete({ data: { id: record._id } }).then(
147 | () => {
148 | hide();
149 | message.success('删除成功');
150 | const pager = { ...this.state.pagination };
151 | pager.current = 1;
152 | this.setState({
153 | pagination: pager,
154 | });
155 |
156 | const params = { page_size: kDefaultPageSize, page_index: 1 };
157 | this.fetchWorklogList(params);
158 | },
159 | err => {
160 | hide();
161 | message.error(err.data.message);
162 | },
163 | );
164 | },
165 | });
166 | };
167 |
168 | handleModalSubmit = () => {
169 | const { substation, transbox } = this.props;
170 | const { curLog } = this.state;
171 | const isAdd = !curLog;
172 | const msg = isAdd ? '正在添加日志' : '正在更新日志';
173 | this.logForm.submitForm(values => {
174 | const hide = message.loading(msg, 0);
175 | if (isAdd) {
176 | let data;
177 | if (substation) {
178 | data = { subId: substation._id, ...values };
179 | }
180 | if (transbox) {
181 | data = { transId: transbox._id, ...values };
182 | }
183 |
184 | api.worklog.create({ data }).then(
185 | () => {
186 | hide();
187 | message.success('添加成功');
188 | this.fetchWorklogList({ page_size: kDefaultPageSize, page_index: 1 });
189 | this.hideModalAndClear();
190 | },
191 | err => {
192 | hide();
193 | message.error(err.data.message);
194 | },
195 | );
196 | } else {
197 | api.worklog.update({ data: { _id: curLog._id, ...values } }).then(
198 | () => {
199 | hide();
200 | message.success('更新成功');
201 | this.fetchWorklogList({ page_size: kDefaultPageSize, page_index: 1 });
202 | this.hideModalAndClear();
203 | },
204 | err => {
205 | hide();
206 | message.error(err.data.message);
207 | },
208 | );
209 | }
210 | });
211 | };
212 |
213 | handleModalCancel = () => {
214 | this.hideModalAndClear();
215 | };
216 |
217 | hideModalAndClear() {
218 | this.setState({ curLog: null, showModal: false });
219 | this.logForm.clearForm();
220 | }
221 |
222 | render() {
223 | const { data, pagination, loading, showModal, curLog } = this.state;
224 | const { substation, transbox } = this.props;
225 | const isAdd = !curLog;
226 | const title = isAdd ? '添加日志' : '编辑日志';
227 | const addAreaStyle = substation || transbox ? { display: 'block' } : { display: 'none' };
228 |
229 | const columns = [
230 | {
231 | title: '变电所/箱变名称',
232 | width: '15%',
233 | render: record => {
234 | if (record.substation) {
235 | return {record.substation.name}
;
236 | }
237 | if (record.transbox) {
238 | return {record.transbox.name}
;
239 | }
240 | return ;
241 | },
242 | },
243 | {
244 | title: '日期',
245 | dataIndex: 'date',
246 | width: '15%',
247 | render: date => moment(date).format('LL'),
248 | },
249 | {
250 | title: '维修内容和故障隐患',
251 | dataIndex: 'content',
252 | width: '30%',
253 | },
254 | {
255 | title: '解决',
256 | dataIndex: 'resolved',
257 | render: resolved => (resolved ? '是' : '否'),
258 | width: '10%',
259 | },
260 | {
261 | title: '通知用户',
262 | dataIndex: 'notify_user',
263 | render: notify_user => (notify_user ? '是' : '否'),
264 | width: '10%',
265 | },
266 | {
267 | title: '操作',
268 | key: 'action',
269 | width: '20%',
270 | render: record => (
271 |
272 |
279 |
280 |
287 |
288 | ),
289 | },
290 | ];
291 |
292 | return (
293 |
294 |
295 |
298 |
299 |
record._id}
302 | dataSource={data}
303 | pagination={pagination}
304 | loading={loading}
305 | onChange={this.handleTableChange}
306 | />
307 |
315 | {
318 | this.logForm = ref;
319 | }}
320 | />
321 |
322 |
323 | );
324 | }
325 | }
326 |
327 | export default Worklog;
328 |
--------------------------------------------------------------------------------