11 changed files with 1213 additions and 146 deletions
			
			
		| @ -0,0 +1,582 @@ | |||
| 'use strict'; | |||
| import React, { useState, useEffect } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { Button, Notification  } from "@douyinfe/semi-ui"; | |||
| import moment from 'moment'; | |||
| import XLSX from 'xlsx'; | |||
| import { fromJS } from 'immutable'; | |||
| import { Request } from '@peace/utils'; | |||
| import FileSaver from 'file-saver'; | |||
| import { IconArrowDown  } from '@douyinfe/semi-icons'; | |||
| 
 | |||
| //通用前端导出组件  使用方法查看底部propTypes | |||
| const ExportData = ({...props}) => { | |||
|     //const [form] = Form.useForm(); | |||
|     const [exportLoading, setExportLoading] = useState(false); | |||
|     const { customRender, title, exportType, style, showIcon } = props; | |||
|     | |||
| 
 | |||
|     const loop = (data, keypath, values) => { // deal with array | |||
|         let dkey = keypath.slice(0, 1)[0]; | |||
|         if (dkey) { | |||
|             let dvalue = data[dkey]; | |||
|             let otherKeypath = keypath.slice(1); | |||
|             if (Array.isArray(dvalue)) { | |||
|                 if (otherKeypath.length) { | |||
|                     let immutableData = fromJS(data); | |||
|                     for (let index = 0; index < dvalue.length; index++) { | |||
|                         let tmp = immutableData.getIn([dkey, index]).toJS(); | |||
|                         loop(tmp, otherKeypath, values); | |||
|                     } | |||
|                 } | |||
|             } else { | |||
|                 values.push(dvalue); | |||
|             } | |||
|         } | |||
|         return values; | |||
|     }; | |||
|     const getColumnData = (opts) => { | |||
|         const { data, keypath, render, spliter, rawdata, valueEnum } = opts; | |||
|         let v = null; | |||
|         let outer = data[keypath[0]]; | |||
|         if (Array.isArray(outer)) { | |||
|             let values = loop(data, keypath, []); | |||
|             v = rawdata ? values : values.join(spliter || ','); | |||
|         } else { | |||
|             v = fromJS(data).getIn(keypath) | |||
|         } | |||
|         //处理proTable 枚举 | |||
|         if(valueEnum && valueEnum[v]?.text){ | |||
|            v =  valueEnum[v]?.text; | |||
|         } | |||
|         //处理render | |||
|         // if (render && typeof render === 'function') { | |||
|         //     v = render(outer, data); | |||
| 
 | |||
|         // } | |||
|         return v; | |||
|     }; | |||
|     const getDataSource = (attrs, filterData) => { | |||
|         debugger | |||
|         let dataSource = filterData.map(item => { | |||
|             let record = {}; | |||
|             attrs.forEach(attr => { | |||
|                 const { key, dataIndex, render, child, valueEnum } = attr; | |||
|                 if (child) { | |||
|                   | |||
|                     record[key] = getDataSource(child, item[key] || []); | |||
|                 } else { | |||
|                     let v = getColumnData({ | |||
|                         data: item, | |||
|                         keypath: Array.isArray(dataIndex) ? dataIndex : [dataIndex], | |||
|                         render: render || null, | |||
|                         valueEnum: valueEnum | |||
|                     }); | |||
|                     record[key] = v; | |||
|                 } | |||
| 
 | |||
| 
 | |||
|             }); | |||
| 
 | |||
|             return record; | |||
|         }); | |||
|         return dataSource; | |||
|          | |||
|          | |||
|     }; | |||
| 
 | |||
|     const getNewColumns = (attrs) => { | |||
|         return attrs.filter(f=> f.dataIndex).map(v=>{ | |||
|            const { dataIndex } = v; | |||
|            return { | |||
|                ...v, | |||
|                key: Array.isArray(dataIndex) ? dataIndex.reduce((p,c)=>{ | |||
|                     p = `${p}${c.trim().replace(c[0], c[0].toUpperCase())}`; | |||
|                     return p | |||
|                },'') : dataIndex | |||
|            } | |||
|         }) | |||
|     } | |||
|     //暂时只处理两层  | |||
|     const getFlatData = (attrs, filterData, dataToAoa, deep = 0) => { | |||
| 
 | |||
|         filterData.map(item => { | |||
|             let cur = dataToAoa[deep] | |||
|             if (!cur) { | |||
|                 cur = dataToAoa[deep] = [] | |||
|             } | |||
|             attrs.map((attr, index) => { | |||
|                 const { key, child } = attr; | |||
|                 if (child) { | |||
|                     if (Array.isArray(item[key])) { | |||
|                         //getFlatData(child,item[key],dataToAoa,deep) | |||
| 
 | |||
|                         item[key].map((s, i) => { | |||
|                             if (i == 0) { | |||
|                                 child.map(c => { | |||
|                                     cur.push(s[c.key]); | |||
|                                 }) | |||
|                             } else { | |||
|                                 deep++ | |||
|                                 let childCur = dataToAoa[deep] = [] | |||
|                                 pushNull(childCur, index); | |||
|                                 child.map(c => { | |||
|                                     childCur.push(s[c.key]); | |||
|                                 }); | |||
|                             } | |||
| 
 | |||
|                         }) | |||
| 
 | |||
|                     } | |||
|                 } else { | |||
|                     cur.push(item[key]); | |||
|                 } | |||
| 
 | |||
|             }); | |||
|             deep++ | |||
|         }); | |||
| 
 | |||
| 
 | |||
|     }; | |||
| 
 | |||
|     const getHeader = (headers, excelHeader, deep, perOffset) => { | |||
|         let offset = 0 | |||
|         let cur = excelHeader[deep] | |||
|         if (!cur) { | |||
|             cur = excelHeader[deep] = [] | |||
|         } | |||
|         pushNull(cur, perOffset - cur.length) | |||
|         for (let i = 0; i < headers.length; i++) { | |||
|             let head = headers[i] | |||
|             cur.push(head.name) | |||
|             if (head.hasOwnProperty('child') && Array.isArray(head.child) && head.child.length > 0) { | |||
|                 let childOffset = getHeader(head.child, excelHeader, deep + 1, cur.length - 1) | |||
|                 pushNull(cur, childOffset - 1) | |||
|                 offset += childOffset | |||
|             } else { | |||
|                 offset++ | |||
|             } | |||
|         } | |||
|         return offset; | |||
|     } | |||
| 
 | |||
|     const pushNull = (arr, count) => { | |||
|         for (let i = 0; i < count; i++) { | |||
|             arr.push(null) | |||
|         } | |||
|     } | |||
|     const fillNull = (arr) => { | |||
|         let max = Math.max(...(arr.map(a => a.length))) | |||
|         arr.filter(e => e.length < max).forEach(e => pushNull(e, max - e.length)) | |||
|     } | |||
|     const doMerges = (arr) => { | |||
|         // 要么横向合并 要么纵向合并 | |||
|         let deep = arr.length; | |||
|         let merges = []; | |||
|         for (let y = 0; y < deep; y++) { | |||
|             // 先处理横向合并 | |||
|             let row = arr[y]; | |||
|             let colSpan = 0 | |||
|             for (let x = 0; x < row.length; x++) { | |||
|                 if (row[x] === null) { | |||
|                     colSpan++ | |||
|                     if (((x + 1) === row.length) && (colSpan > 0 && x > colSpan)) { | |||
|                         merges.push({ s: { r: y, c: x - colSpan }, e: { r: y, c: x } }) | |||
|                     } | |||
|                 } else if (colSpan > 0 && x > colSpan) { | |||
|                     merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } }) | |||
|                     colSpan = 0 | |||
|                 } else { | |||
|                     colSpan = 0 | |||
|                 } | |||
|             } | |||
|         } | |||
|         // 再处理纵向合并 | |||
|         let colLength = arr[0].length | |||
|         for (let x = 0; x < colLength; x++) { | |||
|             let rowSpan = 0 | |||
|             for (let y = 0; y < deep; y++) { | |||
|                 if (arr[y][x] != null) { | |||
|                     rowSpan = 0 | |||
|                 } else { | |||
|                     rowSpan++; | |||
|                 } | |||
|             } | |||
|             if (rowSpan > 0) { | |||
|                 merges.push({ s: { r: deep - rowSpan - 1, c: x }, e: { r: deep - 1, c: x } }) | |||
|             } | |||
|         } | |||
|         return merges; | |||
|     } | |||
|      | |||
|     //内容暂只出了纵向合并 | |||
|     const doContetMerges = (arr, headerLength) => { | |||
|         let deep = arr.length; | |||
|         let merges = []; | |||
|         //处理纵向合并 | |||
|         let colLength = arr[0].length | |||
|         for (let x = 0; x < colLength; x++) { | |||
|             let rowSpan = 0; | |||
|             let mergY = 0; | |||
|             for (let y = 0; y < deep; y++) { | |||
|                 if (rowSpan > 0) { | |||
|                     //如果还有null 继续加 | |||
|                     if (arr[y][x] === null) { | |||
|                         rowSpan = rowSpan + 1 | |||
|                     } else { | |||
|                         //不为null 增加merge | |||
|                         merges.push({ s: { r: headerLength + (y - rowSpan - 1), c: x }, e: { r: headerLength + y - 1, c: x } }); | |||
|                         rowSpan = 0; | |||
|                     } | |||
|                 } else { | |||
|                     if (arr[y][x] === null) { | |||
|                         rowSpan = rowSpan + 1 | |||
|                     } | |||
|                 } | |||
| 
 | |||
|             } | |||
|             if (rowSpan > 0) { | |||
|                 merges.push({ s: { r: headerLength + (deep - rowSpan - 1), c: x }, e: { r: headerLength + deep - 1, c: x } }) | |||
|                 rowSpan = 0; | |||
|             } | |||
|         } | |||
|         return merges; | |||
|     } | |||
| 
 | |||
|     //导出可以纵向合并单元格的数据 不建议使用 | |||
|     const exportMergeExcel = async () => { | |||
|         setExportLoading(true) | |||
|         const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType, header, showYearMouth } = props || {}; | |||
| 
 | |||
|         let resultData = []; | |||
|         if (exportUrl) { | |||
| 
 | |||
|             resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { | |||
|                 //数据接口返回的结果 如果是对象 必须把返回数组放入rows | |||
|                 if (typeof data === 'object' && data.rows) { | |||
| 
 | |||
|                     return data.rows | |||
| 
 | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 }) | |||
|             }) : await Request.get(exportUrl, exportQuery || {}).then(data => { | |||
|                 if (typeof data === 'object' && data.rows) { | |||
| 
 | |||
|                     return data.rows | |||
| 
 | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 }) | |||
|             }); | |||
|             if (!resultData) { | |||
|                 return; | |||
|             } | |||
| 
 | |||
|         } else { | |||
|             resultData = data | |||
|         } | |||
|         let excelHeader = []; | |||
|         const newColumns = getNewColumns(columns); | |||
|         getHeader(newColumns, excelHeader, 0, 0); | |||
|         fillNull(excelHeader); | |||
| 
 | |||
|         //console.log(excelHeader); | |||
| 
 | |||
|         let loopData = getDataSource(newColumns, resultData); | |||
|         //console.log(loopData) | |||
| 
 | |||
|         let dataToAoa = []; | |||
|         getFlatData(newColumns, loopData, dataToAoa, 0); | |||
|         fillNull(dataToAoa); | |||
|         //console.log(dataToAoa); | |||
| 
 | |||
|         let aoa = [].concat(excelHeader, dataToAoa); | |||
|         //console.log(aoa) | |||
| 
 | |||
|         let headerMerges = doMerges(excelHeader); | |||
|         let contentMerages = doContetMerges(dataToAoa, excelHeader.length); | |||
|         let merges = [].concat(headerMerges, contentMerages); | |||
|         // console.log(contentMerages) | |||
| 
 | |||
|         // let opts = { | |||
|         //     defaultCellStyle: { | |||
|         //         font: { name: "宋体", sz: 11, color: { auto: 1 } }, | |||
|         //         border: { | |||
|         //             color: { auto: 1 } | |||
|         //         }, | |||
|         //         alignment: { | |||
|         //             /// 自动换行 | |||
|         //             wrapText: 1, | |||
|         //             // 居中 | |||
|         //             horizontal: "center", | |||
|         //             vertical: "center", | |||
|         //             indent: 0 | |||
|         //         } | |||
|         //    } | |||
|         // } | |||
|         let sheet = XLSX.utils.aoa_to_sheet(aoa); | |||
|         // let newSheet = {};  | |||
|         // for (let [key, value] of Object.entries(sheet)) { | |||
|         //     if(key == '!ref'){ | |||
|         //         newSheet[key] = value | |||
|         //     }else if(typeof value === 'object'){ | |||
|         //         newSheet[key] = { | |||
|         //             ...value, | |||
|         //             s: opts.defaultCellStyle | |||
|         //         } | |||
|         //     } | |||
|         // } | |||
|         const wpx = columns.map(c => { | |||
|             return { | |||
|                 wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100 | |||
|             } | |||
|         }) | |||
|         sheet['!cols'] = wpx; | |||
|         sheet['!merges'] = merges; | |||
| 
 | |||
|         // 构建 workbook 对象 | |||
|         const workbook = XLSX.utils.book_new(); | |||
| 
 | |||
|         const time = moment().format('YYYY-MM-DD'); | |||
| 
 | |||
| 
 | |||
|         XLSX.utils.book_append_sheet(workbook, sheet, 'mySheet'); | |||
|         // 导出 Excel | |||
|         XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : `导出数据-${time}.xlsx`); | |||
|         setExportLoading(false); | |||
|         Notification.success({ | |||
|             content: `成功导出了 ${loopData.length || 0} 条数据`, | |||
|             duration: 3, | |||
|         }) | |||
|     } | |||
|     //FileSaver 方式导出可以自定义样式 columns可定义 headStyle, rowStyle | |||
|     const exportFileSaver = async () => { | |||
|         setExportLoading(true) | |||
|         const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType } = props || {}; | |||
|         let resultData = []; | |||
|         if (exportUrl) { | |||
|             resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { | |||
|                 //数据接口返回的结果 如果是对象 必须把返回数组放入rows | |||
|                 if (typeof data === 'object') { | |||
|                     return data.data ? data.data : data.rows | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 }) | |||
|             }) : await Request.get(exportUrl, exportQuery || {}).then(data => { | |||
|               | |||
|                 if (typeof data === 'object' && data.rows) { | |||
|                     return data.rows | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 }) | |||
|             }); | |||
|             if (!resultData) { | |||
|                 return; | |||
|             } | |||
| 
 | |||
|         } else { | |||
|             resultData = data | |||
|         } | |||
|         const newColumns = getNewColumns(columns); | |||
| 
 | |||
|         const loopData = getDataSource(newColumns, resultData); | |||
| 
 | |||
|         let content = ''; | |||
|         let header = '<tr>'; | |||
|         //header += `<th><div>序号</div></th>`; | |||
|         newColumns.map(colum => { | |||
|             header += `<th style="${colum.headStyle || ''}"><div>${colum.title}</div></th>` | |||
|         }); | |||
|         header += '</tr>'; | |||
|         loopData.map(data => { | |||
|                 content += `<tr>`; | |||
|                 newColumns.map(c => { | |||
|                     if (c.style) { | |||
|                         content += `<th style="${c.rowStyle || ''}"><div>${data[c.key] || ''}</div></th>` | |||
|                     } else { | |||
|                         content += `<th><div>${data[c.key] || ''}</div></th>` | |||
|                     } | |||
|                 }); | |||
|                 content += `</tr>`; | |||
|             }) | |||
| 
 | |||
|         let exportTable = `\uFEFF | |||
|                 <table style='text-alagin:center' border="1"> | |||
|                     ${header} | |||
|                     ${content} | |||
|                 </table> | |||
|             `; | |||
|         const time = moment().format('YYYY-MM-DD'); | |||
|         let tempStrs = new Blob([exportTable], { type: 'text/xls' }) | |||
|         FileSaver.saveAs(tempStrs, fileName ? `${fileName}-${time}.xls` : `导出数据-${time}.xlsx`); | |||
|         setExportLoading(false); | |||
|         Notification.success({ | |||
|             content: `成功导出了 ${loopData.length || 0} 条数据`, | |||
|             duration: 3, | |||
|         }) | |||
|     } | |||
| 
 | |||
|     //普通XLSX导出 | |||
|     const exportExcel = async () => { | |||
|         setExportLoading(true) | |||
|         const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType } = props || {}; | |||
| 
 | |||
|         const newColumns = getNewColumns(columns); | |||
|         | |||
|         const _headers = newColumns | |||
|             .map((item, i) => Object.assign({}, { key: item.key, title: item.title, position: String.fromCharCode(65 + i) + 1 })) | |||
|             .reduce((prev, next) => Object.assign({}, prev, { [next.position]: { key: next.key, v: next.title } }), {}); | |||
|         let resultData = []; | |||
|         if (exportUrl) { | |||
|             resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { | |||
|                 //数据接口返回的结果 如果是对象 必须把返回数组放入rows | |||
|                  | |||
|                 if (typeof data === 'object' && (data.rows || data.data)) {  | |||
|                     return data.data ? data.data : data.rows | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 })            | |||
|              }) : await Request.get(exportUrl, exportQuery || {}).then(data => { | |||
|                 if (typeof data === 'object' && data.rows) { | |||
|                     return data.rows | |||
| 
 | |||
|                 } else { | |||
|                     return data; | |||
|                 } | |||
|             }, err => { | |||
|                 Notification.error({ | |||
|                     content: '获取数据失败,导出失败!', | |||
|                     duration: 3, | |||
|                 }) | |||
|             }); | |||
|             if (!resultData) { | |||
|                 return; | |||
|             } | |||
| 
 | |||
|         } else { | |||
|             resultData = data | |||
|         } | |||
|        | |||
|         const loopData = getDataSource(newColumns, resultData); | |||
|       | |||
| 
 | |||
|         const wpx = newColumns.map(c => { | |||
|             return { | |||
|                 wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100 | |||
|             } | |||
|         }) | |||
|         if (!(loopData.length > 0)) { | |||
|             setExportLoading(false); | |||
|             return; | |||
|         } | |||
|         const _data = loopData | |||
|             .map((item, i) => newColumns.map((key, j) => Object.assign({}, { content: item[key.key], position: String.fromCharCode(65 + j) + (i + 2) }))) | |||
|             // 对刚才的结果进行降维处理(二维数组变成一维数组) | |||
|             .reduce((prev, next) => prev.concat(next)) | |||
|             // 转换成 worksheet 需要的结构 | |||
|             .reduce((prev, next) => Object.assign({}, prev, { [next.position]: { v: next.content } }), {}); | |||
| 
 | |||
|         // 合并 columns 和 data | |||
|         const output = Object.assign({}, _headers, _data); | |||
|         // 获取所有单元格的位置 | |||
|         const outputPos = Object.keys(output); | |||
|         // 计算出范围 ,["A1",..., "H2"] | |||
|         const ref = `${outputPos[0]}:${outputPos[outputPos.length - 1]}`; | |||
| 
 | |||
|         // 构建 workbook 对象 | |||
|         const workbook = { | |||
|             SheetNames: ['mySheet'], | |||
|             Sheets: { | |||
|                 mySheet: Object.assign( | |||
|                     {}, | |||
|                     output, | |||
|                     { | |||
|                         '!ref': ref, | |||
|                         '!cols': wpx, | |||
|                     }, | |||
|                 ), | |||
|             }, | |||
|         }; | |||
|         const time = moment().format('YYYY-MM-DD'); | |||
|         // 导出 Excel | |||
|         XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : `导出数据-${time}.xlsx`); | |||
|         setExportLoading(false); | |||
|         Notification.success({ | |||
|             content: `成功导出了 ${loopData.length || 0} 条数据`, | |||
|             duration: 3, | |||
|         }) | |||
|     } | |||
| 
 | |||
|    | |||
|     const handleExport = async () => { | |||
|        switch (exportType) { | |||
|            case 'fileSaver': | |||
|                await exportFileSaver(); | |||
|            break; | |||
|            case 'xlsx': | |||
|                await exportExcel(); | |||
|            break; | |||
|            case 'merge': | |||
|                await exportMergeExcel(); | |||
|            break; | |||
|            default: | |||
|                await exportExcel(); | |||
|            break; | |||
|        } | |||
|     } | |||
|     | |||
|     return ( | |||
|            customRender ?  | |||
|            <span style={style} loading={exportLoading} onClick={handleExport}> | |||
|                {customRender} | |||
|            </span> : | |||
| 
 | |||
|            <Button style={style}  loading={exportLoading} onClick={handleExport}> | |||
|                {title || '导出'} | |||
|                {showIcon && <IconArrowDown />} | |||
|            </Button>      | |||
|     ) | |||
| } | |||
| 
 | |||
| ExportData.propTypes = { | |||
|     fileName: PropTypes.string, //导出文件名称前缀 | |||
|     showIcon: PropTypes.bool, //导出按钮是否显示icon,默认不显示 | |||
|     customRender: PropTypes.element, //自定义导出组件渲染 不传默认按钮样式 | |||
|     style: PropTypes.object,//透传style | |||
|     title: PropTypes.string, //导出按钮文字 | |||
|     columns: PropTypes.array.isRequired, //导出显示的header数组 兼容antd columns 可直接拿table或者protable的columns使用  注:columns每列的属性wpx设置导出的execl每列的宽度值 默认 100 | |||
|     data: PropTypes.array.isRequired,   //导出的数据 兼容antd table 数组嵌套处理,如果传入exportUrl 则从接口获取数据导出,此参数无效 | |||
|     exportUrl: PropTypes.string,  //导出数据从接口获取的url地址   返回的数据1、数组必须支持columns的设置 ,2、如果是对象,数组需放在rows属性上 | |||
|     exportBody: PropTypes.object, //导出数据接口body参数 | |||
|     exportQuery: PropTypes.object,  //导出数据从接口获取的url地址上的参数 | |||
|     requestType: PropTypes.string, //请求类型 get,post,默认get | |||
|     exportType: PropTypes.string, //导出执行类型函数 'fileSaver','xlsx','merge'纵向单元格合并 | |||
| }; | |||
| 
 | |||
| export default ExportData; | |||
| @ -1,66 +1,340 @@ | |||
| import React, { useEffect, useState, useRef } from 'react'; | |||
| import { connect } from 'react-redux'; | |||
| import ReactECharts from 'echarts-for-react'; | |||
| import echarts from 'echarts'; | |||
| import { Spin, Card, CardGroup, Form, Button,Table } from '@douyinfe/semi-ui'; | |||
| import React, { useEffect, useState, useRef, useMemo } from 'react' | |||
| import { connect } from 'react-redux' | |||
| import { Spin, Card, CardGroup, Form, Button, Table, Pagination, Tooltip } from '@douyinfe/semi-ui' | |||
| import ExportData from '../components/export-data' | |||
| import moment from 'moment' | |||
| 
 | |||
| const Network = props => { | |||
|   const { dispatch, actions, user, clientHeight, thingId, deviceListAlarms, devicesCardStatusList, project } = props | |||
|   const { analysis } = actions | |||
|   const form = useRef() //表单 | |||
|   const [deployData, setDeployData] = useState([]) | |||
|   const [deviceData, setDeviceData] = useState([]) | |||
|   const [deviceMetasDeployed, setDeviceMetasDeployed] = useState([]) | |||
|   const [sensorId, setSensorId] = useState([]) | |||
|   const [sensorsDataItems, setSensorsDataItems] = useState({}) | |||
|   const [tableData, setTableData] = useState([]) //最新一次的数据 | |||
|   const [lastData, setLastData] = useState([]) //最终数据 | |||
|   const [lastDataCopy, setLastDataCopy] = useState([]) //最终数据 | |||
|   const [searchType, setSearchType] = useState('') | |||
|   const [searchName, setSearchName] = useState('') | |||
|   const [typeList, setTypeList] = useState([]) | |||
|   const [query, setQuery] = useState({ limit: 10, page: 0 }) //页码信息 | |||
| 
 | |||
|   const DeviceTypes = { | |||
|     'DTU': 'DTU', | |||
|     'gateway': '网关', | |||
|     'sensor': '传感器', | |||
|     'acqUnit': '采集单元', | |||
|     'dau.gateway': '分布式智能云采集网关', | |||
|     'dau.node': '分布式智能云采集节点', | |||
|     'tcp.dtu': '工作站', | |||
|   } | |||
| 
 | |||
| const Network = (props) => { | |||
|    const { dispatch, actions, user, clientHeight } = props | |||
|   useEffect(() => { | |||
|     setLastData([]) | |||
|     setLastDataCopy([]) | |||
|   }, [project]) | |||
| 
 | |||
|    const form = useRef();//表单 | |||
|   useEffect(() => { | |||
|     if (thingId) { | |||
|       let dataList = [] | |||
|       dispatch(analysis.getThingsDeploy(thingId)).then(rs => { | |||
|         if (rs.success) { | |||
|           setDeployData(rs.payload.data) | |||
|           dataList = rs.payload.data | |||
|           //列表渲染数据 | |||
|           let da = [] | |||
|           if (dataList.instances) { | |||
|             Object.keys(dataList.instances).forEach(i => { | |||
|               if (dataList.instances[i].type == 's.d') { | |||
|                 da.push({ | |||
|                   sensorId: i, | |||
|                   sensorName: dataList.instances[i]?.name, | |||
|                   deviceType: dataList?.instances[i]?.instance?.properties?.deviceType, | |||
|                   collectTime: '--', | |||
|                   data: '--', | |||
|                   iotCardStatus: '--', | |||
|                   status: '--', | |||
|                   option: '--', | |||
|                 }) | |||
|               } | |||
|             }) | |||
|           } | |||
| 
 | |||
|           dispatch(analysis.findDeviceMetaDeployed(thingId)).then(res => { | |||
|             if (res.success) { | |||
|               setDeviceMetasDeployed(res.payload.data) | |||
|               const deviceMetaDeployed = res.payload.data | |||
|               if (deviceMetaDeployed && dataList && deviceMetaDeployed.devices) { | |||
|                 const sensorsId = [] | |||
|                 let alarmSensorId = [] //所有设备的id | |||
|                 const sensorsDataItems = {} | |||
|                 for (const id in dataList.instances) { | |||
|                   alarmSensorId.push(id) | |||
|                   const instances = dataList.instances[id] | |||
| 
 | |||
|    useEffect(() => { | |||
|                   if (instances.type == 's.d' && instances.instance.properties.deviceType == 'sensor') { | |||
|                     const meta = deviceMetaDeployed.devices.find(m => m.id == instances.instance.deviceMetaId) | |||
|                     sensorsDataItems[id] = { | |||
|                       items: {}, | |||
|                       deviceName: instances.name, | |||
|                     } | |||
|                     if (meta) { | |||
|                       sensorsDataItems[id].items = meta.capabilities[0].properties.reduce((p, n) => { | |||
|                         if (n.category == 'Output') { | |||
|                           p[n.name] = { name: n.showName, unit: n.unit } | |||
|                         } | |||
|                         return p | |||
|                       }, {}) | |||
|                     } | |||
|                     sensorsId.push(id) | |||
|                   } | |||
|                 } | |||
|                 dispatch(analysis.getDevicesAlarms({ deviceIds: alarmSensorId }, { limit: 5 })) | |||
|                 dispatch(analysis.findDevicesCardStatus({ deviceIds: alarmSensorId })) | |||
|                 setSensorsDataItems(sensorsDataItems) | |||
|                 setSensorId(sensorsId) | |||
|                 setDeviceData(da) | |||
|               } | |||
|             } | |||
|           }) | |||
|         } | |||
|       }) | |||
|     } | |||
|   }, [thingId]) | |||
| 
 | |||
|    }, []) | |||
|    const columns = [ | |||
|       { | |||
|           title: '设备名称', | |||
|           dataIndex: 'deviceName', | |||
|           width: 200, | |||
|           key:'deviceName' | |||
|       }, | |||
|       { | |||
|           title: '设备类型', | |||
|           dataIndex: 'deviceType', | |||
|           width:200, | |||
|           key:'deviceType' | |||
|       }, | |||
|       { | |||
|          title: '最后采集时间', | |||
|          dataIndex: 'collectTime', | |||
|          width:200, | |||
|          key:'collectTime' | |||
|   useEffect(async () => { | |||
|     if (sensorId && sensorId.length && sensorsDataItems) { | |||
|       const rs = await dispatch(analysis.findSensorLastData(sensorId)) | |||
|       const tableData = [] | |||
|       if (rs.success) { | |||
|         rs.payload.data.forEach(sd => { | |||
|           if (Object.keys(sensorsDataItems).length) { | |||
|             let sensorDataItem = sensorsDataItems[sd.sensorId] | |||
|             let sensorName = sensorDataItem && sensorDataItem.deviceName ? sensorDataItem.deviceName : '' | |||
|             let msg = sd.data.length | |||
|               ? sd.data[0] | |||
|               : { | |||
|                   collectTime: null, | |||
|                   sensorName: sensorName, | |||
|                   data: { noData: '暂无数据' }, | |||
|                 } | |||
|             let dataStr = '' | |||
|             let dataKeys = Object.keys(msg.data) | |||
|             dataKeys.forEach(k => { | |||
|               let item = sensorDataItem && sensorDataItem.items ? sensorDataItem.items[k] : null | |||
|               if (item) { | |||
|                 dataStr += `${item.name}:${msg.data[k]}(${item.unit}); ` | |||
|               } else if (k == 'noData') { | |||
|                 dataStr += msg.data[k] | |||
|               } else { | |||
|                 dataStr += `${k}:${msg.data[k]};` | |||
|               } | |||
|             }) | |||
|             let collectTime = msg.collectTime ? moment(msg.collectTime).format('YYYY-MM-DD HH:mm:ss') : '--' | |||
|             tableData.push({ | |||
|               sensorId: sd.sensorId, | |||
|               sensorName: sensorName, | |||
|               collectTime: collectTime, | |||
|               data: dataStr, | |||
|               deviceType: 'sensor', //传感器 | |||
|               iotCardStatus: '--', | |||
|               status: '--', | |||
|               option: '--', | |||
|             }) | |||
|           } | |||
|         }) | |||
|       } | |||
|       setTableData(tableData) | |||
|     } | |||
|   }, [sensorId]) | |||
|   useEffect(() => { | |||
|     if (deviceData && deviceData.length && tableData && tableData.length) { | |||
|       const dataD = deviceData?.map(p => { | |||
|         const objRslt = tableData.find(q => q.sensorId == p.sensorId) | |||
|         return { | |||
|           sensorId: objRslt ? objRslt.sensorId : p.sensorId, | |||
|           sensorName: objRslt ? objRslt.sensorName : p.sensorName, | |||
|           collectTime: objRslt ? objRslt.collectTime : p.collectTime, | |||
|           data: objRslt ? objRslt.data : p.data, | |||
|           deviceType: DeviceTypes[objRslt ? objRslt.deviceType : p.deviceType], | |||
|           iotCardStatus: | |||
|             devicesCardStatusList && devicesCardStatusList.length | |||
|               ? devicesCardStatusList.find(v => v.deviceId == p.sensorId).status === 0 | |||
|                 ? '正常' | |||
|                 : devicesCardStatusList.find(v => v.deviceId == p.sensorId).status === 1 | |||
|                 ? '未激活' | |||
|                 : '停机' | |||
|               : '--', | |||
|           status: | |||
|             deviceListAlarms && deviceListAlarms.length | |||
|               ? deviceListAlarms?.find(v => v.deviceId == p.sensorId) | |||
|                 ? '异常' | |||
|                 : '正常' | |||
|               : '正常', | |||
|           option: objRslt ? objRslt.option : p.option, | |||
|         } | |||
|       }) | |||
|       const typeList = dataD.reduce((p, c) => { | |||
|         let isExist = p.some(q => q.label === c.deviceType) | |||
|         if (!isExist) { | |||
|           p.push({ label: c.deviceType, value: c.sensorId }) | |||
|         } | |||
|         return p | |||
|       }, []) | |||
|       setTypeList(typeList) | |||
|       setLastData(dataD) | |||
|       setLastDataCopy(dataD) | |||
|     } | |||
|   }, [deviceData, tableData]) | |||
|   //   const lastDataCopy=useMemo(()=>{ | |||
|   //       return lastData | |||
|   //   },[thingId]) | |||
|   // const scroll = useMemo(() => ({ y: 400 }), []) | |||
|   //名称回调事件 | |||
|   const inputChange = e => { | |||
|     setSearchName(e) | |||
|   } | |||
|   //选择设备类型下拉框回调 | |||
|   const selectChange = e => { | |||
|     setSearchType(typeList.find(f => f.value == e)?.label) | |||
|   } | |||
| 
 | |||
|   //查询事件回调 | |||
|   const searchHandler = () => { | |||
|     setLastData( | |||
|       searchName || searchType | |||
|         ? lastDataCopy.filter(f => f.sensorName.includes(searchName) && f.deviceType.includes(searchType)) | |||
|         : lastDataCopy | |||
|     ) | |||
|   } | |||
| 
 | |||
|   const columns = [ | |||
|     { | |||
|       title: '设备名称', | |||
|       dataIndex: 'sensorName', | |||
|       width: 200, | |||
|       key: 'sensorName', | |||
|       render: (_, r) => { | |||
|         return ( | |||
|           <> | |||
|             <Tooltip content={r.sensorName}> | |||
|               <div>{r.sensorName.length > 7 ? `${r.sensorName.substr(0, 7)}...` : r.sensorName}</div> | |||
|             </Tooltip> | |||
|           </> | |||
|         ) | |||
|       }, | |||
|       { | |||
|           title: '更新日期', | |||
|           dataIndex: 'updateTime', | |||
|           sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1), | |||
|           render: value => { | |||
|               return dateFns.format(new Date(value), 'yyyy-MM-dd'); | |||
|           }, | |||
|     }, | |||
|     { | |||
|       title: '设备类型', | |||
|       dataIndex: 'deviceType', | |||
|       width: 200, | |||
|       key: 'deviceType', | |||
|     }, | |||
|     { | |||
|       title: '最后采集时间', | |||
|       dataIndex: 'collectTime', | |||
|       width: 200, | |||
|       key: 'collectTime', | |||
|     }, | |||
|     { | |||
|       title: '数据', | |||
|       dataIndex: 'data', | |||
|       width: 200, | |||
|       key: 'data', | |||
|       render: (_, r) => { | |||
|         return ( | |||
|           <> | |||
|             <Tooltip content={r.data}> | |||
|               <div>{r.data.length > 6 ? `${r.data.substr(0, 6)}...` : r.data}</div> | |||
|             </Tooltip> | |||
|           </> | |||
|         ) | |||
|       }, | |||
|   ]; | |||
|    return ( | |||
| 
 | |||
|     }, | |||
|     { | |||
|       title: '物联网卡状态', | |||
|       width: 200, | |||
|       dataIndex: 'iotCardStatus', | |||
|       key: 'iotCardStatus', | |||
|     }, | |||
|     { | |||
|       title: '状态', | |||
|       width: 200, | |||
|       dataIndex: 'status', | |||
|       key: 'status', | |||
|     }, | |||
|     { | |||
|       title: '操作', | |||
|       width: 200, | |||
|       dataIndex: 'option', | |||
|       key: 'option', | |||
|     }, | |||
|   ] | |||
|   return ( | |||
|     <> | |||
|       <div style={{ marginBottom: 12, display: 'flex' }}> | |||
|         <div> | |||
|           <Form> | |||
|             <Form.Input | |||
|               // suffix={<IconSearch />} | |||
|               field='name' | |||
|               pure | |||
|               showClear | |||
|               label='名称' | |||
|               style={{ width: 260, marginRight: 12 }} | |||
|               placeholder='请输入设备名称' | |||
|               onChange={inputChange} | |||
|             /> | |||
|             <Form.Select | |||
|               optionList={typeList} | |||
|               field='type' | |||
|               pure | |||
|               showClear | |||
|               label='设备类型' | |||
|               onChange={selectChange} | |||
|               style={{ width: 260, marginLeft: 12, marginRight: 12 }} | |||
|               placeholder='请选择设备类型' | |||
|             /> | |||
|             <Button theme='solid' type='primary' htmlType='submit' onClick={searchHandler}> | |||
|               查询 | |||
|             </Button> | |||
|           </Form> | |||
|         </div> | |||
|         <div style={{ marginLeft: 10 }}> | |||
|           {' '} | |||
|           {lastData.length ? ( | |||
|             <ExportData | |||
|               //  showIcon | |||
|               fileName='设备列表' | |||
|               exportType='fileSaver' | |||
|               data={lastData} | |||
|               columns={columns} | |||
|               key='export' | |||
|             /> | |||
|           ) : ( | |||
|             '' | |||
|           )} | |||
|         </div> | |||
|       </div> | |||
|       <Table | |||
|          columns={columns} | |||
|          dataSource={dataSource} | |||
|       ></Table> | |||
| 
 | |||
|    ) | |||
|         // scroll={scroll} | |||
|         columns={columns} | |||
|         dataSource={lastData}></Table> | |||
|     </> | |||
|   ) | |||
| } | |||
| 
 | |||
| function mapStateToProps (state) { | |||
|    const { auth, global, members, webSocket } = state; | |||
|    return { | |||
|       user: auth.user, | |||
|       actions: global.actions, | |||
|       clientHeight: global.clientHeight | |||
|    }; | |||
| function mapStateToProps(state) { | |||
|   const { auth, global, members, webSocket, deviceListAlarms, devicesCardStatus } = state | |||
|   return { | |||
|     user: auth.user, | |||
|     actions: global.actions, | |||
|     clientHeight: global.clientHeight, | |||
|     deviceListAlarms: deviceListAlarms?.data || [], | |||
|     devicesCardStatusList: devicesCardStatus?.data || [], | |||
|   } | |||
| } | |||
| 
 | |||
| export default connect(mapStateToProps)(Network); | |||
| export default connect(mapStateToProps)(Network) | |||
|  | |||
					Loading…
					
					
				
		Reference in new issue