import React, { useMemo, useEffect, useState, Fragment, useCallback, useImperativeHandle, } from 'react'; import { Form, InputNumber, Tabs, Collapse, Row, Col, Input, Select, Tag, Checkbox, Button, Space, Tooltip, Radio, Modal, message, Popconfirm } from 'antd'; import { PlusOutlined, MinusCircleOutlined, DeleteOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; import { FormProps } from 'antd/lib/form/Form'; import ColorPicker from '../../../common/ColorPicker/ColorPicker'; import { canvas } from '../../index'; import { alignNodes } from '@bici-topology/layout/src/align'; import CustomIcon from '../../../config/iconConfig'; import DataBindModal from '../../../FilterDataPoint'; import styles from './index.module.less'; import { getNodeType } from '../../../utils/Property2NodeProps'; import * as _ from 'lodash'; import { echartsObjs } from '@bici-topology/chart-diagram/src/echarts'; import {replacer, reviver} from '../../../utils/serializing'; import { eraseOverlapIntervals } from '../../../utils/cacl'; import {DataPointPropsMap, defaultLineColors, Node, DATA_STATUS} from '../../../data/defines'; import {getTimeLineOption} from "../../../config/charts/timeline"; import CustomizedDynamicForm, {FieldData, FormFieldGroup} from '../../../common/CustomizedDynamicForm'; import {fetchDataSourceList} from '../../../data/api'; import {getPieOptionByChangeProp} from "../../../config/charts/pie"; import {getBarOption} from "../../../config/charts/bar"; import {getHorizontalBarOption} from "../../../config/charts/horizontalbar"; const { Panel } = Collapse; const { TabPane } = Tabs; const { TextArea } = Input; const { Option } = Select; // 对齐方式 key 对齐方式 val 图标名称 const alignObj = { left: ['左对齐', 'iconzuoduiqi'], right: ['右对齐', 'iconyouduiqi'], top: ['顶部对齐', 'iconshangduiqi'], bottom: ['底部对齐', 'iconxiaduiqi'], center: ['垂直居中', 'iconzongxiangjuzhong'], middle: ['水平居中', 'iconhengxiangjuzhong'], }; // 需要显示文件填充的节点列表 const fillStyleNodeList = ['circle', 'rectangle', 'biciVarer', 'combine','dateFormat']; // 字体样式 const fontStyleNodeList = [ 'biciPilot', 'circle', 'rectangle', 'text', 'biciVarer', 'dateFormat', ]; // 边框样式 const boardStyleNodeList = ['circle', 'rectangle', 'biciVarer', 'combine']; // 不展示旋转 const disabledRotateList = ['biciPilot', 'echarts', 'biciCard','QTLiveVideo']; interface ICanvasProps extends FormProps { data?: any; onFormValueChange?: any; onPropertyFormValueChange?: any; onEventValueChange: any; setIsSave?: (isSave: boolean) => void; onAddDataPoint?:(node:Node,disableSource:string[],selectedRowKeys:string[])=>void; onAddVedioSource?:(node:Node,disableSource:string[],selectedRowKeys:string[])=>void; onCustomizedDynamicFormChange?:(group:string,fields:FieldData[])=>void; ref?:any; dataPointPropsMap:DataPointPropsMap, uploaConfig: any;// 要使用mappingID } const NodeCanvasProps: React.FC = React.forwardRef(({ data, onFormValueChange, onPropertyFormValueChange, setIsSave, onAddDataPoint, onAddVedioSource, dataPointPropsMap, onCustomizedDynamicFormChange, uploaConfig, },ref) => { const [form] = Form.useForm(); const [propertyForm] = Form.useForm(); const [visible, setVisible] = useState(false); const [pilotBtnSize, setPilotBtnSize] = useState('small'); const [btnColor, setBtnColor] = useState({ italicBtn: '#fff', boldBtn: '#fff', color:"#222", }); const { x, y, width, height } = data?.node?.rect || {}; const { rotate, lineWidth, strokeStyle, text, id, name, fillStyle } = data?.node || {}; const { color, fontSize, fontFamily, textAlign } = data?.node?.font || {}; const { property } = data?.node; // 用户自定义数据片段 const [dataPointSelectedRows, setDataPointSelectedRows] = useState( property?.dataPointSelectedRows || [] ); const [showSelectDataPoint, setShowSelectDataPoint] = useState(false); const addLineColorBtnRef = React.useRef(); const removeLineColorBtnRef = React.useRef(); const { dataMethod, dataDot, dataSourceId, dataSourceUrl,reqData,dataPointReq } = property || {}; const [refreshProperty, setRefreshProperty] = useState(false); const [selectedVideoRows,setSelectedVideoRows]=useState(property?.video?.selectedRows || []); let disableSource = ['react']; if (data.node.name == 'biciPilot') { disableSource = []; } // 渲染数据点弹出窗口 不包含 disableSource:['react','complex','dataPoint] const selectedRowKeys = []; data.node.property && (data.node.property.dataPointSelectedRows || []).map((row) => { selectedRowKeys.push(row.id); return row; }); useEffect(() => { let temp = { ...btnColor } if (data.node.font.fontStyle !== 'normal') { temp={ ...temp, italicBtn: '#1890ff' } } if (data.node.font.fontWeight !== 'normal') { temp={ ...temp, boldBtn: '#1890ff' } } setBtnColor(temp); // 设置基本表单 form.setFieldsValue({ x: Math.round(x), y: Math.round(y), width: Math.round(width), height: Math.round(height), rotate: parseInt(rotate), lineWidth, strokeStyle, color, fontSize: parseInt(fontSize), fontFamily: fontFamily.split(','), text, textAlign, fillStyle: fillStyle, showFillStyle: fillStyle ? true : false, showBoardColor: strokeStyle ? true : false, }); }, [ x, y, width, height, rotate, text, lineWidth, strokeStyle, color, fontSize, fontFamily, text, fillStyle, textAlign, property, ]); useEffect(() => { setShowSelectDataPoint(property?.limitType == 'custom'); propertyForm.setFieldsValue({ dataMethod, dataDot, reqData, dataPointReq, 'date.show': property?.date?.show, 'date.format': property?.date?.format, 'time.show': property?.time?.show, 'time.format': property?.time?.format, }); if (data.node.name === 'biciCard') { // 设置数据卡片 propertyForm.setFieldsValue({ showTitle: property.showTitle, cardTitle: property.cardTitle, limitType: property.limitType, showLimit: property.showLimit, 'limit.bottom': property.limit.bottom, 'limit.top': property.limit.top, 'normal.fontFamily': property.normal.fontFamily, 'normal.fontSize': property.normal.fontSize, 'normal.color': property.normal.color, 'normal.showBkColor': property.normal.showBkColor, 'normal.bkColor': property.normal.bkColor, 'topLimit.fontFamily': property.topLimit.fontFamily, 'topLimit.fontSize': property.topLimit.fontSize, 'topLimit.color': property.topLimit.color, 'topLimit.showBkColor': property.topLimit.showBkColor, 'topLimit.bkColor': property.topLimit.bkColor, 'bottomLimit.fontFamily': property.bottomLimit.fontFamily, 'bottomLimit.fontSize': property.bottomLimit.fontSize, 'bottomLimit.color': property.bottomLimit.color, 'bottomLimit.showBkColor': property.bottomLimit.showBkColor, 'bottomLimit.bkColor': property.bottomLimit.bkColor, }); } else if (data.node.name === 'biciPilot') { propertyForm.setFieldsValue({ color: property.color, size: Math.round(width / 2), showText: property.showText, text: property.text, stateType: property.stateType, lightRange: property.lightRange, }); const btnSize = width / 2 <= 15 ? 'small' : width / 2 <= 20 ? 'middle' : 'large'; setPilotBtnSize(btnSize); } else if (data.node.name == 'echarts'|| data.node.name == 'biciMeasure'|| data.node.name == 'biciText'|| data.node.name == 'pdMonthReport'|| data.node.name == 'productQueue'|| data.node.name === 'biciCard2'|| data.node.name === 'liveVideo'|| data.node.name === 'QTLiveVideo'|| data.node.name === 'dataTable') { let lineRangedefaultColor = defaultLineColors.map((color) => { return { lineGraphRangeColor: color, lineGraphRangeCheck:true }; }); let nodeLineRangeColor = []; if (property && property.lineGraphRange) { nodeLineRangeColor = _.compact(property.lineGraphRange); } lineRangedefaultColor.map((colorObj, index) => { if (nodeLineRangeColor[index] != null) { lineRangedefaultColor[index] = nodeLineRangeColor[index]; } }); propertyForm.setFieldsValue({ dataMax: property.dataMax, dataMin: property.dataMin, marks: property.marks, markChecked: property.markChecked, chartTitleColor:property.chartTitleColor, chartTitleColorChecked:property.chartTitleColorChecked, pullRate:property.pullRate, pullRateUnit:property.pullRateUnit, dataFormat:property.dataFormat, dataUrl:property.dataUrl, dataSourceId:dataSourceId, dataSourceUrl:dataSourceUrl, videoURL:property.videoURL, 'checked-0': property.dataColors && property.dataColors[0]?.checked, 'color-0': property.dataColors && property.dataColors[0]?.color, 'top-0': property.dataColors && property.dataColors[0]?.top, 'bottom-0': property.dataColors && property.dataColors[0]?.bottom, 'checked-1': property.dataColors && property.dataColors[1]?.checked, 'color-1': property.dataColors && property.dataColors[1]?.color, 'top-1': property.dataColors && property.dataColors[1]?.top, 'bottom-1': property.dataColors && property.dataColors[1]?.bottom, 'checked-2': property.dataColors && property.dataColors[2]?.checked, 'color-2': property.dataColors && property.dataColors[2]?.color, 'top-2': property.dataColors && property.dataColors[2]?.top, 'bottom-2': property.dataColors && property.dataColors[2]?.bottom, 'checked-3': property.dataColors && property.dataColors[3]?.checked, 'color-3': property.dataColors && property.dataColors[3]?.color, 'top-3': property.dataColors && property.dataColors[3]?.top, 'bottom-3': property.dataColors && property.dataColors[3]?.bottom, 'checked-4': property.dataColors && property.dataColors[4]?.checked, 'color-4': property.dataColors && property.dataColors[4]?.color, 'top-4': property.dataColors && property.dataColors[4]?.top, 'bottom-4': property.dataColors && property.dataColors[4]?.bottom, smooth: property.smooth && property.smooth, limitType: property.limitType && property.limitType, dataTopChecked: property.dataTopChecked && property.dataTopChecked, dataTop: property.dataTop && property.dataTop, dataBottom: property.dataBottom && property.dataBottom, selectDataPoint:property.dataBottom+'~'+property.dataTop, chartTitleChecked: property.chartTitleChecked && property.chartTitleChecked, chartTitle: property.chartTitle && property.chartTitle, lineReferenceChecked: property.lineReferenceChecked && property.lineReferenceChecked, lineReferenceColor: property.lineReferenceColor && property.lineReferenceColor, chartUnitChecked: property.chartUnitChecked && property.chartUnitChecked, chartUnit: property.chartUnit && property.chartUnit, lineGraphRange: lineRangedefaultColor, chartBackgroundColor: property.chartBackgroundColor && property.chartBackgroundColor, chartBackgroundChecked: property.chartBackgroundChecked && property.chartBackgroundChecked, }); } }, [property, refreshProperty]); // 对父组件暴露保存数据的接口 useImperativeHandle( ref, () => ({ onDataPointBind:(selectedRowKeys, selectedRows)=>{ onDataPointBind(selectedRowKeys, selectedRows) }, onVedioBind:(selectedRowKeys, selectedRows)=>{ onVedioBind(selectedRowKeys, selectedRows) }, }), [property] ); // 字段值更新时触发的回掉 const handleValuesChange = (changedValues) => { if ('fillStyle' in changedValues) { form.setFieldsValue({ showFillStyle: true }); } if ('strokeStyle' in changedValues) { form.setFieldsValue({ showBoardColor: true }); } if ('showFillStyle' in changedValues) { changedValues.fillStyle = changedValues.showFillStyle ? form.getFieldValue('fillStyle') : ''; } if ('showBoardColor' in changedValues) { changedValues.strokeStyle = changedValues.showBoardColor ? form.getFieldValue('strokeStyle') : '#222'; } onFormValueChange && onFormValueChange(changedValues); }; const handlePropertyValuesChange = (changedValues, allValues) => { if(changedValues.dataPointReq){ const points = (changedValues.dataPointReq||'').split(','); (points||[]).map((pid,index)=>{ data.node.property.dataPointParam.qtDataList[index]={ id: pid, type: 5, time:1, }; }) } onPropertyFormValueChange && onPropertyFormValueChange(allValues); }; // // 设置对齐方式 const handleAlign = (key: string) => { const pens = canvas.activeLayer.pens; const rect = canvas.activeLayer.rect; if (pens.length >= 2) { alignNodes(pens, rect, key); canvas.cache(); canvas.render(); setIsSave(false); } }; // 设置日期格式 const onSetBiciTimerDataFmt = () => {}; // 数据绑定方式 const handlePropertyDataMethodChange = (value) => { data.node.property.dataMethod=value; setRefreshProperty(!refreshProperty); }; // 添加数据点 const addDataPoint = () => { //setVisible(!visible); onAddDataPoint&&onAddDataPoint(data.node,disableSource,selectedRowKeys) }; // 添加视频源 const addVedioSource=()=>{ onAddVedioSource&&onAddVedioSource(data.node,[],data.node.property.video?.selectedRowKeys); } // 获得选中的视频直播源 const onVedioBind = (selectedRowKeys, selectedRows) => { const tmp = { ...data.node.property.video, selectedRowKeys:selectedRowKeys, selectedRows:selectedRows, }; data.node.property.video=tmp; setSelectedVideoRows(selectedRows); setRefreshProperty(!refreshProperty); } // 获得选中的数据点 const onDataPointBind = (selectedRowKeys, selectedRows) => { console.log(selectedRowKeys, selectedRows) // if (selectedRows.length === 0) { // return; // } // associationObject: "11111" // attention: 2 // channel: 4 // dataCode: "Data2103250007" // dataName: "精轧" // dataType: 1 // effectiveLowerLimit: 3000 // effectiveUpperLimit: 4000 // id: "4bb099f2ae4b4db88276567127a5fe7e" // intervalTime: 1 // isOtherPoint: 0 // position: "500精轧控制室" // scopeMax: 2000 // scopeMin: 1000 // status: -1 // type: 1 // unit: "C" // dataPointPropsMap有问题,绑定数据点是type又变成datatype了,后续需要修复 // console.log("selectedRows",selectedRows,selectedRowKeys,dataPointPropsMap) selectedRows=(selectedRows||[]).map(row=>{ return { ...row, id:row[dataPointPropsMap.id], type:row.dataType||row.type, dataName:row[dataPointPropsMap.dataName]||row.name||row.dataName, intervalTime:row.intervalTime||1, scopeMin:row[dataPointPropsMap.scopeMin], scopeMax:row[dataPointPropsMap.scopeMax], status: row.status+'', dataCode: row.dataCode || row.code, } }) const nodeType = getNodeType(data.node); if (property && property.dataPointSelectedRows) { if (nodeType == 'timeLine'|| nodeType == 'dataTable'|| nodeType == 'horizontalBar'|| nodeType == 'circleAndPie'|| nodeType == 'verticalBar') { // 最多可绑定十个数据点 if(nodeType == 'dataTable'){ selectedRows = selectedRows.slice(0, 20); }else{ selectedRows = selectedRows.slice(0, 10); } selectedRows=selectedRows.sort((a,b)=>{ return a.intervalTime-b.intervalTime }) if (data.node.property.dataPointSelectedRows.length <= 20) { data.node.property.dataPointSelectedRows = selectedRows; selectedRows.map((row, index) => { // console.log("row====",row) const q = { id: selectedRows[index][dataPointPropsMap.id], type: row.dataType||row.type, intervalTime:selectedRows[index][dataPointPropsMap.intervalTime]||1, dataCode: selectedRows[index].dataCode || selectedRows[index].code, }; data.node.property.dataPointParam.qtDataList[index] = q; }); setDataPointSelectedRows(selectedRows); if(nodeType == 'dataTable'){ }else{ updateChartOption(); } } } else { const selectedData = selectedRows[0]; if (nodeType === 'biciCard') { const scopeMin = !isNaN(Number(selectedData?.scopeMin)) ? selectedData?.scopeMin : undefined; const scopeMax = !isNaN(Number(selectedData?.scopeMax)) ? selectedData?.scopeMax : undefined; if(property.limitType=='dataPoint'){ data.node.property.limit.bottom = scopeMin; data.node.property.limit.top = scopeMax; propertyForm.setFieldsValue({ dataBottom: scopeMin, dataTop: scopeMax, }); } } // 只绑定一个数据点 data.node.property.dataPointSelectedRows = selectedRows; data.node.property.dataPointParam.qtDataList[0] = { id: selectedData.id, type: selectedData.type||selectedData.dataType, intervalTime:selectedRows[0][dataPointPropsMap.intervalTime]||1, dataCode: selectedRows[0].dataCode || selectedRows[0].code, }; setDataPointSelectedRows(selectedRows); } setIsSave(false); } canvas.dispatch('addDataPoint',{}); setRefreshProperty(!refreshProperty); }; // 选择数据点,将数值配置上区 const handleChangeDataPoint = (value) => { const dataTween = value.split('~'); if(data.node.property.limitType=='dataPoint'){ propertyForm.setFieldsValue({ dataBottom: dataTween[0] == 'undefined' ? '' : dataTween[0], dataTop: dataTween[1] == 'undefined' ? '' : dataTween[1], }); onPropertyFormValueChange && onPropertyFormValueChange({ dataBottom: dataTween[0] == 'undefined' ? '' : dataTween[0], dataTop: dataTween[1] == 'undefined' ? '' : dataTween[1], }); } }; const updateChartOption = () => { data.node.elementRendered = false; const nodeType = getNodeType(data.node); let newOption ={}; switch (nodeType) { case 'timeLine': newOption = getTimeLineOption( data.node, undefined, undefined ); break; case 'circleAndPie': newOption=getPieOptionByChangeProp(data.node,null); break; case 'verticalBar': newOption = getBarOption(data.node,null) break; case 'horizontalBar': newOption = getHorizontalBarOption(data.node,null) break; default: } data.node.data.echarts.option=newOption; // 更新图表数据 echartsObjs[data.node.id].chart.setOption( JSON.parse(JSON.stringify(newOption, replacer), reviver),true ); echartsObjs[data.node.id].chart.resize(); canvas.updateProps(true, [data.node]); }; const renderDataPointModal = useCallback(() => { return ( ); }, [visible]); /** * 渲染位置和大小的表单 */ const renderPositionForm = useMemo(() => { return (
X} /> Y} /> W} /> H} /> {!disabledRotateList.includes(data?.node.name) && ( °} /> )}
); }, [x, y, width, height, rotate, data?.node]); /** * 渲染填充样式 */ const renderFillStyle = useMemo(() => { return (
); }, [fillStyle, data?.node]); /** * 渲染边框样式 */ const renderBorderStyle = useMemo(() => { return (
); }, [strokeStyle, lineWidth, data?.node]); // 字体的斜体和加粗功能 const fontStyleChange = (val: string) => { if (val === 'fontStyle') { // 斜体 if (data.node.font.fontStyle === 'normal') { data.node.font.fontStyle = 'italic'; setBtnColor({ ...btnColor, italicBtn: '#1890ff',color:'#fff' }); } else { data.node.font.fontStyle = 'normal'; setBtnColor({ ...btnColor, italicBtn: '#fff',color:"#222" }); } } else { // 加粗 if (data.node.font.fontWeight === 'normal') { data.node.font.fontWeight = 'bold'; setBtnColor({ ...btnColor, boldBtn: '#1890ff',color:'#fff' }); } else { data.node.font.fontWeight = 'normal'; setBtnColor({ ...btnColor, boldBtn: '#fff',color:'#222' }); } } setIsSave(false); canvas.updateProps(true, [data.node]); }; /** * 渲染字体的表单 */ const renderFontForm = useMemo(() => { return (