diff --git a/api/app/lib/controllers/alarm/app.js b/api/app/lib/controllers/alarm/app.js index 5959423..6010b7a 100644 --- a/api/app/lib/controllers/alarm/app.js +++ b/api/app/lib/controllers/alarm/app.js @@ -1,5 +1,5 @@ 'use strict'; - +const fs = require('fs'); const moment = require('moment') const { alarmConfirmLog } = require('./alarmConfirmLog'); async function inspection (ctx) { @@ -258,7 +258,7 @@ async function apiErrorList (ctx) { const models = ctx.fs.dc.models; const { clickHouse } = ctx.app.fs const { utils: { anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs - const { keyword, errType, confirmState, state, sustainTimeStart, sustainTimeEnd, limit, page, pepProjectId } = ctx.query + const { keyword, errType, confirmState, state, sustainTimeStart, sustainTimeEnd, limit, page, pepProjectId, toExport } = ctx.query let pomsProject = await pomsProjectRange({ ctx, pepProjectId, @@ -267,6 +267,7 @@ async function apiErrorList (ctx) { }) const pomsProjectIds = pomsProject.map(p => p.id) let findOption = { + distinct: true, where: { $or: [] }, @@ -332,11 +333,13 @@ async function apiErrorList (ctx) { } ]) } - if (limit) { - findOption.limit = limit - } - if (page && limit) { - findOption.offset = page * limit + if (!toExport) {//非导出时考虑分页 + if (limit) { + findOption.limit = limit + } + if (page && limit) { + findOption.offset = page * limit + } } if (!findOption.where.$or.length) { delete findOption.where.$or @@ -353,9 +356,59 @@ async function apiErrorList (ctx) { } } } + if (toExport) {//数据导出相关 + await exportAppAlarms(ctx, listRes); + } else { + ctx.status = 200; + ctx.body = listRes + } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} +async function exportAppAlarms(ctx, listRes) { + let typeData = { element: "元素异常", apiError: "接口报错 ", timeout: "加载超时" } + try { + const { utils: { simpleExcelDown, getExportAlarmHeader } } = ctx.app.fs; + let header = await getExportAlarmHeader(ctx, 'app'); + let exportData = [] + for (let { dataValues: item } of listRes.rows) { + let projectName = []; + for (let project of item.app.projectCorrelations) { + if (project.dataValues.name) { + projectName.push(project.dataValues.name) + } else { + projectName.push(project.dataValues.pepProject.projectName) + } + } + item.projectName = projectName.join('\r\n');//项目名称 + + item.appName = item.app.dataValues.name; + item.url = item.app.dataValues.url; + item.type = item.type ? typeData[item.type] : '无';//异常类型 + + let time = moment(item.confirmTime || item.updateTime || moment().format("YYYY-MM-DD HH:mm:ss")).diff(moment(item.createTime), 'seconds') + item.sustainTime = time < 60 ? '< 1分钟' : time > 3600 ? Math.floor(time / 3600) + '小时' + Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟' : Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟'; + + item.alarmContent = item.alarmContent || '无'; + item.updateTime = item.updateTime || '无'; + item.confirm = item.confirm || '无'; + item.confirmTime = item.confirmTime || '无'; + + exportData.push(item) + } + const fileName = `应用异常列表_${moment().format('YYYYMMDDHHmmss')}` + '.csv' + const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName }) + const fileData = fs.readFileSync(filePath); ctx.status = 200; - ctx.body = listRes + ctx.set('Content-Type', 'application/x-xls'); + ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName)); + ctx.body = fileData; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; diff --git a/api/app/lib/controllers/alarm/data.js b/api/app/lib/controllers/alarm/data.js index a2cd793..a34e7d5 100644 --- a/api/app/lib/controllers/alarm/data.js +++ b/api/app/lib/controllers/alarm/data.js @@ -1,4 +1,5 @@ 'use strict'; +const fs = require('fs'); const moment = require('moment'); const { alarmConfirmLog } = require('./alarmConfirmLog'); async function groupList (ctx) { @@ -34,7 +35,7 @@ async function list (ctx) { const { utils: { judgeSuper, anxinStrucIdRange } } = ctx.app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config - const { pepProjectId, keywordTarget, keyword, groupId, groupUnitId, sustainTimeStart, sustainTimeEnd, limit, page, state, onlineState } = ctx.query + const { pepProjectId, keywordTarget, keyword, groupId, groupUnitId, sustainTimeStart, sustainTimeEnd, limit, page, state, onlineState, toExport } = ctx.query let anxinStruc = await anxinStrucIdRange({ ctx, pepProjectId, keywordTarget, keyword @@ -117,8 +118,8 @@ async function list (ctx) { AlarmContent ${alarmQueryOptionStr} ORDER BY alarms.StartTime DESC - ${limit ? 'LIMIT ' + limit : ''} - ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} + ${!toExport && limit ? 'LIMIT ' + limit : ''} + ${!toExport && limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} `).toPromise(); // TODO lukai 说这里要加也不知道啥时候加 @@ -186,10 +187,15 @@ async function list (ctx) { // 告警详情的数量 ar.detailCount = (alarmDetailCount.find(adc => adc.AlarmId == ar.AlarmId) || { count: 0 }).count }) - ctx.status = 200; - ctx.body = { - count: countAlarm[0].count, - rows: alarmRes + + if (toExport) { + await exportDataAlarms(ctx, alarmRes, groupId); + } else { + ctx.status = 200; + ctx.body = { + count: countAlarm[0].count, + rows: alarmRes + } } } else { ctx.body = { @@ -207,7 +213,92 @@ async function list (ctx) { } } -async function detail (ctx) { +async function getAlarmGroups(ctx) { + try { + const { clickHouse } = ctx.app.fs + const groupRes = await clickHouse.anxinyun.query(` + SELECT * FROM t_alarm_group +`).toPromise(); + + for (let g of groupRes) { + g.unit = await await clickHouse.anxinyun.query(` + SELECT * FROM t_alarm_group_unit WHERE group_id = ${g.id} + `).toPromise(); + } + return groupRes + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} +async function exportDataAlarms(ctx, alarmList, groupId) { + try { + const { utils: { simpleExcelDown, getExportAlarmHeader } } = ctx.app.fs; + let header = await getExportAlarmHeader(ctx, groupId); + let alarmGroups = await getAlarmGroups(ctx); + + let ggroups = [] + alarmGroups.filter(ag => groupId.split(',').indexOf(ag.id + '') != -1).map(g => { + g.unit.map(u => { + ggroups.push(u) + }) + }) + + let exportData = [] + for (let item of alarmList.slice(0, 1000000)) {//最多百万条 + let projectNames = item.pomsProject.map(p => { + return p.name || p.pepProject.projectName + }) + item.projectName = projectNames.join('\r\n') || '无';//项目名称 + + let ycType = ggroups.find(r => r.id == item.AlarmGroupUnit) ? ggroups.find(r => r.id == item.AlarmGroupUnit).name : '无';//异常类型 + //AlarmGroupUnit中断类型 + item.AlarmGroupUnit = ycType; + + //type异常类型 + item.type = groupId == "4,5" ? item.DeviceStatus == 0 ? "离线" : '' : ycType; + + //Strategy策略类型 + item.Strategy = ycType; + //State命中状态 + item.State = item.State == 3 || item.State == 4 ? "历史" : "当前" + + //station位置信息 + item.station = item.StructureLongitude && item.StructureLatitude ? item.StructureLongitude + '. ' + item.StructureLatitude : "无"; + + //venderName设备厂家 + item.venderName = item.platform ? '未知' : item.venderName ? item.venderName : "无";//存疑 参考前端得到 + + let time = moment(item.confirmedTime || item.EndTime || moment().format("YYYY-MM-DD HH:mm:ss")).diff(moment(item.StartTime), 'seconds') + item.sustainTime = time < 60 ? '< 1分钟' : time > 3600 ? Math.floor(time / 3600) + '小时' + Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟' : Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟'; + + item.AlarmCodeName = item.AlarmCodeName || '无'; + item.EndTime = item.EndTime || '无'; + item.confirmedContent = item.confirmedContent || '无'; + item.confirmedTime = item.confirmedTime || '无'; + + exportData.push(item) + } + let ttype = groupId == '1' ? '数据中断' : groupId == '2' ? '数据异常' : groupId == '3' ? '策略命中' : '设备异常';//数据类告警类别 + const fileName = `数据异常列表_${ttype}_${moment().format('YYYYMMDDHHmmss')}` + '.csv' + const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName }) + const fileData = fs.readFileSync(filePath); + ctx.status = 200; + ctx.set('Content-Type', 'application/x-xls'); + ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName)); + ctx.body = fileData; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} +async function detail(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs diff --git a/api/app/lib/controllers/alarm/video.js b/api/app/lib/controllers/alarm/video.js index 3c7a959..95a2f3d 100644 --- a/api/app/lib/controllers/alarm/video.js +++ b/api/app/lib/controllers/alarm/video.js @@ -1,4 +1,5 @@ 'use strict'; +const fs = require('fs'); const moment = require('moment') const { alarmConfirmLog } = require('./alarmConfirmLog'); async function deviceType (ctx) { @@ -27,7 +28,7 @@ async function alarmList (ctx) { const { clickHouse } = ctx.app.fs const { utils: { judgeSuper, anxinStrucIdRange } } = ctx.app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config - const { pepProjectId, keywordTarget, keyword, state, kindId, sustainTimeStart, sustainTimeEnd, limit, page, } = ctx.query + const { pepProjectId, keywordTarget, keyword, state, kindId, sustainTimeStart, sustainTimeEnd, limit, page, toExport } = ctx.query let anxinStruc = await anxinStrucIdRange({ ctx, pepProjectId, keywordTarget, keyword @@ -137,8 +138,8 @@ async function alarmList (ctx) { AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} ) - ${limit ? 'LIMIT ' + limit : ''} - ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} + ${!toExport && limit ? 'LIMIT ' + limit : ''} + ${!toExport && limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} ) AS cameraAlarm LEFT JOIN camera_status ON cameraAlarm.platform = camera_status.platform @@ -254,9 +255,62 @@ async function alarmList (ctx) { } } } + if (toExport) {//数据导出相关 + await exportVideoAlarms(ctx, returnD); + } else { + ctx.status = 200; + ctx.body = returnD + } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function exportVideoAlarms(ctx, alarmList) { + let accessType = { yingshi: "萤石云", nvr: "NVR", ipc: "IPC", cascade: "级联" } + try { + const { clickHouse, utils: { simpleExcelDown, getExportAlarmHeader } } = ctx.app.fs; + const kindRes = await clickHouse.vcmp.query(` + SELECT * FROM camera_kind + `).toPromise() + let header = await getExportAlarmHeader(ctx, 'video'); + let exportData = [] + for (let item of alarmList) { + let projectNames = item.pomsProject.map(p => { + return p.name || p.pepProject.projectName + }) + let structNames = item.struc.map(str => str.name); + item.projectName = projectNames.join('\r\n') || '无';//项目名称 + item.structureName = structNames.join('\r\n');//结构物名称 + item.stations = item.station.length ? item.station.map(st => st.position).join('\r\n') : '无';//位置信息 + item.cameraKindId = kindRes.find(k => k.value == item.cameraKindId) ? kindRes.find(k => k.value == item.cameraKindId).name : '其他';//设备类型 ?????????? + + let time = moment(item.confirmTime || item.updateTime || moment().format("YYYY-MM-DD HH:mm:ss")).diff(moment(item.createTime), 'seconds') + item.sustainTime = time < 60 ? '< 1分钟' : time > 3600 ? Math.floor(time / 3600) + '小时' + Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟' : Math.floor((time - Math.floor(time / 3600) * 3600) / 60) + '分钟'; + item.venderName = item.platform ? '未知' : item.venderName;//设备厂家 + item.point = item.station.length ? item.station.map(st => st.name).join('\r\n') : '无';//测点 + item.platform = accessType[item.platform] || '无';//接入方式 + let resolves = item.resolve.map(rs => rs.resolve); + item.resolve = resolves.join('\r\n');//解决方案 + + item.updateTime = item.updateTime || '无'; + item.confirmContent = item.confirmContent || '无'; + item.confirmTime = item.confirmTime || '无'; + + exportData.push(item) + } + const fileName = `视频异常列表_${moment().format('YYYYMMDDHHmmss')}` + '.csv' + const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName }) + const fileData = fs.readFileSync(filePath); ctx.status = 200; - ctx.body = returnD + ctx.set('Content-Type', 'application/x-xls'); + ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName)); + ctx.body = fileData; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; @@ -265,8 +319,7 @@ async function alarmList (ctx) { } } } - -async function confirm (ctx) { +async function confirm(ctx) { try { const { alarmId, content, confirmPost } = ctx.request.body; // TODO: 以视频·应用的秘钥进行鉴权 diff --git a/api/app/lib/utils/exportAlarmHeader.js b/api/app/lib/utils/exportAlarmHeader.js new file mode 100644 index 0000000..87f3ef0 --- /dev/null +++ b/api/app/lib/utils/exportAlarmHeader.js @@ -0,0 +1,267 @@ +'use strict'; + +//'projectName', 'StructureName', 'SourceName', 'AlarmGroupUnit', 'AlarmCodeName', 'sustainTime', 'createTime', 'AlarmContent', +// 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime' +let headerZD = [{ + title: "项目名称", + key: 'projectName' +}, { + title: "结构物名称", + key: 'StructureName' +}, { + title: "告警源", + key: 'SourceName' +}, { + title: "中断类型", + key: 'AlarmGroupUnit' +}, { + title: "常见原因", + key: 'AlarmCodeName' +}, { + title: "持续时间", + key: 'sustainTime' +}, { + title: "产生时间", + key: 'StartTime' +}, { + title: "告警信息", + key: 'AlarmContent' +}, { + title: "告警等级", + key: 'CurrentLevel' +}, { + title: "更新时间", + key: 'EndTime' +}, { + title: "产生次数", + key: 'detailCount' +}, { + title: "确认信息", + key: 'confirmedContent' +}, { + title: "确认/恢复时间", + key: 'confirmedTime' +}]; +//'projectName', 'StructureName', 'SourceName', 'type', 'createTime', 'sustainTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime +let headerYC = [{ + title: "项目名称", + key: 'projectName' +}, { + title: "结构物名称", + key: 'StructureName' +}, { + title: "告警源", + key: 'SourceName' +}, { + title: "异常类型", + key: 'type' +}, { + title: "产生时间", + key: 'StartTime' +}, { + title: "持续时间", + key: 'sustainTime' +}, { + title: "告警信息", + key: 'AlarmContent' +}, { + title: "告警等级", + key: 'CurrentLevel' +}, { + title: "更新时间", + key: 'EndTime' +}, { + title: "产生次数", + key: 'detailCount' +}, { + title: "确认信息", + key: 'confirmedContent' +}, { + title: "确认/恢复时间", + key: 'confirmedTime' +}]; +//'projectName', 'StructureName', 'SourceName', 'Strategy', 'State', 'createTime', 'sustainTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime' +let headerMZ = [{ + title: "项目名称", + key: 'projectName' +}, { + title: "结构物名称", + key: 'StructureName' +}, { + title: "告警源", + key: 'SourceName' +}, { + title: "策略类型", + key: 'Strategy' +}, { + title: "命中状态", + key: 'State' +}, { + title: "产生时间", + key: 'StartTime' +}, { + title: "持续时间", + key: 'sustainTime' +}, { + title: "告警信息", + key: 'AlarmContent' +}, { + title: "告警等级", + key: 'CurrentLevel' +}, { + title: "更新时间", + key: 'EndTime' +}, { + title: "产生次数", + key: 'detailCount' +}, { + title: "确认信息", + key: 'confirmedContent' +}, { + title: "确认/恢复时间", + key: 'confirmedTime' +}]; +//'projectName', 'StructureName', 'SourceName', 'station', 'sustainTime', 'venderName', 'AlarmContent', 'AlarmCodeName', 'createTime', 'updateTime', 'confirm', 'confirmTime' +let headerSB = [{ + title: "项目名称", + key: 'projectName' +}, { + title: "结构物名称", + key: 'StructureName' +}, { + title: "告警源", + key: 'SourceName' +}, { + title: "位置信息", + key: 'station' +}, { + title: "持续时间", + key: 'sustainTime' +}, { + title: "设备厂家", + key: 'venderName' +}, { + title: "告警信息", + key: 'AlarmContent' +}, { + title: "常见原因", + key: 'AlarmCodeName' +}, { + title: "产生时间", + key: 'StartTime' +}, { + title: "更新时间", + key: 'EndTime' +}, { + title: "确认信息", + key: 'confirmedContent' +}, { + title: "确认/恢复时间", + key: 'confirmedTime' +}]; +let header_video = [{ + title: "项目名称", + key: 'projectName' +}, { + title: "结构物名称", + key: 'structureName' +}, { + title: "告警源", + key: 'cameraName' +}, { + title: "位置信息", + key: 'stations' +}, { + title: "设备类型", + key: 'cameraKindId' +}, { + title: "持续时间", + key: 'sustainTime' +}, { + title: "设备厂家", + key: 'venderName' +}, { + title: "测点", + key: 'point' +}, { + title: "序列号", + key: 'cameraSerialNo' +}, { + title: "通道号", + key: 'cameraChannelNo' +}, { + title: "接入方式", + key: 'platform' +}, { + title: "告警信息", + key: 'statusDescribe' +}, { + title: "解决方案", + key: 'resolve' +}, { + title: "产生时间", + key: 'createTime' +}, { + title: "更新时间", + key: 'updateTime' +}, { + title: "确认信息", + key: 'confirmContent' +}, { + title: "确认/恢复时间", + key: 'confirmTime' +}]; +let header_app = [{ + title: "项目名称", + key: "projectName", +}, { + title: "应用名称", + key: "appName", +}, { + title: "URL地址", + key: "url", +}, { + title: "异常类型", + key: "type", +}, { + title: "异常信息", + key: "alarmContent", +}, { + title: "产生时间", + key: "createTime", +}, { + title: "持续时间", + key: "sustainTime", +}, { + title: "更新时间", + key: "updateTime", +}, { + title: "确认信息", + key: "confirm", +}, { + title: "确认/恢复时间", + key: "confirmTime", +}]; +async function getExportAlarmHeader(ctx, type) { + try { + let headerMap = { + "1": headerZD,//数据中断 + "2": headerYC,//数据异常 + "3": headerMZ,//策略命中 + "4,5": headerSB,//设备异常 + video: header_video, + app: header_app, + } + return headerMap[type] || [] + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +module.exports = { + getExportAlarmHeader +}; \ No newline at end of file diff --git a/web/client/src/sections/problem/components/tableData.jsx b/web/client/src/sections/problem/components/tableData.jsx index e49f91c..8c649c6 100644 --- a/web/client/src/sections/problem/components/tableData.jsx +++ b/web/client/src/sections/problem/components/tableData.jsx @@ -10,7 +10,7 @@ import qs from "qs"; const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition, pepProjectId, - selected, setSelected, setIfBulk, setConfirm, genre, setGenre, query, setQuery, tableData, setTableData, location }) => { + selected, setSelected, setIfBulk, setConfirm, genre, setGenre, query, setQuery, tableData, setTableData, location, user }) => { const { problem } = actions const [count, setCount] = useState(0) // @@ -23,7 +23,7 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition const kindName = useRef() const groupId = useRef() let title = { dataLnterrupt: "数据中断详情", dataAbnormal: "数据异常详情", strategyHit: "策略命中详情", videoAbnormal: "视频异常详情", useAbnormal: "应用异常详情", deviceAbnormal: "设备异常详情" } - + const [exportUrl, setExportUrl] = useState('') useEffect(() => { switch (route) { case 'useAbnormal': @@ -156,7 +156,33 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition } }, []) - + const handleExport = () => { + let url = '' + let { keywordTarget, keyword = '', errType = '', state = '', kindId = '', groupUnitId = '', confirmState = '', onlineState = '', sustainTimeStart = '', sustainTimeEnd = '' } = search.current + switch (route) { + case 'useAbnormal': + url = `alarm/application/api?token=${user.token}&toExport=1×tamp=${moment().valueOf()}&keyword=${keyword}&errType=${errType} + &state=${state}stainTimeStart=${sustainTimeStart}&sustainTimeEnd=${sustainTimeEnd}&pepProjectId=${pepProjectId || ''}` + break; + case 'videoAbnormal': + url = `alarm/video/list?token=${user.token}&toExport=1×tamp=${moment().valueOf()}&keywordTarget=${keywordTarget} + &keyword=${keyword}&kindId=${kindId}&state=${state}&sustainTimeStart=${sustainTimeStart}&sustainTimeEnd=${sustainTimeEnd} + &pepProjectId=${pepProjectId || ''}` + break; + default: + let groups = { + "dataLnterrupt": "1", + "dataAbnormal": "2", + "strategyHit": "3", + "deviceAbnormal": "4,5" + } + url = `alarm/data/list?token=${user.token}&toExport=1×tamp=${moment().valueOf()}&state=${state}&keywordTarget=${keywordTarget}&keyword=${keyword}&kindId=${kindId} + &groupUnitId=${groupUnitId}&errType=${errType}&confirmState=${confirmState}&onlineState=${onlineState}&sustainTimeStart=${sustainTimeStart} + &sustainTimeEnd=${sustainTimeEnd}&pepProjectId=${pepProjectId || ''}&groupId=${groups[route]}` + break; + } + setExportUrl(url); + } return ( <> @@ -243,7 +269,24 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition marginBottom: 16 }} > - + { + api.current.validate().then((v) => { + search.current = { + state: v.state, + keywordTarget: v.keywordTarget, + keyword: v.keyword, + kindId: v.kindId, + groupUnitId: v.groupUnitId, + errType: v.errType, + confirmState: v.confirmState, + onlineState: v.onlineState, + sustainTimeStart: v.time && v.time.length > 0 ? moment(v.time[0]).format("YYYY-MM-DD HH:mm:ss") : "", + sustainTimeEnd: v.time && v.time.length > 0 ? moment(v.time[1]).format("YYYY-MM-DD HH:mm:ss") : "", + } + handleExport(); + }); + }} /> setSetup(true)} />