From 5db8674bc6b5df7b65db1041fc775212d9511925 Mon Sep 17 00:00:00 2001 From: dengyinhuan Date: Tue, 7 Mar 2023 14:40:52 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E7=9A=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/organization/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/lib/controllers/organization/user.js b/api/app/lib/controllers/organization/user.js index 14e0577..66c1c2b 100644 --- a/api/app/lib/controllers/organization/user.js +++ b/api/app/lib/controllers/organization/user.js @@ -172,7 +172,7 @@ async function getUser (ctx, next) { const models = ctx.fs.dc.models; const { depId } = ctx.params; let userRes = null; - if (depId !== 'null') { + if (depId !== 'undefined') { userRes = await models.User.findAll({ where: { departmentId: parseInt(depId), From 91fbc450861c9deb244f31aa6c27aaac807fe20b Mon Sep 17 00:00:00 2001 From: dengyinhuan Date: Wed, 8 Mar 2023 08:57:44 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/patrolManage/yujingguanli.js | 64 +++++++++++++++++++ .../lib/routes/patrolManage/yujingguanli.js | 11 ++++ api/config.js | 2 +- .../4.updata_patrol_record_issue__handle.sql | 1 + web/client/src/app.js | 3 +- .../src/sections/auth/containers/login.js | 1 + .../sections/patrolManage/actions/index.js | 2 + .../patrolManage/actions/yujingguanli.js | 13 ++++ .../patrolManage/components/xiafagaojin.js | 47 +++++--------- .../patrolManage/containers/yujingguanli.js | 46 +++++++------ .../src/sections/patrolManage/nav-item.js | 4 +- .../src/sections/shouye/actions/index.js | 9 +++ .../src/sections/shouye/containers/index.js | 9 +++ .../src/sections/shouye/containers/shouye.js | 62 ++++++++++++++++++ web/client/src/sections/shouye/index.js | 15 +++++ web/client/src/sections/shouye/nav-item.js | 21 ++++++ .../src/sections/shouye/reducers/index.js | 5 ++ web/client/src/sections/shouye/routes.js | 32 ++++++++++ web/client/src/sections/shouye/style.less | 20 ++++++ web/client/src/utils/webapi.js | 1 + 20 files changed, 312 insertions(+), 56 deletions(-) create mode 100644 api/app/lib/controllers/patrolManage/yujingguanli.js create mode 100644 api/app/lib/routes/patrolManage/yujingguanli.js create mode 100644 web/client/src/sections/patrolManage/actions/yujingguanli.js create mode 100644 web/client/src/sections/shouye/actions/index.js create mode 100644 web/client/src/sections/shouye/containers/index.js create mode 100644 web/client/src/sections/shouye/containers/shouye.js create mode 100644 web/client/src/sections/shouye/index.js create mode 100644 web/client/src/sections/shouye/nav-item.js create mode 100644 web/client/src/sections/shouye/reducers/index.js create mode 100644 web/client/src/sections/shouye/routes.js create mode 100644 web/client/src/sections/shouye/style.less diff --git a/api/app/lib/controllers/patrolManage/yujingguanli.js b/api/app/lib/controllers/patrolManage/yujingguanli.js new file mode 100644 index 0000000..2b39b8b --- /dev/null +++ b/api/app/lib/controllers/patrolManage/yujingguanli.js @@ -0,0 +1,64 @@ +'use strict'; +async function varfiyCode(ctx) { + try { + const { models } = ctx.fs.dc; + const { pushBySms, pushByEmail } = ctx.app.fs.utils + const { phone, type ,email} = ctx.request.body + + // 伪造的请求可能由相同的sig参数组成 + // const checkSigUsed = await models.PhoneValidateCode.findOne({ + // where: { sig: sig } + // }); + // if (checkSigUsed) { + // throw '参数错误!' + // } + + // // 验证sig正确性 + // const checkSig = Hex.stringify(SHA1(phone + r)); + // if (!r || !sig || sig != checkSig) { + // throw '参数错误!' + // } + + let varifyCode = '' + for (let i = 0; i < 6; i++) { + varifyCode += Math.floor(Math.random() * 10) + } + + if(type.includes(1)){ + await pushBySms({ + phone: phone, + templateCode: 'SMS_261950020', + templateParam: { + code: varifyCode + }, + }) + } + if(type.includes(2)){ + await pushByEmail({ + email: email, + title: '测试', + text:'你知道吗' + }) + } + + // await models.PhoneValidateCode.create({ + // phone: phone, + // code: varifyCode, + // sig: sig, + // expired: moment().add(10, 'minutes').format('YYYY-MM-DD HH:mm:ss') + // }) + + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : '获取验证码失败' + } + } +} + + module.exports = { + varfiyCode, + // pushByEmail +} \ No newline at end of file diff --git a/api/app/lib/routes/patrolManage/yujingguanli.js b/api/app/lib/routes/patrolManage/yujingguanli.js new file mode 100644 index 0000000..b968474 --- /dev/null +++ b/api/app/lib/routes/patrolManage/yujingguanli.js @@ -0,0 +1,11 @@ +'use strict'; + +const yujingguanli = require('../../controllers/patrolManage/yujingguanli'); + +module.exports = function (app, router, opts) { + + + app.fs.api.logAttr['POST/yujingguanli'] = { content: '下发预警邮件', visible: true }; + router.post('/yujingguanli', yujingguanli.varfiyCode); + +}; \ No newline at end of file diff --git a/api/config.js b/api/config.js index b752ec8..3aa9876 100644 --- a/api/config.js +++ b/api/config.js @@ -94,7 +94,7 @@ const product = { host: 'smtp.exmail.qq.com', port: 465, sender: { - name: '中鼎服务', + name: '运维服务', address: 'fsiot@free-sun.com.cn', password: 'Fs2689' } diff --git a/script/1.0.4/schema/4.updata_patrol_record_issue__handle.sql b/script/1.0.4/schema/4.updata_patrol_record_issue__handle.sql index d5ac8f5..e10d398 100644 --- a/script/1.0.4/schema/4.updata_patrol_record_issue__handle.sql +++ b/script/1.0.4/schema/4.updata_patrol_record_issue__handle.sql @@ -1,4 +1,5 @@ ALTER TABLE patrol_record_issue_handle ADD yanshoushijian timestamp(6); ALTER TABLE patrol_record_issue_handle ADD yanshoucishu integer; ALTER TABLE patrol_record_issue_handle ADD yujingshijian timestamp(6); +ALTER TABLE patrol_record_issue_handle ADD yujingafchishu integer; ALTER TABLE patrol_record_issue_handle ADD isgaojing bool; diff --git a/web/client/src/app.js b/web/client/src/app.js index e737fac..1541740 100644 --- a/web/client/src/app.js +++ b/web/client/src/app.js @@ -8,6 +8,7 @@ import ProjectRegime from './sections/projectRegime'; import Organization from './sections/organization'; import PatrolManage from './sections/patrolManage'; import IssueHandle from './sections/issueHandle' +import Shouye from './sections/shouye'; import { Func } from '$utils'; const App = props => { const { projectName } = props @@ -19,7 +20,7 @@ const App = props => { return ( ) diff --git a/web/client/src/sections/auth/containers/login.js b/web/client/src/sections/auth/containers/login.js index 5ca98ad..0fbb02b 100644 --- a/web/client/src/sections/auth/containers/login.js +++ b/web/client/src/sections/auth/containers/login.js @@ -31,6 +31,7 @@ const Login = props => { const [form] = Form.useForm(); const tourl = () => { + return '/shouye' if (Func.isAuthorized("STRU_INFO_CONFIG")) { return '/projectRegime/information' } diff --git a/web/client/src/sections/patrolManage/actions/index.js b/web/client/src/sections/patrolManage/actions/index.js index ad713eb..690f08c 100644 --- a/web/client/src/sections/patrolManage/actions/index.js +++ b/web/client/src/sections/patrolManage/actions/index.js @@ -5,6 +5,7 @@ import * as record from './record' import * as report from './report' import * as template from './template' import * as checkItems from './checkItems' +import * as yujingguanli from './yujingguanli' export default { ...plan, @@ -12,4 +13,5 @@ export default { ...report, ...template, ...checkItems, + ...yujingguanli } \ No newline at end of file diff --git a/web/client/src/sections/patrolManage/actions/yujingguanli.js b/web/client/src/sections/patrolManage/actions/yujingguanli.js new file mode 100644 index 0000000..2ed6a3f --- /dev/null +++ b/web/client/src/sections/patrolManage/actions/yujingguanli.js @@ -0,0 +1,13 @@ +import { basicAction } from '@peace/utils' +import { ApiTable } from '$utils' + +export function putxinxi (data) { + return dispatch => basicAction({ + type: 'post', + data, + dispatch: dispatch, + actionType: 'PUT_XINXI', + url: ApiTable.yujingguanli, + msg: { option: '发送信息' }, + }); +} \ No newline at end of file diff --git a/web/client/src/sections/patrolManage/components/xiafagaojin.js b/web/client/src/sections/patrolManage/components/xiafagaojin.js index 1d4b1cd..f9a177c 100644 --- a/web/client/src/sections/patrolManage/components/xiafagaojin.js +++ b/web/client/src/sections/patrolManage/components/xiafagaojin.js @@ -2,15 +2,17 @@ import { Button, Form, Input, Modal, Select, DatePicker,Checkbox } from 'antd'; import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { createPatrolTemplate, delPatrolTemplate, updatePatrolTemplate, getPatrolTemplate } from '../actions/template'; +import {putxinxi} from '../actions/yujingguanli' import moment from 'moment'; const { RangePicker } = DatePicker; const { TextArea } = Input; -const PlanModal = ({ visible, onCancel, dispatch, type, curRecord, tableRef, checkItemsGroup }) => { +const PlanModal = ({ visible, onCancel, dispatch, type, curRecord, tableRef, checkItemsGroup,userlist }) => { const [form] = Form.useForm(); const shigutypes = [{value:1,label: '邮件告警'}, {value:2,label:'短信告警'}] + console.log(userlist,'userlist') return ( { - const params = { ...values, } - - if (type === 'create') { - dispatch(createPatrolTemplate(params)).then(res => { - if (res.success) { - tableRef.current.reload(); - form.resetFields(); - onCancel(); - } - }) - } else { - dispatch(updatePatrolTemplate({ - ...params, - id: curRecord.id - })).then(res => { - if (res.success) { - tableRef.current.reload(); - form.resetFields(); - onCancel(); - } - }) - } + console.log('user,',userlist) + let usedata = userlist.filter(i=>i?.username===values.name) + console.log(usedata,'usedata') + dispatch(putxinxi({phone:[params.name],email:[usedata[0]?.email],type:params.type})).then(res=>{ + console.log(res,'res') + }) + console.log(params,'params') }) .catch((info) => { console.log('Validate Failed:', info); @@ -60,22 +47,20 @@ const PlanModal = ({ visible, onCancel, dispatch, type, curRecord, tableRef, che form={form} // layout="vertical" name="form_in_modal" - initialValues={{ - ...curRecord, - checkItems: curRecord?.checkItems?.map(c => c.id) - }} - labelCol={{ span: 5 }} wrapperCol={{ span: 19 }} offe + + labelCol={{ span: 5 }} wrapperCol={{ span: 19 }} > - + - + diff --git a/web/client/src/sections/patrolManage/containers/yujingguanli.js b/web/client/src/sections/patrolManage/containers/yujingguanli.js index 97e9774..8abfafd 100644 --- a/web/client/src/sections/patrolManage/containers/yujingguanli.js +++ b/web/client/src/sections/patrolManage/containers/yujingguanli.js @@ -3,11 +3,12 @@ import { connect } from 'react-redux'; import { Button, Popconfirm, Tag,Tabs } from 'antd'; import ProTable from '@ant-design/pro-table'; import Xiafagaojin from '../components/xiafagaojin'; +import {getDepUser} from '../../organization/actions/user' import { createPatrolTemplate, delPatrolTemplate, updatePatrolTemplate, getPatrolTemplate } from '../actions/template'; import { getCheckItemsGroup } from '../actions/checkItems'; import moment from 'moment'; function YujingGuanli (props) { - const { dispatch, user,actions } = props; + const { dispatch, user,actions,depUser} = props; const tableRef = useRef(); const format = 'YYYY-MM-DD HH:mm:ss' const { patrolManage } = actions @@ -35,29 +36,30 @@ function YujingGuanli (props) { if (res.success) { console.log(res,'水平') let obj = {} - res?.payload?.data?.map(i=>{ - if(obj[i?.points?.project?.id]){ - // if(obj[i?.points?.project?.id.toString()][i.pointId.toString()]){ - // obj[i?.points?.project?.id.toString()][i.pointId.toString()].ponintname=i.points?.itemData?.name - // obj[i?.points?.project?.id.toString()][i.pointId.toString()].num=obj[i?.points?.project?.id.toString()][i.pointId.toString()].num+i?.patrolRecordIssueHandles[0]?.state==6?1:0 + // res?.payload?.data?.map(i=>{ + // if(obj[i?.points?.project?.id]){ + // // if(obj[i?.points?.project?.id.toString()][i.pointId.toString()]){ + // // obj[i?.points?.project?.id.toString()][i.pointId.toString()].ponintname=i.points?.itemData?.name + // // obj[i?.points?.project?.id.toString()][i.pointId.toString()].num=obj[i?.points?.project?.id.toString()][i.pointId.toString()].num+i?.patrolRecordIssueHandles[0]?.state==6?1:0 - // }else{ - // obj[i?.points?.project?.id.toString()][i.pointId.toString()].ponintname=i.points?.itemData?.name - // obj[i?.points?.project?.id.toString()][i.pointId.toString()].num=i?.patrolRecordIssueHandles[0]?.state==6?1:0 - // } - obj[i?.points?.project?.id].push({pointId:i.pointId,pointname:i.points.itemData.name}) - // i?patrolRecordIssueHandles[0]?.state==6 - obj[i?.points?.project?.id].num= obj[i?.points?.project?.id].num + i?.patrolRecordIssueHandles[0]?.state==6 ?1:0 - }else{ - obj[i?.points?.project?.id]={name:i?.points?.project?.name,num:i?.patrolRecordIssueHandles[0]?.state==6 ?1:0 } + // // }else{ + // // obj[i?.points?.project?.id.toString()][i.pointId.toString()].ponintname=i.points?.itemData?.name + // // obj[i?.points?.project?.id.toString()][i.pointId.toString()].num=i?.patrolRecordIssueHandles[0]?.state==6?1:0 + // // } + // obj[i?.points?.project?.id]?.push({pointId:i.pointId,pointname:i.points.itemData.name}) + // // i?patrolRecordIssueHandles[0]?.state==6 + // obj[i?.points?.project?.id].num= obj[i?.points?.project?.id].num + i?.patrolRecordIssueHandles[0]?.state==6 ?1:0 + // }else{ + // obj[i?.points?.project?.id]={name:i?.points?.project?.name,num:i?.patrolRecordIssueHandles[0]?.state==6 ?1:0 } - } - console.log(obj,'obj') - }) + // } + // console.log(obj,'obj') + // }) } }) } + console.log(depUser,'depUser') useEffect(() => { // dispatch(patrolManage.records(`patrolRecord/all/null/null/true/null`)).then(res=>{ // let obj = {} @@ -81,7 +83,7 @@ function YujingGuanli (props) { // console.log(obj,'obj') // console.log(res,'res') // }) - + dispatch(getDepUser()) queryData() dispatch(getCheckItemsGroup()) }, []) @@ -187,11 +189,12 @@ console.log(tableList,'tablist') ]} /> { - visible ? + visible &&depUser.filter(i=>i.username&&i.email).length!==0 ? i.username&&i.email)} onCancel={() => { setVisible(false); setCurRecord({}) @@ -204,10 +207,11 @@ console.log(tableList,'tablist') } function mapStateToProps (state) { - const { auth, global } = state + const { auth, global ,depUser} = state return { user: auth.user, actions: global.actions, + depUser: depUser.data || [], } } export default connect(mapStateToProps)(YujingGuanli); diff --git a/web/client/src/sections/patrolManage/nav-item.js b/web/client/src/sections/patrolManage/nav-item.js index 69580d9..8dc3592 100644 --- a/web/client/src/sections/patrolManage/nav-item.js +++ b/web/client/src/sections/patrolManage/nav-item.js @@ -26,9 +26,9 @@ export function getNavItem (user, dispatch) { {Func.isAuthorized('CHECKMOULD') && 巡检模板 } - {/* { + { 预警管理 - } */} + } ); } \ No newline at end of file diff --git a/web/client/src/sections/shouye/actions/index.js b/web/client/src/sections/shouye/actions/index.js new file mode 100644 index 0000000..e603f22 --- /dev/null +++ b/web/client/src/sections/shouye/actions/index.js @@ -0,0 +1,9 @@ +'use strict'; + + + + +export default { + + +} \ No newline at end of file diff --git a/web/client/src/sections/shouye/containers/index.js b/web/client/src/sections/shouye/containers/index.js new file mode 100644 index 0000000..3923e11 --- /dev/null +++ b/web/client/src/sections/shouye/containers/index.js @@ -0,0 +1,9 @@ +'use strict'; + + + +import Shouye from './shouye' + + + +export { Shouye }; diff --git a/web/client/src/sections/shouye/containers/shouye.js b/web/client/src/sections/shouye/containers/shouye.js new file mode 100644 index 0000000..a07a517 --- /dev/null +++ b/web/client/src/sections/shouye/containers/shouye.js @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Spin, Card, Form, Input, Select, Button, Table, Modal, Popconfirm, Tooltip } from 'antd'; +import moment from "moment"; +import '../style.less'; +import { push } from 'react-router-redux'; +import { Model } from 'echarts'; + + +const Information = (props) => { + const { dispatch, actions, user, loading } = props + const topdata =[] + + + + return ( + <> +
+
+
+
今日巡检
+
0
+
+
+
完成巡检:2个
+
巡检上报:2个
+
+
+
+
+
今日巡检
+
0
+
+
+
完成巡检:2个
+
巡检上报:2个
+
+
+
+
+
今日巡检
+
0
+
+
+
完成巡检:2个
+
巡检上报:2个
+
+
+
+ + ) +} + +function mapStateToProps (state) { + const { auth, global } = state; + return { + user: auth.user, + actions: global.actions, + }; +} + +export default connect(mapStateToProps)(Information); diff --git a/web/client/src/sections/shouye/index.js b/web/client/src/sections/shouye/index.js new file mode 100644 index 0000000..319442b --- /dev/null +++ b/web/client/src/sections/shouye/index.js @@ -0,0 +1,15 @@ +'use strict'; + +import reducers from './reducers'; +import routes from './routes'; +import actions from './actions'; +import { getNavItem } from './nav-item'; + +export default { + key: 'shouye', + name: '首页', + reducers: reducers, + routes: routes, + actions: actions, + getNavItem: getNavItem +}; \ No newline at end of file diff --git a/web/client/src/sections/shouye/nav-item.js b/web/client/src/sections/shouye/nav-item.js new file mode 100644 index 0000000..0de1336 --- /dev/null +++ b/web/client/src/sections/shouye/nav-item.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Menu } from 'antd'; +import { SettingOutlined } from '@ant-design/icons'; +import { Func } from '$utils'; +const SubMenu = Menu.SubMenu; + +export function getNavItem (user, dispatch) { + // return } title={'首页'}> + // {/* {Func.isAuthorized('STRU_INFO_CONFIG') && + // 结构物基础信息管理 + // } + // {Func.isAuthorized('QR_CODE_CONFIG') && + // 二维码管理 + // } */} + // + return + 首页 + + +} \ No newline at end of file diff --git a/web/client/src/sections/shouye/reducers/index.js b/web/client/src/sections/shouye/reducers/index.js new file mode 100644 index 0000000..7ed1088 --- /dev/null +++ b/web/client/src/sections/shouye/reducers/index.js @@ -0,0 +1,5 @@ +'use strict'; + +export default { + +} \ No newline at end of file diff --git a/web/client/src/sections/shouye/routes.js b/web/client/src/sections/shouye/routes.js new file mode 100644 index 0000000..73ae24b --- /dev/null +++ b/web/client/src/sections/shouye/routes.js @@ -0,0 +1,32 @@ +'use strict'; +import { Shouye } from './containers'; + +export default [{ + type: 'inner', + route: { + path: '/shouye', + key: 'shouye', + breadcrumb: '首页', + component: Shouye, + // 不设置 component 则面包屑禁止跳转 + // childRoutes: [{ + // path: '/information', + // key: 'information', + // breadcrumb: '结构物基础信息管理', + // component: Information, + // childRoutes: [ { + // path: '/:id', + // key: ':id', + // component: Point, + // breadcrumb: '点位', + // }, + // ] + // }, { + // path: '/qrCode', + // key: 'qrCode', + // component: QrCode, + // breadcrumb: '二维码管理', + // }, + // ] + } +}]; \ No newline at end of file diff --git a/web/client/src/sections/shouye/style.less b/web/client/src/sections/shouye/style.less new file mode 100644 index 0000000..1e0398d --- /dev/null +++ b/web/client/src/sections/shouye/style.less @@ -0,0 +1,20 @@ +.shouyetop{ + display: flex; + justify-content: space-between; + .shouyetopitem{ + width: 25%; + display: flex; + justify-content: space-between; + box-shadow: 0 0 10px #F0F2F5; + border:1px solid #F0F2F5; + color: rgba(0, 0, 0, 0.45); + font-size: 1.875rem; + height: 7.125rem; + .shouyetopitem-left{ + width: 50%; + } + .shouyetopitem-right{ + width: 50%; + } + } +} \ No newline at end of file diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 8232b71..2e1aaee 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -64,6 +64,7 @@ export const ApiTable = { delCheckTask: '/delcheckTask/:id', addPatrolRecordIssueHandle: 'patrolRecord/issue/handle', modifyPatrolRecordIssueHandle: 'patrolRecord/issue/handle/{id}', + yujingguanli:'/yujingguanli', //协调申请 getCoordinateList: 'risk/coordinate', From 17e0ee80c60dc3d8179e662c4c421bb7dc824e3c Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 8 Mar 2023 08:58:27 +0800 Subject: [PATCH 3/6] =?UTF-8?q?(+)=E7=BB=93=E6=9E=84=E7=89=A9=E5=B8=83?= =?UTF-8?q?=E8=AE=BE=E5=9B=BE=E5=92=8C=E7=82=B9=E4=BD=8D=E5=B8=83=E8=AE=BE?= =?UTF-8?q?=20=E8=84=9A=E6=9C=AC=E5=92=8Capi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/pointDeploy/index.js | 209 ++++++++++++++++++ api/app/lib/models/project_graph.js | 44 ++++ api/app/lib/models/project_points_deploy.js | 53 +++++ api/app/lib/routes/pointDeploy/index.js | 23 ++ .../schema/2.project_graph_point_deploy.sql | 33 +++ 5 files changed, 362 insertions(+) create mode 100644 api/app/lib/controllers/pointDeploy/index.js create mode 100644 api/app/lib/models/project_graph.js create mode 100644 api/app/lib/models/project_points_deploy.js create mode 100644 api/app/lib/routes/pointDeploy/index.js create mode 100644 script/1.0.5/schema/2.project_graph_point_deploy.sql diff --git a/api/app/lib/controllers/pointDeploy/index.js b/api/app/lib/controllers/pointDeploy/index.js new file mode 100644 index 0000000..49b6fec --- /dev/null +++ b/api/app/lib/controllers/pointDeploy/index.js @@ -0,0 +1,209 @@ +'use strict'; + +async function findSingleGraph(ctx, next) { + let error = { name: 'FindSingleError', message: '查询单一数据失败' }; + let rslt = null; + const { projectId } = ctx.params; + try { + rslt = await ctx.fs.dc.models.ProjectGraph.findOne({ + where: { projectId: projectId } + }); + error = null; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = rslt; + } +} + +async function createGraph(ctx, next) { + let error = { name: 'AddError', message: '添加数据失败' }; + let graphId = null; + try { + const data = ctx.request.body; + let dataToSave = { + projectId: data.projectId, + graph: data.graph, + } + const t = await ctx.fs.dc.orm.transaction(); + try { + let planarGraph = await ctx.fs.dc.models.ProjectGraph.create(dataToSave); + graphId = planarGraph.id; + await t.commit(); + } catch (e) { + await t.rollback(); + throw e; + } + error = null; + // 日志信息 + ctx.fs.api = ctx.fs.api || {}; + ctx.fs.api.actionParameter = JSON.stringify(data); + ctx.fs.api.actionParameterShow = `新增graphId:${graphId}`; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = { id: graphId }; + } +} + +async function updateGraph(ctx, next) { + let error = { name: 'UpdateError', message: '修改数据失败' }; + const { id } = ctx.params; + const data = ctx.request.body; + if (id) { + if (data && Object.keys(data).length) { + try { + const models = ctx.fs.dc.models; + let planarGraph = await models.ProjectGraph.findOne({ where: { id: id } }); + const dataToSave = {}; + if (planarGraph) { + const { projectId, graph } = data; + if (projectId && !(projectId == planarGraph.projectId)) + dataToSave.projectId = projectId; + if (graph && !(graph == planarGraph.graph)) + dataToSave.graph = graph; + } + dataToSave.id = planarGraph.id; + if (Object.keys(dataToSave).length) { + await models.ProjectGraph.update(dataToSave, { where: { id: planarGraph.id } }); + } + error = null; + // 日志信息 + ctx.fs.api = ctx.fs.api || {}; + ctx.fs.api.actionParameter = JSON.stringify(data); + ctx.fs.api.actionParameterShow = `结构物平面图id:${data.id}`; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + } + } else { + error = { name: 'UpdateError', message: `不存在{id=${id}}的结构物平面图` }; + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = { message: "结构物平面图修改成功" }; + } +} + +async function delProjectGraph(ctx) { + try { + const { id } = ctx.params; + const models = ctx.fs.dc.models; + let info = await models.ProjectGraph.findOne({ where: { id: id } }); + if (info) { + await models.ProjectPointsDeploy.destroy({ where: { graphId: id } }); + await models.ProjectGraph.destroy({ where: { id } }) + ctx.status = 204; + } else { + ctx.status = 400; + ctx.body = { message: '数据不存在' } + } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + "message": "删除结构物布设图失败" + } + } +} + +async function getDeployPoints(ctx) { + try { + const pictureId = ctx.params.pictureId; + const models = ctx.fs.dc.models; + const heatmap = await models.ProjectGraph.findOne({ where: { id: pictureId } }) + if (heatmap) { + let allPoints = await models.Point.findAll({ + attributes: ['id', 'name'], + where: { projectId: heatmap.dataValues.projectId } + }) + let setedPoints = await models.ProjectPointsDeploy.findAll({ + where: { graphId: pictureId } + }) + ctx.status = 200; + ctx.body = { + allPoints, + setedPoints + }; + } else { + throw new Error('pictureId not found'); + } + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + ctx.status = 400; + ctx.body = { + name: 'FindError', + message: '获取结构物平面图测点布设失败' + } + } +} + +async function setDeployPoints(ctx) { + const pictureId = ctx.params.pictureId; + const req = ctx.request.body; + const models = ctx.fs.dc.models; + const orm = ctx.fs.dc.orm; + + try { + ctx.fs.api = ctx.fs.api || {}; + ctx.fs.api.actionParameter = req; + ctx.fs.api.actionParameterShow = '结构物平面图测点部署: ' + JSON.stringify(req); + + const t = await orm.transaction(); + + try { + await models.ProjectPointsDeploy.destroy({ where: { graphId: pictureId }, transaction: t }); + + const layout = req.spots.map((hotspot, index) => { + let msg = { + graphId: pictureId, + pointId: hotspot.pointId, + position: JSON.stringify(hotspot.position) + } + return msg; + }); + + await models.ProjectPointsDeploy.bulkCreate(layout, { transaction: t }); + + await t.commit(); + } catch (e) { + await t.rollback(); + throw e; + } + ctx.status = 200; + ctx.body = { + name: "CreateSuccess", + message: "结构物平面图测点部署成功" + }; + + } catch (err) { + ctx.fs.logger.error(err); + ctx.status = 400; + ctx.body = { + name: 'CreateError', + message: "结构物平面图测点部署失败" + } + } +} + +module.exports = { + findSingleGraph, + createGraph, + updateGraph, + delProjectGraph, + getDeployPoints, + setDeployPoints +}; \ No newline at end of file diff --git a/api/app/lib/models/project_graph.js b/api/app/lib/models/project_graph.js new file mode 100644 index 0000000..8948a59 --- /dev/null +++ b/api/app/lib/models/project_graph.js @@ -0,0 +1,44 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const ProjectGraph = sequelize.define("projectGraph", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "project_graph_id_uindex" + }, + projectId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_id", + autoIncrement: false + }, + graph: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "graph", + autoIncrement: false + } + }, { + tableName: "project_graph", + comment: "", + indexes: [] + }); + dc.models.ProjectGraph = ProjectGraph; + return ProjectGraph; +}; \ No newline at end of file diff --git a/api/app/lib/models/project_points_deploy.js b/api/app/lib/models/project_points_deploy.js new file mode 100644 index 0000000..a0e7f6e --- /dev/null +++ b/api/app/lib/models/project_points_deploy.js @@ -0,0 +1,53 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const ProjectPointsDeploy = sequelize.define("projectPointsDeploy", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "project_points_deploy_id_uindex" + }, + pointId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "point_id", + autoIncrement: false + }, + graphId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "graph_id", + autoIncrement: false + }, + position: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "position", + autoIncrement: false + } + }, { + tableName: "project_points_deploy", + comment: "", + indexes: [] + }); + dc.models.ProjectPointsDeploy = ProjectPointsDeploy; + return ProjectPointsDeploy; +}; \ No newline at end of file diff --git a/api/app/lib/routes/pointDeploy/index.js b/api/app/lib/routes/pointDeploy/index.js new file mode 100644 index 0000000..87d06a6 --- /dev/null +++ b/api/app/lib/routes/pointDeploy/index.js @@ -0,0 +1,23 @@ +'use strict'; + +const pointDeploy = require('../../controllers/pointDeploy'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/project/:projectId/planarGraph'] = { content: '获取结构物平面图数据', visible: false }; + router.get('/project/:projectId/planarGraph', pointDeploy.findSingleGraph); + + app.fs.api.logAttr['POST/planarGraph/add'] = { content: '新增结构物平面图', visible: true }; + router.post('/planarGraph/add', pointDeploy.createGraph); + + app.fs.api.logAttr['PUT/planarGraph/:id/modify'] = { content: '修改结构物平面图', visible: true }; + router.post('/planarGraph/:id/modify', pointDeploy.updateGraph); + + app.fs.api.logAttr['DEL/project/graph/:id'] = { content: '删除结构物布设图', visible: false }; + router.del('/project/graph/:id', pointDeploy.delProjectGraph); + + app.fs.api.logAttr['GET/picture/:pictureId/deploy/points'] = { content: '获取点位布设信息', visible: false }; + router.get('/picture/:pictureId/deploy/points', pointDeploy.getDeployPoints); + + app.fs.api.logAttr['POST/set/picture/:pictureId/deploy/points'] = { content: '点位布设', visible: true }; + router.post('/set/picture/:pictureId/deploy/points', pointDeploy.setDeployPoints); +}; \ No newline at end of file diff --git a/script/1.0.5/schema/2.project_graph_point_deploy.sql b/script/1.0.5/schema/2.project_graph_point_deploy.sql new file mode 100644 index 0000000..9e819d3 --- /dev/null +++ b/script/1.0.5/schema/2.project_graph_point_deploy.sql @@ -0,0 +1,33 @@ +/*结构物布设图表*/ +create table project_graph +( + id serial not null, + project_id int not null, + graph varchar(255) not null +); + +create unique index project_graph_id_uindex + on project_graph (id); + +alter table project_graph + add constraint project_graph_pk + primary key (id); + + + + +/*点位布设表*/ +create table project_points_deploy +( + id serial not null, + point_id int not null, + graph_id int not null, + position varchar(1000) not null +); + +create unique index project_points_deploy_id_uindex + on project_points_deploy (id); + +alter table project_points_deploy + add constraint project_points_deploy_pk + primary key (id); \ No newline at end of file From 8aae0a44087c7f3c60646248e68d762ef3a2f582 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 8 Mar 2023 08:59:39 +0800 Subject: [PATCH 4/6] =?UTF-8?q?(+)=E7=BB=93=E6=9E=84=E7=89=A9=E5=B8=83?= =?UTF-8?q?=E8=AE=BE=E5=9B=BE=E5=92=8C=E7=82=B9=E4=BD=8D=E5=B8=83=E8=AE=BE?= =?UTF-8?q?=20web=E4=BB=A3=E7=A0=81=E6=9A=82=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sections/projectRegime/actions/graph.js | 72 ++++ .../sections/projectRegime/actions/index.js | 3 +- .../projectRegime/actions/projectSituation.js | 1 + .../components/pointDeploy/heatmap.js | 57 +++ .../components/pointDeploy/station-spot.js | 114 ++++++ .../components/pointDeploy/style.css | 91 +++++ .../projectRegime/containers/index.js | 4 +- .../projectRegime/containers/information.js | 9 +- .../containers/pointDeploy/default.js | 384 ++++++++++++++++++ .../containers/pointDeploy/deploy-style.less | 114 ++++++ .../pointDeploy/upload-img-modal.js | 94 +++++ .../src/sections/projectRegime/routes.js | 22 +- web/client/src/utils/webapi.js | 7 + web/package.json | 2 + 14 files changed, 962 insertions(+), 12 deletions(-) create mode 100644 web/client/src/sections/projectRegime/actions/graph.js create mode 100644 web/client/src/sections/projectRegime/components/pointDeploy/heatmap.js create mode 100644 web/client/src/sections/projectRegime/components/pointDeploy/station-spot.js create mode 100644 web/client/src/sections/projectRegime/components/pointDeploy/style.css create mode 100644 web/client/src/sections/projectRegime/containers/pointDeploy/default.js create mode 100644 web/client/src/sections/projectRegime/containers/pointDeploy/deploy-style.less create mode 100644 web/client/src/sections/projectRegime/containers/pointDeploy/upload-img-modal.js diff --git a/web/client/src/sections/projectRegime/actions/graph.js b/web/client/src/sections/projectRegime/actions/graph.js new file mode 100644 index 0000000..6a8e2f5 --- /dev/null +++ b/web/client/src/sections/projectRegime/actions/graph.js @@ -0,0 +1,72 @@ +'use strict'; + +import { basicAction } from '@peace/utils' +import { ApiTable } from '$utils' + +export function getProjectGraph(projectId) { + return (dispatch) => basicAction({ + type: 'get', + dispatch, + actionType: 'GET_PROJECT_PLANAR_GRAPH', + url: ApiTable.getProjectGraph.replace('{projectId}', projectId), + msg: { option: '获取结构物平面图' }, + reducer: { name: 'projectGraph' } + }); +} + + +export function createGraph(data) { + return (dispatch) => basicAction({ + type: 'post', + data, + dispatch, + actionType: 'ADD_PROJECT_PLANAR_GRAPH', + url: ApiTable.createGraph, + msg: { option: '新增结构物平面图' }, + }); +} + +export function updateGraph(id, data) { + return (dispatch) => basicAction({ + type: 'post', + data, + dispatch, + actionType: 'UPDATE_PROJECT_PLANAR_GRAPH', + url: ApiTable.updateGraph.replace('{id}', id), + msg: { option: '修改结构物平面图' }, + }); +} + +export function deleteGraph(id) { + return (dispatch) => basicAction({ + type: 'del', + dispatch, + actionType: 'DELETE_PROJECT_GRAPH', + url: ApiTable.deleteGraph.replace('{id}', id), + msg: { + option: '删除结构物布设图', + }, + }); +} + +export function getDeployPoints(pictureId) { + return (dispatch) => basicAction({ + type: 'get', + dispatch, + actionType: 'GET_PROJECT_DEPLOY_POINTS', + url: ApiTable.getDeployPoints.replace('{pictureId}', pictureId), + msg: { option: '获取结构物平面图测点分布' }, + reducer: { name: 'projectDeployPoints' } + }); +} + +export function setDeployPoints(pictureId, data) { + return (dispatch) => basicAction({ + type: 'post', + data, + dispatch, + actionType: 'SET_PROJECT_DEPLOY_POINTS', + url: ApiTable.setDeployPoints.replace('{pictureId}', pictureId), + msg: { option: '结构物平面图点位布设' }, + }); +} \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/actions/index.js b/web/client/src/sections/projectRegime/actions/index.js index 4e50055..8e9388b 100644 --- a/web/client/src/sections/projectRegime/actions/index.js +++ b/web/client/src/sections/projectRegime/actions/index.js @@ -2,8 +2,9 @@ import * as projectSituation from './projectSituation' +import * as projectGraph from './graph' export default { ...projectSituation, - + ...projectGraph } \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/actions/projectSituation.js b/web/client/src/sections/projectRegime/actions/projectSituation.js index ab045b8..f159551 100644 --- a/web/client/src/sections/projectRegime/actions/projectSituation.js +++ b/web/client/src/sections/projectRegime/actions/projectSituation.js @@ -60,6 +60,7 @@ export function positionList (query) { actionType: 'POSITION_LIST', url: ApiTable.position, msg: { error: '获取点位列表失败', }, + reducer: { name: 'projectPoints' } }); } diff --git a/web/client/src/sections/projectRegime/components/pointDeploy/heatmap.js b/web/client/src/sections/projectRegime/components/pointDeploy/heatmap.js new file mode 100644 index 0000000..33545ac --- /dev/null +++ b/web/client/src/sections/projectRegime/components/pointDeploy/heatmap.js @@ -0,0 +1,57 @@ +'use strict' + +import React, { Component, PropTypes } from 'react'; +import { DropTarget } from 'react-dnd'; +import StationSpot from './station-spot'; + +const heatmapTarget = { + drop(props, monitor) { + //get item from station-spot.js + //item:{deployed, rect, spotInlist, info} + const item = monitor.getItem(); + const move = monitor.getDifferenceFromInitialOffset(); + props.onDeploySpot({ ...item, move }); + } +}; + +function collect(connect, monitor) { + return { + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver() + }; +} + +class Heatmap extends React.Component { + componentDidMount() { + + } + + renderSpots() { + const { width, height, spots, onRemoveSpot } = this.props; + return spots.map(s => ); + } + + render() { + const { connectDropTarget, height, width, image } = this.props; + + let targetStyle = { + position: 'relative', + width: width, + // overflow:'hidden', + height: height, + background: `url("/_file-server/${image}") no-repeat`, + backgroundSize: '100% 100%', + }; + + return connectDropTarget( +
+ {this.renderSpots()} +
+ ) + } +} + +export default DropTarget('stationSpot', heatmapTarget, collect)(Heatmap); + diff --git a/web/client/src/sections/projectRegime/components/pointDeploy/station-spot.js b/web/client/src/sections/projectRegime/components/pointDeploy/station-spot.js new file mode 100644 index 0000000..a92412d --- /dev/null +++ b/web/client/src/sections/projectRegime/components/pointDeploy/station-spot.js @@ -0,0 +1,114 @@ +/** + * Created by yuanfenghua on 2018/6/18. + */ +'use strict' + +import React, { Component } from 'react'; +import { findDOMNode } from 'react-dom'; +import { connect } from 'react-redux'; +import { DragSource } from 'react-dnd'; +import { Tooltip } from 'antd'; +import { MinusOutlined } from '@ant-design/icons'; +import Style from './style.css'; + +const stationSource = { + beginDrag(props, monitor, component) { + const dom = findDOMNode(component); + const rect = { + x: dom.offsetLeft - dom.offsetParent.scrollLeft, + y: dom.offsetTop - dom.offsetParent.scrollTop + }; + const spotInlist = { + x: dom.getBoundingClientRect().left, + y: dom.getBoundingClientRect().top + }; + + return { + info: props.info, + rect: rect, + spotInlist: spotInlist, + deployed: props.info.deployed + }; + }, + endDrag(props, monitor) { + if (!monitor.didDrop() && props.onRemoveSpot) { + props.onRemoveSpot(monitor.getItem().info); + } + }, + canDrag(props) { + if (props.size) { + //热点图上的热点可拖拽 + return true; + } else { + //测点树未布设的叶结点可拖拽 + return !props.children && props.info.deployed == false + } + }, +}; + +function collect(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + } +} + +class StationSpot extends React.Component { + constructor(props) { + super(props); + } + + + renderTreeTitle = () => { + const { isDragging, info } = this.props; + const { spotId, location, deployed } = info; + const opacity = isDragging ? 0.4 : 1; + return ( + + + + {location.length >= 12 ? location.substring(0, 12) + "..." : location} + + {deployed ? : null} + + + ); + }; + + onRemoveSpot = () => { + + const { onRemoveSpot, info } = this.props; + if (onRemoveSpot) { + onRemoveSpot(info); + } + }; + + renderHotspot = () => { + const { info, size } = this.props; + const { key, location, x, y, screenWidth, screenHeight } = info; + const { width, height } = size; + let style = { + position: 'absolute', + left: width * x / screenWidth, + top: height * y / screenHeight, + cursor: 'move' + }; + + return + +
+
+
+
+
+ }; + + render() { + const { connectDragSource, size } = this.props; + return connectDragSource( + size ? this.renderHotspot() : this.renderTreeTitle() + ); + } +} + +export default connect()(DragSource('stationSpot', stationSource, collect)(StationSpot)); diff --git a/web/client/src/sections/projectRegime/components/pointDeploy/style.css b/web/client/src/sections/projectRegime/components/pointDeploy/style.css new file mode 100644 index 0000000..f93610e --- /dev/null +++ b/web/client/src/sections/projectRegime/components/pointDeploy/style.css @@ -0,0 +1,91 @@ +.station-tree-node-normal{ + margin-right: 6px; + padding: 0 4px; + height: 15px; + line-height: 15px; + background: #108ee9; + color: #fff; + font-size: 12px; + text-align: center; +} + +.icon .tip{ + margin-left:10px; + -webkit-transition:opacity 0.1s 0.2s; + opacity:0; + pointer-events:none; +} +.icon:hover .tip{ + -webkit-transition:opacity 0.2s; + opacity:1; + pointer-events:auto; +} +.icon .tip:hover{ + -webkit-transition:none; +} + + +:global(.no-card-border>.ant-card-head){ + border: none; +} + +.cardCoverBox{ + position: absolute; + top: 50px; + left: 0; + right: 0; + bottom: 0; +} +.cardCover{ + display: flex; + flex-direction: column; + justify-content: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + background-color: rgba(240 , 240, 240, 0.9); + z-index: 2; +} +.cardCover .btnCell{ + text-align: center; + margin: 25px; +} + +.cardFootCover{ + display: flex; + justify-content: space-around; + align-items: center; + position: absolute; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.9); + z-index: 1; + opacity: 0; +} +.cardFootCover:hover{ + opacity: 1; +} +.deleteBox{ + margin: 0 40px; +} +.deleteBox h3{ + text-align: center; + margin-bottom: 5px; +} +.deleteBox .btnRow{ + display: flex; + justify-content: space-around; + margin-top: 10px; + width: 100%; +} +.loadingBox{ + width: 100%; + margin-bottom: 50px; + text-align: center; + padding: 50px 0; +} diff --git a/web/client/src/sections/projectRegime/containers/index.js b/web/client/src/sections/projectRegime/containers/index.js index 8dee879..4393996 100644 --- a/web/client/src/sections/projectRegime/containers/index.js +++ b/web/client/src/sections/projectRegime/containers/index.js @@ -4,6 +4,6 @@ import QrCode from './qrCode' import Information from './information' import Point from './point' +import PointDeploy from './pointDeploy/default' - -export { QrCode, Information, Point }; +export { QrCode, Information, Point, PointDeploy }; \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/containers/information.js b/web/client/src/sections/projectRegime/containers/information.js index f5936b0..aa9eea0 100644 --- a/web/client/src/sections/projectRegime/containers/information.js +++ b/web/client/src/sections/projectRegime/containers/information.js @@ -95,7 +95,7 @@ const Information = (props) => { key: 'operation', render: (text, record, index) => { return ( -
+
*/} +
) } @@ -228,7 +231,7 @@ const Information = (props) => { ) } -function mapStateToProps (state) { +function mapStateToProps(state) { const { auth, global } = state; return { user: auth.user, diff --git a/web/client/src/sections/projectRegime/containers/pointDeploy/default.js b/web/client/src/sections/projectRegime/containers/pointDeploy/default.js new file mode 100644 index 0000000..fa3f90f --- /dev/null +++ b/web/client/src/sections/projectRegime/containers/pointDeploy/default.js @@ -0,0 +1,384 @@ +'use strict' + +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { findDOMNode } from 'react-dom'; +import Heatmap from '../../components/pointDeploy/heatmap'; +import { DragDropContext } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; +import { Layout, Tree, Button, Input, Popconfirm, message, Spin } from 'antd' +const { Content, Sider } = Layout; +const Search = Input.Search; +const TreeNode = Tree.TreeNode; +import StationSpot from '../../components/pointDeploy/station-spot'; +import './deploy-style.less'; +import { getProjectGraph, deleteGraph, getDeployPoints, setDeployPoints } from '../../actions/graph'; +import UploadImgModal from './upload-img-modal'; +import PerfectScrollbar from 'perfect-scrollbar'; + +class ConfigPlanarGraph extends Component { + constructor(props) { + super(props); + this.ps = null; + this.state = { + uploadImgModal: '', + searchValue: '', + deployState: -1, //0未布,1已布,-1全部 + filteredSpots: [], + spots: [], + dataHasChanged: false, + } + this.projectId = props?.match?.params?.id + } + + componentDidUpdate() { + let ele = document.getElementById('security-spots-scroller'); + if (ele) { + this.ps = new PerfectScrollbar(ele); + } + } + + componentDidMount() { + this.getData(); + } + + getData = () => { + this.props.dispatch(getProjectGraph(this.projectId)).then(_ => { + if (_.success) { + let graph = _.payload.data; + if (graph) {//有图片 + this.props.dispatch(getDeployPoints(graph.id));//获取平面图点位分布 + } else { + this.setSpotsState([]); + } + } + }); + } + + componentWillReceiveProps(nextProps) { + const { projectDeployPoints } = nextProps; + if (projectDeployPoints && projectDeployPoints != this.props.projectDeployPoints) { + this.setSpotsState(projectDeployPoints); + } + } + + setSpotsState = (projectDeployPoints) => { + let deployedSpotsMap = new Map(); + projectDeployPoints.setedPoints?.forEach(s => { + deployedSpotsMap.set(s.pointId, s); + }); + let tempData = []; + projectDeployPoints.allPoints?.map(m => { + let x = null, y = null, screenH = null, screenW = null; + let deployed = false; + let station = deployedSpotsMap.get(m.id); + if (station) { + let p = JSON.parse(station.position); + x = p.x; + y = p.y; + screenH = p.screenHeight; + screenW = p.screenWidth; + deployed = true; + } + tempData.push({ + groupId: 1, + groupName: '全部', + pointId: m.id, + location: m.name, + x: x, + y: y, + screenHeight: screenH, + screenWidth: screenW, + deployed: deployed, + }) + }); + this.setState({ + spots: tempData, + filteredSpots: tempData, + }); + } + + onAddImgClick = () => { + this.openPcrModal(null) + }; + + editHandler = (heatmap) => { + this.openPcrModal(heatmap) + }; + + openPcrModal = (heatmap) => { + const { dispatch } = this.props; + this.setState({ + uploadImgModal: , + }) + } + + closeUploadPointsImgModal = () => { + this.setState({ uploadImgModal: '' }) + }; + + onDeploySpot = (spot) => { + const { pictureInfo, clientHeight, clientWidth } = this.props; + const { spots, deployState, searchValue, partsSpots } = this.state; + const that = this; + let h = clientHeight / 1.3; + let w = clientWidth / 1.3; + function dealPosition(spot, item) { + if (spot.deployed) { + item.x = spot.rect.x + spot.move.x; + item.y = spot.rect.y + spot.move.y; + item.screenHeight = h; + item.screenWidth = w; + } else { + const boundingClientRect = findDOMNode(that.refs.heatmapComponent).getBoundingClientRect(); + item.x = spot.spotInlist.x + spot.move.x - boundingClientRect.left; + item.y = spot.spotInlist.y + spot.move.y - boundingClientRect.top; + item.screenHeight = h; + item.screenWidth = w; + item.deployed = true; + } + } + if (pictureInfo) { + let tempSpots = Object.assign([], spots); + if (spot.info.pointId) { + tempSpots.forEach(item => { + if (item.pointId == spot.info.pointId) { + dealPosition(spot, item) + } + }); + } + + this.setState({ spots: tempSpots }); + this.filterSpots(deployState, searchValue); + this.setState({ changedTreeNodeKey: spot.info.key, dataHasChanged: true }); + } + }; + + filterSpots = (deployState, searchValue) => { + let deploySpots = this.state.spots; + if (deployState != -1) { + deploySpots = this.state.spots.filter(s => s.deployed == (deployState == 1 ? true : false)); + } + + let searchSpots = deploySpots; + if (searchValue.trim().length > 0) { + searchSpots = deploySpots.filter(s => s.location.indexOf(searchValue) >= 0); + } + + this.setState({ + searchValue, + deployState, + filteredSpots: searchSpots + }); + }; + + onSearch = (searchValue) => { + this.filterSpots(this.state.deployState, searchValue); + }; + handleStateChange = (deployState) => { + this.filterSpots(deployState, this.state.searchValue); + }; + + onRemoveSpot = (spot) => { + const { pictureInfo } = this.props; + const { spots, deployState, searchValue, partsSpots } = this.state; + if (pictureInfo) { + let tempSpots; + if (spot.pointId) { + tempSpots = Object.assign([], spots); + tempSpots.forEach(item => { + if (item.pointId == spot.pointId) { + item.x = null; + item.y = null; + item.screenWidth = null; + item.screenHeight = null; + item.deployed = false; + } + }); + tempSpots = tempSpots.concat(partsSpots) + } else { + tempSpots = Object.assign([], partsSpots); + tempSpots.forEach(item => { + if (item.partId == spot.partId) { + item.x = null; + item.y = null; + item.screenWidth = null; + item.screenHeight = null; + item.deployed = false; + } + }); + tempSpots = tempSpots.concat(spots) + } + this.filterSpots(deployState, searchValue); + this.setState({ changedTreeNodeKey: spot.key, dataHasChanged: true }); + } + }; + + loop = (data) => { + if (!data || data.length == 0) return; + + const treeNodes = []; + data.forEach((item) => { + let title = ; + if (item.children) { + treeNodes.push( + + {this.loop(item.children)} + + ); + } else { + let titleProps = { + info: item, + children: item.children, + onRemoveSpot: this.onRemoveSpot, + }; + //性能优化,减少组件渲染 + if (this.state.changedTreeNodeKey == item.key) titleProps.key = Math.random(); + let nodeTitle = ; + treeNodes.push(); + } + }); + return treeNodes; + }; + + formatTreeSource = (data) => { + if (!data || data.length == 0) return; + let tempGroups = new Map(); + data.map(item => { + if (tempGroups.has(item.groupId)) { + let groupChildren = tempGroups.get(item.groupId).children; + item.key = `0-${item.groupId}-${item.pointId}`; + groupChildren.set(item.pointId, item); + } else { + tempGroups.set(item.groupId, { + 'key': `0-${item.groupId}`, + 'location': item.groupName, + 'groupId': item.groupId, + 'children': new Map(), + }); + let groupChildren = tempGroups.get(item.groupId).children; + item.key = `0-${item.groupId}-${item.pointId}`; + groupChildren.set(item.pointId, item); + } + }); + return tempGroups; + }; + + onSaveClick = () => { + this.saveHotspots(this.state.spots); + }; + + saveHotspots = (data) => { + const { pictureInfo } = this.props; + let postData = data.filter(s => s.x != null && s.y != null).map(item => { + const { x, y, screenWidth, screenHeight } = item; + let relativeX = parseFloat((x / screenWidth).toFixed(4)); + let relativeY = parseFloat((y / screenHeight).toFixed(4)); + if (item.pointId) { + return ({ + pointId: item.pointId, + position: { x, y, screenWidth, screenHeight, relativeX, relativeY } + }) + } + }); + this.props.dispatch(setDeployPoints(pictureInfo.id, { "spots": postData })).then(res => { + message.success(res.payload.message); + this.setState({ dataHasChanged: false, deployState: -1 }, () => { + this.props.dispatch(getDeployPoints(pictureInfo.id));//获取平面图点位分布 + }); + }); + }; + + render() { + const { pictureInfo, clientHeight, clientWidth } = this.props; + const { deployState, spots, filteredSpots, dataHasChanged } = this.state; + const treeDataSource = this.formatTreeSource(filteredSpots); + let h = clientHeight / 1.3; + let w = clientWidth / 1.3; + return (
+ + +
+ + + +
+ this.onSearch(e.target.value)} + /> +
+
+ { + treeDataSource ? +
+ + {this.loop(treeDataSource)} +
:
暂无点位
+ } +
+ + + {spots && pictureInfo?.graph ? + s.deployed == true)} + onRemoveSpot={this.onRemoveSpot} + onDeploySpot={this.onDeploySpot} + /> + :
+ 暂无热点图 +
+ } +
+
说明:拖拽点位进行布设,拖出画布移除点位。
+ { + pictureInfo ? +
+ { + this.props.dispatch(deleteGraph(pictureInfo.id)).then(_ => { + this.getData(); + }) + }}> + + + + +
+ : +
+ +
+ } +
+
+
+
+ {this.state.uploadImgModal} +
) + } +} +function mapStateToProps(state) { + const { global, projectGraph, projectDeployPoints } = state; + return { + pictureInfo: projectGraph.data, + projectDeployPoints: projectDeployPoints.data, + clientHeight: global.clientHeight, + clientWidth: global.clientWidth + }; +} + +export default connect(mapStateToProps)(DragDropContext(HTML5Backend)(ConfigPlanarGraph)); \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/containers/pointDeploy/deploy-style.less b/web/client/src/sections/projectRegime/containers/pointDeploy/deploy-style.less new file mode 100644 index 0000000..c61a5f1 --- /dev/null +++ b/web/client/src/sections/projectRegime/containers/pointDeploy/deploy-style.less @@ -0,0 +1,114 @@ +.search-panel { + text-align: center; +} + +.search-panel input { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.spots-opr-content { + padding: 0; + margin: 0; + position: relative; +} + +.opr-tip-row { + display: flex; + margin-top: 10px; + justify-content: space-between; +} + +.opr-button { + + .ant-btn-disabled, + .ant-btn.disabled, + .ant-btn[disabled] { + //按钮 禁用 + background-color: #3198F7; + } +} + +.graph-cfg-btn { + color: #fff; + background-color: #3198F7; + border-color: #3198F7; +} + +//蓝色按钮 鼠标悬浮 +.graph-cfg-btn:hover, +.graph-cfg-btn:focus { + //color: #fff; + background-color: #3198F7; + border-color: #3198F7; +} + +.patrolLayout { + margin-top: 20px; + + .ant-layout { + background-color: transparent; + } + + .ant-input:hover, + .ant-input:focus { + border-color: #fff; + } +} + +.equip-tree { + + //测点 树结构 + .ant-tree.ant-tree-show-line li span.ant-tree-switcher { + //展开, 关闭按钮 + color: #fff; + } + + .ant-tree li .ant-tree-node-content-wrapper { + //文字白色 + color: #fff; + } + + .ant-tree.ant-tree-show-line li:not(:last-child)::before { + //不显示竖线 + width: 0px; + border: 0px; + } + + .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected { + background-color: #98e5f381; + } + + .ant-tree-node-content-wrapper:hover, + .ant-tree-node-content-wrapper:focus { + //文字鼠标悬浮 背景色 蓝色 + background-color: #3198F7 !important; + } +} + +//查询按钮 +.ant-input-group-addon { + background-color: transparent; + + .ant-btn { + background-color: transparent !important; + } + + .ant-btn-primary { + float: right; + width: 90px; + height: 45px; + margin: 5px 0px 0px 10px; + background-color: #3198f7; + border-color: #3198f7; + } + + .ant-btn-primary:hover { + border-color: #3198f7; + } + + .ant-btn-primary:focus { + border-color: #3198f7; + } +} \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/containers/pointDeploy/upload-img-modal.js b/web/client/src/sections/projectRegime/containers/pointDeploy/upload-img-modal.js new file mode 100644 index 0000000..8917fe4 --- /dev/null +++ b/web/client/src/sections/projectRegime/containers/pointDeploy/upload-img-modal.js @@ -0,0 +1,94 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import Uploads from '$components/Uploads'; +import { Input, Modal, Form, Button, message, Select } from 'antd'; +import { getProjectGraph, createGraph, updateGraph } from '../../actions/graph'; + +const DisclosureModal = (props) => { + const { dispatch, onCancel, projectId, pictureInfo, getData } = props; + let files = pictureInfo ? [{ storageUrl: pictureInfo.graph }] : [] + const [form] = Form.useForm(); + const [editUrl, setEditUrl] = useState(files); + //初始化表单数据 + const getinitialValues = () => { + if (pictureInfo) { + return { files: 1 }; + } + return {} + }; + + useEffect(() => { + }, []); + + const handleOk = () => { + form.validateFields().then(values => { + let data = { + projectId: projectId, + graph: editUrl[0]?.storageUrl, + } + if (pictureInfo) {//更新 + dispatch(updateGraph(pictureInfo.id, data)).then(_ => { + getData() + }); + } else {//新增 + dispatch(createGraph(data)).then(_ => { + getData(); + }); + } + onCancel() + }) + } + + const vsjunct = (params) => { + if (params.length) { + let appendix = [] + for (let p of params) { + appendix.push({ + fName: p.name, + size: p.size, + fileSize: p.size, + storageUrl: p.storageUrl,//必须有storageUrl + }) + } + setEditUrl(appendix) + } else { + setEditUrl([]) + } + } + + return ( + +
+ + + + +
说明:附件格式为png、jpeg、jpg,大小不超过10MB
+
+
+
+ ) +} + +function mapStateToProps(state) { + const { auth, global } = state; + return { + user: auth.user, + actions: global.actions, + } +} + +export default connect(mapStateToProps)(DisclosureModal); \ No newline at end of file diff --git a/web/client/src/sections/projectRegime/routes.js b/web/client/src/sections/projectRegime/routes.js index e722522..d553570 100644 --- a/web/client/src/sections/projectRegime/routes.js +++ b/web/client/src/sections/projectRegime/routes.js @@ -1,5 +1,5 @@ 'use strict'; -import { Information, QrCode, Point } from './containers'; +import { Information, QrCode, Point, PointDeploy } from './containers'; export default [{ type: 'inner', @@ -13,13 +13,23 @@ export default [{ key: 'information', breadcrumb: '结构物基础信息管理', component: Information, - childRoutes: [ { - path: '/:id', - key: ':id', + childRoutes: [{ + path: '/:id', + key: ':id', + //component: null, + breadcrumb: '结构物', + childRoutes: [{ + path: '/point', + key: 'point', component: Point, breadcrumb: '点位', - }, - ] + }, { + path: '/deploy', + key: 'deploy', + component: PointDeploy, + breadcrumb: '布设', + }] + }] }, { path: '/qrCode', key: 'qrCode', diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 8232b71..79ad0a1 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -129,6 +129,13 @@ export const ApiTable = { //项目状态配置 editProjectStatus: 'project/status', + //工地平面图 + getProjectGraph: 'project/{projectId}/planarGraph', + createGraph: 'planarGraph/add', + updateGraph: 'planarGraph/{id}/modify', + deleteGraph: 'project/graph/{id}', + getDeployPoints: 'picture/{pictureId}/deploy/points', + setDeployPoints: 'set/picture/{pictureId}/deploy/points', }; export const RouteTable = { diff --git a/web/package.json b/web/package.json index 1899540..2cd6963 100644 --- a/web/package.json +++ b/web/package.json @@ -95,6 +95,8 @@ "npm": "^7.20.6", "qrcode": "^1.5.1", "qs": "^6.10.1", + "react-dnd": "^7", + "react-dnd-html5-backend": "^7", "react-color": "^2.19.3", "react-router-breadcrumbs-hoc": "^4.0.1", "react-sortable-hoc": "^2.0.0", From 18d4346775ac1a1f605f1b1b911a92ce1f91c469 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 8 Mar 2023 09:00:52 +0800 Subject: [PATCH 5/6] =?UTF-8?q?(*)=E7=82=B9=E4=BD=8D=20=E5=8A=A0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BC=96=E5=8F=B7=E5=92=8C=E5=9E=8B=E5=8F=B7=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/sections/projectRegime/components/pointModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/client/src/sections/projectRegime/components/pointModel.js b/web/client/src/sections/projectRegime/components/pointModel.js index ade7c55..da2aa37 100644 --- a/web/client/src/sections/projectRegime/components/pointModel.js +++ b/web/client/src/sections/projectRegime/components/pointModel.js @@ -164,14 +164,14 @@ const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, q rules={[{ required: true, message: '请输入描述内容', },]}>