Compare commits
3 Commits
91fbc45086
...
03b2e0bc85
Author | SHA1 | Date |
---|---|---|
wuqun | 03b2e0bc85 | 2 years ago |
wuqun | 8aae0a4408 | 2 years ago |
wuqun | 17e0ee80c6 | 2 years ago |
19 changed files with 1324 additions and 12 deletions
@ -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 |
|||
}; |
@ -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; |
|||
}; |
@ -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; |
|||
}; |
@ -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); |
|||
}; |
@ -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); |
@ -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: '结构物平面图点位布设' }, |
|||
}); |
|||
} |
@ -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 => <StationSpot key={s.sensorId} info={s} |
|||
size={{ "width": width, "height": height }} |
|||
onRemoveSpot={onRemoveSpot} />); |
|||
} |
|||
|
|||
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( |
|||
<div id="dragTarget" style={targetStyle}> |
|||
{this.renderSpots()} |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default DropTarget('stationSpot', heatmapTarget, collect)(Heatmap); |
|||
|
@ -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 ( |
|||
<span style={{ lineHeight: '15px', opacity }} className={Style['icon']}> |
|||
<span key={spotId}> |
|||
<Tooltip title={location}> |
|||
<span className={deployed == false ? Style['station-tree-node-normal'] : null}>{location.length >= 12 ? location.substring(0, 12) + "..." : location}</span> |
|||
</Tooltip> |
|||
{deployed ? <MinusOutlined type="minus-circle-o" className={Style['tip']} onClick={this.onRemoveSpot} /> : null} |
|||
</span> |
|||
</span> |
|||
); |
|||
}; |
|||
|
|||
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 <span style={style}> |
|||
<Tooltip title={location}> |
|||
<div style={{ height: 24, width: 24, borderRadius: '100%', backgroundColor: 'rgba(16,142,233,0.2)', padding: '5px' }}> |
|||
<div style={{ height: 14, width: 14, borderRadius: '100%', backgroundColor: '#108ee9', boxShadow: '0 0 10px #108ee9' }}></div> |
|||
</div> |
|||
</Tooltip> |
|||
</span> |
|||
}; |
|||
|
|||
render() { |
|||
const { connectDragSource, size } = this.props; |
|||
return connectDragSource( |
|||
size ? this.renderHotspot() : this.renderTreeTitle() |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default connect()(DragSource('stationSpot', stationSource, collect)(StationSpot)); |
@ -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; |
|||
} |
@ -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: <UploadImgModal |
|||
dispatch={dispatch} |
|||
projectId={this.projectId} |
|||
pictureInfo={heatmap} |
|||
onCancel={this.closeUploadPointsImgModal} |
|||
getData={this.getData} |
|||
/>, |
|||
}) |
|||
} |
|||
|
|||
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 = <StationSpot info={item} children={item.children} />; |
|||
if (item.children) { |
|||
treeNodes.push( |
|||
<TreeNode key={item.key} title={title}> |
|||
{this.loop(item.children)} |
|||
</TreeNode> |
|||
); |
|||
} else { |
|||
let titleProps = { |
|||
info: item, |
|||
children: item.children, |
|||
onRemoveSpot: this.onRemoveSpot, |
|||
}; |
|||
//性能优化,减少组件渲染
|
|||
if (this.state.changedTreeNodeKey == item.key) titleProps.key = Math.random(); |
|||
let nodeTitle = <StationSpot {...titleProps} />; |
|||
treeNodes.push(<TreeNode key={item.key} title={nodeTitle} />); |
|||
} |
|||
}); |
|||
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 (<div className='patrolLayout'> |
|||
<Layout> |
|||
<Sider width={230} style={{ background: 'transparent', height: 400 }}> |
|||
<div className='search-panel'> |
|||
<Button className={deployState == -1 ? 'btn-spots-filter' : 'btn-default'} style={{ borderRadius: 0 }} onClick={() => this.handleStateChange(-1)}>全部</Button> |
|||
<Button className={deployState == 1 ? 'btn-spots-filter' : 'btn-default'} style={{ borderRadius: 0 }} onClick={() => this.handleStateChange(1)}>已布</Button> |
|||
<Button className={deployState == 0 ? 'btn-spots-filter' : 'btn-default'} style={{ borderRadius: 0 }} onClick={() => this.handleStateChange(0)}>未布</Button> |
|||
<div style={{ marginTop: -1 }}> |
|||
<Search |
|||
placeholder={"请输入名称关键字搜索"} |
|||
style={{ borderRadius: 0 }} |
|||
onChange={e => this.onSearch(e.target.value)} |
|||
/> |
|||
</div> |
|||
</div> |
|||
{ |
|||
treeDataSource ? |
|||
<div id='security-spots-scroller' style={{ position: 'relative', height: 600 }}> |
|||
<Tree className='equip-tree' showLine defaultExpandAll={true}> |
|||
{this.loop(treeDataSource)} |
|||
</Tree> </div> : <div style={{ textAlign: 'center' }}>暂无点位</div> |
|||
} |
|||
</Sider> |
|||
<Layout> |
|||
<Content className='spots-opr-content' style={{ width: w }}> |
|||
{spots && pictureInfo?.graph ? |
|||
<Heatmap key={Math.random()} |
|||
ref="heatmapComponent" |
|||
height={h} |
|||
width={w} |
|||
image={pictureInfo?.graph} |
|||
spots={spots.filter(s => s.deployed == true)} |
|||
onRemoveSpot={this.onRemoveSpot} |
|||
onDeploySpot={this.onDeploySpot} |
|||
/> |
|||
: <div style={{ border: '1px dashed #999', width: w, height: h, paddingTop: clientHeight * 0.4, textAlign: 'center' }}> |
|||
暂无热点图 |
|||
</div> |
|||
} |
|||
<div className='opr-tip-row'> |
|||
<div>说明:拖拽点位进行布设,拖出画布移除点位。</div> |
|||
{ |
|||
pictureInfo ? |
|||
<div className='opr-button'> |
|||
<Popconfirm |
|||
title='确认删除该结构物布设图?(已布点位将同步删除)' |
|||
position='top' |
|||
onConfirm={() => { |
|||
this.props.dispatch(deleteGraph(pictureInfo.id)).then(_ => { |
|||
this.getData(); |
|||
}) |
|||
}}> |
|||
<Button className='graph-cfg-btn'>删除图片</Button> |
|||
</Popconfirm> |
|||
<Button className='graph-cfg-btn' onClick={() => this.editHandler(pictureInfo)} style={{ marginLeft: 15 }}>修改图片</Button> |
|||
<Button className='graph-cfg-btn' type='primary' onClick={this.onSaveClick} disabled={!dataHasChanged} style={{ marginLeft: 15 }}>完成</Button> |
|||
</div> |
|||
: |
|||
<div className='opr-button'> |
|||
<Button className='graph-cfg-btn' type='primary' onClick={this.onAddImgClick}>添加布设图</Button> |
|||
</div> |
|||
} |
|||
</div> |
|||
</Content> |
|||
</Layout> |
|||
</Layout> |
|||
{this.state.uploadImgModal} |
|||
</div>) |
|||
} |
|||
} |
|||
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)); |
@ -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; |
|||
} |
|||
} |
@ -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 ( |
|||
<Modal title='添加布设图' visible={true} destroyOnClose |
|||
onCancel={onCancel} onOk={handleOk}> |
|||
<Form form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 17 }} initialValues={getinitialValues()}> |
|||
<Form.Item label="布设图" name='files' |
|||
rules={[{ required: true, message: '请上传布设图' }]}> |
|||
<Uploads |
|||
className='upload' |
|||
listType='card' |
|||
uploadType='project' |
|||
maxFilesNum={1} |
|||
maxFileSize={10} |
|||
isQiniu={true} |
|||
onChange={vsjunct} |
|||
fileTypes={["png", "jpeg", "jpg"]} |
|||
value={editUrl} |
|||
defaultValue={editUrl} |
|||
/> |
|||
</Form.Item> |
|||
<Form.Item style={{ paddingLeft: '17%' }}> |
|||
<div style={{ color: '#999', width: 460 }}>说明:附件格式为png、jpeg、jpg,大小不超过10MB</div> |
|||
</Form.Item> |
|||
</Form> |
|||
</Modal> |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(DisclosureModal); |
Loading…
Reference in new issue