Browse Source

自动化报表

dev
wenlele 1 year ago
parent
commit
30e2b43cd0
  1. 252
      api/app/lib/controllers/service/report.js
  2. 70
      api/app/lib/models/reportFile.js
  3. 151
      api/app/lib/models/report_automatic.js
  4. 32
      api/app/lib/routes/service/report.js
  5. 30
      script/3.6/schema/1.create_report_file copy.sql
  6. 26
      script/3.6/schema/2.create_report_automatic.sql
  7. 2
      web/client/src/layout/components/header/index.jsx
  8. 3
      web/client/src/sections/service/actions/index.js
  9. 105
      web/client/src/sections/service/actions/report.js
  10. 318
      web/client/src/sections/service/components/automatic-Modal.jsx
  11. 140
      web/client/src/sections/service/components/fileModal.jsx
  12. 770
      web/client/src/sections/service/containers/automaticReport.jsx
  13. 171
      web/client/src/sections/service/containers/reportFile.jsx
  14. 10
      web/client/src/sections/service/nav-item.jsx
  15. 7
      web/client/src/utils/webapi.js

252
api/app/lib/controllers/service/report.js

@ -0,0 +1,252 @@
'use strict';
const moment = require('moment');
async function postReportFile (ctx) {
try {
const { models } = ctx.fs.dc;
const data = ctx.request.body
await models.ReportFile.create(data)
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function getReportFile (ctx) {
try {
const { models } = ctx.fs.dc;
const { limit, page, projectId } = ctx.query;
const { userInfo } = ctx.fs.api;
let options = {
where: {},
order: [['startTime', 'desc']]
}
if (limit || page) {
options.limit = Number(limit)
options.page = Number(page) * Number(limit)
}
if (projectId) {
options.where.projectId = projectId
}
let res = await models.ReportFile.findAndCountAll(options);
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function delReportFile (ctx) {
try {
const { models } = ctx.fs.dc;
const { id } = ctx.params
await models.ReportFile.destroy({
where: {
id: id
}
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function getFactorList (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse, utils: { anxinStrucIdRange } } = ctx.app.fs
const { userInfo } = ctx.fs.api;
const { pepProjectId } = ctx.query
let anxinStruc = await anxinStrucIdRange({
ctx, pepProjectId
})
if (anxinStruc.length) {
const anxinStrucIds = anxinStruc.map(a => a.strucId)
let factorProto = [1002, 1001, 4009, 2001, 3001, 4004, 5002, 4001, 4002, 4008, 4007, 1004]
const factor = anxinStrucIds.length ? await clickHouse.anxinyun.query(`
SELECT
id,name,proto,
t_structure_factor.structure AS structure
FROM t_factor
INNER JOIN t_structure_factor
ON t_structure_factor.factor = t_factor.id
AND t_structure_factor.structure IN (${anxinStrucIds.join(',')}, -1)
WHERE
t_factor.proto IN (${factorProto.join(',')}, -1)
`).toPromise() : []
const factorId = factor.map(a => a.id)
const sensor = factorId.length ? await clickHouse.anxinyun.query(`
SELECT
id,name,factor
FROM t_sensor
WHERE
t_sensor.factor IN (${factorId.join(',')}, -1)
`).toPromise() : []
// WSDJC(温湿度监测) 1002
// FSFXJC(风速风向监测) 1001
// SSFJC(伸缩缝监测) 4009
// SLJC(索力监测) 2001
// YBJC(应力应变监测) 3001
// NDJC(挠度监测) 4004
// ZDJC(振动监测) 5002
// CLZHJC(车辆载荷监测)
// ZZWYJC(支座位移监测) 4001
// QTPWJC(桥塔偏位监测) 4002
// LFJC(裂缝监测) 4008
// QDQXJC(桥墩倾斜监测) 4007
// JGWDJC(结构温度监测) 1004
anxinStruc.forEach(s => {
s.factor = factor.filter(d => {
if (d.structure == s.strucId) {
d.sensor = sensor.filter(f => f.factor == d.id)
return true
} else {
return false
}
})
})
ctx.status = 200;
ctx.body = anxinStruc
} else {
ctx.status = 200;
ctx.body = []
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function postAutomaticReport (ctx) {
try {
const { models } = ctx.fs.dc;
const data = ctx.request.body
if (data.id) {
await models.ReportAutomatic.update(data, {
where: {
id: data.id
}})
} else {
await models.ReportAutomatic.create(data)
}
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function getAutomaticReport (ctx) {
try {
const { models } = ctx.fs.dc;
const { limit, page, projectId, keyword } = ctx.query;
const { userInfo } = ctx.fs.api;
let options = {
where: {},
order: [['time', 'desc']]
}
if (limit || page) {
options.limit = Number(limit)
options.page = Number(page) * Number(limit)
}
if (projectId) {
options.where.projectId = { $in: String(projectId).split(',') }
}
if (keyword) {
options.where.reportName = { $iLike: `%${keyword}%` }
}
let res = await models.ReportAutomatic.findAndCountAll(options);
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function delAutomaticReport (ctx) {
try {
const { models } = ctx.fs.dc;
const { id } = ctx.params
await models.ReportAutomatic.destroy({
where: {
id: id
}
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
module.exports = {
getReportFile,
postReportFile,
delReportFile,
getFactorList,
postAutomaticReport,
getAutomaticReport,
delAutomaticReport
};

70
api/app/lib/models/reportFile.js

@ -0,0 +1,70 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const ReportFile = sequelize.define("reportFile", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "id",
primaryKey: true,
field: "id",
autoIncrement: true
},
projectId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "项目id",
primaryKey: false,
field: "project_id",
autoIncrement: false
},
fileName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件名称",
primaryKey: false,
field: "file_name",
autoIncrement: false
},
url: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件路径",
primaryKey: false,
field: "url",
autoIncrement: false
},
reportType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "报表类型",
primaryKey: false,
field: "report_type",
autoIncrement: false
},
startTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "开始时间",
primaryKey: false,
field: "start_time",
autoIncrement: false
},
}, {
tableName: "report_file",
comment: "",
indexes: []
});
dc.models.ReportFile = ReportFile;
return ReportFile;
};

151
api/app/lib/models/report_automatic.js

@ -0,0 +1,151 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const ReportAutomatic = sequelize.define("reportAutomatic", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "id",
primaryKey: true,
field: "id",
autoIncrement: true
},
reportName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "report_name",
autoIncrement: false
},
projectId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "项目id",
primaryKey: false,
field: "project_id",
autoIncrement: false
},
projectName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "project_name",
autoIncrement: false
},
reportType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "报表类型",
primaryKey: false,
field: "report_type",
autoIncrement: false
},
reportPicPath: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "reportpic_path",
autoIncrement: false
},
framer: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "framer",
autoIncrement: false
},
auditor: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "auditor",
autoIncrement: false
},
ratifier: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "ratifier",
autoIncrement: false
},
structId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "struct_id",
autoIncrement: false
},
projectOverview: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "project_overview",
autoIncrement: false
},
reportStartTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "开始时间",
primaryKey: false,
field: "report_start_time",
autoIncrement: false
},
reportEndTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "开始时间",
primaryKey: false,
field: "report_end_time",
autoIncrement: false
},
time: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "time",
autoIncrement: false
},
factors: {
type: DataTypes.JSON,
allowNull: false,
defaultValue: null,
comment: "",
primaryKey: false,
field: "factors",
autoIncrement: false
},
}, {
tableName: "report_automatic",
comment: "",
indexes: []
});
dc.models.ReportAutomatic = ReportAutomatic;
return ReportAutomatic;
};

32
api/app/lib/routes/service/report.js

@ -0,0 +1,32 @@
'use strict';
const report = require('../../controllers/service/report');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/report/file'] = { content: '获取服务记录列表', visible: true };
router.get('/report/file', report.getReportFile);
app.fs.api.logAttr['POST/report/file'] = { content: '上传文件', visible: true };
router.post('/report/file', report.postReportFile);
app.fs.api.logAttr['DEL/report/file/:id'] = { content: '删除报表文件', visible: true };
router.del('/report/file/:id', report.delReportFile);
app.fs.api.logAttr['GET/factor/list'] = { content: '获取监测因素信息', visible: true };
router.get('/factor/list', report.getFactorList);
app.fs.api.logAttr['POST/automatic/report'] = { content: '新增/编辑报表生成规则', visible: true };
router.post('/automatic/report', report.postAutomaticReport);
app.fs.api.logAttr['GET/automatic/report'] = { content: '获取报表生成规则', visible: true };
router.get('/automatic/report', report.getAutomaticReport);
app.fs.api.logAttr['DEL/automatic/report/:id'] = { content: '删除报表规则', visible: true };
router.del('/automatic/report/:id', report.delAutomaticReport);
// app.fs.api.logAttr['GET/respond-record'] = { content: '获取响应记录数据', visible: true };
// router.get('/respond-record', record.respondRecord);
};

30
script/3.6/schema/1.create_report_file copy.sql

@ -0,0 +1,30 @@
create table report_file
(
-- Only integer types can be auto increment
id serial not null,
file_name varchar(255) not null,
project_id int not null,
url varchar(1024) not null,
start_time timestamp not null,
report_type varchar(255) not null
);
comment on table report_file is '报表文件';
comment on column report_file.file_name is '报表文件名';
comment on column report_file.project_id is '运维项目id';
comment on column report_file.url is '文件路径';
comment on column report_file.start_time is '产生时间';
comment on column report_file.report_type is '报表类型';
create unique index report_file_id_uindex
on report_file (id);
alter table report_file
add constraint report_file_pk
primary key (id);

26
script/3.6/schema/2.create_report_automatic.sql

@ -0,0 +1,26 @@
create table report_automatic
(
id serial not null,
report_name varchar(255) not null,
project_id int not null,
project_Name varchar(255) not null,
report_type varchar(255) not null,
reportpic_path varchar(255) not null,
framer varchar(255) not null,
auditor varchar(255) not null,
ratifier varchar(255) not null,
struct_id int not null,
report_start_time timestamp not null,
report_end_time timestamp not null,
factors json not null,
time timestamp not null,
project_overview varchar not null
);
create unique index report_automatic_id_uindex
on report_automatic (id);
alter table report_automatic
add constraint report_automatic_pk
primary key (id);

2
web/client/src/layout/components/header/index.jsx

@ -197,7 +197,7 @@ const Header = (props) => {
}}>全局</div>} itemKey="全局">
</TabPane>
<TabPane tab={<div onClick={() => {
setPepProjectId('')
// setPepProjectId('')
}}>自定义分组</div>} itemKey="自定义分组">
<div style={{ width: '100%', height: 1, background: "#d5cfcf8c", margin: "10px 0" }} />
<div style={{ display: 'flex' }}>

3
web/client/src/sections/service/actions/index.js

@ -5,6 +5,7 @@ import * as redcord from './record'
import * as maintenancePlan from './maintenancePlan'
import * as equipment from './equipment'
import * as firmwareUpgrade from './firmwareUpgrade'
import * as report from './report'
export default {
...emPush, ...redcord, ...maintenancePlan, ...equipment,...firmwareUpgrade
...emPush, ...redcord, ...maintenancePlan, ...equipment, ...firmwareUpgrade, ...report
}

105
web/client/src/sections/service/actions/report.js

@ -0,0 +1,105 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function getReportFile (query = {}) { //获取报表文件
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query,
actionType: 'GET_REPORT_FILE',
url: `${ApiTable.reportFile}`,
msg: { option: '获取报表文件信息' },
reducer: {
name: "reportFile",
params: { noClear: true }
}
});
}
export function postReportFile (data = {}) { //上传文件
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_REPORT_FILE',
url: `${ApiTable.reportFile}`,
msg: { option: '上传文件' },
});
}
export function delReportFile (id) {//删除报表文件
return dispatch => basicAction({
type: 'del',
dispatch: dispatch,
actionType: 'DEL_REPORT_FILE',
url: `${ApiTable.delReportFile.replace('{id}', id)}`,
msg: { option: '删除报表文件' },
});
}
export function getFactorList (query = {}) { //获取报表文件
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query,
actionType: 'GET_FACTOR_LIST',
url: `${ApiTable.factorList}`,
msg: { error: '获取监测因素信息失败' },
reducer: {
name: "",
params: { noClear: true }
}
});
}
export function postAutomaticReport (data = {}) { //上传文件
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_AUTOMATIC_REPORT',
url: `${ApiTable.automaticReport}`,
msg: { option: data?.id ? '编辑报表生成规则' : "新增报表生成规则" },
});
}
export function getAutomaticReport (query = {}) { //获取报表文件
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query,
actionType: 'GET_AUTOMATIC_REPORT',
url: `${ApiTable.automaticReport}`,
msg: { error: '获取报表生成规则' },
reducer: {
name: "automaticReport",
params: { noClear: true }
}
});
}
export function delAutomaticReport (id) {//删除报表文件
return dispatch => basicAction({
type: 'del',
dispatch: dispatch,
actionType: 'DEL_AUTOMATIC_REPORT',
url: `${ApiTable.delAutomaticReport.replace('{id}', id)}`,
msg: { option: '删除报表规则' },
});
}

318
web/client/src/sections/service/components/automatic-Modal.jsx

@ -0,0 +1,318 @@
import React, { useState, useRef, useEffect } from "react";
import { connect } from "react-redux";
import { Modal, Form, Notification, Tooltip, Upload, Button, Checkbox, CheckboxGroup, Collapse } from "@douyinfe/semi-ui";
import { IconUpload } from '@douyinfe/semi-icons';
import moment from "moment";
const AutomaticModal = ({ actions, dispatch, apiRoot, qiniuUrl, visible, eidtData, close, success, projectList }) => {
const { service, problem } = actions;
const form = useRef();//
const [strucData, setStrucData] = useState([]) //
const [projectId, setProjectId] = useState(); //id
const [structId, setStructId] = useState(); //id
const [factorId, setFactorId] = useState([]); //id
const [factorList, setFactorList] = useState([]); //
const [factorChech, setFactorChech] = useState([]); //
useEffect(async () => {
if (eidtData?.id) {
setProjectId(eidtData?.projectId)
setStructId(eidtData?.structId)
setFactorId(eidtData?.factors?.map(s => s.codeName) || [])
let data = await getData(eidtData?.projectId)
let Factor = data?.find(s => s.strucId == eidtData?.structId)?.factor || []
setFactorList(Factor)
setFactorChech(Factor?.filter(s => eidtData?.factors?.map(s => s.codeName)?.includes(s.proto)))
}
}, [])
const getData = async (projectId) => {
let data = []
await dispatch(service.getFactorList({ pepProjectId: projectId })).then((res) => {
if (res.success) {
setStrucData(res.payload.data)
data = res.payload.data
}
})
return data
}
return (
<>
<Modal
title={eidtData.id ? "编辑生成规制" : '新增生成规制'}
okText="确定"
cancelText="取消"
visible={visible}
onOk={() => {
form.current.validate().then((v) => {
console.log(v);
let data = {
id: eidtData?.id,
reportName: v.reportName,
projectId: v.projectId,
projectName: v.projectName,
reportType: v.reportType,
reportPicPath: v.reportPicPath[0]?.response?.url,
framer: v.framer,
auditor: v.auditor,
ratifier: v.ratifier,
structId: v.structId,
projectOverview: v.projectOverview,
reportStartTime: moment(v.reportTime[0]).format('YYYY-MM-DD HH:mm:ss'),
reportEndTime: moment(v.reportTime[1]).format('YYYY-MM-DD HH:mm:ss'),
time: moment().format('YYYY-MM-DD HH:mm:ss'),
factors: []
}
v.factorId?.forEach(d => {
let index = d.length
let factorData = {}
for (let key in v) {
factorData.codeName = d
factorData.tempName = factorData.tempName || []
if (key?.indexOf(d) != -1) {
if (key?.slice(index) == 'pointDescrip') factorData.pointDescrip = v[key]
if (key?.slice(index) == 'pointPicPath') factorData.pointPicPath = v[key] && v[key][0]?.response?.url
if (key?.slice(index) == 'factorDescrip') factorData.factorDescrip = v[key]
if (key?.slice(index) == 'sensorNames') factorData.sensorNames = factorChech?.find(p => p.proto == d)?.sensor?.filter(f => v[key]?.includes(f.id))?.map(c => ({ id: c.id, name: c.name })) || []
if (key?.slice(index) == 'startEndTime') {
factorData.startTime = v[key] && moment(v[key][0]).format('YYYY-MM-DD HH:mm:ss')
factorData.endTime = v[key] && moment(v[key][1]).format('YYYY-MM-DD HH:mm:ss')
}
if (key?.slice(index) == 'tempName1') {
factorData.tempName?.push({
index: 1,
id: v[key],
name: factorChech?.find(p => p.proto == d)?.sensor?.find(f => v[key] == f.id)?.name
})
}
if (key?.slice(index) == 'tempName2') {
factorData.tempName?.push({
index: 2,
id: v[key],
name: factorChech?.find(p => p.proto == 1004)?.sensor?.find(f => v[key] == f.id)?.name
})
}
if (key?.slice(index) == 'factorDescrip') factorData.factorDescrip = v[key]
if (key?.slice(index) == 'glStaName') factorData.glStaName = v[key]
if (key?.slice(index) == 'tempStaName') factorData.tempStaName = v[key]
if (key?.slice(index) == 'initialTime') factorData.initialTime = v[key] && moment(v[key]).format('YYYY-MM-DD HH:mm:ss')
if (key?.slice(index) == 'releTime') {
factorData.releStartTime = v[key] && moment(v[key][0]).format('YYYY-MM-DD HH:mm:ss')
factorData.releEndTime = v[key] && moment(v[key][1]).format('YYYY-MM-DD HH:mm:ss')
}
}
}
data.factors?.push(factorData)
})
console.log(111, data);
dispatch(service.postAutomaticReport(data)).then((res) => {
console.log(res);
if (res.success) {
close()
success()
}
})
})
}}
width={700}
onCancel={() => close()}
>
<Form
labelPosition="left"
labelAlign="right"
labelWidth="150px"
onValueChange={(values, field) => { }}
getFormApi={(formApi) => (form.current = formApi)}
>
<Form.Input field="reportName" label='报表名称' style={{ width: 300 }} placeholder="请输入报表名称" showClear
initValue={eidtData?.reportName || ""}
rules={[{ required: true, message: "请输入报表名称" }]}
/>
<Form.Select label="所属项目" field="projectId" placeholder="请选择项目" style={{ width: 300 }} filter
initValue={eidtData?.projectId || ""}
rules={[{ required: true, message: "请选择项目" }]}
onChange={v => {
setProjectId(v)
getData(v)
form.current.setValue('structId', null)
setStructId("")
setFactorList([])
form.current.setValue('factorId', [])
setFactorChech([])
}} >
{projectList?.map((item) => {
return <Form.Select.Option value={item.id} label={item.name || item.pepProjectName}></Form.Select.Option>
})}
</Form.Select>
<Form.Input field="projectName" label='项目名称' style={{ width: 300 }} placeholder="请输入项目名称" showClear
initValue={eidtData?.projectName || ""}
rules={[{ required: true, message: "请输入项目名称" }]}
/>
<Form.Select label="报表类型" field="reportType" placeholder="请选择报表类型" style={{ width: 300 }}
rules={[{ required: true, message: "请选择报表类型" }]}
initValue={eidtData?.reportType || ""}
optionList={[{ value: "月报表", label: "月报表" }, { value: "季报表", label: "季报表" }, { value: "年报表", label: "年报表" }]}
/>
<Form.Upload label="首页图片" field="reportPicPath" style={{ display: 'inline-block', }}
initValue={eidtData?.reportPicPath && [{ url: `/_file-server/${eidtData?.reportPicPath?.slice(qiniuUrl.length + 1)}`, name: eidtData.reportPicPath?.split('/')?.pop(), status: 'success', preview: ['png', 'jpg', 'jpeg'].includes(eidtData.reportPicPath?.split('.')?.pop()?.replace('.', '')) }] || null}
rules={[{ required: true, message: "请上传首页图片" }]}
action={`${apiRoot}/attachments/p`}
accept={'.txt, .doc, .docx, .xls, .xlsx, .pdf, .png, .jpg, .rar, .zip'}
limit={1} maxSize={5120}
>
<Button icon={<IconUpload />} theme="light">
文件上传
</Button>
</Form.Upload>
<Form.Input field="framer" label='制定者' style={{ width: 300 }} placeholder="请输入制定者" showClear
initValue={eidtData?.framer || ""}
rules={[{ required: true, message: "请输入制定者" }]}
/>
<Form.Input field="auditor" label='审核者' style={{ width: 300 }} placeholder="请输入审核者" showClear
initValue={eidtData?.auditor || ""}
rules={[{ required: true, message: "请输入审核者" }]}
/>
<Form.Input field="ratifier" label='批准者' style={{ width: 300 }} placeholder="请输入批准者" showClear
initValue={eidtData?.ratifier || ""}
rules={[{ required: true, message: "请输入批准者" }]}
/>
<Form.Select label="结构物" field="structId" placeholder="请选择结构物" style={{ width: 300 }} filter
rules={[{ required: true, message: "请选择结构物" }]} disabled={projectId ? false : true}
initValue={eidtData?.structId || ""}
onChange={v => {
setFactorList(strucData?.find(s => s.strucId == v)?.factor || [])
setStructId(v)
form.current.setValue('factorId', [])
setFactorChech([])
}} >
{strucData?.map((item) => {
return <Form.Select.Option value={item.strucId} label={item.strucName}></Form.Select.Option>
})}
</Form.Select>
<Form.TextArea field="projectOverview" label='报表描述' style={{ width: 420 }} autosize={{ minRows: 2, maxRows: 10 }} placeholder="请输入报表描述" showClear
initValue={eidtData?.projectOverview || ""}
rules={[{ required: true, message: "请输入报表描述" }]}
/>
<Form.DatePicker field='reportTime' label='开始结束时间' type='dateTimeRange' showClear
initValue={eidtData?.reportStartTime && [moment(eidtData?.reportStartTime).format('YYYY-MM-DD HH:mm:ss'), moment(eidtData?.reportEndTime).format('YYYY-MM-DD HH:mm:ss')] || null}
rules={[{ required: true, message: "请选择开始结束时间" }]}
/>
<Form.Select label="包含的监测因素" field="factorId" placeholder="请选择监测因素" style={{ width: 300 }} filter multiple={true}
initValue={eidtData?.factors?.map(s => s.codeName) || []}
rules={[{ required: true, message: "请选择监测因素" }]} disabled={structId ? false : true}
onChange={v => {
setFactorChech(factorList?.filter(s => v.includes(s.proto)))
}} >
{factorList?.map((item) => {
return <Form.Select.Option value={item.proto} label={item.name}></Form.Select.Option>
})}
</Form.Select>
{factorChech?.length > 0 ? <Collapse style={{ margin: '20px 0 20px 30px', width: '90%', background: '#b7c9e624' }}>
{
factorChech?.map(s => {
return <Collapse.Panel header={s.name} itemKey={s.proto}>
<div style={{ background: "#FFF" }}>
<Form.TextArea field={s.proto + "pointDescrip"} label='布点描述' style={{ width: 400 }} autosize={{ minRows: 2, maxRows: 10 }} placeholder="请输入布点描述" showClear
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.pointDescrip || ""}
/>
<Form.Upload label="布点图片" field={s.proto + "pointPicPath"} style={{ display: 'inline-block', }}
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.pointPicPath && [{ url: `/_file-server/${eidtData?.factors?.find(c => c.codeName == s.proto)?.pointPicPath?.slice(qiniuUrl.length + 1)}`, name: eidtData?.factors?.find(c => c.codeName == s.proto)?.pointPicPath?.split('/')?.pop(), status: 'success', preview: ['png', 'jpg', 'jpeg'].includes(eidtData?.factors?.find(c => c.codeName == s.proto)?.pointPicPath?.split('.')?.pop()?.replace('.', '')) }] || null}
action={`${apiRoot}/attachments/p`}
accept={'.png, .jpg, .jpeg'}
limit={1} maxSize={5120}
>
<Button icon={<IconUpload />} theme="light">
文件上传
</Button>
</Form.Upload>
{s.proto == 2001 &&
<Form.TextArea field={s.proto + "factorDescrip"} label='索力监测描述' style={{ width: 400 }} autosize={{ minRows: 2, maxRows: 10 }} placeholder="请输入布点描述" showClear
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.factorDescrip || ""}
/>
}
<Form.Select label="测点选择" field={s.proto + "sensorNames"} multiple={true} placeholder="请选择测点选择" style={{ width: 300 }} filter
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.sensorNames?.map(a => a.id) || []}
>
{s.sensor?.map((item) => {
return <Form.Select.Option value={item.id} label={item.name}></Form.Select.Option>
})}
</Form.Select>
<Form.DatePicker field={s.proto + 'startEndTime'} label='开始结束时间' type='dateTimeRange' showClear style={{ width: 360 }}
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.startTime && [moment(eidtData?.factors?.find(c => c.codeName == s.proto)?.startTime).format('YYYY-MM-DD HH:mm:ss'), moment(eidtData?.factors?.find(c => c.codeName == s.proto)?.endTime).format('YYYY-MM-DD HH:mm:ss')] || null}
/>
{
['2001', '4004', '4007', '4008'].includes(s.proto) &&
<Form.DatePicker field={s.proto + 'initialTime'} label='数据初始时间' type='dateTime' showClear
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.initialTime && moment(eidtData?.factors?.find(c => c.codeName == s.proto)?.initialTime).format('YYYY-MM-DD HH:mm:ss')}
/>
}
<Form.Select label="关联温度的测点" field={s.proto + "tempName1"} placeholder="请选择关联的温度测点" style={{ width: 300 }} filter
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.tempName?.find(c => c.index == 1)?.id || ""}
>
{s.sensor?.map((item) => {
return <Form.Select.Option value={item.id} label={item.name}></Form.Select.Option>
})}
</Form.Select>
<Form.Select label="温度测点" field={s.proto + "tempName2"} placeholder="请选择温度测点" style={{ width: 300 }} filter
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.tempName?.find(c => c.index == 2)?.id || ""}
>
{factorList?.find(d => s.proto == 1004)?.sensor?.map((item) => {
return <Form.Select.Option value={item.name} label={item.name}></Form.Select.Option>
})}
</Form.Select>
<Form.Input field={s.proto + "glStaName"} label='关联温度的测点名称' style={{ width: 300 }} placeholder="请输入关联温度的测点名称" showClear
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.glStaName || ""}
/>
<Form.Input field={s.proto + "tempStaName"} label='关联温度名称' style={{ width: 300 }} placeholder="请输入关联温度名称" showClear
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.tempStaName || ""}
/>
<Form.DatePicker field={s.proto + 'releTime'} label='关联开始结束时间' type='dateTimeRange' showClear style={{ width: 360 }}
initValue={eidtData?.factors?.find(c => c.codeName == s.proto)?.releStartTime && [moment(eidtData?.factors?.find(c => c.codeName == s.proto)?.releStartTime).format('YYYY-MM-DD HH:mm:ss'), moment(eidtData?.factors?.find(c => c.codeName == s.proto)?.releEndTime).format('YYYY-MM-DD HH:mm:ss')] || null}
/>
</div>
</Collapse.Panel>
})
}
</Collapse> : ""}
</Form>
</Modal >
</>
);
}
function mapStateToProps (state) {
const { auth, global, members } = state;
return {
// loading: members.isRequesting,
user: auth.user,
actions: global.actions,
apiRoot: global.apiRoot,
qiniuUrl: global.qiniu?.domain
};
}
export default connect(mapStateToProps)(AutomaticModal);

140
web/client/src/sections/service/components/fileModal.jsx

@ -0,0 +1,140 @@
import React, { useState, useRef, useEffect } from "react";
import { connect } from "react-redux";
import { Modal, Form, Notification, Tooltip, Upload, Button } from "@douyinfe/semi-ui";
import { IconUpload } from '@douyinfe/semi-icons';
import moment from "moment";
const FileModal = ({ actions, dispatch, apiRoot, qiniuUrl, visible, close,success, projectList }) => {
const { service, problem } = actions;
const form = useRef();//
const [abnormal, setAbnormal] = useState(false); //disable
const [usersList, setUsersList] = useState([]); //
const [structure, setStructure] = useState(true); //disable
const [projectPoms, setProjectPoms] = useState([]); //
const [projectStructure, setProjectStructure] = useState([]); //
const [timeTypeDis, setTimeTypeDis] = useState(true); //disable
const [projectStatus, setProjectStatus] = useState([]); //
const timeTypePOMS = useRef([]);//
const [interval1, setInterval1] = useState(undefined); //
const [interval2, setInterval2] = useState(undefined); //
const [interval3, setInterval3] = useState(undefined); //
const [deviceProportion, setDeviceProportion] = useState(undefined); //
const [subType, setSubType] = useState([]); //
const [factorShow, setFactorShow] = useState([]); //
const [firstPass, setFirstPass] = useState(true)
const [uploadData, setUploadData] = useState({})
return (
<>
<Modal
title={'文件上传'}
okText="确定"
cancelText="取消"
visible={visible}
onOk={() => {
form.current.validate().then((v) => {
if (uploadData?.url) {
dispatch(service.postReportFile({
projectId: v.projectId,
fileName: v.fileName,
reportType: v.reportType,
startTime: moment().format('YYYY-MM-DD HH:mm:ss'),
url: qiniuUrl + '/' + uploadData?.url
})).then((res) => {
if (res.success) {
close()
success()
}
})
} else {
Notification.error({
content: '请上传文件',
duration: 2,
})
}
})
}}
width={500}
onCancel={() => close()}
>
<Form
labelPosition="left"
labelAlign="right"
labelWidth="128px"
onValueChange={(values, field) => {
}}
getFormApi={(formApi) => (form.current = formApi)}
>
<Form.Select
label="请选择项目:"
field="projectId"
placeholder="请选择项目"
style={{ width: 300 }}
rules={[{ required: true, message: "请选择项目" }]}
filter
optionList={projectList?.map(v => ({ value: v.id, label: v.name || v.pepProjectName }))}
/>
<Form.Input
field="fileName"
label='文件名称'
style={{ width: 300 }}
placeholder="请输入文件名称"
showClear
rules={[{ required: true, message: "请输入文件名称" }]}
/>
<Form.Select
label="报表类型"
field="reportType"
placeholder="请选择报表类型"
style={{ width: 300 }}
rules={[{ required: true, message: "请选择报表类型" }]}
filter
optionList={[{ value: "月报表", label: "月报表" }, { value: "季报表", label: "季报表" }, { value: "年报表", label: "年报表" }]}
/>
<div style={{ display: 'flex', marginLeft: 69, marginTop: 10 }}>
<div style={{ marginTop: 6 }}>文件:<span style={{ color: 'red' }}>*</span></div>
<Upload
style={{ display: 'inline-block', marginLeft: 22 }}
action={`${apiRoot}/attachments/p`}
accept={'.txt, .doc, .docx, .xls, .xlsx, .pdf, .png, .jpg, .rar, .zip'}
limit={1}
maxSize={51200}
onRemove={() => {
setUploadData({})
}}
onSuccess={(responseBody, file) => {
setUploadData({
name: file.name,
size: file.size,
url: responseBody?.uploaded,
uploadTime: moment().format("YYYY-MM-DD HH:mm:ss")
})
}}
>
<Button icon={<IconUpload />} theme="light">
文件上传
</Button>
</Upload>
</div>
</Form>
</Modal >
</>
);
}
function mapStateToProps (state) {
const { auth, global, members } = state;
return {
// loading: members.isRequesting,
user: auth.user,
actions: global.actions,
apiRoot: global.apiRoot,
qiniuUrl: global.qiniu?.domain
};
}
export default connect(mapStateToProps)(FileModal);

770
web/client/src/sections/service/containers/automaticReport.jsx

@ -1,674 +1,157 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Tooltip } from '@douyinfe/semi-ui';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Tooltip, Input } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen, } from "$components";
import moment from "moment";
import PushModal from '../components/pushModal'
import '../style.less'
import { Setup } from "$components";
import AutomaticModal from '../components/automatic-Modal'
import SimpleBar from 'simplebar-react';
const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socket, projectPoms, pepProjectId }) => {
const { service, } = actions;
const AutomaticReport = (props) => {
const form = useRef();//
const { dispatch, actions, user, loading, socket } = props
const { service, problem } = actions;
const [setup, setSetup] = useState(false); //
const [setupp, setSetupp] = useState([]);//
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [limits, setLimits] = useState()//
const [limits, setLimits] = useState()//
const mylimits = useRef(); //
const [pushModal, setPushModal] = useState(false) //
const [pushEdit, setPushEdit] = useState(false) //
const [change, setChange] = useState(false) //
const [allTableData, setAllTableData] = useState([]) //
const [editObj, setEditObj] = useState({});//
const [projectStatus, setProjectStatus] = useState([]); //
const [subTypeData, setSubTypedata] = useState({
data_outages: [],
data_exception: [],
strategy_hit: [],
video_exception: [],
app_exception: [],
device_exception: [],
}); //
const [automaticModal, setAutomaticModall] = useState(false) //
const [eidtData, setEidtData] = useState({}) //
const [tableData, setTableData] = useState([]) //
const [projectList, setProjectList] = useState([]) //
const [projectId, setProjectId] = useState() //id
const [projectSearch, setProjectSearch] = useState() //
const [keyword, setKeyword] = useState() //
const page = useRef(query.page);//
const EMPUSH = "empush";
const tableList = [//
{
title: '推送信息',
list: [
{ name: "关联项目", value: "projectName" },
{ name: "策略名称", value: "name" },
{ name: "创建时间", value: "createTime" },
{ name: "接收人", value: "receiverPepUser" },
{ name: "推送方式", value: "pushType" },
{ name: "监听问题模块", value: "alarmType" },
{ name: "生效项目节点", value: "timeType" },
{ name: "推送机制", value: "tactics" },
{ name: "启用状态", value: "disable" },
{ name: "推送次数", value: "pushCount" },
]
},
];
const alarmTypeObj = {
data_outages: '数据中断',
data_exception: '数据异常',
strategy_hit: '策略命中',
video_exception: '视频异常',
app_exception: '应用异常',
device_exception: '设备异常',
}
const tacticsObj = {
immediately: '即时推送机制',
continue: '持续时长推送机制',
abnormal_rate: '异常率推送机制',
}
function handleRow (record, index) {//
//
if (index % 2 === 0) {
return {
style: {
background: '#FAFCFF',
}
};
} else {
return {};
}
}
const [tableData, setTableData] = useState([]) //
useEffect(() => {
localStorage.getItem(EMPUSH) == null
? localStorage.setItem(
EMPUSH,
JSON.stringify(['projectName', 'name', 'createTime', 'receiverPepUser', 'alarmType', 'timeType', 'tactics', 'disable'])
)
: "";
getProjectStatusList()
getPushList(query);
//
dispatch(problem.getAlarmDataGroup({ showAll: 'true' })).then((res) => {
if (res.success) {
let data = { ...subTypeData }
res.payload.data?.map(v => {
if (v.id === 1) {
data['data_outages'].push(v.unit)
} else if (v.id === 2) {
data['data_exception'].push(v.unit)
} else if (v.id == 3) {
data['strategy_hit'].push(v.unit)
} else {
data['device_exception'].push(v.unit)
}
})
//
dispatch(problem.getAlarmVideoDeviceKind({ showAll: true })).then((res) => {
if (res.success) {
data['video_exception'].push(res.payload.data)
if (projectPoms?.length) {
let dataList = projectPoms?.filter(v => v.pepProjectIsDelete != 1 && (!pepProjectId || (pepProjectId?.length > 0 ? pepProjectId?.split(',')?.map(s => Number(s.id))?.includes(v.id) : pepProjectId)))
setProjectList(dataList)
getData({ limit: 10, page: 0 })
setQuery({ limit: 10, page: 0 })
}
})
data['app_exception'].push([{ id: 'apiError', name: "接口报错", }, { id: 'element', name: "元素异常", }, { id: 'timeout', name: "加载超时", },])
setSubTypedata(data)
}
})
}, [])
}, [projectPoms, pepProjectId, projectSearch])
useEffect(() => {
let showTableData = JSON.parse(JSON.stringify(allTableData)).slice(query.page * query.limit, (query.page + 1) * query.limit)
setTableData(showTableData)
mylimits.current = showTableData.length
}, [change]);
function getPushList (query) {
let val = form.current.getValues()
dispatch(service.getPush({ ...val })).then((res) => {//
if (res.success) {
let mytableData = JSON.parse(JSON.stringify(res.payload.data));
for (let index = 0; index < mytableData.length; index++) {
mytableData[index].key = String(mytableData[index].id)
}
setAllTableData(mytableData)
let showTableData = mytableData.slice(query.page * query.limit, (query.page + 1) * query.limit)
setTableData(showTableData)
setQuery(query)
setLimits(res.payload.data.length)
mylimits.current = showTableData.length
}
})
}
function getProjectStatusList () {//
dispatch(service.getProjectStatus()).then((res) => {
const getData = (data = {}) => {
dispatch(service.getAutomaticReport({ projectId: pepProjectId, ...query, keyword, ...data })).then((res) => {
if (res.success) {
setProjectStatus(res.payload?.data)
attribute(res.payload?.data);
setTableData(res.payload.data?.rows)
setLimits(res.payload.data?.count)
}
})
}
const columns = [//
{
const columns = [{
title: "序号",
dataIndex: "index",
key: 'index',
render: (txet, row, index) => index + 1
}, {
title: "报表名称",
dataIndex: "reportName",
key: 'reportName',
}, {
title: "所属项目",
dataIndex: "projectId",
key: 'projectId',
render: (_, row) => {
let project = projectPoms?.find(s => s.id == row?.projectId)
return project?.name || project?.pepProjectName || '--'
}
}, {
title: "报表类型",
dataIndex: "reportType",
key: 'reportType',
}, {
title: "最近生产时间",
dataIndex: "time",
key: 'time',
render: (txet, row) => txet && moment(txet).format('YYYY-MM-DD HH:mm:ss') || '--'
}, {
title: "操作",
width: "12%",
dataIndex: "text",
key: 'text',
render: (_, row) => {
return (
<div style={{ display: "flex" }}>
<Button
theme="borderless"
onClick={() => {
setEditObj(row)
setPushModal(true);
setPushEdit(true)
}}
>
修改
</Button>
{row?.disable ? (
<Button
theme="borderless"
style={{ color: '#F31C1C' }}
onClick={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: false, disable: false, msg: '更改推送配置状态' })).then(() => {
getPushList({ limit: query.limit, page: page.current });
})
}}
>
已禁用
<Button theme="borderless" onClick={() => {
setEidtData(row)
setAutomaticModall(true)
}}>
编辑
</Button>
) : (
<Popconfirm
title="禁用后,通知策略将会失效。"
title="确定删除该报表生成规则吗?"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: false, disable: true, msg: '更改推送配置状态' })).then(() => {
getPushList({ limit: query.limit, page: page.current });
})
}}
>
<Button theme="borderless">已启用</Button>
</Popconfirm>
)}
<Popconfirm
title="删除后通知策略将会失效。"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: true, disable: false, msg: '删除推送配置' })).then(() => {
if (page.current > 0 && mylimits.current < 2) {
getPushList({ limit: query.limit, page: page.current - 1 });
} else {
getPushList({ limit: query.limit, page: page.current });
dispatch(service.delAutomaticReport(row.id)).then((res) => {
if (res.success) {
setQuery({ limit: 10, page: 0 })
getData({ limit: 10, page: 0, keyword: keyword })
}
})
}}
>
<Button theme="borderless">删除</Button>
<Button theme="borderless" type='danger'>删除</Button>
</Popconfirm>
<Button theme="borderless" onClick={() => {
}}>
立即生成
</Button>
</div>
);
},
},
]
function expandRowRender (record, index) {
return (
<div style={{}}>
结构物
{
record.structure?.map((item, index) => {
return (
<span key={index} style={{
marginRight: 5, padding: '1px 17px', color: '#0F7EFB',
display: 'inline-block', marginBottom: 5
}}>
{item.name}
</span>
)
})
}
</div>
)
}
//
function attribute (val) {
const arr = localStorage.getItem(EMPUSH)
? JSON.parse(localStorage.getItem(EMPUSH))
: [];
const column = [
{
title: '关联项目',
dataIndex: "projectName",
key: "projectName",
render: (_, row) => {
let projectData = []
row.pomsProject?.map(v => {
projectData.push({
projectName: v.pepProject?.projectName,
name: v.name,
anxinerror: v.del,
constructionStatus: v.pepProject?.constructionStatus,
})
})
let anxinerror = false
let anxinerrorArr = ''
if (row.pomsProject.del == true) {
anxinerror = true
anxinerrorArr = row.pomsProject.pepProject?.projectName || row.pomsProject.name
}
return (
projectData.map((u, index) => <div key={'projecname' + index} style={{ display: 'flex', alignItems: 'center' }}>
{
u.anxinerror ? (
<Tooltip content={(u.projectName || u.name) + ',项目已在【项企PEP】或【映射关系】中被删除,请重选项目!'}>
<div style={{ marginRight: 5 }}>
<img src="/assets/images/install/risk.png" alt="" style={{ height: 24, width: 24, }} />
</div>
</Tooltip>) : ('')
}
{
<div className='myseparator' style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip content={(u.projectName || u.name)}>
<div style={{ width: u.projectName?.length > 7 || u.name?.length > 7 ? '112px' : '', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: row.pomsProject.del ? '#F93920' : '' }}>
{u.projectName || u.name}
</div>
</Tooltip>
</div>
}
{
u.projectName ? (
<div style={{
height: 18, marginLeft: 4, width: 76,
background: 'linear-gradient(180deg, #EBF5FF 0%, #EBF5FF 0%, #D3E8FF 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_zhengque.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#0F7EFB', fontSize: 11, marginRight: 12 }}>
{u.constructionStatus}
</div>
</div>
) : (
<div style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #99C7DD 0%, #3048FC 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
POMS
</div>
</div>
)
}
</div>)
)
}
},
{
title: '策略名称',
dataIndex: "name",
key: 'name',
render: (_, row) => {
return row.name
}
},
{
title: "创建时间",
dataIndex: "createTime",
key: "createTime",
render: (_, r, index) => {
return moment(r.createTime).format('YYYY-MM-DD HH:mm:ss');
},
},
{
title: '接收人',
dataIndex: "receiverPepUser",
key: 'receiverPepUser',
render: (_, row) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.receiverPepUser.map((item, index) => {
return (
<div className='myseparator' key={index} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: index > 1 ? 'none' : '', color: '#005ABD' }}>
{item.name}
</div>
<div className='separator' style={{ width: 1, height: 12, border: '1px solid #DCDEE0', margin: '0px 10px', display: index > 0 ? 'none' : '' }}></div>
</div>
)
})
}
{
row.receiverPepUser.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.receiverPepUser.map((item, index) => {
return (
<div key={index}>
{item.name},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.receiverPepUser.length - 2}
</div>
</Tooltip>
) : ('')
}
</div>
)
}
},
{
title: "推送方式",
dataIndex: "pushType",
key: "pushType",
render: (_, r, index) => {
return '邮件通知';
},
},
{
title: "监听问题模块",
dataIndex: "alarmType",
key: "alarmType",
render: (_, row) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.alarmType.map((item, index) => {
return (
<div className='myseparator' key={index} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: index > 1 ? 'none' : '' }}>
{alarmTypeObj[item]}
</div>
<div className='separator' style={{ width: 1, height: 12, border: '1px solid #DCDEE0', margin: '0px 10px', display: index > 0 ? 'none' : '' }}></div>
</div>
)
})
}
{
row.alarmType.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.alarmType.map((item, index) => {
return (
<div key={index} >
{alarmTypeObj[item]},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.alarmType.length - 2}
</div>
</Tooltip>
) : ('')
}
</div>
)
}
},
{
title: "生效项目节点",
dataIndex: "timeType",
key: "timeType",
render: (_, row, index) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.timeType.length > 0 ? (
row.timeType.map((item, index) => {
return (
<div key={index} style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #EBF5FF 0%, #EBF5FF 0%, #D3E8FF 100%)',
borderRadius: 2, display: index > 1 ? 'none' : 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_zhengque.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#0F7EFB', fontSize: 11, marginRight: 12 }}>
{
val.map((ite, idx) => {
return (
<div key={idx}>
{ite.id == item ? ite.construction_status : ''}
</div>
)
})
}
</div>
</div>
)
})
) : (
<div style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #99C7DD 0%, #3048FC 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
POMS
</div>
</div>
)
}
{
row.timeType.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.timeType.map((item, index) => {
return (
<div key={index}>
{
val.map((ite, idx) => {
return (
<span key={idx}>
{ite.id == item ? ite.construction_status : ''}
</span>
)
})
},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.timeType.length - 2}
</div>
</Tooltip>
) : ('')
function handleRow (record, index) {//
//
if (index % 2 === 0) {
return {
style: {
background: '#FAFCFF',
}
</div>
)
},
},
{
title: "推送机制",
dataIndex: "tactics",
key: "tactics",
render: (_, r, index) => {
return tacticsObj[r.tactics]
},
},
{
title: "启用状态",
dataIndex: "disable",
key: "disable",
render: (_, row, index) => {
let enableType = ''
if (row.disable) {
enableType = '禁用'
} else {
let construcId = row.pomsProject?.map(v => (v.pepProject?.constructionStatusId || 'POMS')) || []
if (construcId?.includes('POMS')) {
enableType = '已生效'
} else {
let timeType = row.timeType?.map(Number) || []
for (let i = 0; i < timeType.length; i++) {
if (construcId?.includes(timeType[i])) {
enableType = '已生效'
break
};
} else {
enableType = '未生效'
}
return {};
}
}
}
return (
<div style={{ textAlign: 'center', padding: '1px 17px', color: enableType == '禁用' ? '#FB0F0F' : enableType == '已生效' ? '#0F7EFB' : '#646566', background: enableType == '禁用' ? 'rgba(255,221,221,0.38)' : enableType == '已生效' ? 'rgba(221,237,255,0.38)' : 'rgba(192,192,192,0.38)', }}>
{enableType}
</div>
)
},
},
{
title: "推送次数",
dataIndex: "pushCount",
key: "pushCount",
render: (_, r, index) => {
return (r.pushCount || 0) + '次'
},
},
];
for (let i = 0; i < arr.length; i++) {
let colum = column.filter((item) => {
return item.key === arr[i];
});
columns.splice(columns.length - 1, 0, colum[0]);
}
setSetupp(columns);
}
return (
<>
<div style={{ background: '#FFFFFF', margin: '8px 12px', padding: '20px 20px 0px 20px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ background: '#FFFFFF', margin: '8px 12px', padding: '20px 20px 0px 20px', }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ width: 0, height: 20, borderLeft: '3px solid #005ABD', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div>
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>EM推送</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>Em push</div>
</div>
<div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='myempush'>
<Form
onSubmit={(values) => console.log(values)}
getFormApi={(formApi) => (form.current = formApi)}
layout="horizontal"
style={{ position: "relative", width: "100%", flex: 1 }}
>
<Form.Select
pure
field="keywordTarget"
placeholder="请选择搜索类型"
style={{ width: 200 }}
initValue={"pepProject"}
>
<Form.Select.Option value='pepProject'>项目</Form.Select.Option>
<Form.Select.Option value='struc'>结构物</Form.Select.Option>
<Form.Select.Option value='tactics'>策略名</Form.Select.Option>
</Form.Select>
<Form.Input
suffix={<IconSearch />}
field="keyword"
pure
showClear
style={{ width: 260, marginLeft: 12, marginRight: 12 }}
placeholder="请输入或选择关键词"
/>
<Form.Select
label='推送机制:'
labelPosition="left"
field='tactics'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部"
filter
showClear
>
<Form.Select.Option value="immediately">即时推送机制</Form.Select.Option>
<Form.Select.Option value="continue">持续时长推送机制</Form.Select.Option>
<Form.Select.Option value="abnormal_rate">异常率推送机制</Form.Select.Option>
</Form.Select>
<Form.Select
label='启用状态:'
labelPosition="left"
field='state'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部"
filter
showClear
>
<Form.Select.Option value='takeEffect'>已生效</Form.Select.Option>
<Form.Select.Option value='notYet'>未生效</Form.Select.Option>
<Form.Select.Option value='disable'>禁用</Form.Select.Option>
</Form.Select>
</Form>
<Button
theme="solid"
type="primary"
style={{
width: 80,
height: 32,
borderRadius: 2,
marginRight: 32,
background: '#FFFFFF',
color: '#005ABD',
border: '1px solid #005ABD'
}}
onClick={() => {
getPushList({ limit: query.limit, page: 0 })
}}
>
查询
</Button>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img title='设置' src="/assets/images/problem/setup.png" style={{ width: 18, height: 18, cursor: "pointer" }} onClick={() => setSetup(true)} />
</div>
<Button
theme="solid"
type="primary"
style={{
width: 136,
height: 32,
borderRadius: 2,
marginLeft: 32
}}
onClick={() => {
setPushModal(true);
setPushEdit(false);
setEditObj({})
}}
>
添加推送策略
</Button>
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>自动化报表</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>AUTOMATIC REPORT</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '16px 0' }}>
<Button theme='solid' type='secondary' onClick={() => {
setAutomaticModall(true)
}}>新增报表生成规则</Button>
<div>
<Input style={{ width: 220, marginRight: 20 }} value={keyword} placeholder='请输入报表关键字' onChange={(e) => {
setKeyword(e)
}} />
<Button theme='solid' type='secondary' onClick={() => {
setQuery({ limit: 10, page: 0 })
getData({ limit: 10, page: 0, keyword: keyword })
}}>查询</Button>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, marginTop: 5 }}>
<div style={{ fontSize: 12, color: '#8B8B8B' }}>EM推送提供对映射关系组的项目结构物问题的监听和通知服务支持对设备异常率问题持续时间即时响应等策略定义的动态推送</div>
</div>
<div style={{ marginTop: 20 }}>
<Skeleton
loading={loading}
// loading={false}
@ -677,17 +160,16 @@ const AutomaticReport = (props) => {
>
<Table
rowKey="name"
columns={setupp.filter((s) => s)}
columns={columns}
dataSource={tableData}
bordered={false}
hideExpandedColumn={false}
empty="暂无数据"
expandedRowRender={expandRowRender}
pagination={false}
onRow={handleRow}
/>
</Skeleton>
<div
{limits > 0 && <div
style={{
display: "flex",
justifyContent: "space-between",
@ -708,53 +190,41 @@ const AutomaticReport = (props) => {
pageSizeOpts={[10, 20, 30, 40]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
page.current = currentPage - 1
setChange(!change)
getData({ limit: pageSize, page: currentPage - 1 })
}}
/>
</div>
</div>
</div>
</div>}
</div>
{//
pushModal ?
<PushModal
automaticModal ?
<AutomaticModal
visible={true}
pushEdit={pushEdit}
editObj={editObj}
subTypeData={subTypeData}
cancel={() => {
setPushModal(false);
}}
close={() => {
setPushModal(false);
getPushList(query)
}} >
</PushModal> : ''
}
{setup ? (
<Setup
tableType={EMPUSH}
tableList={tableList}
projectList={projectList}
eidtData={eidtData}
close={() => {
setSetup(false);
attribute(projectStatus);
setAutomaticModall(false);
setEidtData({})
}}
/>
) : (
""
)}
success={() => {
setQuery({ limit: 10, page: 0 })
getData({ limit: 10, page: 0, keyword: keyword })
}}
/> : ''
}
</>
)
}
function mapStateToProps (state) {
const { auth, global, getPush } = state;
const { auth, global, automaticReport, ProjectPoms } = state;
return {
loading: getPush.isRequesting,
loading: automaticReport.isRequesting,
user: auth.user,
actions: global.actions,
// members: members.data,
pepProjectId: global.pepProjectId,
clientHeight: global?.clientHeight,
projectPoms: ProjectPoms?.data?.rows
};
}

171
web/client/src/sections/service/containers/reportFile.jsx

@ -1,86 +1,66 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Tooltip } from '@douyinfe/semi-ui';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Tooltip, Input } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen, } from "$components";
import moment from "moment";
import PushModal from '../components/pushModal'
import FileModal from '../components/fileModal'
import SimpleBar from 'simplebar-react';
import '../style.less'
import { Setup } from "$components";
const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socket, projectPoms }) => {
const form = useRef();//
const { service, problem } = actions;
const [setup, setSetup] = useState(false); //
const ReportFile = ({ dispatch, actions, user, clientHeight, loading, socket, qiniuUrl, projectPoms, pepProjectId }) => {
const { service, } = actions;
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [limits, setLimits] = useState()//
const [limits, setLimits] = useState()//
const mylimits = useRef(); //
const [pushModal, setPushModal] = useState(false) //
const [pushEdit, setPushEdit] = useState(false) //
const [fileModal, setFileModal] = useState(false) //
const [change, setChange] = useState(false) //
const [editObj, setEditObj] = useState({});//
const [projectStatus, setProjectStatus] = useState([]); //
const [tableData, setTableData] = useState([]) //
const [projectList, setProjectList] = useState([]) //
const [projectId, setProjectId] = useState() //id
const [projectSearch, setProjectSearch] = useState() //
const [subTypeData, setSubTypedata] = useState({
data_outages: [],
data_exception: [],
strategy_hit: [],
video_exception: [],
app_exception: [],
device_exception: [],
}); //
const page = useRef(query.page);//
const EMPUSH = "empush";
const tableList = [//
{
title: '推送信息',
list: [
{ name: "关联项目", value: "projectName" },
{ name: "策略名称", value: "name" },
{ name: "创建时间", value: "createTime" },
{ name: "接收人", value: "receiverPepUser" },
{ name: "推送方式", value: "pushType" },
{ name: "监听问题模块", value: "alarmType" },
{ name: "生效项目节点", value: "timeType" },
{ name: "推送机制", value: "tactics" },
{ name: "启用状态", value: "disable" },
{ name: "推送次数", value: "pushCount" },
]
},
];
useEffect(() => {
if (projectPoms?.length) {
let dataList = projectPoms?.filter(v => v.pepProjectIsDelete != 1)
let dataList = projectPoms?.filter(v => (!projectSearch || (v.name || v.pepProjectName).indexOf(projectSearch) != -1) && v.pepProjectIsDelete != 1 && (!pepProjectId || (pepProjectId?.length > 0 ? pepProjectId?.split(',')?.map(s => Number(s.id))?.includes(v.id) : pepProjectId)))
setProjectId(dataList[0]?.id)
setProjectList(dataList)
}
}, [projectPoms])
getData({ projectId: dataList[0]?.id, limit: 10, page: 0 })
setQuery({ limit: 10, page: 0 })
}
}, [projectPoms, pepProjectId, projectSearch])
const getData = (data = {}) => {
dispatch(service.getReportFile({ projectId: projectId, ...query, ...data })).then((res) => {
if (res.success) {
setTableData(res.payload.data?.rows)
setLimits(res.payload.data?.count)
}
})
}
const columns = [{
title: "文件名称",
dataIndex: "name",
key: 'name',
dataIndex: "fileName",
key: 'fileName',
}, {
title: "报表类型",
dataIndex: "type",
key: 'type',
dataIndex: "reportType",
key: 'reportType',
}, {
title: "最近生产时间",
dataIndex: "time",
key: 'time',
dataIndex: "startTime",
key: 'tistartTimeme',
render: (txet, row) => txet && moment(txet).format('YYYY-MM-DD HH:mm:ss') || '--'
}, {
title: "操作",
dataIndex: "text",
@ -88,27 +68,23 @@ const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socke
render: (_, row) => {
return (
<div style={{ display: "flex" }}>
<Button
theme="borderless"
onClick={() => {
setEditObj(row)
setPushModal(true);
setPushEdit(true)
}}
>
<Button theme="borderless" onClick={() => {
console.log(`/_file-server/${row?.url?.slice(qiniuUrl.length + 1) + '?filename=' + encodeURIComponent(row?.fileName)}`);
}}>
<a href={`/_file-server/${row?.url?.slice(qiniuUrl.length + 1) + '?filename=' + encodeURIComponent(row?.fileName)}`}>
下载
</a>
</Button>
<Popconfirm
title="删除后通知策略将会失效。"
title="确定删除该报表文件吗?"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: true, disable: false, msg: '删除推送配置' })).then(() => {
if (page.current > 0 && mylimits.current < 2) {
getPushList({ limit: query.limit, page: page.current - 1 });
} else {
getPushList({ limit: query.limit, page: page.current });
dispatch(service.delReportFile(row.id)).then((res) => {
if (res.success) {
setQuery({ limit: 10, page: 0 })
getData({ limit: 10, page: 0 })
}
})
}}
@ -144,33 +120,28 @@ const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socke
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>报表文件</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>REPORT FILE</div>
</div>
<Button style={{ width: 78 }} theme='solid' type='secondary' onClick={() => {
}}>上传 </Button>
<Button style={{ width: 78 }} theme='solid' type='secondary' onClick={() => setFileModal(true)}>上传 </Button>
</div>
<div style={{ display: 'flex' }}>
<div style={{ width: 200, height: '100%', padding: '16px 10px', boxShadow: '0 0 4px 2px #0000000d' }}>
<div style={{ width: 200, height: '100%', padding: '16px 10px', boxShadow: '0 0 4px 2px #0000000d', marginRight: 20 }}>
<Input placeholder='请输入项目名称' value={projectSearch} onChange={v => setProjectSearch(v)} />
{/* <SimpleBar style={{ height: 'calc(100% - 24px', }} forceVisible="y" > */}
<SimpleBar style={{ width: 200, height: clientHeight - 100 }} forceVisible="y">
<SimpleBar style={{ width: 200, height: clientHeight - 190 }} forceVisible="y">
{projectList?.map(v => {
return <div key={'pepProjectId' + v.pepProjectId} title={v.name || v.pepProjectName}
return <div key={'pepProjectId' + v.id} title={v.name || v.pepProjectName}
style={{
cursor: 'pointer', background: v.pepProjectId == pepProjectId ? 'rgb(15 126 251 / 16%)' : '',
cursor: 'pointer', background: v.id == projectId ? 'rgb(15 126 251 / 16%)' : '',
width: 180, height: 30, display: 'flex', alignItems: 'center'
}}
onClick={() => {
setPepProjectId(v.pepProjectId)
fileList(v.pepProjectId)
setDataSource([])
setFileId('')
setProjectId(v.id)
setQuery({ limit: 10, page: 0 })
getData({ projectId: v.id, limit: 10, page: 0 })
}}>
<img src="/assets/images/icon/project-icon.png" style={{ width: 14, marginRight: 8 }} />
<div style={{ fontSize: 14, width: 152, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}> {v.pepProjectName}</div>
<div style={{ fontSize: 14, width: 152, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}> {v.name || v.pepProjectName}</div>
</div>
})}
{/* </SimpleBar> */}
</SimpleBar>
</div>
@ -213,8 +184,7 @@ const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socke
pageSizeOpts={[10, 20, 30, 40]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
page.current = currentPage - 1
setChange(!change)
getData({ limit: pageSize, page: currentPage - 1 })
}}
/>
</div>
@ -225,47 +195,34 @@ const AutomaticReport = ({ dispatch, actions, user, clientHeight, loading, socke
</div>
{//
pushModal ?
<PushModal
fileModal ?
<FileModal
visible={true}
pushEdit={pushEdit}
editObj={editObj}
subTypeData={subTypeData}
cancel={() => {
setPushModal(false);
}}
projectList={projectList}
close={() => {
setPushModal(false);
getPushList(query)
}} >
</PushModal> : ''
}
{setup ? (
<Setup
tableType={EMPUSH}
tableList={tableList}
close={() => {
setSetup(false);
attribute(projectStatus);
setFileModal(false);
}}
/>
) : (
""
)}
success={() => {
setQuery({ limit: 10, page: 0 })
getData({ limit: 10, page: 0 })
}}
/> : ''
}
</>
)
}
function mapStateToProps (state) {
const { auth, global, getPush, ProjectPoms } = state;
console.log(global);
const { auth, global, reportFile, ProjectPoms } = state;
return {
loading: getPush.isRequesting,
loading: reportFile.isRequesting,
user: auth.user,
actions: global.actions,
qiniuUrl: global.qiniu?.domain,
pepProjectId: global.pepProjectId,
clientHeight: global?.clientHeight,
projectPoms: ProjectPoms?.data?.rows
};
}
export default connect(mapStateToProps)(AutomaticReport);
export default connect(mapStateToProps)(ReportFile);

10
web/client/src/sections/service/nav-item.jsx

@ -17,11 +17,11 @@ export function getNavItem (user, dispatch) {
items: [{
itemKey: 'reportManagement', to: '/service/reportingServices/reportManagement', text: '报表管理'
}
// , {
// itemKey: 'automaticReport', to: '/service/reportingServices/automaticReport', text: ''
// }, {
// itemKey: 'reportFile', to: '/service/reportingServices/reportFile', text: ''
// }
, {
itemKey: 'automaticReport', to: '/service/reportingServices/automaticReport', text: '自动化报表'
}, {
itemKey: 'reportFile', to: '/service/reportingServices/reportFile', text: '报表文件'
}
]
}, {
itemKey: 'maintenancePlan',

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

@ -77,6 +77,13 @@ export const ApiTable = {
getProjectStatus: "project/status", //获取项目状态列表
putPushPushId: "push/{pushId}", //更改推送配置状态(禁用或删除)
// 报表服务
reportFile: "report/file", //报表上传
delReportFile: "report/file/{id}",//报表删除
factorList: "factor/list", //获取监测因素数据
automaticReport: "automatic/report", //自动化报表
delAutomaticReport: "automatic/report/{id}", //删除报表规则
//控制台
consoleToollink: 'console/toollink', //常用工具
deleteConsoleToollink: 'console/toollink/{linkId}', //删除常用工具

Loading…
Cancel
Save