├── 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 |
9 |
维修日志
10 | 11 |
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 | ![login](https://raw.githubusercontent.com/Rannie/pems/master/screenshots/pems-1.png) 14 | 15 | * List Page 16 | 17 | ![list](https://raw.githubusercontent.com/Rannie/pems/master/screenshots/pems-2.png) 18 | 19 | * Add Page 20 | 21 | ![add](https://raw.githubusercontent.com/Rannie/pems/master/screenshots/pems-3.png) 22 | 23 | * Add Popup 24 | 25 | ![popup](https://raw.githubusercontent.com/Rannie/pems/master/screenshots/pems-4.png) 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 | ![mongo](https://raw.githubusercontent.com/Rannie/pems/master/screenshots/mongo-path.png) 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 | ![wechat](https://raw.githubusercontent.com/Rannie/Rannie.github.io/master/images/Wechat.jpeg) 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 |
50 | 51 | {getFieldDecorator('name', { 52 | rules: [{ required: true, message: '请输入用户名' }], 53 | })( 54 | } 58 | />, 59 | )} 60 | 61 | 62 | {getFieldDecorator('password', { 63 | rules: [{ required: true, message: '请输入密码' }], 64 | })( 65 | } 69 | />, 70 | )} 71 | 72 | 75 |
76 | ); 77 | } 78 | } 79 | const PEMSLoginForm = Form.create({})(LoginForm); 80 | 81 | class Login extends React.Component { 82 | render() { 83 | return ( 84 |
85 |
86 |
电力设施管理系统
87 |
88 |
89 |
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 | 24 | {menuList.map(item => { 25 | return ( 26 | 27 | 28 | 29 | {collapsed ? '' : item.menuTitle} 30 | 31 | 32 | ); 33 | })} 34 | 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 |
71 | {!collapsed && ( 72 | 73 | 设备管理系统 74 | 75 | )} 76 |
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 | appear 74 |
75 | 76 | 77 |
78 |
变电所地理位置:
79 | location 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 | appear 80 |
81 | 82 | 83 |
84 |
变电所地理位置:
85 | location 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 | 42 | 43 | {getFieldDecorator('serial_number', { 44 | rules: [{ required: true, message: '请输入变压器编号' }], 45 | initialValue: editTransformer.serial_number || '', 46 | })()} 47 | 48 | 49 | {getFieldDecorator('model', { initialValue: editTransformer.model })()} 50 | 51 | 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 | 42 | 43 | {getFieldDecorator('number', { 44 | rules: [{ required: true, message: '请输入调度号' }], 45 | initialValue: editLowvol.number || '', 46 | })()} 47 | 48 | 49 | {getFieldDecorator('range', { initialValue: editLowvol.range })()} 50 | 51 | 52 | {getFieldDecorator('model', { initialValue: editLowvol.model })()} 53 | 54 | 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 | 42 | 43 | {getFieldDecorator('number', { 44 | rules: [{ required: true, message: '请输入调度号' }], 45 | initialValue: editVoltage.number || '', 46 | })()} 47 | 48 | 49 | {getFieldDecorator('range', { initialValue: editVoltage.range })()} 50 | 51 | 52 | {getFieldDecorator('model', { initialValue: editVoltage.model })()} 53 | 54 | 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 |
163 | 164 |
点击上传
165 |
166 | ); 167 | 168 | const title = isAdd ? '添加箱变' : '编辑箱变'; 169 | 170 | return ( 171 |
172 |
{title}
173 |
174 | 175 | {getFieldDecorator('name', { 176 | rules: [{ required: true, message: '请输入箱变名称' }], 177 | initialValue: transbox.name, 178 | })()} 179 | 180 | 181 | {getFieldDecorator('superior', { 182 | rules: [{ required: true, message: '请输入上级电源' }], 183 | initialValue: transbox.superior, 184 | })()} 185 | 186 | 187 | {getFieldDecorator('area', { 188 | rules: [{ required: true, message: '请选择区域' }], 189 | initialValue: transbox.area, 190 | })( 191 | , 200 | )} 201 | 202 | 203 | {getFieldDecorator('user_comp', { initialValue: transbox.user_comp })()} 204 | 205 | 206 | {getFieldDecorator('contact_info', { initialValue: transbox.contact_info })()} 207 | 208 | 209 | 215 | {appearUrl ? ( 216 | appear 217 | ) : ( 218 | uploadButton(appearLoading) 219 | )} 220 | 221 | {appearUrl ? ( 222 | 225 | ) : ( 226 |
227 | )} 228 | 229 | 230 | 236 | {locationUrl ? ( 237 | location 238 | ) : ( 239 | uploadButton(locationLoading) 240 | )} 241 | 242 | {locationUrl ? ( 243 | 246 | ) : ( 247 |
248 | )} 249 | 250 | 251 | 260 | 261 | 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 |
50 | 51 | {getFieldDecorator('date', { 52 | rules: [{ required: true, message: '请选择日期' }], 53 | initialValue: date, 54 | })()} 55 | 56 | 57 | {getFieldDecorator('road_name', { 58 | rules: [{ required: true, message: '请输入道路名称' }], 59 | initialValue: roadname, 60 | })()} 61 | 62 | 63 | {getFieldDecorator('record', { 64 | rules: [{ required: true, message: '请输入故障记录' }], 65 | initialValue: record, 66 | })()} 67 | 68 | 69 | {getFieldDecorator('resolved', { 70 | valuePropName: 'checked', 71 | initialValue: resolved, 72 | })(是否解决)} 73 | 74 | 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 |
164 | 165 |
点击上传
166 |
167 | ); 168 | 169 | const title = isAdd ? '添加变电所' : '编辑变电所'; 170 | 171 | return ( 172 |
173 |
{title}
174 |
175 | 176 | {getFieldDecorator('name', { 177 | rules: [{ required: true, message: '请输入变电所名称' }], 178 | initialValue: substation.name, 179 | })()} 180 | 181 | 182 | {getFieldDecorator('superior', { 183 | rules: [{ required: true, message: '请输入上级电源' }], 184 | initialValue: substation.superior, 185 | })()} 186 | 187 | 188 | {getFieldDecorator('area', { 189 | rules: [{ required: true, message: '请选择区域' }], 190 | initialValue: substation.area, 191 | })( 192 | , 201 | )} 202 | 203 | 204 | {getFieldDecorator('user_comp', { initialValue: substation.user_comp })()} 205 | 206 | 207 | {getFieldDecorator('contact_info', { initialValue: substation.contact_info })( 208 | , 209 | )} 210 | 211 | 212 | {getFieldDecorator('number', { initialValue: substation.number })()} 213 | 214 | 215 | 221 | {appearUrl ? ( 222 | appear 223 | ) : ( 224 | uploadButton(appearLoading) 225 | )} 226 | 227 | {appearUrl ? ( 228 | 231 | ) : ( 232 |
233 | )} 234 | 235 | 236 | 242 | {locationUrl ? ( 243 | location 244 | ) : ( 245 | uploadButton(locationLoading) 246 | )} 247 | 248 | {locationUrl ? ( 249 | 252 | ) : ( 253 |
254 | )} 255 | 256 | 257 | 266 | 267 | 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 |
51 | 52 | {getFieldDecorator('date', { 53 | rules: [{ required: true, message: '请选择日期' }], 54 | initialValue: date, 55 | })()} 56 | 57 | 58 | {getFieldDecorator('content', { 59 | rules: [{ required: true, message: '请输入维修内容和故障隐患' }], 60 | initialValue: content, 61 | })(