peng.peng 2 years ago
parent
commit
9173ea2bf2
  1. 6
      api/Dockerfile
  2. 256
      api/app/lib/controllers/patrolManage/patrolRecord.js
  3. 2
      api/app/lib/controllers/patrolManage/patrolTemplate.js
  4. 6
      api/app/lib/index.js
  5. 15
      api/app/lib/routes/patrolManage/patrolRecord.js
  6. 33
      jenkinsfile_api
  7. 33
      jenkinsfile_web
  8. 5
      weapp/app.json
  9. 2
      weapp/custom-tab-bar/index.js
  10. 3
      weapp/images/arrow_right_blue.svg
  11. BIN
      weapp/images/calendar.png
  12. BIN
      weapp/images/calendar_card_bg.png
  13. BIN
      weapp/images/calendar_icon.png
  14. BIN
      weapp/images/device.png
  15. BIN
      weapp/images/down1.png
  16. BIN
      weapp/images/edit.png
  17. BIN
      weapp/images/fault_icon.png
  18. BIN
      weapp/images/right_bg.png
  19. BIN
      weapp/images/right_card_bg.png
  20. BIN
      weapp/images/right_icon.png
  21. BIN
      weapp/images/shape1.png
  22. BIN
      weapp/images/shape2.png
  23. BIN
      weapp/images/shape3.png
  24. 168
      weapp/package/deviceBigdataGraph/deviceBigdataGraph.js
  25. 11
      weapp/package/deviceBigdataGraph/deviceBigdataGraph.json
  26. 63
      weapp/package/deviceBigdataGraph/deviceBigdataGraph.wxml
  27. 91
      weapp/package/deviceBigdataGraph/deviceBigdataGraph.wxss
  28. 120
      weapp/package/deviceBigdataGraph/statusDetail/statusDetail.js
  29. 10
      weapp/package/deviceBigdataGraph/statusDetail/statusDetail.json
  30. 26
      weapp/package/deviceBigdataGraph/statusDetail/statusDetail.wxml
  31. 76
      weapp/package/deviceBigdataGraph/statusDetail/statusDetail.wxss
  32. 143
      weapp/package/inspectionInput/inspectionInput.js
  33. 121
      weapp/package/inspectionInput/inspectionInput.wxml
  34. 39
      weapp/package/polling/inspectionRecordDetail/inspectionRecordDetail.js
  35. 110
      weapp/package/polling/inspectionRecordDetail/inspectionRecordDetail.wxml
  36. 7
      weapp/package/polling/polling.js
  37. 4
      weapp/package/polling/polling.json
  38. 5
      weapp/package/polling/polling.wxml
  39. 543
      weapp/package/report/report.js
  40. 25
      weapp/package/report/report.json
  41. 96
      weapp/package/report/report.wxml
  42. 131
      weapp/package/report/report.wxss
  43. 94
      weapp/package/riskManagement/riskCalendar/riskCalendar.js
  44. 8
      weapp/package/riskManagement/riskCalendar/riskCalendar.json
  45. 78
      weapp/package/riskManagement/riskCalendar/riskCalendar.wxml
  46. 104
      weapp/package/riskManagement/riskCalendar/riskCalendar.wxss
  47. 76
      weapp/package/riskManagement/riskManagement.js
  48. 54
      weapp/package/riskManagement/riskManagement.wxml
  49. 45
      weapp/package/riskManagement/riskManagement.wxss
  50. 3
      weapp/package/subSystem/subSystem.js
  51. 34
      weapp/package/subSystem/subSystem.wxml
  52. 44
      weapp/package/subSystem/subSystem.wxss
  53. 15
      weapp/package/troubleshooting/shootingForm/index.js
  54. 11
      weapp/package/troubleshooting/shootingForm/index.wxml
  55. 8
      weapp/pages/home/home.js
  56. 4
      weapp/pages/home/home.wxml
  57. 2
      weapp/pages/home/home.wxss
  58. 7
      weapp/pages/overview/overview.js
  59. 4
      weapp/pages/workbench/workbench.js
  60. 5
      weapp/project.config.json
  61. 22
      weapp/utils/getApiUrl.js
  62. 8
      web/Dockerfile
  63. BIN
      web/client/assets/images/menu/device.png
  64. 4
      web/client/src/app.js
  65. 56
      web/client/src/sections/deviceManage/actions/device.js
  66. 7
      web/client/src/sections/deviceManage/actions/index.js
  67. 213
      web/client/src/sections/deviceManage/components/importDevicesModal.js
  68. 111
      web/client/src/sections/deviceManage/components/modelModal.js
  69. 0
      web/client/src/sections/deviceManage/constants/index.js
  70. 254
      web/client/src/sections/deviceManage/containers/deviceManage.js
  71. 5
      web/client/src/sections/deviceManage/containers/index.js
  72. 0
      web/client/src/sections/deviceManage/containers/style.css
  73. 0
      web/client/src/sections/deviceManage/containers/style.less
  74. 15
      web/client/src/sections/deviceManage/index.js
  75. 13
      web/client/src/sections/deviceManage/nav-item.js
  76. 5
      web/client/src/sections/deviceManage/reducers/index.js
  77. 13
      web/client/src/sections/deviceManage/routes.js
  78. 33
      web/client/src/sections/issueHandle/components/isuue-handle-mdal.js
  79. 2
      web/client/src/sections/issueHandle/containers/patrolRecord.js
  80. 89
      web/client/src/sections/patrolManage/containers/patrolRecord.js
  81. 12
      web/client/src/sections/projectRegime/components/pointModel.js
  82. 32
      web/client/src/sections/projectRegime/components/projectAddModel.js
  83. 7
      web/client/src/sections/projectRegime/containers/point.js
  84. 5
      web/client/src/utils/webapi.js

6
api/Dockerfile

@ -1,16 +1,16 @@
FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12-dev as builder
COPY . /var/app
COPY ./api/ /var/app
WORKDIR /var/app
EXPOSE 8080
RUN npm config set registry=http://10.8.30.22:7000
RUN npm config set registry=https://nexus.ngaiot.com/repository/fs-npm/
RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json
RUN npm cache clean -f
RUN rm -rf package-lock.json
RUN npm install --registry http://10.8.30.22:7000
RUN npm install --registry https://nexus.ngaiot.com/repository/fs-npm/
FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12

256
api/app/lib/controllers/patrolManage/patrolRecord.js

@ -2,6 +2,7 @@
const moment = require("moment");
async function findPatrolRecord(ctx, next) {
let rslt = [];
let error = { name: 'FindError', message: '获取巡检记录失败' };
@ -442,7 +443,254 @@ function getSubSystemPatrolAbout(opts) {
}
}
}
//
/**
* 查询故障风险统计
* @param structures {String} 结构物id, example: 1,2,3
*/
function getPatrolRecordStatistic(opts) {
return async function (ctx, next) {
try {
let rslt = {
monthAlarmCount: 0, // 本月上报风险
monthHandleCount: 0, // 本月处理风险
historyTrend: [], // 历史风险趋势
monthDeviceAlarm: [], // 设备故障统计
};
const models = ctx.fs.dc.models;
const sequelize = ctx.fs.dc.orm;
const { structures } = ctx.query;
const monthStartTime = moment().startOf("month").format('YYYY-MM-DD HH:mm:ss');
const historyStartTime = moment().startOf("month").subtract(11, 'months').format('YYYY-MM-DD HH:mm:ss');
const endTime = moment().endOf("month").format('YYYY-MM-DD HH:mm:ss');
const monthAlarm = await models.PatrolRecord.findAndCountAll({
where: {
inspectionTime: { $between: [monthStartTime, endTime] },
projectId: { $in: structures.split(',') },
alarm: true
},
include: [{
model: models.Project,
where: { type: '管廊' }
}],
})
rslt.monthAlarmCount = monthAlarm.count;
let abnormalDevice = [];
for (const r of monthAlarm.rows) {
if (Array.isArray(r.points.inspectContent)) {
for (const d of r.points.inspectContent) {
if (d.deviceName && d.alarm) {
let abnormalCount = 0, abnormalScore = 0, slight = 0, middle = 0, severity = 0, itemsCount = [];
const index = abnormalDevice.findIndex(e => e.deviceId === d.deviceId);
if (index !== -1) {
itemsCount = abnormalDevice[index].itemsCount;
slight = abnormalDevice[index].slight;
middle = abnormalDevice[index].middle;
severity = abnormalDevice[index].severity;
}
for (const item of d.checkItems) {
if (item.isNormal === false) {
abnormalCount += 1;
switch (item.level) {
case '轻微':
slight += 1;
abnormalScore += 1;
break;
case '中度':
middle += 1;
abnormalScore += 3;
break;
case '严重':
severity += 1;
abnormalScore += 5;
break;
default:
break;
}
const itemIndex = itemsCount.findIndex(i => i.name === item.name);
if (itemIndex === -1) {
itemsCount.push({ name: item.name, count: 1 });
} else {
itemsCount[itemIndex].count += 1;
}
}
}
if (index !== -1) {
abnormalDevice[index].abnormalCount += abnormalCount;
abnormalDevice[index].abnormalScore += abnormalScore;
abnormalDevice[index].itemsCount = itemsCount;
abnormalDevice[index].slight = slight;
abnormalDevice[index].middle = middle;
abnormalDevice[index].severity = severity;
} else {
abnormalDevice.push({
deviceId: d.deviceId, deviceName: d.deviceName, project: r.points.project.name,
abnormalCount, abnormalScore, itemsCount, slight, middle, severity,
})
}
}
}
}
}
rslt.monthDeviceAlarm = abnormalDevice;
rslt.monthHandleCount = await models.PatrolRecord.count({
where: {
inspectionTime: { $between: [monthStartTime, endTime] },
projectId: { $in: structures.split(',') },
alarm: true
},
include: [{
model: models.PatrolRecordIssueHandle,
where: { state: 6 } // 验收通过
}, {
model: models.Project,
where: { type: '管廊' }
}],
})
// rslt.historyTrend = await sequelize.query(
// `select to_char(inspection_time::DATE, 'YYYY-MM') as month, COUNT(*) as num from patrol_record INNER JOIN "project" ON "patrol_record"."project_id" = "project"."id" AND "project"."type" = '管廊' where inspection_time >= '${historyStartTime}' and inspection_time <= '${endTime}' group by month order by month;`
// );
const monthFn = sequelize.fn('to_char', sequelize.col('inspection_time'), 'YYYY-MM');
rslt.historyTrend = await models.PatrolRecord.findAll({
attributes: [
[monthFn, 'month'],
[sequelize.fn('COUNT', sequelize.col('*')), 'count'],
],
group: [monthFn],
order: [monthFn],
where: {
inspectionTime: { $between: [historyStartTime, endTime] },
projectId: { $in: structures.split(',') },
alarm: true
},
include: [{
attributes: [],
model: models.Project,
where: { type: '管廊' }
}],
})
ctx.status = 200;
ctx.body = rslt
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '查询故障风险统计失败' }
}
}
}
//根据子系统查询点位信息
function getPointInfo(opts) {
return async function (ctx, next){
try{
let rslt=[]
const models = ctx.fs.dc.models;
const {keywords}=ctx.query
let generalInclude = [{model:models.Project,where:{subType :{$like: `%${keywords}%`}}},{model:models.Device}]
rslt=await models.Point.findAll({
include:generalInclude
})
let userInfo = ctx.fs.api.userInfo;
rslt = rslt.filter(f => f)
if (userInfo.username != 'SuperAdmin') {
if (userInfo.structure) {
rslt = rslt.filter(s => userInfo.structure.find(x => x == s.project.userId))
} else {
rslt = []
}
}
ctx.status = 200;
ctx.body = rslt
}catch(error){
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '根据子系统查询点位信息失败' }
}
}
}
//根据结构物查询对应巡检计划的模板
function getTemplate(opts){
return async function (ctx, next){
try{
let rslt=[]
const models = ctx.fs.dc.models;
const {keywords}=ctx.query
rslt=await models.PatrolPlan.findAll({
include:[
{model:models.Project,
where:{subType:{$like: `%${keywords}%`}}},
{model:models.PatrolTemplate}]
})
let userInfo = ctx.fs.api.userInfo;
rslt = rslt.filter(f => f)
if (userInfo.username != 'SuperAdmin') {
if (userInfo.structure) {
rslt = rslt.filter(s => userInfo.structure.find(x => x == s.project.userId))
} else {
rslt = []
}
}
ctx.status = 200;
ctx.body = rslt
}catch(error){
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '根据结构物查询对应巡检计划的模板失败' }
}
}
}
//上报问题和发现问题接口
function reportQuest(opts){
return async function (ctx, next){
try{
const transaction = await ctx.fs.dc.orm.transaction();
const models = ctx.fs.dc.models;
const data = ctx.request.body;
let { patrolPlanId, inspectionTime, points, alarm, pointId, projectId } = data
const pointRecord = await models.PatrolRecord.findAll({
where: { pointId: pointId },
order: [['inspectionTime', 'desc']],
attributes: ['inspectionTime'],
});
const lastInspectionTime = pointRecord.length ? pointRecord[0].dataValues.inspectionTime : null;
const recordRes = await models.PatrolRecord.create(
// record
{
patrolPlanId: patrolPlanId,
lastInspectionTime,
inspectionTime,
points,
alarm,
pointId: pointId,
projectId
},{transaction}
);
if (alarm) {
await models.PatrolRecordIssueHandle.create({
patrolRecordId: recordRes.id,
state: 1,
}, { transaction });
}
await transaction.commit();
ctx.status = 204;
}catch(error){
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '上报问题失败' }
}
}
}
//根据子系统查询点位信息
// function getPointInfo(opts) {
// return async function (ctx, next){
// }
// }
Array.prototype.group = function (callback, thisArg = null) {
// 参数合法性判断
@ -473,5 +721,9 @@ module.exports = {
getPatrolRecordIssueHandleById,
addPatrolRecordIssueHandle,
editPatrolRecordIssueHandle,
getSubSystemPatrolAbout
getSubSystemPatrolAbout,
getPatrolRecordStatistic,
getPointInfo,
getTemplate,
reportQuest
}

2
api/app/lib/controllers/patrolManage/patrolTemplate.js

@ -16,7 +16,7 @@ async function getPatrolTemplate (ctx, next) {
model: models.CheckItems,
}, {
model: models.PatrolPlan,
attributes: ['name'],
attributes: ['structure_id','name'],
}]
};
if (id) {

6
api/app/lib/index.js

@ -111,4 +111,10 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
PointDevice.belongsTo(Device, { foreignKey: 'deviceId', targetKey: 'id' });
Device.hasMany(PointDevice, { foreignKey: 'deviceId', sourceKey: 'id' });
Device.belongsToMany(Point,{ through: PointDevice, foreignKey: 'deviceId', otherKey: 'pointId' })
Point.belongsToMany(Device,{ through: PointDevice, foreignKey: 'pointId', otherKey: 'deviceId' })
};

15
api/app/lib/routes/patrolManage/patrolRecord.js

@ -34,4 +34,19 @@ module.exports = function (app, router, opts) {
//子系统巡检记录
app.fs.api.logAttr['GET/patrolRecord/subSystemPatrolAbout'] = { content: '子系统查询巡检记录', visible: true };
router.get('/patrolRecord/subSystemPatrolAbout', patrolRecord.getSubSystemPatrolAbout(opts))
//故障风险管理-统计接口
app.fs.api.logAttr['GET/patrolRecord/statistic'] = { content: '故障风险统计', visible: true };
router.get('/patrolRecord/statistic', patrolRecord.getPatrolRecordStatistic(opts))
//点位信息
app.fs.api.logAttr['GET/patrolRecord/pointInfo'] = { content: '点位信息', visible: true };
router.get('/patrolRecord/pointInfo', patrolRecord.getPointInfo(opts))
//查询模板
app.fs.api.logAttr['GET/patrolRecord/getTemplate'] = { content: '查询模板', visible: true };
router.get('/patrolRecord/getTemplate', patrolRecord.getTemplate(opts))
//查询模板
app.fs.api.logAttr['POST/patrolRecord/reportQuest'] = { content: '上报问题', visible: true };
router.post('/patrolRecord/reportQuest', patrolRecord.reportQuest(opts))
};

33
jenkinsfile_api

@ -1,18 +1,25 @@
pipeline {
agent {
node{
label 'jnlp-slave'
}
}
podTemplate {
node('pod-templ-jenkins-slave-common') {
env.IMAGE_NAME = "${IOT_IMAGES_REGISTRY}/${IOT}/${JOB_NAME}"
env.IMAGE_NAME_SHORT = "${IOT}/${JOB_NAME}"
env.CODE_ADDR = "${GIT_ADDRESS}/free-sun/Inspection.git"
stage('Run shell') {
git branch: 'master', credentialsId: 'gitea-builder', url: "${CODE_ADDR}"
stages {
stage('巡检 api ......') {
steps {
buildName "#${BUILD_NUMBER} ~/smartcity/${JOB_NAME}:${IMAGE_VERSION}"
buildDescription "harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION}"
sh 'nerdctl build -t harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION} ./api'
sh 'nerdctl push harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION}'
container('image-builder') {
sh'''
pwd
ls -al
/kaniko/executor --context=${BUILD_WORKSPACE} --dockerfile=./api/Dockerfile --destination=${IMAGE_NAME}:${IMAGE_VERSION} --cache=false --cleanup
'''
}
buildName "${IMAGE_NAME_SHORT}:${IMAGE_VERSION}"
buildDescription "${IMAGE_NAME}:${IMAGE_VERSION}"
}
}
}

33
jenkinsfile_web

@ -1,18 +1,25 @@
pipeline {
agent {
node{
label 'jnlp-slave'
}
}
podTemplate {
node('pod-templ-jenkins-slave-common') {
env.IMAGE_NAME = "${IOT_IMAGES_REGISTRY}/${IOT}/${JOB_NAME}"
env.IMAGE_NAME_SHORT = "${IOT}/${JOB_NAME}"
env.CODE_ADDR = "${GIT_ADDRESS}/free-sun/Inspection.git"
stage('Run shell') {
git branch: 'master', credentialsId: 'gitea-builder', url: "${CODE_ADDR}"
stages {
stage('巡检 web ......') {
steps {
buildName "#${BUILD_NUMBER} ~/smartcity/${JOB_NAME}:${IMAGE_VERSION}"
buildDescription "harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION}"
sh 'nerdctl build -t harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION} ./web'
sh 'nerdctl push harbor.anxinyun.cn/smartcity/${JOB_NAME}:${IMAGE_VERSION}'
container('image-builder') {
sh'''
pwd
ls -al
/kaniko/executor --context=${BUILD_WORKSPACE} --dockerfile=./web/Dockerfile --destination=${IMAGE_NAME}:${IMAGE_VERSION} --cache=false --cleanup
'''
}
buildName "${IMAGE_NAME_SHORT}:${IMAGE_VERSION}"
buildDescription "${IMAGE_NAME}:${IMAGE_VERSION}"
}
}
}

5
weapp/app.json

@ -23,7 +23,10 @@
"pointsStatus/pointsStatus",
"subSystem/subSystem",
"riskManagement/riskManagement",
"riskManagement/riskCalendar/riskCalendar"
"riskManagement/riskCalendar/riskCalendar",
"deviceBigdataGraph/deviceBigdataGraph",
"deviceBigdataGraph/statusDetail/statusDetail",
"report/report"
]
}
],

2
weapp/custom-tab-bar/index.js

@ -9,7 +9,7 @@ Component({
let userRole = wx.getStorageSync('userRole');
// 0 表示普通用户 1表示管理员
console.log('userRole', userRole);
if (userRole && userRole.includes('管理')) {
if (userRole && userRole.includes('巡检')) {
this.setData({
list: getApp().globalData.managerList
})

3
weapp/images/arrow_right_blue.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" class="design-iconfont">
<path d="M8.918625,6.235875 C8.924625,6.22423828 8.93436328,6.21451172 8.939625,6.20248828 C9.04275,5.98798828 9.009,5.7255 8.826375,5.55298828 L4.68675,1.637625 C4.46098828,1.42423828 4.10513672,1.43436328 3.891375,1.65976172 C3.67798828,1.88551172 3.68775,2.24136328 3.91351172,2.45513672 L7.62788672,5.968875 L3.928125,9.53475 C3.704625,9.75036328 3.697875,10.1062383 3.91351172,10.330125 C4.02375,10.4448633 4.17113672,10.5022617 4.31851172,10.5022617 C4.45913672,10.5022617 4.59976172,10.4497617 4.70888672,10.3447617 L8.81101172,6.39113672 C8.81889844,6.38325 8.821875,6.37238672 8.82976172,6.36414844 C8.83577344,6.35852344 8.84213672,6.35438672 8.848125,6.34839844 C8.88,6.315 8.896875,6.27411328 8.918625,6.235875 L8.918625,6.235875 Z" fill-opacity=".65" fill="#1684FF" fill-rule="nonzero"/>
</svg>

After

Width:  |  Height:  |  Size: 895 B

BIN
weapp/images/calendar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
weapp/images/calendar_card_bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

BIN
weapp/images/calendar_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
weapp/images/device.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
weapp/images/down1.png

Binary file not shown.

BIN
weapp/images/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
weapp/images/fault_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

BIN
weapp/images/right_bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
weapp/images/right_card_bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
weapp/images/right_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
weapp/images/shape1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
weapp/images/shape2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

BIN
weapp/images/shape3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

168
weapp/package/deviceBigdataGraph/deviceBigdataGraph.js

@ -0,0 +1,168 @@
// package/riskManagement/riskCalendar/riskCalendar.js
import * as echarts from '../components/ec-canvas/echarts';
Page({
initECharts(option) {
this.ecComponent.init((canvas, width, height, dpr) => {
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr,
});
chart.setOption(option);
this.chart = chart;
return chart;
});
},
initDeviceECharts(option) {
this.ecDeviceComponent.init((canvas, width, height, dpr) => {
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr,
});
chart.setOption(option);
this.chart = chart;
return chart;
});
},
/**
* 页面的初始数据
*/
data: {
ec:{}
},
navigator(e) {
wx.navigateTo({
url: '/package/deviceBigdataGraph/statusDetail/statusDetail',
})
console.log('xxxxxx',e)
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const that = this
that.ecComponent = that.selectComponent('#mychart-dom-pie');
that.ecDeviceComponent=that.selectComponent('#mychart-device-pie');
var option = {
backgroundColor: "#ffffff",
legend: {
bottom: 10,
left: 'center',
},
series: [{
label: {
normal: {
fontSize: 14
}
},
type: 'pie',
center: ['50%', '50%'],
radius: ['20%', '40%'],
data: [{
name: '类型一',
value: 1
},
{
name: '类型二',
value: 2
},
{
name: '类型三',
value: 3
},
{
name: '类型四',
value: 4
}
]
}]
};
var optionDevice = {
backgroundColor: "#ffffff",
legend: {
bottom: 10,
left: 'center',
},
series: [{
label: {
normal: {
fontSize: 14
}
},
type: 'pie',
center: ['50%', '50%'],
radius: ['20%', '40%'],
data: [{
name: '正常',
value: 1
},
{
name: '未知',
value: 2
},
{
name: '异常',
value: 3
},
]
}]
};
that.initECharts(option);
that.initDeviceECharts(optionDevice);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

11
weapp/package/deviceBigdataGraph/deviceBigdataGraph.json

@ -0,0 +1,11 @@
{
"navigationBarBackgroundColor": "#1979ff",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "设备大数据图谱",
"enablePullDownRefresh": false,
"usingComponents": {
"ec-canvas": "../components/ec-canvas/ec-canvas",
"van-button": "@vant/weapp/button/index",
"van-progress": "@vant/weapp/progress/index"
}
}

63
weapp/package/deviceBigdataGraph/deviceBigdataGraph.wxml

@ -0,0 +1,63 @@
<!--package/riskManagement/riskCalendar/riskCalendar.wxml-->
<view class="container">
<!--质保图谱-->
<view class="card">
<image src="/images/shape3.png" class="imgStyle"></image>
<view class="top">
<view style="display: flex; align-items: center;">
<image style="width: 30px; height: 30px;" src="/images/edit.png" />
<text class="fontStyle">质保图谱</text>
</view>
<view class="detailStyle">
<van-button type="info" round size="small">查看详情</van-button>
</view>
</view>
<view class="progress-container">
<text class="label">过保比率:</text>
<view class="progress-wrapper">
<progress class="progress" percent="50" color="#4E87FF"></progress>
<text>50%</text>
</view>
</view>
<view class="progress-container">
<text class="label">质保期比例:</text>
<view class="progress-wrapper">
<progress class="progress" percent="50"></progress>
<text>50%</text>
</view>
</view>
</view>
<!--设备状态-->
<view class="card">
<image src="/images/shape1.png" class="imgStyle"></image>
<view class="top">
<view style="display: flex; align-items: center;">
<image style="width: 30px; height: 30px;" src="/images/device.png" />
<text class="fontStyle">设备状态</text>
</view>
<view class="detailStyle">
<van-button type="info" round size="small" bindtap="navigator">查看详情</van-button>
</view>
</view>
<view style="height: 250px;">
<ec-canvas id="mychart-device-pie" canvas-id="mychart-device-pie" ec="{{ ec }}"></ec-canvas>
</view>
</view>
<!--设备类型-->
<view class="card">
<image src="/images/shape1.png" class="imgStyle"></image>
<view class="top">
<view style="display: flex; align-items: center;">
<image style="width: 30px; height: 30px;" src="/images/device.png" />
<text class="fontStyle">设备类型</text>
</view>
<view class="countStyle">
总数:
</view>
</view>
<view style="height: 250px;">
<ec-canvas id="mychart-dom-pie" canvas-id="mychart-pie" ec="{{ ec }}"></ec-canvas>
</view>
</view>
</view>

91
weapp/package/deviceBigdataGraph/deviceBigdataGraph.wxss

@ -0,0 +1,91 @@
/* package/riskManagement/riskCalendar/riskCalendar.wxss */
.container {
background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%);
padding: 0 15px;
}
.card {
position: relative;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
/* padding: 10px; */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
margin-top: 12px;
width: 100%;
background-image: linear-gradient(0deg, #F3F7FF 84%, #DBE6FF 100%);
}
.top {
display: flex;
justify-content: space-between;
padding: 10px;
/* background-position: bottom; */
}
.fontStyle {
width: 64px;
height: 22px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #000000d9;
letter-spacing: 0;
margin-left: 5px;
}
.imgStyle {
position: absolute;
width: 115px;
height: 67px;
right: 0;
}
.detailStyle {
z-index: 1;
}
.progress-container {
display: flex;
align-items: center;
width: 100%;
margin: 0px 0px 10px 10px;
}
.label {
margin-right: 10px;
width: 50%;
height: 20px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #383A3B;
}
.progress-wrapper {
display: flex;
align-items: center;
width: 100%;
}
.percentage {
margin-right: 10px;
}
.progress {
width: 75%;
}
.countStyle {
width: 89px;
height: 24px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 17px;
color: #1684FF;
letter-spacing: 0;
}

120
weapp/package/deviceBigdataGraph/statusDetail/statusDetail.js

@ -0,0 +1,120 @@
// package/deviceBigdataGraph/detail/detail.js
import * as echarts from '../../components/ec-canvas/echarts';
function setOption(chart, data) {
const option = {
grid: {
top: '5%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: data,
type: 'line'
}
]
};
chart.setOption(option);
}
Page({
/**
* 页面的初始数据
*/
data: {
ec: {
// onInit: initChart,
lazyLoad: true, // 将 lazyLoad 设为 true 后,需要手动初始化图表
},
isLoaded: false,
list: [1,2,3]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
setTimeout(() => {
this.initChart([250, 300, 100, 147, 260, 123, 311])
}, 1000)
},
initChart: function (data) {
this.ecComponent.init((canvas, width, height, dpr) => {
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr // new
});
setOption(chart, data);
// 将图表实例绑定到 this 上,可以在其他成员函数中访问
this.chart = chart;
this.setData({
isLoaded: true,
});
// 注意这里一定要返回 chart 实例,否则会影响事件处理等
return chart;
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
this.ecComponent = this.selectComponent('#device-status-chart');
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

10
weapp/package/deviceBigdataGraph/statusDetail/statusDetail.json

@ -0,0 +1,10 @@
{
"navigationBarBackgroundColor": "#1979ff",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "设备状态",
"enablePullDownRefresh": false,
"usingComponents": {
"ec-canvas": "../../components/ec-canvas/ec-canvas"
}
}

26
weapp/package/deviceBigdataGraph/statusDetail/statusDetail.wxml

@ -0,0 +1,26 @@
<!--package/deviceBigdataGraph/detail/detail.wxml-->
<view class="status-detail">
<view class="icon"><text class="icon-text">设备总数</text></view>
<view>300</view>
<view class="flex flex-around">
<view class="title-item flex flex-col">
<view>设备故障率</view>
<view><text class="title-num">{{86}}%</text></view>
</view>
<view class="title-item flex flex-col">
<view>完好率</view>
<view><text class="title-num">{{300}}%</text></view>
</view>
</view>
<view class="card">
<view class="flex flex-start">
<!-- <image src="" class="card-img" /> -->
<view class="card-img" style="background: blue;"></view>
<view class="card-title">历史风险趋势</view>
</view>
<view class="chart">
<ec-canvas id="device-status-chart" canvas-id="device-status-chart" ec="{{ ec }}"></ec-canvas>
</view>
</view>
</view>

76
weapp/package/deviceBigdataGraph/statusDetail/statusDetail.wxss

@ -0,0 +1,76 @@
/* package/deviceBigdataGraph/detail/detail.wxss */
.status-detail {
height: 100vh;
background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%);
padding: 0 15px;
}
.icon {
width: 61px;
height: 31.86px;
background-image: linear-gradient(0deg, #EAF2FF 5%, #2578F0 100%);
box-shadow: 0 3px 4px 1px #bfdbfa4f;
}
.icon-text {
width: 48px;
height: 17px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 12px;
color: #FFFFFF;
}
.title-item {
width: 150px;
color: #ffffffd9;
}
.title-num {
font-size: 20px;
color: #FFFFFF;
}
.title-unit {
font-size: 10px;
color: #FFFFFE;
margin-left: 10px;
}
.card {
background: #FFFFFF;
box-shadow: 2px 2px 11px 0 #00000008, 0 0 4px 0 #00000012;
border-radius: 4px;
padding: 12px;
margin-top: 12px;
}
.card-img {
width: 18px;
height: 18px;
margin-right: 13px;
}
.card-title {
font-weight: 500;
font-size: 16px;
color: #383A3B;
}
.card-link {
font-weight: 500;
font-size: 14px;
color: #1684FF;
}
.chart {
width: 100%;
height: 195px;
margin-top: 20px;
}
.list {
margin-top: 10px;
padding: 10px 7px;
background-color: #F1F7FF;
}

143
weapp/package/inspectionInput/inspectionInput.js

@ -13,7 +13,7 @@ Page({
address: '', // 当前位置
imgUrl: getApp().globalData.imgUrl,
checkItems: [], // 检查项
inspectContent: {}, // 巡检内容
inspectContentArr: [], // 巡检内容
isCommitting: false,
/*** 扫码巡检 ***/
planList: null, // 巡检计划列表
@ -61,21 +61,41 @@ Page({
},
// 获取巡检模板
getPatrolTemplate (templateId) {
getPatrolTemplate(templateId, pointDevices = []) {
Request.get(getPatrolTemplate(templateId)).then(res => {
const checkItems = res.rows[0].checkItems;
const inspectContent = {};
for (const c of checkItems) {
inspectContent[c.name] = {
let inspectContentArr = [];
// 有绑定设备的点位,每个设备都要检查各个检查项
if (pointDevices.length) {
pointDevices.forEach(device => {
inspectContentArr.push({
deviceName: device.device.name,
deviceId: device.deviceId,
checkItems: checkItems.map(c => ({
id: `${device.deviceId}-${c.id}`,
name: c.name,
isNormal: null,
msgInp: null,
level: null,
imgs: [],
};
}))
})
});
} else {
inspectContentArr.push({
checkItems: checkItems.map(c => ({
id: c.id,
name: c.name,
isNormal: null,
msgInp: null,
level: null,
imgs: [],
}))
})
}
this.setData({
checkItems,
inspectContent,
inspectContentArr: inspectContentArr,
})
})
},
@ -90,11 +110,12 @@ Page({
});
const curPlan = this.data.planList[e.detail.columns[0].index];
const nextItemData = curPlan.points.find(p => p.id == this.data.scenePointId)
this.setData({
dataList: curPlan,
itemData: curPlan.points.find(p => p.id == this.data.scenePointId)
itemData: nextItemData
});
this.getPatrolTemplate(curPlan.templateId);
this.getPatrolTemplate(curPlan.templateId, nextItemData.pointDevices);
},
onPickerCancel(e) {
@ -142,40 +163,41 @@ Page({
handleChangeTwo(e) {
const isNormal = e.detail === 'normal';
const inspectContent = this.data.inspectContent;
inspectContent[e.currentTarget.dataset.item].isNormal = isNormal;
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].isNormal = isNormal;
if (isNormal) { // 清除异常数据
inspectContent[e.currentTarget.dataset.item].msgInp = null;
inspectContent[e.currentTarget.dataset.item].level = null;
inspectContent[e.currentTarget.dataset.item].imgs = [];
nextInspectContentArr[deviceidx].checkItems[itemidx].msgInp = null;
nextInspectContentArr[deviceidx].checkItems[itemidx].level = null;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = [];
}
this.setData({
inspectContent,
})
this.setData({ inspectContentArr: nextInspectContentArr })
},
handleChangeThree(e) {
const inspectContent = this.data.inspectContent;
inspectContent[e.currentTarget.dataset.item].level = e.detail;
this.setData({
inspectContent
})
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].level = e.detail;
this.setData({ inspectContentArr: nextInspectContentArr })
},
// 巡查详情
bindInput: function (e) {
const inspectContent = this.data.inspectContent;
inspectContent[e.currentTarget.dataset.item].msgInp = e.detail.value;
this.setData({
inspectContent
})
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].msgInp = e.detail.value;
this.setData({ inspectContentArr: nextInspectContentArr })
},
// 上传图片
chooseImg: function (e) { // 这里是选取图片的方法
const { deviceidx, itemidx } = e.currentTarget.dataset;
const that = this;
let pics = [];
const detailPics = that.data.inspectContent[e.currentTarget.dataset.item].imgs;
const detailPics = that.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
if (detailPics.length >= 20) {
wx.showToast({
title: '最多选择20张图片上传',
@ -203,16 +225,16 @@ Page({
}
pics.push(imgs[i].tempFilePath)
}
that.uploadimg({
that.uploadImg({
url: getApp().globalData.webUrl + '_upload/attachments/project', // 图片上传的接口
path: pics, // 选取的图片的地址数组
}, e.currentTarget.dataset.item);
}, deviceidx, itemidx);
},
})
},
//多张图片上传
uploadimg: function (data, itemName) {
uploadImg: function (data, deviceidx, itemidx) {
wx.showLoading({
title: '上传中...',
mask: true,
@ -221,7 +243,7 @@ Page({
i = data.i ? data.i : 0,
success = data.success ? data.success : 0,
fail = data.fail ? data.fail : 0;
let imgs = that.data.inspectContent[itemName].imgs;
let imgs = that.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
wx.uploadFile({
url: data.url,
filePath: data.path[i],
@ -232,19 +254,15 @@ Page({
let str = JSON.parse(resp.data) // 返回的结果,可能不同项目结果不一样
str = str.uploaded
if (imgs.length >= 20) {
const inspectContent = that.data.inspectContent;
inspectContent[itemName].imgs = imgs;
that.setData({
inspectContent,
});
let nextInspectContentArr = that.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = imgs;
that.setData({ inspectContentArr: nextInspectContentArr });
return false;
} else {
imgs.push(str);
const inspectContent = that.data.inspectContent;
inspectContent[itemName].imgs = imgs;
that.setData({
inspectContent,
})
let nextInspectContentArr = that.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = imgs;
that.setData({ inspectContentArr: nextInspectContentArr });
}
},
fail: (res) => {
@ -260,7 +278,7 @@ Page({
data.i = i;
data.success = success;
data.fail = fail;
that.uploadimg(data, itemName); // 递归,回调自己
that.uploadImg(data, deviceidx, itemidx); // 递归,回调自己
}
}
});
@ -268,22 +286,19 @@ Page({
// 删除图片
deleteImg: function (e) {
let imgs = this.data.inspectContent[e.currentTarget.dataset.item].imgs;
const index = e.currentTarget.dataset.index;
const { deviceidx, itemidx, index } = e.currentTarget.dataset;
let imgs = this.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
imgs.splice(index, 1);
const inspectContent = this.data.inspectContent;
inspectContent[e.currentTarget.dataset.item].imgs = imgs;
this.setData({
inspectContent
});
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = imgs;
this.setData({ inspectContentArr: nextInspectContentArr })
},
// 预览图片
previewImg: function (e) {
// 获取当前图片的下标
const index = e.currentTarget.dataset.index;
const { deviceidx, itemidx, index } = e.currentTarget.dataset;
// 所有图片
const imgs = this.data.inspectContent[e.currentTarget.dataset.item].imgs;
const imgs = this.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
const newImgs = imgs.map(i => this.data.imgUrl + i);
wx.previewImage({
// 当前显示图片
@ -307,12 +322,11 @@ Page({
if (that.data.isCommitting) { return }
let {
itemData,
inspectContent,
inspectContentArr,
dataList,
address
} = that.data;
let alarm = false;
if (!address) {
wx.showToast({
title: '请获取当前位置',
@ -321,9 +335,10 @@ Page({
})
return;
}
for (const item in inspectContent) {
if (inspectContent[item].isNormal === null) {
let reportArr = inspectContentArr.map(d => ({ ...d, alarm: false }));
for (const [index, device] of inspectContentArr.entries()) {
for (const item of device.checkItems) {
if (item.isNormal === null) {
wx.showToast({
title: '请填写完整',
icon: 'none',
@ -331,7 +346,7 @@ Page({
})
return;
}
if ((!inspectContent[item].isNormal) && (!inspectContent[item].level || !inspectContent[item].msgInp)) {
if ((!item.isNormal) && (!item.level || !item.msgInp)) {
wx.showToast({
title: '异常项必须输入巡查详情和选择严重等级',
icon: 'none',
@ -339,8 +354,10 @@ Page({
})
return;
}
if (inspectContent[item].isNormal === false) {
alarm = true;
if (item.isNormal === false) {
alarm = true; // 巡检记录异常
reportArr[index].alarm = true; // 设备异常
}
}
}
const { id, name, departmentId, deptName } = wx.getStorageSync('userInfo');
@ -353,7 +370,7 @@ Page({
project: dataList.project,
frequency: dataList.frequency,
itemData: itemData,
inspectContent,
inspectContent: reportArr,
address: address
},
alarm,
@ -398,7 +415,7 @@ Page({
dataList,
itemData
})
that.getPatrolTemplate(dataList.templateId);
that.getPatrolTemplate(dataList.templateId, itemData.pointDevices);
}
},

121
weapp/package/inspectionInput/inspectionInput.wxml

@ -1,8 +1,24 @@
<!-- package/inspectionInput/inspectionInput.wxml -->
<view class="popBox">
<view wx:if="{{planList}}">
<t-cell class="block" title="选择巡检计划" arrow hover note="{{planListText}}" bind:click="onPlanListPicker" />
<t-picker visible="{{planListVisible}}" value="{{planListValue}}" data-key="planList" title="选择巡检计划" cancelBtn="取消" confirmBtn="确认" bindchange="onPickerChange" bindcancel="onPickerCancel">
<t-cell
class="block"
title="选择巡检计划"
arrow
hover
note="{{planListText}}"
bind:click="onPlanListPicker"
/>
<t-picker
visible="{{planListVisible}}"
value="{{planListValue}}"
data-key="planList"
title="选择巡检计划"
cancelBtn="取消"
confirmBtn="确认"
bindchange="onPickerChange"
bindcancel="onPickerCancel"
>
<t-picker-item options="{{planList}}" format></t-picker-item>
</t-picker>
</view>
@ -24,36 +40,113 @@
</view>
</view>
<view class="divider" />
<view wx:for="{{checkItems}}" wx:key="id">
<!-- 渲染巡检内容 -->
<view
wx:for="{{inspectContentArr}}"
wx:key="deviceId"
wx:for-item="device"
wx:for-index="deviceidx"
>
<view wx:if="{{device.deviceName}}" class="flex flex-start" style="height: 40px"
>{{device.deviceName}}</view
>
<view wx:for="{{device.checkItems}}" wx:key="id" wx:for-index="itemidx">
<view class="flex-between">
<view class="item-name">{{item.name}}:</view>
<van-radio-group style="padding:10px 15px;" data-item="{{item.name}}" bindchange="handleChangeTwo">
<van-radio style="margin-right: 20px;" class="radio-text" color="#1979ff" name="normal">正常</van-radio>
<van-radio-group
style="padding:10px 15px;"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
bindchange="handleChangeTwo"
>
<van-radio style="margin-right: 20px;" class="radio-text" color="#1979ff" name="normal"
>正常</van-radio
>
<van-radio class="radio-text" checked-color="#CC0000" name="abnormal">异常</van-radio>
</van-radio-group>
</view>
<view class="divider" />
<van-radio-group class="flex-end" style="padding:10px 15px;" data-item="{{item.name}}" bindchange="handleChangeThree" wx:if="{{inspectContent[item.name].isNormal === false}}">
<van-radio style="margin-right: 20px;" class="radio-text" checked-color="#FF9900" name="轻微">轻微</van-radio>
<van-radio style="margin-right: 20px;" class="radio-text" checked-color="#FF3300" name="中度">中度</van-radio>
<van-radio-group
class="flex-end"
style="padding:10px 15px;"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
bindchange="handleChangeThree"
wx:if="{{item.isNormal === false}}"
>
<van-radio
style="margin-right: 20px;"
class="radio-text"
checked-color="#FF9900"
name="轻微"
>轻微</van-radio
>
<van-radio
style="margin-right: 20px;"
class="radio-text"
checked-color="#FF3300"
name="中度"
>中度</van-radio
>
<van-radio class="radio-text" checked-color="#990000" name="严重">严重</van-radio>
</van-radio-group>
<textarea class="textarea" placeholder="请输入巡查详情" maxlength="-1" wx:if="{{inspectContent[item.name].isNormal === false}}" data-item="{{item.name}}" bindinput="bindInput"></textarea>
<view class="weui-uploader" style="padding: 20rpx 30rpx;overflow-y:scroll;" wx:if="{{inspectContent[item.name].isNormal === false}}">
<textarea
class="textarea"
placeholder="请输入巡查详情"
maxlength="-1"
wx:if="{{item.isNormal === false}}"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
bindinput="bindInput"
/>
<view
class="weui-uploader"
style="padding: 20rpx 30rpx;overflow-y:scroll;"
wx:if="{{item.isNormal === false}}"
>
<view class="img-v weui-uploader__bd" style="overflow:hidden;">
<view class='pic' wx:for="{{inspectContent[item.name].imgs}}" wx:for-item="img" wx:key="*this">
<image class='weui-uploader__img showImg' src="{{imgUrl + img}}" data-index="{{index}}" data-item="{{item.name}}" mode="aspectFill" bindtap="previewImg">
<icon type='cancel' class="delete-btn" data-index="{{index}}" data-item="{{item.name}}" catchtap="deleteImg"></icon>
<view
class="pic"
wx:for="{{item.imgs}}"
wx:for-item="img"
wx:key="*this"
>
<image
class="weui-uploader__img showImg"
src="{{imgUrl + img}}"
data-index="{{index}}"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
mode="aspectFill"
bindtap="previewImg"
>
<icon
type="cancel"
class="delete-btn"
data-index="{{index}}"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
catchtap="deleteImg"
/>
</image>
</view>
<!-- 用来提示用户上传图片 -->
<view class="weui-uploader__input-box pic" data-item="{{item.name}}" bindtap="chooseImg">
<view
class="weui-uploader__input-box pic"
data-item="{{item.name}}"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
bindtap="chooseImg"
>
<image class="upload" src="/images/upload.png" />
</view>
</view>
</view>
<view class="divider" />
</view>
</view>
<view class="btnBox">
<view class="cancel" bindtap="bindCancel">取消</view>
<view class="submit" bindtap="addPatrolRecord">提交</view>

39
weapp/package/polling/inspectionRecordDetail/inspectionRecordDetail.js

@ -1,4 +1,5 @@
// package/inspectionRecord/inspectionRecordDetail/inspectionRecordDetail.js
const moment = require("../../../utils/moment");
Page({
/**
@ -7,21 +8,34 @@ Page({
data: {
dataList: '',
imgUrl: getApp().globalData.imgUrl,
activeValues: []
activeValues: [],
multiActiveValues: [],
// 1.1 版本(2023-10-18)之前 dataList.points.inspectContent 为 Object, 1.1版本因增加点位关联设备改为 Array
priorToV1_1: false,
},
handleChange(e) {
this.setData({
activeValues: e.detail,
});
handleCollapseChange(e) {
this.setData({ activeValues: e.detail });
},
handleMultiCollapseChange(e) {
const { deviceidx } = e.currentTarget.dataset;
let nextMultiActiveValues = this.data.multiActiveValues;
nextMultiActiveValues[deviceidx] = e.detail;
this.setData({ multiActiveValues: nextMultiActiveValues });
},
// 预览图片
previewImg: function (e) {
// 获取当前图片的下标
const index = e.currentTarget.dataset.index;
const { index, deviceidx, itemidx } = e.currentTarget.dataset;
// 所有图片
const imgs = this.data.dataList.points.inspectContent[e.currentTarget.dataset.key].imgs;
let imgs = []
if (this.data.priorToV1_1) {
imgs = this.data.dataList.points.inspectContent[e.currentTarget.dataset.key].imgs;
} else {
imgs = this.data.dataList.points.inspectContent[deviceidx].checkItems[itemidx].imgs;
}
const newImgs = imgs.map(i => this.data.imgUrl + i);
wx.previewImage({
current: newImgs[index],
@ -35,7 +49,16 @@ Page({
onLoad(options) {
const that = this;
const data = JSON.parse(decodeURIComponent(options.data))
that.setData({ dataList: data })
const priorToV1_1 = moment(data.inspectionTime).isBefore(moment('2023-10-18'))
let nextMultiActiveValues = []
if (!priorToV1_1) {
nextMultiActiveValues = data.points.inspectContent.map(c => ([]))
}
that.setData({
dataList: data,
priorToV1_1,
nextMultiActiveValues,
})
},
/**

110
weapp/package/polling/inspectionRecordDetail/inspectionRecordDetail.wxml

@ -7,35 +7,119 @@
<van-cell size="large" title="巡检频次:" value="{{dataList.points.frequency}}" />
<van-cell size="large" title="上次巡检日期:" value="{{dataList.lastInspectionTime}}" />
<van-cell size="large" title="本次巡检日期:" value="{{dataList.inspectionTime}}" />
<van-cell size="large" title="巡检结果:" value="{{dataList.alarm ? '异常' : '正常'}}" style="--cell-value-color: {{dataList.alarm ? '#FF0000' : '#006BE3'}}" />
<van-cell
size="large"
title="巡检结果:"
value="{{dataList.alarm ? '异常' : '正常'}}"
style="--cell-value-color: {{dataList.alarm ? '#FF0000' : '#006BE3'}}"
/>
<van-cell size="large" title="当前点位:" value="{{dataList.points.itemData.name}}" />
<van-cell size="large" title="当前位置:" value="{{dataList.points.address}}" title-width="80px" />
<van-cell
size="large"
title="当前位置:"
value="{{dataList.points.address}}"
title-width="80px"
/>
</van-cell-group>
<view style="margin-top: 16px">
<van-collapse value="{{ activeValues }}" bind:change="handleChange">
<view wx:for="{{dataList.points.inspectContent}}" wx:key="key" wx:for-index="key" wx:for-item="value">
<block wx:if="{{ priorToV1_1 }}">
<van-collapse value="{{ activeValues }}" bind:change="handleCollapseChange">
<view
wx:for="{{dataList.points.inspectContent}}"
wx:key="key"
wx:for-index="key"
wx:for-item="value"
>
<van-collapse-item
title="{{key}}"
value="{{value.isNormal ? '正常' : '异常'}}"
style="--cell-value-color: {{value.isNormal ? '#006BE3' : '#FF0000'}}; --collapse-item-title-disabled-color: #323233"
disabled="{{value.isNormal}}"
>
<view class='content'>
<view class='title'>描述:</view>
<view class='value'>{{value.msgInp || '--'}}</view>
<view class="content">
<view class="title">描述:</view>
<view class="value">{{value.msgInp || '--'}}</view>
</view>
<view class='content'>
<view class='title'>异常等级:</view>
<view class='value'>{{value.level || '--'}}</view>
<view class="content">
<view class="title">异常等级:</view>
<view class="value">{{value.level || '--'}}</view>
</view>
<view class='content'>巡检图片:</view>
<view class="content">巡检图片:</view>
<view class="img-v weui-uploader__bd" style="overflow:hidden;">
<view class='pic' wx:for="{{value.imgs}}" wx:for-item="item" wx:key="*this">
<image class='weui-uploader__img showImg' src="{{imgUrl + item}}" data-img="{{imgUrl + item}}" data-index="{{index}}" data-key="{{key}}" mode="aspectFill" bindtap="previewImg"></image>
<view class="pic" wx:for="{{value.imgs}}" wx:for-item="item" wx:key="*this">
<image
class="weui-uploader__img showImg"
src="{{imgUrl + item}}"
data-img="{{imgUrl + item}}"
data-index="{{index}}"
data-key="{{key}}"
mode="aspectFill"
bindtap="previewImg"
></image>
</view>
</view>
</van-collapse-item>
</view>
</van-collapse>
</block>
<block wx:else>
<view
wx:for="{{dataList.points.inspectContent}}"
wx:for-item="device"
wx:for-index="deviceidx"
style="margin-top: 16px"
>
<van-collapse
value="{{ multiActiveValues[deviceidx] }}"
data-deviceidx="{{deviceidx}}"
bind:change="handleMultiCollapseChange"
>
<view
wx:if="{{device.deviceName}}"
class="flex flex-between"
style="padding: 8px 16px 0; background: #fff"
>
<view>{{device.deviceName}}</view>
<view style="color: {{device.alarm ? '#FF0000' : '#006BE3'}};">
{{device.alarm ? '异常': '正常'}}
</view>
</view>
<view wx:for="{{device.checkItems}}" wx:for-index="itemidx">
<van-collapse-item
title="{{item.name}}"
value="{{item.isNormal ? '正常' : '异常'}}"
style="--cell-value-color: {{item.isNormal ? '#006BE3' : '#FF0000'}}; --collapse-item-title-disabled-color: #323233"
disabled="{{item.isNormal}}"
>
<view class="content">
<view class="title">描述:</view>
<view class="value">{{item.msgInp || '--'}}</view>
</view>
<view class="content">
<view class="title">异常等级:</view>
<view class="value">{{item.level || '--'}}</view>
</view>
<view class="content">巡检图片:</view>
<view class="img-v weui-uploader__bd" style="overflow:hidden;">
<view class="pic" wx:for="{{item.imgs}}" wx:for-item="img" wx:key="*this">
<image
class="weui-uploader__img showImg"
src="{{imgUrl + img}}"
data-img="{{imgUrl + img}}"
data-index="{{index}}"
data-key="{{key}}"
data-deviceidx="{{deviceidx}}"
data-itemidx="{{itemidx}}"
mode="aspectFill"
bindtap="previewImg"
></image>
</view>
</view>
</van-collapse-item>
</view>
</van-collapse>
</view>
</block>
</view>
</view>

7
weapp/package/polling/polling.js

@ -48,7 +48,12 @@ Page({
},
recordHidden: true,
},
jumpToReport (options) {
const key='主动上报'
wx.navigateTo({
url: `/package/report/report?key=${key}`,
})
},
// 顶部tab切换
onChange(event) {
this.setData({

4
weapp/package/polling/polling.json

@ -9,6 +9,8 @@
"van-tabs": "@vant/weapp/tabs/index",
"van-dropdown-menu": "@vant/weapp/dropdown-menu/index",
"van-dropdown-item": "@vant/weapp/dropdown-item/index",
"van-datetime-picker": "@vant/weapp/datetime-picker/index"
"van-datetime-picker": "@vant/weapp/datetime-picker/index",
"van-icon": "@vant/weapp/icon/index"
}
}

5
weapp/package/polling/polling.wxml

@ -79,5 +79,10 @@
</view>
</view>
</van-tab>
<van-tab title='主动上报' >
<view>
<van-button type="info" round bindtap="jumpToReport"><van-icon name="plus" /></van-button>
</view>
</van-tab>
</van-tabs>
</view>

543
weapp/package/report/report.js

@ -0,0 +1,543 @@
// package/report/report.js
import { getPointList,getPatrolTemplate,getTemplates,reportQuest,getPatrolPlan } from "../../utils/getApiUrl";
import {Request} from "../../common";
const moment = require("../../utils/moment");
Page({
data: {
isPlanState: false,
structList: [{
id: 0,
name: '指挥中心'
}, {
id: 1,
name: '管廊'
},
{
id: 2,
name: '电梯系统'
}, {
id: 3,
name: '供配电系统'
}, {
id: 4,
name: '燃气仓'
},
{
id: 5,
name: '给水仓'
}, {
id: 6,
name: '防雷与接地系统'
}, {
id: 7,
name: '电气仓'
},
{
id: 8,
name: '高压电力仓'
}, {
id: 9,
name: '安防系统'
}
],
data:[],//巡检计划的数据(包括点位,设备等等)
structListIndex: undefined,//结构物id
pointList:[],//点位列表
pointIndex:undefined,//点位索引
devicesList:[],//设备列表
dataList: '', // 当前巡检计划
patrolTemplate:[],//巡检模板
templateData:[],//巡检模板总数居
// curPlanTemplateId:0,//当前巡检计划的模板id
patrolTemplateIndex:undefined,//巡检模板索引
itemData: '', // 点位
address: '', // 当前位置
imgUrl: getApp().globalData.imgUrl,
checkItems: [], // 检查项
inspectContentArr: [], // 巡检内容
isCommitting: false,
planList: null, // 巡检计划列表
structListVisible: true,
scenePointId: null, // 当前点位id
},
//巡检计划
getPatrolPlan: function (scenePointId) {
let that = this;
wx.showLoading({
title: '加载中',
})
Request.get(getPatrolPlan()).then(res => {
wx.hideLoading();
let pointPlan = res.rows.filter(plan => {
for (const point of plan.points) {
if (point.id == scenePointId) {
return true;
}
}
return false;
}).map(p => ({
label: p.name,
value: p.name,
...p
}))
that.setData({
planList: pointPlan
})
})
},
//点位改变函数
pointChange(e){
const that = this
that.getPatrolPlan(that.data.data[e.detail.value].id)
that.setData({
inspectContentArr:[],
pointIndex:e.detail.value,
devicesList:that.data.data[e.detail.value].devices,
scenePointId:that.data.data[e.detail.value].id
})
},
// 预览图片
previewImg: function (e) {
const { deviceidx, itemidx, index } = e.currentTarget.dataset;
// 所有图片
const imgs = this.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
const newImgs = imgs.map(i => this.data.imgUrl + i);
wx.previewImage({
// 当前显示图片
current: newImgs[index],
// 所有图片
urls: newImgs
})
},
//结构物改变函数
structChange(event) {
const that = this
that.setData({
structListIndex: event.detail.value,
// isPlanState: true,
pointList:[],//选择结构物后先置空先前的点位列表
})
const keywords=that.data?.structList[event.detail.value]?.name
const query={keywords}
Request.get(getTemplates(query)).then(res=>{
if(res){
const rlst=res.map(item=>item.patrolTemplate)
that.setData({patrolTemplate:rlst,templateData:res})
}else{
}
})
Request.get(getPointList(query)).then(res => {
if(res){
const pointList=res.map(item=>{
return {
id:item.id,
name:item.name
}
})
that.setData({pointList:pointList,data:res})
}else {
wx.hideLoading();
}
})
},
//整理设备和检查项
getPatrolTemplate(templateId,pointDevices=[]) {
const that=this
Request.get(getPatrolTemplate(templateId)).then(res => {
const checkItems = res.rows[0].checkItems;
let inspectContentArr = [];
// 有绑定设备的点位,每个设备都要检查各个检查项
if (pointDevices.length) {
pointDevices.forEach(device => {
inspectContentArr.push({
deviceName: device.name,
deviceId: device.id,
checkItems: checkItems.map(c => ({
id: `${device.id}-${c.id}`,
name: c.name,
isNormal: null,
msgInp: null,
level: null,
imgs: [],
}))
})
});
} else {
inspectContentArr.push({
checkItems: checkItems.map(c => ({
id: c.id,
name: c.name,
isNormal: null,
msgInp: null,
level: null,
imgs: [],
}))
})
}
this.setData({
checkItems,
inspectContentArr: inspectContentArr,
})
})
},
//选择异常或者正常
handleChangeTwo(e) {
const isNormal = e.detail === 'normal';
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].isNormal = isNormal;
if (isNormal) { // 清除异常数据
nextInspectContentArr[deviceidx].checkItems[itemidx].msgInp = null;
nextInspectContentArr[deviceidx].checkItems[itemidx].level = null;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = [];
}
this.setData({ inspectContentArr: nextInspectContentArr })
},
//返回前一页
bindCancel() {
wx.navigateBack();
},
// 开始巡检录入
addPatrolRecord: function () {
const that = this;
if (that.data.isCommitting) { return }
let {
patrolTemplate,
patrolTemplateIndex,
structListIndex,
pointIndex,
pointList,
inspectContentArr,
dataList,
address,
data,
templateData
} = that.data;
let alarm = false;
if (!address) {
wx.showToast({
title: '请获取当前位置',
icon: 'none',
duration: 1500
})
return;
}
if (!structListIndex) {
wx.showToast({
title: '请选择结构物',
icon: 'none',
duration: 1500
})
return;
}
if (!patrolTemplateIndex) {
wx.showToast({
title: '请选择模板',
icon: 'none',
duration: 1500
})
return;
}
if (!pointIndex) {
wx.showToast({
title: '请选择点位',
icon: 'none',
duration: 1500
})
return;
}
let reportArr = inspectContentArr.map(d => ({ ...d, alarm: false }));
for (const [index, device] of inspectContentArr.entries()) {
for (const item of device.checkItems) {
if (item.isNormal === null) {
wx.showToast({
title: '请填写完整',
icon: 'none',
duration: 1500
})
return;
}
if ((!item.isNormal) && (!item.level || !item.msgInp)) {
wx.showToast({
title: '异常项必须输入巡查详情和选择严重等级',
icon: 'none',
duration: 2000
})
return;
}
if (item.isNormal === false) {
alarm = true; // 巡检记录异常
reportArr[index].alarm = true; // 设备异常
}
}
}
const { id, name, departmentId, deptName } = wx.getStorageSync('userInfo');
const curPlan = that.data.planList.find(item=>item.id=patrolTemplate[patrolTemplateIndex].id)
const nextItemData = curPlan.points.find(p => p.id == this.data.scenePointId)
const aboutSend=templateData.find(item=>item.patrolTemplate.id===patrolTemplate[patrolTemplateIndex].id)
let datas = {
patrolPlanId: -1,
pointId: pointList[pointIndex].id,
inspectionTime: moment().format('YYYY-MM-DD HH:mm:ss'),
points: {
user: { id, name, department: { id: departmentId, name: deptName } },
project: aboutSend.project,
frequency: aboutSend.frequency,
itemData:nextItemData,
inspectContent: reportArr,
address: address
},
alarm,
projectId: aboutSend.project.id
}
wx.showLoading({ title: '提交中...' });
that.setData({ isCommitting: true });
Request.post(reportQuest(), datas).then(res => {
wx.hideLoading();
that.setData({ isCommitting: false });
wx.showToast({
title: '提交成功',
icon: 'success'
})
setTimeout(() => {
that.bindCancel();
}, 1500)
})
},
//多张图片上传
uploadImg: function (data, deviceidx, itemidx) {
wx.showLoading({
title: '上传中...',
mask: true,
})
let that = this,
i = data.i ? data.i : 0,
success = data.success ? data.success : 0,
fail = data.fail ? data.fail : 0;
let imgs = that.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
wx.uploadFile({
url: data.url,
filePath: data.path[i],
name: 'file',
success: (resp) => {
wx.hideLoading();
success++;
let str = JSON.parse(resp.data) // 返回的结果,可能不同项目结果不一样
str = str.uploaded
if (imgs.length >= 20) {
let nextInspectContentArr = that.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = imgs;
that.setData({ inspectContentArr: nextInspectContentArr });
return false;
} else {
imgs.push(str);
let nextInspectContentArr = that.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].imgs = imgs;
that.setData({ inspectContentArr: nextInspectContentArr });
}
},
fail: (res) => {
fail++;
console.log('fail:' + i + "fail:" + fail);
},
complete: () => {
i++;
if (i == data.path.length) { // 当图片传完时,停止调用
console.log('执行完毕');
console.log('成功:' + success + " 失败:" + fail);
} else { // 若图片还没有传完,则继续调用函数
data.i = i;
data.success = success;
data.fail = fail;
that.uploadImg(data, deviceidx, itemidx); // 递归,回调自己
}
}
});
},
// 上传图片
chooseImg: function (e) { // 这里是选取图片的方法
const { deviceidx, itemidx } = e.currentTarget.dataset;
const that = this;
let pics = [];
const detailPics = that.data.inspectContentArr[deviceidx].checkItems[itemidx].imgs;
if (detailPics.length >= 20) {
wx.showToast({
title: '最多选择20张图片上传',
icon: 'none'
});
return;
}
wx.chooseMedia({
count: 20, // 基础库2.25.0前,最多可支持9个文件,2.25.0及以后最多可支持20个文件
mediaType: ['image'], // 文件类型
sizeType: ['original', 'compressed'], // original 原图,compressed 压缩图,默认二者都有
sourceType: ['album', 'camera'], // album 从相册选图,camera 使用相机,默认二者都有
success: function (res) {
const imgs = res.tempFiles;
for (let i = 0; i < imgs.length; i++) {
if (res.tempFiles[i].size > 15728640) {
wx.showToast({ title: '图片大于15M,不可上传', icon: 'none' });
return;
}
const fileNameArr = res.tempFiles[i].tempFilePath.split('.');
const extension = res.tempFiles[i].tempFilePath.split('.')[fileNameArr.length - 1];
if (extension !== 'jpg' && extension !== 'png' && extension !== 'jpeg') {
wx.showToast({ title: '只能上传jpg、jpeg、png格式的图片', icon: 'none' });
return;
}
pics.push(imgs[i].tempFilePath)
}
that.uploadImg({
url: getApp().globalData.webUrl + '_upload/attachments/project', // 图片上传的接口
path: pics, // 选取的图片的地址数组
}, deviceidx, itemidx);
},
})
},
// 巡查详情
bindInput: function (e) {
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].msgInp = e.detail.value;
this.setData({ inspectContentArr: nextInspectContentArr })
},
handleChangeThree(e) {
const { deviceidx, itemidx } = e.currentTarget.dataset;
let nextInspectContentArr = this.data.inspectContentArr;
nextInspectContentArr[deviceidx].checkItems[itemidx].level = e.detail;
this.setData({ inspectContentArr: nextInspectContentArr })
},
//巡检模板改变
patrolTemplateChange(e){
const that=this
that.getPatrolTemplate(that.data.patrolTemplate[e.detail.value].id,that.data.devicesList)
that.setData({
patrolTemplateIndex:e.detail.value
})
},
bindShowMsg() {
this.setData({
select: !this.data.select
})
},
mySelect(e) {
var name = e.currentTarget.dataset.name
this.setData({
tihuoWay: name,
select: false
})
},
/**
* 页面的初始数据
*/
// data: {
// },
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const that=this
wx.setNavigationBarTitle({
title: options.key,
});
},
onStructListPicker() {
this.setData({
structListVisible: true
});
},
// 获取当前位置
selfLocation() {
const that = this
wx.showLoading({
title: '定位中',
mask: true,
});
wx.getLocation({
type: 'wgs84',
success: (res) => {
wx.request({
url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${res.latitude},${res.longitude}&key=${getApp().globalData.key}`,
success: function (res) {
wx.hideLoading();
// 根据自己项目需求获取res内容
that.setData({
address: res.data.result.address
})
}
})
},
fail: (res) => {
wx.hideLoading();
wx.showToast({
title: res.errMsg,
icon: 'none',
duration: 1000
});
}
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

25
weapp/package/report/report.json

@ -0,0 +1,25 @@
{
"navigationBarBackgroundColor": "#1979ff",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "发现问题",
"enablePullDownRefresh": false,
"componentFramework": "glass-easel",
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"van-field": "@vant/weapp/field/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-picker": "@vant/weapp/picker/index",
"van-popup": "@vant/weapp/popup/index",
"van-icon": "@vant/weapp/icon/index",
"van-collapse": "@vant/weapp/collapse/index",
"van-collapse-item": "@vant/weapp/collapse-item/index",
"van-divider": "@vant/weapp/divider/index",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-picker": "tdesign-miniprogram/picker/picker",
"t-picker-item": "tdesign-miniprogram/picker-item/picker-item",
"van-radio": "@vant/weapp/radio/index",
"van-radio-group": "@vant/weapp/radio-group/index"
}
}

96
weapp/package/report/report.wxml

@ -0,0 +1,96 @@
<view class="popBox">
<view>
<van-cell-group class="mission-card">
<van-cell>
<view style="display:flex">
<view class="fs-cell-title" style="">结构物:</view>
<picker style="width:100%;text-align:left" bindchange="structChange" data-type='jiegouwu' value="{{0}}" range="{{structList}}" range-key="name">
<view class="fs-cell-content" style="width:100%">
{{structListIndex||structListIndex==0?structList[structListIndex].name:'请选择'}}
<van-icon name="arrow" style="float:right;position:relative; top:4px" />
</view>
</picker>
</view>
</van-cell>
<van-cell>
<view style="display:flex">
<view class="fs-cell-title" style="">当前点位:</view>
<picker style="width:100%;text-align:left" bindchange="pointChange" data-type='point' range="{{pointList}}" range-key="name">
<view class="fs-cell-content" style="width:100%">
{{pointIndex||pointIndex==0?pointList[pointIndex].name:'请选择'}}
<van-icon name="arrow" style="float:right;position:relative; top:4px" />
</view>
</picker>
</view>
</van-cell>
<van-cell>
<view style="overflow: hidden;">
<view style="float: left;" class="fs-cell-title">当前位置:</view>
<view style="float:right;" class="fs-cell-content" wx:if="{{address}}">
{{address}}
</view>
<view style="float:right;">
<image wx:if="{{!address}}" class="icon" src="../../images/landmark.svg" />
<view style="display: inline-block;" bindtap="selfLocation" wx:if="{{!address}}">
点击获取
</view>
</view>
</view>
</van-cell>
<van-cell>
<view style="display:flex">
<view class="fs-cell-title">巡检模板:</view>
<picker style="width:100%;text-align:left" bindchange="patrolTemplateChange" data-type='template' value="{{0}}" range="{{patrolTemplate}}" range-key="name">
<view class="fs-cell-content" style="width:100%">
{{patrolTemplateIndex||patrolTemplateIndex==0?patrolTemplate[patrolTemplateIndex].name:'请选择'}}
<van-icon name="arrow" style="float:right;position:relative; top:4px" />
</view>
</picker>
</view>
</van-cell>
</van-cell-group>
</view>
<!-- 渲染巡检内容 -->
<view wx:for="{{inspectContentArr}}" wx:key="id" wx:for-item="device" wx:for-index="deviceidx" >
<view wx:if="{{device.deviceName}}" class="flex flex-start" style="height: 40px">{{device.deviceName}}</view>
<view wx:for="{{device.checkItems}}" wx:key="id" wx:for-index="itemidx">
<view class="flex-between">
<view class="item-name">{{item.name}}:</view>
<van-radio-group style="padding:10px 15px;" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" bindchange="handleChangeTwo">
<van-radio style="margin-right: 20px;" class="radio-text" color="#1979ff" name="normal">正常</van-radio>
<van-radio class="radio-text" checked-color="#CC0000" name="abnormal">异常</van-radio>
</van-radio-group>
</view>
<view class="divider" />
<van-radio-group class="flex-end" style="padding:10px 15px;" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" bindchange="handleChangeThree" wx:if="{{item.isNormal === false}}">
<van-radio style="margin-right: 20px;" class="radio-text" checked-color="#FF9900" name="轻微">轻微</van-radio>
<van-radio style="margin-right: 20px;" class="radio-text" checked-color="#FF3300" name="中度">中度</van-radio>
<van-radio class="radio-text" checked-color="#990000" name="严重">严重</van-radio>
</van-radio-group>
<textarea class="textarea" placeholder="请输入巡查详情" maxlength="-1" wx:if="{{item.isNormal === false}}" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" bindinput="bindInput" />
<view class="weui-uploader" style="padding: 20rpx 30rpx;overflow-y:scroll;" wx:if="{{item.isNormal === false}}">
<view class="img-v weui-uploader__bd" style="overflow:hidden;">
<view class="pic" wx:for="{{item.imgs}}" wx:for-item="img" wx:key="*this">
<image class="weui-uploader__img showImg" src="{{imgUrl + img}}" data-index="{{index}}" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" mode="aspectFill" bindtap="bindInput">
<icon type="cancel" class="delete-btn" data-index="{{index}}" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" catchtap="deleteImg" />
</image>
</view>
<!-- 用来提示用户上传图片 -->
<view class="weui-uploader__input-box pic" data-item="{{item.name}}" data-deviceidx="{{deviceidx}}" data-itemidx="{{itemidx}}" bindtap="chooseImg">
<image class="upload" src="/images/upload.png" />
</view>
</view>
</view>
<view class="divider" />
</view>
</view>
<view class="btnBox">
<view class="cancel" bindtap="bindCancel">取消</view>
<view class="submit" bindtap="addPatrolRecord">提交</view>
</view>
</view>

131
weapp/package/report/report.wxss

@ -0,0 +1,131 @@
.divider {
width: 100%;
height: 0px;
border-top: 1px solid #F5F5F5;
}
.flex-between {
display: flex;
justify-content: space-between;
}
.flex-end {
display: flex;
justify-content: flex-end;
}
.popBox {
position: absolute;
left: 50%;
z-index: 1000;
background: #fff;
width: 95%;
margin-left: -356rpx;
padding: 20rpx 0;
}
.item-name {
margin: 20rpx 0 0 30rpx;
}
.btnBox {
padding: 50px 30rpx;
overflow: hidden;
font-size: 30rpx;
display: flex;
justify-content: space-between;
}
.cancel {
width: 38vw;
height: 42px;
line-height: 42px;
text-align: center;
background: #fff;
border: 1px solid #006BE3;
border-radius: 24px;
font-weight: 600;
font-size: 16px;
color: #1684FF;
}
.submit {
width: 38vw;
height: 42px;
line-height: 42px;
text-align: center;
background: #1684FF;
border: 1px solid #006BE3;
border-radius: 24px;
font-weight: 600;
font-size: 16px;
color: #FFFFFF;
}
.pic {
float: left;
position: relative;
margin-right: 8px;
margin-bottom: 8px;
}
.showImg {
width: 160rpx;
height: 160rpx;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
}
.upload {
width: 63px;
height: 63px;
}
.block {
display: block;
}
.icon {
width: 18px;
height: 18px;
margin-right: 5px;
}
.radio-text {
font-size: 14px;
color: #323233;
}
.van-radio-group {
display: flex;
}
.textarea {
width: 84%;
margin: 0 auto;
padding: 20rpx;
height: 120rpx;
border: 1px solid #61616166;
}
.mission-card-title {
background-color: #fff;
overflow: auto;
padding: 24rpx 16px;
display: flex;
align-items: center
}
.fs-cell-title {
max-width: 6.2em;
min-width: 6.2em;
margin-right: 12px;
text-align: left;
color: var(--field-label-color, #646566)
}
.fs-cell-content {
color: var(--field-input-text-color, #323233)
}

94
weapp/package/riskManagement/riskCalendar/riskCalendar.js

@ -1,18 +1,108 @@
// package/riskManagement/riskCalendar/riskCalendar.js
Page({
import { getPatrolRecord } from "../../../utils/getApiUrl";
import { Request } from "../../../common";
import moment from '../../../utils/moment';
Page({
/**
* 页面的初始数据
*/
data: {
show: false,
level: ['全部', '轻微', '中度', '严重'],
curLevel: '全部',
minDate: moment().startOf('day').subtract(6, 'days').valueOf(),
maxDate: moment().endOf('day').valueOf(),
curDate: moment().format('YYYY-MM-DD'),
showList: [],
formatter(day) { return day; },
},
dayData: [], // 每天的异常数据
onDateSelect(e) {
this.setData({ curDate: moment(e.detail).format('YYYY-MM-DD') })
this.calcShowList(this.data.curLevel, moment(e.detail).format('YYYY-MM-DD'))
},
showPopup() {
this.setData({ show: true })
},
onCancel() {
this.setData({ show: false })
},
onConfirm(e) {
this.setData({ curLevel: e.detail.value, show: false })
this.calcShowList(e.detail.value, this.data.curDate)
},
calcShowList(level = this.data.curLevel, date = this.data.curDate) {
const { dayData } = this;
let nextShowList = []
for (const day of dayData) {
if (day.formatDate === date) {
nextShowList = day.data;
}
}
if (level !== '全部') {
nextShowList = nextShowList.filter(l => l.level === level)
}
this.setData({ showList: nextShowList })
},
// 查看详情
toDetail(e) {
let data = JSON.stringify(e.currentTarget.dataset.item);
wx.navigateTo({
url: '/package/polling/inspectionRecordDetail/inspectionRecordDetail?data=' + encodeURIComponent(data),
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const { minDate, maxDate } = this.data;
Request.get(getPatrolRecord('all', moment(minDate).format('YYYY-MM-DD HH:mm:ss'), moment(maxDate).format('YYYY-MM-DD HH:mm:ss'), true, 'null')).then(res => {
let dayData = new Array(7)
for (let i = 6; i >= 0; i--) {
const month = moment().subtract(i, 'day').month();
const date = moment().subtract(i, 'day').date();
let data = [];
res.forEach(d => {
if (moment(d.inspectionTime).date() === date) {
// 计算此记录的异常等级(检查项中异常最高等级),用于列表展示
let tempLevel = '轻微';
for (const device of d.points.inspectContent) {
if (device.alarm === true) {
for (const item of device.checkItems) {
if (item.level) { tempLevel = item.level; }
}
}
}
data.push({
...d, level: tempLevel,
inspectionTime: moment(d.inspectionTime).format('YYYY-MM-DD HH:mm:ss'),
lastInspectionTime: d.lastInspectionTime ? moment(d.lastInspectionTime).format('YYYY-MM-DD HH:mm:ss') : '--',
})
}
})
dayData[6 - i] = { month, date, formatDate: moment().subtract(i, 'day').format('YYYY-MM-DD'), data }
}
const formatter = (day) => {
const month = day.date.getMonth();
const date = day.date.getDate();
dayData.forEach(d => {
if (d.month === month && d.date === date && d.data.length) {
day.bottomInfo = '🔸'
}
})
return day;
}
this.setData({ formatter });
this.dayData = dayData;
this.calcShowList();
})
},
/**

8
weapp/package/riskManagement/riskCalendar/riskCalendar.json

@ -1,9 +1,11 @@
{
"navigationBarBackgroundColor": "#1979ff",
"navigationBarBackgroundColor": "#006BE3",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "故障日历",
"navigationBarTitleText": "故障风险管理",
"enablePullDownRefresh": false,
"usingComponents": {
"ec-canvas": "../../components/ec-canvas/ec-canvas"
"van-calendar": "@vant/weapp/calendar/index",
"van-picker": "@vant/weapp/picker/index",
"van-popup": "@vant/weapp/popup/index"
}
}

78
weapp/package/riskManagement/riskCalendar/riskCalendar.wxml

@ -1,2 +1,78 @@
<!--package/riskManagement/riskCalendar/riskCalendar.wxml-->
<text>package/riskManagement/riskCalendar/riskCalendar.wxml</text>
<view class="risk-calendar">
<view class="card">
<view class="card-top flex flex-between">
<view class="card-left flex">
<image class="card-icon" src="/images/calendar_icon.png" />
<view class="title">巡检日历</view>
</view>
<view class="card-right">{{curDate}}</view>
<image src="/images/calendar_card_bg.png" class="card-bg" />
</view>
<view class="calendar-box">
<van-calendar
show-mark="{{ false }}"
show-title="{{ false }}"
show-subtitle="{{ false }}"
poppable="{{ false }}"
show-confirm="{{ false }}"
min-date="{{ minDate }}"
max-date="{{ maxDate }}"
formatter="{{ formatter }}"
bind:select="onDateSelect"
row-height="48"
color="#1684FF"
class="calendar"
/>
</view>
</view>
<view class="title-box flex flex-between ">
<view class="title">异常等级</view>
<view class="flex" bind:tap="showPopup">
<view class="picker-text">{{curLevel}}</view>
<image class="arrow" src="/images/arrow_right_blue.svg" />
</view>
</view>
<van-popup show="{{ show }}" round position="bottom" close-on-click-overlay="{{false}}">
<van-picker
show-toolbar
columns="{{ level }}"
bind:cancel="onCancel"
bind:confirm="onConfirm"
/>
</van-popup>
<view wx:for="{{showList}}" class="card" style="margin-bottom: 10px">
<view class="card-top flex flex-between">
<view class="card-left flex">
<image class="card-icon" src="/images/right_icon.png" />
<view class="title">{{item.points.project.name}}</view>
</view>
<view class="card-right-btn flex" data-item="{{item}}" bindtap="toDetail">查看详情</view>
<image src="/images/right_card_bg.png" class="card-bg" />
</view>
<view class="card-content">
<view class="row flex flex-between">
<view class="content-left">本次巡检日期</view>
<view class="content-right">{{item.inspectionTime}}</view>
</view>
<view class="row flex flex-between">
<view class="content-left">点位</view>
<view class="content-right">{{item.points.itemData.name}}</view>
</view>
<view class="row flex flex-between">
<view class="content-left">巡检人</view>
<view class="content-right">{{item.points.user.name}}</view>
</view>
<view class="row flex flex-between">
<view class="content-left">巡检结果</view>
<view class="content-right" style="color: red;">异常</view>
</view>
<view class="row flex flex-between">
<view class="content-left">异常等级</view>
<view class="content-right" style="color: red;">{{item.level}}</view>
</view>
</view>
</view>
</view>

104
weapp/package/riskManagement/riskCalendar/riskCalendar.wxss

@ -1 +1,105 @@
/* package/riskManagement/riskCalendar/riskCalendar.wxss */
.risk-calendar {
height: 100%;
background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%);
padding: 0 15px;
}
.card {
box-sizing: border-box;
background: #FFFFFF;
box-shadow: 2px 2px 11px 0 #00000008, 0 0 4px 0 #00000012;
border-radius: 4px;
}
.card-top {
height: 68px;
background-image: linear-gradient(0deg, #F3F7FF 84%, #DBE6FF 100%);
border-radius: 4px;
position: relative;
}
.card-bg {
position: absolute;
top: 0;
right: 11px;
width: 115px;
height: 67px;
}
.card-left {
margin-left: 23px;
font-weight: 500;
font-size: 16px;
color: #000000d9;
}
.card-right {
margin-right: 18px;
color: #1684FF;
}
.card-right-btn {
width: 76px;
height: 26px;
margin-right: 18px;
background: #1684FF;
border-radius: 16px;
color: #fff;
font-size: 13px;
z-index: 99;
}
.card-icon {
width: 30px;
height: 30px;
margin-right: 8px;
}
.calendar-box {
padding-bottom: 12px;
}
.calendar {
--calendar-height: 300px;
}
.title-box {
margin: 10px 0;
}
.title {
font-weight: 600;
font-size: 16px;
color: #383A3B;
}
.picker-text {
font-weight: 600;
color: #1684FF;
text-align: right;
}
.arrow {
width: 12px;
height: 12px;
transform: rotate(90deg);
margin-left: 9px;
}
.card-content {
padding-bottom: 10px;
}
.row {
margin: 4px 26px;
color: #31373E;
}
.content-left {
font-size: 12px;
}
.content-right {
font-size: 14px;
}

76
weapp/package/riskManagement/riskManagement.js

@ -1,5 +1,8 @@
// package/riskManagement/riskManagement.js
import * as echarts from '../components/ec-canvas/echarts';
import { Request } from "../../common";
import { getPatrolRecordStatistic } from '../../utils/getApiUrl';
import moment from '../../utils/moment';
function setOption(chart, data) {
const option = {
@ -10,17 +13,42 @@ function setOption(chart, data) {
bottom: '3%',
containLabel: true
},
tooltip: {
trigger: 'axis',
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
data: data.map(d => d.month),
boundaryGap: false,
axisLabel: {
formatter: (value) => {
const temp = value.split('-');
return + temp[1] + '月' + '\n ' + temp[0];
}
},
},
yAxis: {
type: 'value'
type: 'value',
splitLine: { // 网格线
lineStyle: {
type: 'dashed'
},
}
},
series: [
{
data: data,
type: 'line'
data: data.map(d => d.count),
type: 'line',
symbol: 'circle',
symbolSize: 5,
itemStyle: {
normal: {
color: '#008AFF',
lineStyle: {
color: '#008AFF',
}
}
},
}
]
};
@ -38,7 +66,12 @@ Page({
lazyLoad: true, // 将 lazyLoad 设为 true 后,需要手动初始化图表
},
isLoaded: false,
list: [1,2,3]
list: [1, 2, 3, 4],
monthAlarmCount: 0,
monthHandleCount: 0,
historyTrend: [],
monthDeviceRank: [],
monthDeviceScoreRank: [],
},
// 初始化图表
@ -65,14 +98,41 @@ Page({
});
},
toCalendar() {
wx.navigateTo({
url: '/package/riskManagement/riskCalendar/riskCalendar',
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const userInfo = wx.getStorageSync("userInfo");
// 请求数据
setTimeout(() => {
this.initChart([250, 300, 100, 147, 260, 123, 311])
}, 1000)
wx.showLoading()
Request.get(getPatrolRecordStatistic(userInfo.structure.join())).then(res => {
wx.hideLoading()
let historyTrend = new Array(12)
for (let i = 11; i >= 0; i--) {
const month = moment().subtract(i, 'month').format('YYYY-MM')
historyTrend[11 - i] = {
month: month,
count: res.historyTrend?.find(h => h.month === month)?.count || 0,
}
}
const monthDeviceRank = [...res.monthDeviceAlarm]
.sort((a, b) => b.abnormalCount - a.abnormalCount)
.map(d => ({ ...d, itemsCount: d.itemsCount.sort((a, b) => b.count - a.count) }))
this.setData({
monthAlarmCount: res.monthAlarmCount,
monthHandleCount: res.monthHandleCount,
historyTrend,
monthDeviceRank: monthDeviceRank,
monthDeviceScoreRank: [...res.monthDeviceAlarm].sort((a, b) => b.abnormalScore - a.abnormalScore),
})
this.initChart(historyTrend)
})
},
/**

54
weapp/package/riskManagement/riskManagement.wxml

@ -1,22 +1,23 @@
<!-- package / riskManagement / riskManagement.wxml -->
<view class="risk-management">
<view class="icon"><text class="icon-text">故障统计</text></view>
<image src="/images/right_bg.png" class="page-bg" />
<view class="icon flex"><text class="icon-text">故障统计</text></view>
<view class="flex flex-between">
<view class="title-item flex flex-col">
<view>本月上报风险</view>
<view><text class="title-num">{{86}}</text><text class="title-unit">个</text></view>
<view><text class="title-num">{{monthAlarmCount}}</text><text class="title-unit">个</text></view>
</view>
<view class="title-item flex flex-col">
<view>故障风险管理</view>
<view><text class="title-num">{{300}}</text><text class="title-unit">个</text></view>
<view>本月处理风险</view>
<view><text class="title-num">{{monthHandleCount}}</text><text class="title-unit">个</text></view>
</view>
</view>
<view class="card">
<view class="flex flex-start">
<!-- <image src="" class="card-img" /> -->
<view class="card-img" style="background: blue;"></view>
<image src="/images/fault_icon.png" class="card-img" />
<view class="card-title">历史风险趋势</view>
</view>
<view class="chart">
@ -26,16 +27,45 @@
<view class="card">
<view class="flex flex-between">
<!-- <image src="" class="card-img" /> -->
<view class="flex">
<view class="card-img" style="background: blue;"></view>
<image src="/images/fault_icon.png" class="card-img" />
<view class="card-title">故障排行榜</view>
</view>
<view class="card-link">查看详情 ></view>
<view class="card-link" bindtap="toCalendar">查看详情 ></view>
</view>
<view style="margin-top: 10px">【故障次数统计】</view>
<view class="list" wx:for="{{monthDeviceRank}}">
<view class="list-title">{{item.deviceName}}</view>
<view class="list-line" />
<view class="list-content flex flex-between">
<view class="content-item content-left">
<view>所属结构物</view>
<view>故障次数</view>
<view>问题概述</view>
</view>
<view class="content-item content-right">
<view>{{item.project}}</view>
<view>{{item.abnormalCount}}次</view>
<view>{{item.itemsCount[0].name}}</view>
</view>
</view>
</view>
<view style="margin-top: 10px">【故障评分统计】</view>
<view class="list" wx:for="{{monthDeviceScoreRank}}">
<view class="list-title">{{item.deviceName}}</view>
<view class="list-line" />
<view class="list-content flex flex-between">
<view class="content-item content-left">
<view>所属结构物</view>
<view>故障评分</view>
<view>等级分布</view>
</view>
<view class="content-item content-right">
<view>{{item.project}}</view>
<view>{{item.abnormalScore}}</view>
<view>严重:{{item.severity}}次,中度{{item.middle}}次,轻微{{item.slight}}次</view>
</view>
</view>
<view>【故障次数统计】</view>
<view class="list" wx:for="{{list}}">
<view class="list-title">设备{{item}}</view>
</view>
</view>
</view>

45
weapp/package/riskManagement/riskManagement.wxss

@ -1,10 +1,18 @@
/* package/riskManagement/riskManagement.wxss */
.risk-management {
height: 100vh;
height: 100%;
background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%);
padding: 0 15px;
}
.page-bg {
position: absolute;
width: 161px;
height: 174px;
opacity: 0.61;
right: 43px;
}
.icon {
width: 61px;
height: 31.86px;
@ -22,6 +30,7 @@
}
.title-item {
margin-top: 50px;
width: 150px;
color: #ffffffd9;
}
@ -43,6 +52,7 @@
border-radius: 4px;
padding: 12px;
margin-top: 12px;
position: relative;
}
.card-img {
@ -73,4 +83,37 @@
margin-top: 10px;
padding: 10px 7px;
background-color: #F1F7FF;
border-radius: 4px;
}
.list-title {
font-weight: 500;
font-size: 15px;
color: #1684FF;
}
.list-line {
width: 100%;
border-top: 1px solid #DAE6F6;
margin: 5px 0;
}
.list-content {
color: #31373E;
}
.content-item {
height: 72px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.content-left {
font-size: 12px;
}
.content-right {
font-size: 14px;
text-align: right;
}

3
weapp/package/subSystem/subSystem.js

@ -141,7 +141,7 @@ Page({
if (res) {
//巡查内容
that.setData({
currentRepairCount: res?.filter(i => i?.patrolRecordIssueHandles[0]?.yanshoushijian && parseInt(moment(i?.patrolRecordIssueHandles[0]?.yanshoushijian).format('YYYYMMDD')) === parseInt(moment().format('YYYYMMDD'))).length || 0,
currentRepairCount: res?.filter(i =>i.patrolRecordIssueHandles.length>0 ).length || 0,
currentPatrolCount: res.length,
level1Count: that.filterLevelCount(res,'轻微')||0,
level2Count: that.filterLevelCount(res,'中度')||0,
@ -160,7 +160,6 @@ Page({
}
})
that.ecComponent = that.selectComponent('#mychart-dom-pie');
console.log('that.level1Count',this.data.level2Count)
var option = {
backgroundColor: "#ffffff",
legend: {

34
weapp/package/subSystem/subSystem.wxml

@ -1,6 +1,7 @@
<!--package/subSystem/subSystem.wxml-->
<view style="background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%); padding: 0 15px; ">
<!--总览图-->
<view style="display: flex;justify-content: space-around;margin-bottom: 10px; padding-top: 10px; background-image: linear-gradient(179deg, #006BE3 0%, #4E87FF 16%, #4e87ff00 93%);">
<view style="display: flex;justify-content: space-around;margin-bottom: 10px; padding-top: 10px; ">
<view>
<view>本月巡检次数</view>
<view class="number">{{currentPatrolCount}}</view>
@ -12,24 +13,29 @@
</view>
<!--饼图-->
<view class="card ">
<ec-canvas id="mychart-dom-pie" canvas-id="mychart-pie" ec="{{ ec }}"></ec-canvas>
<image src="/images/shape1.png" class="imgStyle"></image>
<view class="top">
<view style="display: flex; align-items: center;">
<image style="width: 30px; height: 30px;" src="/images/device.png" />
<text class="fontStyle">故障</text>
</view>
<view class="countStyle">
总数:{{currentPatrolCount}}
</view>
</view>
<view style="height: 300px"> <ec-canvas id="mychart-dom-pie" canvas-id="mychart-pie" ec="{{ ec }}"></ec-canvas>
</view>
</view>
<!--巡检日历-->
<view class="card heightStyle">
<view class="header">
<view style="display:flex;align-items: center;">
<image style="width: 30px; height: 30px;" src="/images/calendar.png" />
<view class="xunjian">巡检日历</view>
</view>
<image src="/images/shape2.png" class="imgStyle"></image>
<view class="yearMonth">{{currentYear+'年'+currentMonth+'月'}}</view>
</view>
<van-calendar
max-date="{{lastDay}}"
v-model="selectedDate"
readonly
show-title="{{false}}"
show-subtitle="{{false}}"
poppable="{{ false }}"
show-confirm="{{ false }}"
default-date="{{selectedDate}}"
formatter="{{formatter}}"
color="#0000FF"
class="calendar" />
<van-calendar max-date="{{lastDay}}" v-model="selectedDate" readonly show-title="{{false}}" show-subtitle="{{false}}" poppable="{{ false }}" show-confirm="{{ false }}" default-date="{{selectedDate}}" formatter="{{formatter}}" color="#0000FF" class="calendar" />
</view>
</view>

44
weapp/package/subSystem/subSystem.wxss

@ -18,13 +18,14 @@
border: 1px solid #ddd;
border-radius: 8px;
/* padding: 10px 10px; */
margin: 5px 5px;
padding: 12px;
margin-top: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
/* width: 300px; */
height: 300px;
background-image: linear-gradient(0deg, #F3F7FF 84%, #DBE6FF 100%);
}
.heightStyle{
height: auto;
height: 100%;
}
.xunjian{
@ -48,9 +49,12 @@
}
.header{
display: flex;
justify-content: space-around;
justify-content: space-between;
margin-bottom: 10px;
margin-top:10px;
align-items: center;
position: relative;
/* margin-left: -22px; */
}
.yellowClass .van-calendar__bottom-info{
@ -62,8 +66,38 @@
font-size: 50px;
color:green;
}
.imgStyle {
position: absolute;
width: 115px;
height: 67px;
right: 0;
}
.top{
display: flex;
justify-content: space-between;
padding: 10px;
}
.fontStyle {
width: 64px;
height: 22px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #000000d9;
letter-spacing: 0;
margin-left: 5px;
}
.countStyle {
width: 89px;
height: 24px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 17px;
color: #1684FF;
letter-spacing: 0;
}

15
weapp/package/troubleshooting/shootingForm/index.js

@ -69,14 +69,20 @@ Page({
points: {
...issue.PatrolRecord.points,
inspectContent: (() => {
// 1.1 版本(2023-10-18)之前 dataList.points.inspectContent 为 Object, 1.1版本因增加点位关联设备改为 Array
const priorToV1_1 = moment(issue.PatrolRecord.inspectionTime).isBefore(moment('2023-10-18'))
if (priorToV1_1) {
let inspectContent = []
for (let k in issue.PatrolRecord.points.inspectContent) {
inspectContent.push({
...issue.PatrolRecord.points.inspectContent[k],
itemName: k
name: k,
})
}
return inspectContent
return [{ checkItems: inspectContent }]
} else {
return issue.PatrolRecord.points.inspectContent
}
})()
}
}
@ -88,6 +94,7 @@ Page({
isRepair: tabIndex == 0 && (issue.state == 4 || issue.state == 7),
isCheck: tabIndex == 0 && issue.state == 5,
})
console.log(nextData, 'nextData')
this.setData({
strucFocusUser: {
...strucUser,
@ -215,8 +222,8 @@ Page({
// 预览图片
previewImg: function (e) {
const { index, itemindex, type } = e.currentTarget.dataset
const imgs = type == 'point' ? this.data.data.PatrolRecord.points.inspectContent[itemindex].imgs : this.data[type];
const { index, itemindex, deviceidx, type } = e.currentTarget.dataset
const imgs = type == 'point' ? this.data.data.PatrolRecord.points.inspectContent[deviceidx].checkItems[itemindex].imgs : this.data[type];
const newImgs = imgs.map(i => this.data.imgServer + i);
wx.previewImage({
current: newImgs[index],

11
weapp/package/troubleshooting/shootingForm/index.wxml

@ -15,10 +15,12 @@
</view>
<van-field value="{{data.PatrolRecord.points.itemData.name}}" label="点位" readonly accordion />
<van-collapse wx:if="{{data.PatrolRecord.points.inspectContent}}" value="{{ pointItemCollapseActiveNames }}" bind:change="onPointItemCollapseActiveChange" border="{{false}}">
<block wx:for="{{data.PatrolRecord.points.inspectContent}}" wx:key="index">
<van-collapse-item wx:if="{{!item.isNormal}}" name="{{item.itemName}}" border="{{false}}">
<block wx:for="{{data.PatrolRecord.points.inspectContent}}" wx:key="index" wx:for-item="device" wx:for-index="deviceidx">
<van-field wx:if="{{device.deviceName && device.alarm}}" value="{{device.deviceName}}" label="设备" readonly accordion />
<block wx:for="{{device.checkItems}}">
<van-collapse-item wx:if="{{!item.isNormal}}" name="{{item.id}}" border="{{false}}">
<view slot="title">
<van-field value="{{item.itemName}}" label="检查项" readonly />
<van-field value="{{item.name}}" label="检查项" readonly />
</view>
<view slot="">
<van-field value="{{item.level}}" label="异常等级" readonly />
@ -28,7 +30,7 @@
<view class="fs-cell-title" style="">现场照片</view>
<view class="fs-cell-content" style="">
<block wx:for="{{item.imgs}}" wx:for-index="imgIndex" wx:for-item="imgUrl" wx:key="imgIndex">
<image style="height:160rpx;width:160rpx;padding-right:12rpx;" src="{{imgServer+imgUrl}}" data-img="{{imgServer+imgUrl}}" data-index="{{imgIndex}}" data-key="{{imgIndex}}" data-itemindex="{{index}}" mode="aspectFill" bindtap="previewImg" data-type="point"></image>
<image style="height:160rpx;width:160rpx;padding-right:12rpx;" src="{{imgServer+imgUrl}}" data-img="{{imgServer+imgUrl}}" data-index="{{imgIndex}}" data-key="{{imgIndex}}" data-itemindex="{{index}}" data-deviceidx="{{deviceidx}}" mode="aspectFill" bindtap="previewImg" data-type="point"></image>
</block>
</view>
</view>
@ -36,6 +38,7 @@
</view>
</van-collapse-item>
</block>
</block>
</van-collapse>
<view class="mission-card-title mission-center-card-title">
<image class="icon" src="../../../images/title_icon.svg"></image>

8
weapp/pages/home/home.js

@ -98,7 +98,7 @@ Page({
//过去七天的所有巡检记录
const sevenDaysList=list?.filter(item=>
{
const recordDate = moment(date, 'YYYY-MM-DD'); // 解析日期
const recordDate = moment(item?.inspectionTime, 'YYYY-MM-DD'); // 解析日期
const daysDifference = moment().diff(recordDate, 'days'); // 计算日期差
// 返回近七天内的记录
return daysDifference >= 0 && daysDifference < 7;
@ -106,10 +106,10 @@ Page({
)||[]
that.setData({
allCount:list.length || 0,
allQuestionCount:list?.filter(i=>i.patrolRecordIssueHandles>0)?.length || 0,
allQuestionCount:list?.filter(i=>i.patrolRecordIssueHandles.length>0)?.length || 0,
allHandleCount:list?.filter(i => i?.patrolRecordIssueHandles[0]?.yanshoushijian && parseInt(moment(i?.patrolRecordIssueHandles[0]?.yanshoushijian).format('YYYYMMDD')) === parseInt(moment().format('YYYYMMDD'))).length || 0,
sevenDaysCount:sevenDaysList.length||0,
sevenDaysQuestionCount:sevenDaysList?.filter(i=>i.patrolRecordIssueHandles>0)?.length || 0,
sevenDaysCount:sevenDaysList?.length||0,
sevenDaysQuestionCount:sevenDaysList?.filter(i=>i.patrolRecordIssueHandles.length>0)?.length || 0,
sevenDaysHandleCount:sevenDaysList?.filter(i => i?.patrolRecordIssueHandles[0]?.yanshoushijian && parseInt(moment(i?.patrolRecordIssueHandles[0]?.yanshoushijian).format('YYYYMMDD')) === parseInt(moment().format('YYYYMMDD'))).length || 0,
pageHeight:pageHeight+'px',
swiperData:data.datas

4
weapp/pages/home/home.wxml

@ -1,9 +1,9 @@
<!--pages/home/home.wxml-->
<view style="height:{{pageHeight}} ; overflow: auto;">
<view style="height:{{pageHeight}} ; overflow: auto; padding: 0 15px;">
<!--轮播图-->
<view class="card">
<swiper class="home-swiper" indicator-dots="true" autoplay="{{true}}" interval="{{2000}}" duration="{{500}}">
<block wx:for-items="{{swiperData}}">
<block wx:for-items="{{swiperData}}" wx:key="*this">
<swiper-item>
<image src="{{item.imgurl}}" class="slide-image" />
</swiper-item>

2
weapp/pages/home/home.wxss

@ -12,7 +12,7 @@
border-radius: 8px;
/* padding: 10px; */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
margin: 10px;
margin-top: 12px;
}
.home-swiper {
width: 95%;

7
weapp/pages/overview/overview.js

@ -45,6 +45,13 @@ Page({
* 生命周期函数--监听页面显示
*/
onShow() {
const userRole = wx.getStorageSync('userRole');
if (userRole && userRole.includes('管理')) {
wx.reLaunch({
url: '/pages/workbench/workbench'
});
return
}
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: 1

4
weapp/pages/workbench/workbench.js

@ -14,7 +14,7 @@ Page({
{
iconPath: '/images/workbench/report.png',
text: '设备大数据图谱',
page: '/package/riskManagement/riskCalendar/riskCalendar'
page: '/package/deviceBigdataGraph/deviceBigdataGraph'
},
{
iconPath: '/images/workbench/report.png',
@ -34,7 +34,7 @@ Page({
{
iconPath: '/images/workbench/issues.png',
text: '发现问题',
page: '/package/riskManagement/riskManagement'
page: '/package/report/report'
},
]
},

5
weapp/project.config.json

@ -43,11 +43,12 @@
"disablePlugins": [],
"outputPath": ""
},
"condition": false
"condition": false,
"ignoreUploadUnusedFiles": true
},
"compileType": "miniprogram",
"libVersion": "2.19.4",
"appid": "wxdd82ae635b22ccdb",
"appid": "wx79ff58f03d17f24d",
"projectname": "miniprogram-92",
"condition": {},
"editorSetting": {

22
weapp/utils/getApiUrl.js

@ -33,6 +33,11 @@ exports.getPatrolPlan = () => {
exports.addPatrolRecord = () => {
return `/patrolRecord/add`
}
//上报问题
exports.reportQuest = () => {
return `/patrolRecord/reportQuest`
}
// 获取巡检记录
exports.getPatrolRecord = (patrolPlanId, startTime, endTime, alarm, pointId) => {
@ -44,6 +49,13 @@ exports.getSubSystemPatrolAbout = (query) => {
return `/patrolRecord/subSystemPatrolAbout?STime=${STime}&ETime=${ETime}&keywords=${keywords}`
}
// 获取点位信息
exports.getPointList = (query) => {
const {keywords } = query;
return `/patrolRecord/pointInfo?keywords=${keywords}`
}
// 获取点位最新一条巡检记录
exports.getdPointCurPatrolRecord = (pointId) => {
return `/patrolRecord/${pointId}/cur`
@ -53,6 +65,11 @@ exports.getdPointCurPatrolRecord = (pointId) => {
exports.getPatrolTemplate = (id) => {
return `/patrolTemplate?id=${id}`
}
//根据结构物获取巡检模板
exports.getTemplates = (query) => {
const {keywords } = query;
return `/patrolRecord/getTemplate?keywords=${keywords}`
}
exports.getPatrolRecordIssueHandle = () => {
return `/patrolRecord/issue/handle`
@ -80,3 +97,8 @@ exports.getPatrolReport = (query) => {
const { projectId, startTime, endTime } = query;
return `/patrolReport?projectId=${projectId}&startTime=${startTime}&endTime=${endTime}`
}
// 故障风险管理统计
exports.getPatrolRecordStatistic = (structures) => {
return `/patrolRecord/statistic?structures=${structures}`
}

8
web/Dockerfile

@ -1,6 +1,6 @@
#FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
COPY . /var/app
COPY ./web/ /var/app
WORKDIR /var/app
EXPOSE 8080
@ -17,15 +17,15 @@ RUN apk update && apk add --no-cache \
giflib-dev \
python \
;
RUN npm config set registry=http://10.8.30.22:7000
RUN npm config set registry=https://nexus.ngaiot.com/repository/fs-npm/
RUN npm cache clean -f
#RUN npm install -g node-gyp
RUN rm -rf package-lock.json
RUN npm install --registry http://10.8.30.22:7000
RUN npm install --registry https://nexus.ngaiot.com/repository/fs-npm/
RUN npm run build
RUN rm -rf client/src
RUN rm -rf node_modules
RUN npm install --production --registry http://10.8.30.22:7000
RUN npm install --production --registry https://nexus.ngaiot.com/repository/fs-npm/
#RUN npm cache clean -f && npm install --production --force --registry http://10.8.30.22:7000
CMD ["-u", "http://localhost:8088"]
ENTRYPOINT [ "node", "server.js" ]

BIN
web/client/assets/images/menu/device.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

4
web/client/src/app.js

@ -9,6 +9,8 @@ import Organization from './sections/organization';
import PatrolManage from './sections/patrolManage';
import IssueHandle from './sections/issueHandle'
import Shouye from './sections/shouye';
import DeviceManage from './sections/deviceManage';
import { Func } from '$utils';
const App = props => {
const { projectName } = props
@ -20,7 +22,7 @@ const App = props => {
return (
<Layout
title={projectName}
sections={[Auth,Shouye, ProjectRegime, Safetymanage, Organization, PatrolManage, IssueHandle]}
sections={[Auth, Shouye, ProjectRegime, Safetymanage, Organization, PatrolManage, IssueHandle, DeviceManage]}
/>
)

56
web/client/src/sections/deviceManage/actions/device.js

@ -0,0 +1,56 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getDeviceList(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query || {},
actionType: 'GET_Device_REPORT',
url: `${ApiTable.getDeviceList}`,
msg: { error: '获取设备列表失败' },
reducer: { name: 'device' }
});
}
export function addDevice(params) {
return (dispatch) => basicAction({
type: 'post',
data: params,
dispatch,
actionType: 'ADD_Device_REPORT',
url: ApiTable.addDevice,
msg: {
option: '设备新增',
},
});
}
export function deleteDevice(id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DELETE_Device_REPORT',
url: ApiTable.modifyDevice.replace('{id}', id),
msg: {
option: '设备删除',
},
});
}
export function modifyDevice(id, params, msg) {
return (dispatch) => basicAction({
type: 'put',
data: params,
dispatch,
actionType: 'MODIFY_Device_REPORT',
url: ApiTable.modifyDevice.replace('{id}', id),
msg: {
option: msg || '设备编辑',
},
});
}

7
web/client/src/sections/deviceManage/actions/index.js

@ -0,0 +1,7 @@
'use strict';
import * as device from './device'
export default {
...device
}

213
web/client/src/sections/deviceManage/components/importDevicesModal.js

@ -0,0 +1,213 @@
'use strict';
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
import { Modal, Form, Button, Upload, message } from 'antd';
import { DownloadOutlined } from '@ant-design/icons';
import XLSX from 'xlsx';
import { DEVICE_TYPES } from './modelModal';
// import porvince from './province.json'
const workerKeys = {
name: '设备名称',
type: '设备类型',
specification: '规格型号',
dateProduced: '生产日期',
dateInstall: '安装时间',
dateGuarantee: '质保期',
dateMainten: '维保期',
}
//下载模板和上传文件读取
const ImportDeviceModal = props => {
const { dispatch, actions, onCancel, onOk, devices } = props;
const { deviceManage } = actions;
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState(false);
const [postData, setPostData] = useState([]);
//初始化
useEffect(() => {
}, []);
const confirm = () => {
if (postData.length) {
setLoading(true)
//导入明细接口通用
dispatch(deviceManage?.addDevice(postData)).then(res => {
if (res.success) {
onOk()
}
setLoading(false)
})
} else {
message.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 })
}
}
const dldCsvMb = () => {
//表头
let head = [];
Object.keys(workerKeys).map(key => {
head.push(workerKeys[key]);
})
head = head.join(',') + "\n";
//数据
//let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
let templateCsv = "data:text/xls;charset=utf-8,\ufeff" + head;
//创建一个a标签
let link = document.createElement("a");
//为a标签设置属性
link.setAttribute("href", templateCsv);
link.setAttribute("download", `设备导入模板.xls`);
//点击a标签
link.click();
}
const download = () => {
dldCsvMb();
}
const judgeTimeValid = (v) => {
let valid = true;
if (v.split('/').length !== 3) {
valid = false;
} else {
let time = new Date(v);
// if (!time) {
// return valid;//可以不填
// }
const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日
if (time instanceof Date) {
let timeStr = moment(time).format('YYYY/MM/DD');
if (!ymd.test(timeStr)) {
valid = false;
}
} else {
valid = false;
}
}
return valid;
}
return (<Modal
title={"导入设备信息"} visible={true}
onOk={confirm} width={520}
confirmLoading={loading}
onCancel={() => {
setMsg('')
setLoading(false)
setPostData([])
onCancel()
}}
>
<Form>
<Upload
action={'/'} accept={'.xls,.xlsx'}
maxCount={1}
onRemove={(currentFile, fileList, fileItem) => {
setMsg('');
setPostData([]);
}}
customRequest={async (data) => {
const { file, onSuccess, onError } = data
const error = (msg) => {
setMsg(msg)
onError({ message: msg })
}
let isLt = file.size / 1024 / 1024 < 200
if (!isLt) {
error(`文件最大不超过200M`);
return;
}
const reader = new FileReader();
reader.onload = function (e) {
const data = e.target.result;
const workbook = XLSX.read(data, { type: 'binary', codepage: 936 });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
//XLSX.utils.sheet_to_json解析 ------把身份证号和手机号变成 字符串------
Object.keys(worksheet).forEach(k => {
worksheet[k].w ? worksheet[k].v = worksheet[k].w : ''
})
//------------------end------------------
const res = XLSX.utils.sheet_to_json(worksheet);
if (res.length > 1000) {
error('一次性上传数据行数应小于1000行,请分批上传')
return
}
if (!res.length) {
error('请填写至少一行数据')
return
}
let postData = []
for (let i = 0; i < res.length; i++) {
let d = res[i]
let obj = {};
Object.keys(workerKeys).map(key => {
obj[key] = d[workerKeys[key]] || null;
})
//必填项
let notNullKeys = ['name', 'type', 'specification', 'dateProduced', 'dateGuarantee', 'dateMainten', 'dateInstall']
for (let k = 0; k < notNullKeys.length; k++) {
let key = notNullKeys[k];
if (!obj[key]) {
error(`${i + 2}行【${workerKeys[key]}】不能为空`)
return
}
}
//判断设备类型
let ext = DEVICE_TYPES.find(m => m == obj.type);
if (!ext) {
error(`${i + 2}行的【设备类型】错误,请填写【${DEVICE_TYPES.toString()}】中的一种`)
return
}
let tValid = judgeTimeValid(obj.dateProduced) && judgeTimeValid(obj.dateGuarantee)
&& judgeTimeValid(obj.dateMainten) && judgeTimeValid(obj.dateInstall);
if (!tValid) {
error(`${i + 2}行【日期格式】错误,请填写yyyy/mm/dd格式`)
return;
}
let dateInstallValid = moment(obj.dateInstall).valueOf() > moment().startOf('d').add(1, 'd').valueOf();
if (dateInstallValid) {
error(`${i + 2}行【安装日期】不能填写今天之后的时间`)
return;
}
postData.push(obj)
}
setPostData(postData)
let msg = '文件解析完成,点击确定按钮上传保存!'
setMsg(msg)
onSuccess({ message: msg })
}
reader.readAsBinaryString(file);
}}>
<Button icon={<DownloadOutlined />} theme="light">
请选择文件
</Button>
</Upload>
<span>{msg}</span>
<div style={{ color: '#ccc', marginTop: 20 }}>最大不超过200M导入文件需与
<span onClick={() => download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板</span>
一致</div>
</Form>
</Modal>)
}
function mapStateToProps(state) {
const { auth, global } = state;
return {
user: auth.user,
actions: global.actions,
}
}
export default connect(mapStateToProps)(ImportDeviceModal);

111
web/client/src/sections/deviceManage/components/modelModal.js

@ -0,0 +1,111 @@
import React, { useRef } from 'react';
import { Button, Form } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import {
ModalForm,
ProFormSelect,
ProFormText,
ProFormDatePicker
} from '@ant-design/pro-form';
import moment from 'moment';
export default (props) => {
const { title, triggerRender, editData = null, onFinish, devices } = props;
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
const initialValues = editData ? {
...editData,
} : {};
const [form] = Form.useForm();
const formRef = useRef();
const disabledDate = (value) => {
return value.valueOf() > moment().startOf('d').add(1, 'd').valueOf();
}
return (
<ModalForm
formRef={formRef}
title={title || ''}
initialValues={initialValues}
trigger={
triggerRender ? triggerRender : <Button type="primary" >
{title || ''}
</Button>
}
layout="horizontal"
grid={true}
{...formItemLayout}
modalProps={{
destroyOnClose: true,
onCancel: () => { },
}}
onFinish={async (values) => {
return onFinish && await onFinish(values, editData, form)
// return true;
}}
width={500}
>
<ProFormText
rules={[
{ required: true, message: '请输入设备名称' },
{ max: 255, message: '设备名称长度不能大于255个字符' },
]}
name="name"
label="设备名称"
/>
<ProFormSelect
rules={[{ required: true, message: '请选择设备类型' }]}
options={
DEVICE_TYPES
.map(s => {
return { label: s, value: s }
})
}
name="type"
label="设备类型"
/>
<ProFormText
rules={[
{ required: true, message: '请输入规格型号' },
{ max: 20, message: '规格型号长度不能大于20个字符' },
]}
name="specification"
label="规格型号"
/>
<ProFormDatePicker
rules={[{ required: true, message: '请输入生产日期' }]}
name="dateProduced"
label="生产日期"
fieldProps={
{ disabledDate: disabledDate }
}
/>
<ProFormDatePicker
rules={[{ required: true, message: '请输入安装日期' }]}
name="dateInstall"
label="安装日期"
fieldProps={
{ disabledDate: disabledDate }
}
/>
<ProFormDatePicker
rules={[{ required: true, message: '请输入质保期' }]}
name="dateGuarantee"
label="质保期"
/>
<ProFormDatePicker
rules={[{ required: true, message: '请输入维保期' }]}
name="dateMainten"
label="维保期"
/>
</ModalForm>
);
};
export const DEVICE_TYPES = ['安防系统', '厨房系统', '电梯', '供电系统', '空调', '排水系统', '水系统', '通道门禁',
'通风系统', '通信系统', '显示视频', '消防系统', '照明系统']

0
web/client/src/sections/deviceManage/constants/index.js

254
web/client/src/sections/deviceManage/containers/deviceManage.js

@ -0,0 +1,254 @@
import React, { useEffect, useState } from 'react'
import { Spin, Popconfirm, message, Button, Input } from 'antd';
import { connect } from 'react-redux';
import ProTable from '@ant-design/pro-table';
import DeviceModal from '../components/modelModal'
import moment from 'moment';
import ImportDeviceModal from '../components/importDevicesModal'
function DeviceManagement(props) {
const { loading, clientHeight, actions, dispatch, devices } = props;
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [rowSelected, setRowSelected] = useState([])
const [showImportModal, setShowImportModal] = useState(false);
const [name, setName] = useState();
const queryData = (search) => {
const query = {
limit: search ? 10 : pageSize || 10,
page: search ? 1 : currentPage || 1,
name: name
}
dispatch(actions.deviceManage.getDeviceList(query));
}
useEffect(() => {
queryData();
}, [pageSize, currentPage]);
const handleDelete = (id) => {
dispatch(actions.deviceManage.deleteDevice(id)).then(() => {
queryData();
setRowSelected([])
});
};
const onFinish = async (values, editData) => {
if (editData) {
const dataToSave = { ...values }
return dispatch(
actions.deviceManage.modifyDevice(editData.id, dataToSave, values?.msg || ''),
).then((res) => {
if (res.success) {
queryData();
return true;
} else {
return false;
}
});
}
return dispatch(actions.deviceManage.addDevice({
...values,
})).then(res => {
if (res.success) {
queryData();
return true;
} else {
return false;
}
});
};
const columns = [
{
title: '设备名称',
dataIndex: 'name',
ellipsis: true,
},
{
title: '设备类型',
dataIndex: 'type',
ellipsis: true,
},
{
title: '规格型号',
dataIndex: 'specification',
ellipsis: true,
},
{
title: '生产日期',
dataIndex: 'dateProduced',
ellipsis: true,
},
{
title: '安装时间',
dataIndex: 'dateInstall',
ellipsis: true,
},
{
title: '质保期',
dataIndex: 'dateGuarantee',
ellipsis: true,
search: false,
},
{
title: '维保期',
dataIndex: 'dateMainten',
ellipsis: true,
},
{
title: '设备投入使用时长',
dataIndex: 'length',
ellipsis: true,
render: (text, record) => {
const start = moment(record?.dateInstall);
const end = moment();
const days = end.diff(start, 'days');
return days + '天'
}
},
{
title: '操作',
width: 160,
key: 'option',
valueType: 'option',
render: (text, record) => {
const options = [];
options.push(<DeviceModal
triggerRender={<a>编辑</a>}
editData={record}
title="编辑设备"
onFinish={onFinish}
key="editModel"
/>)
options.push(
<Popconfirm
key="del"
placement="top"
title="是否确认删除该设备?"
onConfirm={() => handleDelete(record.id)}
okText="是"
cancelText="否"
>
<a>删除</a>
</Popconfirm>)
return options;
},
},
];
return <div id='patrol-record' className='global-main'>
<Spin spinning={loading}>
<div style={{ marginBottom: 19 }}>
<div className='top' style={{ marginBottom: 19 }}>
<div className='title'>
<span className='line'></span>
<span className='cn'>设备管理</span>
<span className='en'>&nbsp;DEVICE</span>
</div>
<div>
<DeviceModal
triggerRender={<Button type='primary'>新建</Button>}
title="新建设备"
onFinish={onFinish}
key="addModel"
/>
<Button type="primary" style={{ marginRight: 10, marginLeft: 10 }} onClick={() => { setShowImportModal(true) }}>批量新增</Button>
<Popconfirm title="确认删除?" onConfirm={() => {
rowSelected?.length > 0 ? handleDelete(rowSelected?.toString()) : message.warning('请先选择要删除的设备')
}}>
<Button>批量删除</Button>
</Popconfirm>
<Input onChange={e => setName(e?.target?.value)} style={{ width: '13vw', marginLeft: 20, marginRight: 10 }} />
<Button type="primary" onClick={() => {
setPageSize(10)
setCurrentPage(1)
queryData(true)
}}>查询</Button>
</div>
</div>
</div>
<ProTable
columns={columns}
rowKey="id"
dateFormatter="string"
scroll={
{
scrollToFirstRowOnChange: true,
y: clientHeight - 260
}
}
pagination={{
size: 'large',
total: devices?.count,
showSizeChanger: true,
// showQuickJumper: true,
current: currentPage,
pageSize: pageSize || 10,
pageSizeOptions: [10, 20, 50],
showTotal: (total) => {
return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / pageSize)}页,${total}`}</span>
},
onShowSizeChange: (currentPage, pageSize) => {
setCurrentPage(currentPage);
setPageSize(pageSize);
},
onChange: (page, pageSize) => {
setCurrentPage(page);
setPageSize(pageSize);
}
}}
dataSource={devices?.rows || []}
rowSelection={{
selectedRowKeys: rowSelected,
onChange: (selectedRowKeys) => {
setRowSelected(selectedRowKeys);
},
getCheckboxProps: (record) => {
return {
disabled: record.username === 'SuperAdmin',
}
}
}}
options={false}
search={false}
/>
{showImportModal && <ImportDeviceModal
devices={devices?.rows || []}
onCancel={() => {
setShowImportModal(false)
}}
onOk={() => {
setShowImportModal(false)
queryData()
}}
/>}
</Spin>
</div>
}
function mapStateToProps(state) {
const {
auth, global, device
} = state;
return {
loading: device.isRequesting,
clientHeight: global.clientHeight,
actions: global.actions,
devices: device?.data || {}
};
}
export default connect(mapStateToProps)(DeviceManagement);

5
web/client/src/sections/deviceManage/containers/index.js

@ -0,0 +1,5 @@
'use strict';
import DeviceManage from './deviceManage'
export { DeviceManage };

0
web/client/src/sections/deviceManage/containers/style.css

0
web/client/src/sections/deviceManage/containers/style.less

15
web/client/src/sections/deviceManage/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: 'deviceManage',
name: '设备管理',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

13
web/client/src/sections/deviceManage/nav-item.js

@ -0,0 +1,13 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Menu } from 'antd';
import { Func } from '$utils';
export function getNavItem(user, dispatch) {
return (
<Menu.Item icon={<img src='/assets/images/menu/device.png' style={{ width: 24, height: 24 }} />}
key="deviceManage">
<Link to="/deviceManage">设备管理</Link>
</Menu.Item>
);
}

5
web/client/src/sections/deviceManage/reducers/index.js

@ -0,0 +1,5 @@
'use strict';
export default {
}

13
web/client/src/sections/deviceManage/routes.js

@ -0,0 +1,13 @@
'use strict';
import { DeviceManage } from './containers';
export default [{
type: 'inner',
route: {
path: '/deviceManage',
key: 'deviceManage',
breadcrumb: '设备管理',
component: DeviceManage,
}
}];

33
web/client/src/sections/issueHandle/components/isuue-handle-mdal.js

@ -70,28 +70,38 @@ export default (props) => {
<div className="item-title">{"问题详情"}</div>
<Row>
<Col span={6}>
<Col span={12}>
<Form.Item label="点位名称:" {...formItemLayout}>
<Input value={editData?.points?.itemData?.name} readOnly />
</Form.Item>
</Col>
<Col span={18}>
</Row>
<Row>
<Col span={24}>
{
editData?.points?.inspectContent && Object.keys(editData?.points?.inspectContent).map(key => {
if (editData?.points?.inspectContent[key]?.isNormal == false) {
editData?.points?.inspectContent && Array.isArray(editData?.points?.inspectContent) &&
editData?.points?.inspectContent?.map(s => {
if (s?.alarm == true) {
return <>
{s?.deviceName && <Col span={12}>
<Form.Item label="设备名称:" {...formItemLayout}>
<Input value={s?.deviceName} readOnly />
</Form.Item>
</Col>}
{
s?.checkItems?.map(k => {
return <Row style={{ marginBottom: 15 }}>
<Col span={12}>
<Form.Item label="检查项:" {...formItemLayout}>
<Input value={key} readOnly />
<Input value={k.name} readOnly />
</Form.Item>
<Form.Item label="异常等级:" {...formItemLayout}>
<Input value={editData?.points?.inspectContent[key]?.level} readOnly />
<Input value={k?.level} readOnly />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="问题描述:" {...formItemLayout}>
<Input value={editData?.points?.inspectContent[key]?.msgInp} readOnly title={222} />
<Input value={k?.msgInp} readOnly title={222} />
</Form.Item>
<Form.Item label="现场图片:" {...formItemLayout}>
@ -106,7 +116,7 @@ export default (props) => {
defaultValue={
(() => {
let nextV = []
for (let s of (editData?.points?.inspectContent[key].imgs || [])) {
for (let s of (k.imgs || [])) {
if (s) {
nextV.push({
storageUrl: s
@ -121,6 +131,11 @@ export default (props) => {
</Form.Item>
</Col>
</Row>
})
}
</>
}
})
}

2
web/client/src/sections/issueHandle/containers/patrolRecord.js

@ -119,7 +119,7 @@ const PatrolRecord = (props) => {
dataIndex: 'source',
key: 'source',
showInDetail: true,
render: (text, record, index) => '巡检上报' //暂定巡检上报 后续会增加平台录入
render: (text, record, index) => record?.patrolPlanId == -1 ? '主动上报' : '巡检上报' //暂定巡检上报 后续会增加平台录入
},
{
title: '严重等级',

89
web/client/src/sections/patrolManage/containers/patrolRecord.js

@ -43,7 +43,7 @@ const PatrolRecord = (props) => {
width: '10%',
showInDetail: true,
render: (text, record, index) => {
return !record.points?.project ? '' : <div>{record.points.project.name}</div>
return !record.points?.project ? '--' : <div>{record.points.project.name}</div>
}
}, {
title: '巡检计划',
@ -52,7 +52,7 @@ const PatrolRecord = (props) => {
width: '10%',
showInDetail: true,
render: (text, record, index) => {
return !record.patrolPlan ? '' : <div>{record.patrolPlan.name}</div>
return !record.patrolPlan ? '--' : <div>{record?.patrolPlan?.name || '-'}</div>
}
}, {
title: '巡检点位',
@ -61,7 +61,7 @@ const PatrolRecord = (props) => {
showInDetail: true,
width: '10%',
render: (text, record, index) => {
return !record.points?.user ? '' : <div>{record.points.itemData.name}</div>
return !record.points?.user ? '--' : <div>{record.points.itemData.name}</div>
}
}, {
title: '巡检人',
@ -70,7 +70,7 @@ const PatrolRecord = (props) => {
showInDetail: true,
width: '10%',
render: (text, record, index) => {
return !record.points?.user ? '' : <div>{record.points.user.name}</div>
return !record.points?.user ? '--' : <div>{record.points.user.name}</div>
}
}, {
title: '巡检单位',
@ -79,7 +79,7 @@ const PatrolRecord = (props) => {
key: 'type',
width: '10%',
render: (text, record, index) => {
return !record.points?.user ? '' : <div>{record.points.user.department.name}</div>
return !record.points?.user ? '--' : <div>{record.points.user.department.name}</div>
}
}, {
title: '巡检频次',
@ -88,7 +88,7 @@ const PatrolRecord = (props) => {
showInDetail: true,
width: '10%',
render: (text, record, index) => {
return !record.points ? '' : <div>{record.points.frequency}</div>
return !record.points ? '--' : <div>{record.points.frequency || '-'}</div>
}
}, {
title: '上次巡检日期',
@ -207,22 +207,83 @@ const PatrolRecord = (props) => {
}
})
if (modelData && modelData.points && modelData.points.inspectContent) {
if (modelData?.points?.itemData?.pointDevices?.length > 0) {
let inspectContent = modelData.points.inspectContent
for (let k in inspectContent) {
inspectContent?.map(s => {
dataArr.push(
<>
<Row>
<Col span={5} style={{}}>{k} </Col>
<Col span={5} style={{}}>{s?.deviceName} </Col>
<Col span={19}>
{
inspectContent[k].isNormal ? '正常' :
!s.alarm ? '正常' :
<Collapse ghost style={{ padding: 0 }}>
<Panel header="异常" key="1" style={{ padding: 0 }}>
{s?.checkItems?.map(k => {
return <>
<Row style={{ fontWeight: 'bold' }}>{k.name}:</Row>
<Row style={{ marginBottom: 20 }}>
<Col span={4} style={{}}>巡查详情 </Col>
<Col span={20}>{k.msgInp}</Col>
<Col span={4} style={{}}>严重等级 </Col>
<Col span={20}>{k.level}</Col>
<Col span={4} style={{}}>现场照片 </Col>
<Col span={20}>
<Uploads
listType='picture-card'
uploadType='project'
maxFilesNum={1}
maxFileSize={10}
isQiniu={true}
disabled={true}
fileTypes={["png", "jpg"]}
defaultValue={
(() => {
let nextV = []
for (let s of (k.imgs || [])) {
if (s) {
nextV.push({
storageUrl: s
})
}
}
return nextV
})()
}
/>
</Col>
</Row>
</>
})}
</Panel>
</Collapse>
}
</Col>
</Row>
</>
)
})
} else {
let inspectContent = modelData.points.inspectContent
for (let v in inspectContent) {
inspectContent[v]?.checkItems?.map(k => {
dataArr.push(
<>
<Row>
<Col span={5} style={{}}>{k?.name} </Col>
<Col span={19}>
{
!inspectContent[v].alarm ? '正常' :
<Collapse ghost style={{ padding: 0 }}>
<Panel header="异常" key="1" style={{ padding: 0 }}>
<Row>
<Col span={4} style={{}}>巡查详情 </Col>
<Col span={20}>{inspectContent[k].msgInp}</Col>
<Col span={20}>{k.msgInp}</Col>
<Col span={4} style={{}}>严重等级 </Col>
<Col span={20}>{inspectContent[k].level}</Col>
<Col span={20}>{k.level}</Col>
<Col span={4} style={{}}>现场照片 </Col>
<Col span={20}>
<Uploads
@ -236,7 +297,7 @@ const PatrolRecord = (props) => {
defaultValue={
(() => {
let nextV = []
for (let s of (inspectContent[k].imgs || [])) {
for (let s of (k.imgs || [])) {
if (s) {
nextV.push({
storageUrl: s
@ -256,7 +317,11 @@ const PatrolRecord = (props) => {
</Row>
</>
)
})
}
}
}
return dataArr
})()}

12
web/client/src/sections/projectRegime/components/pointModel.js

@ -7,7 +7,7 @@ import Uploads from '$components/Uploads';
import { useEffect } from 'react';
import moment from 'moment';
const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, qrCodeId }) => {
const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, qrCodeId, devices }) => {
const { projectRegime } = actions
const [showBaiduMap, setShowBaiduMap] = useState(false)
@ -172,6 +172,16 @@ const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, q
initialValue={modelData?.equipmentModel}>
<Input placeholder="请输入设备型号" allowClear />
</Form.Item>
<Form.Item label='设备绑定' name="devices" style={{}}
initialValue={modelData?.pointDevices?.map(s => s?.deviceId) || []}>
<Select mode="multiple">
{
devices?.map(s => <Select.Option
disabled={s?.pointDevices?.length > 0 && !s?.pointDevices?.find(x => x.pointId == modelData?.id)}
value={s.id} >{s?.name}</Select.Option>)
}
</Select>
</Form.Item>
<Form.Item
label="点位图片"
name='img'

32
web/client/src/sections/projectRegime/components/projectAddModel.js

@ -6,11 +6,15 @@ import { connect } from 'react-redux';
import Uploads from '$components/Uploads';
import { useEffect } from 'react';
// import moment from 'moment';
const type_options = [
{ value: '桥梁', label: '桥梁' },
{ value: '隧道', label: '隧道' },
{ value: '管廊', label: '管廊' }]
const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, firmList }) => {
const { projectRegime } = actions
const [showBaiduMap, setShowBaiduMap] = useState(false)
const [type, setType] = useState(modelData?.type || type_options[0])
const [form] = Form.useForm();
useEffect(() => {
@ -103,15 +107,33 @@ const ProjectAddModel = ({ dispatch, actions, user, modelData, close, success, f
// rules={[{ required: true, message: '请选择结构物类型' },]}
>
<Select
onChange={value => { setType(value) }}
bordered={false}
allowClear
options={[
{ value: '桥梁', label: '桥梁' },
{ value: '隧道', label: '隧道' },
{ value: '管廊', label: '管廊' }]}
options={type_options}
/>
</Form.Item>
</div>
{type == '管廊' && <Form.Item label='子系统' name="subType"
initialValue={modelData?.subType || '指挥中心'}
rules={[{ required: true, message: '请选择子系统' }]}
>
<Select
bordered={false}
options={[
{ value: '指挥中心', label: '指挥中心' },
{ value: '管廊本体', label: '管廊本体' },
{ value: '电梯系统', label: '电梯系统' },
{ value: '供配电系统', label: '供配电系统' },
{ value: '防雷与接地系统', label: '防雷与接地系统' },
{ value: '燃气仓', label: '燃气仓' },
{ value: '给水仓', label: '给水仓' },
{ value: '电气仓', label: '电气仓' },
{ value: '安防系统', label: '安防系统' },
{ value: '高压电力仓', label: '高压电力仓' },
]}
/>
</Form.Item>}
<div style={{ position: 'relative' }}>
<Form.Item label="所在地区:" labelCol={{ span: 9 }} labelAlign='right' name="longitude" style={{ display: 'inline-block', width: 'calc(60% - 50px)', }}
rules={[{ required: true, message: '', }, {

7
web/client/src/sections/projectRegime/containers/point.js

@ -5,7 +5,7 @@ import '../style.less';
import PointModel from '../components/pointModel'
const Information = (props) => {
const { dispatch, actions } = props
const { dispatch, actions, devices } = props
const { projectRegime } = actions
const [tableList, settableList] = useState([])
const [addModel, setAddModel] = useState(false)
@ -28,6 +28,7 @@ const Information = (props) => {
const projectList = (obj) => {
const { limit, page } = obj
dispatch(actions.deviceManage.getDeviceList());
dispatch(projectRegime.positionList({ limit, page: 0, projectId: qrCodeId })).then(res => {
if (res.success) {
let data = []
@ -166,6 +167,7 @@ const Information = (props) => {
<PointModel
modelData={modelData}
qrCodeId={qrCodeId}
devices={devices}
close={() => {
setAddModel(false)
setModelData({})
@ -183,10 +185,11 @@ const Information = (props) => {
}
function mapStateToProps(state) {
const { auth, global } = state;
const { auth, global, device } = state;
return {
user: auth.user,
actions: global.actions,
devices: device?.data?.rows || []
};
}

5
web/client/src/utils/webapi.js

@ -139,6 +139,11 @@ export const ApiTable = {
getProjectPoints: 'project/{projectId}/all/points',
getDeployPoints: 'picture/{pictureId}/deploy/points',
setDeployPoints: 'set/picture/{pictureId}/deploy/points',
//设备管理
getDeviceList: 'device',
addDevice: 'device',
modifyDevice: 'device/{id}',
};
export const RouteTable = {

Loading…
Cancel
Save