|
|
const e="48ba7a60-4d22-4b7e-ada8-e3a9f9d51816",n="custom-scrolltable-node",t="表格",a='<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695274327794" class="icon" viewBox="0 0 1142 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12610" xmlns:xlink="http://www.w3.org/1999/xlink" width="223.046875" height="200"><path d="M1102.769231 39.384615v945.23077H39.384615V39.384615h1063.384616m0-39.384615H39.384615a39.384615 39.384615 0 0 0-39.384615 39.384615v945.23077a39.384615 39.384615 0 0 0 39.384615 39.384615h1063.384616a39.384615 39.384615 0 0 0 39.384615-39.384615V39.384615a39.384615 39.384615 0 0 0-39.384615-39.384615z" fill="#1296db" p-id="12611"></path><path d="M39.384615 393.846154h1063.384616v39.384615H39.384615zM39.384615 590.769231h1063.384616v39.384615H39.384615zM39.384615 787.692308h1063.384616v39.384615H39.384615zM39.384615 196.923077h1063.384616v39.384615H39.384615z" fill="#1296db" p-id="12612"></path><path d="M315.076923 196.923077v787.692308H275.692308V196.923077zM590.769231 196.923077v787.692308h-39.384616V196.923077zM866.461538 196.923077v787.692308h-39.384615V196.923077z" fill="#1296db" p-id="12613"></path><path d="M39.384615 39.384615h1063.384616v157.538462H39.384615z" fill="#1296db" p-id="12614"></path></svg>',o="svg",r="动态",l="数据展示",u=!1,c=!0,i="时序",d=`{"type":"page","id":"u:270584784ce1","name":"page1","asideResizor":false,"style":{"boxShadow":" 0px 0px 0px 0px transparent"},"pullRefresh":{"disabled":true},"body":[{"type":"tabs","name":"tab","tabs":[{"title":"样式","icon":"fa fa-th-large","body":[{"type":"form","title":"","name":"basicPropForm","body":[{"type":"input-text","label":"名称","name":"nodeAlias","id":"u:6b126f0520cb","size":"full","mode":"horizontal","inputControlClassName":"w-100","className":"m-b"},{"type":"input-text","label":"ID ","name":"id","id":"u:6232710ac003","size":"full","mode":"horizontal","inputControlClassName":"w-100","className":"m-b"},{"type":"grid","id":"u:c605398a724c","className":"m-b","columns":[{"body":[{"type":"input-number","label":"宽度","name":"width","keyboard":true,"id":"u:dcc0c21d16f6","step":1,"suffix":"px","placeholder":"组件左边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:14cc19d6ffb0","md":6},{"body":[{"type":"input-number","label":"高度","name":"height","keyboard":true,"id":"u:cd6fdff9ca88","step":1,"suffix":"px","placeholder":"组件上边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:4931801ca9b8","md":6}]},{"type":"grid","id":"u:da449a94908a","className":"m-b","columns":[{"body":[{"type":"input-number","label":"X 轴","name":"x","keyboard":true,"id":"u:29852d093d9d","step":1,"suffix":"px","placeholder":"组件左边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:1b561d652acc","md":6},{"body":[{"type":"input-number","label":"Y 轴","name":"y","keyboard":true,"id":"u:dc8c1daed8ed","step":1,"suffix":"px","placeholder":"组件上边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:9672575193ac","md":6}]},{"type":"grid","id":"u:a332a7bf83c1","className":"m-b","columns":[{"body":[{"type":"input-number","label":"旋转","name":"rotation","id":"u:f6a2dbb518f9","placeholder":"组件旋转角度","mode":"horizontal","size":"full","className":"","keyboard":true,"step":1,"suffix":"deg","value":0,"labelAlign":"left","inputClassName":"w-full"}],"id":"u:646cd98b7955","md":6},{"body":[{"type":"input-number","label":"透明","name":"opacity","id":"u:cf80f59d8d42","placeholder":"组件透明度","mode":"horizontal","size":"full","className":"m-b","keyboard":true,"step
"nodes": [ { "id": "d11b46e7-237d-4ca8-96fc-4c0f7a011316", "type": "custom-scrolltable-node", "x": 200, "y": 200, "text": { "value": "", "x": 200, "y": 200 }, "properties": { "id": "d11b46e7-237d-4ca8-96fc-4c0f7a011316", "width": 500, "height": 200, "x": 200, "y": 200, "rotation": 0, "opacity": 1, "emptyTable": false, "headerHeight": 35, "tableBGC": "rgba(5, 32, 73, 1)", "headerBGC": "#00BAFF", "oddRowBGC": "", "evenRowBGC": "", "highlightBGC": "", "highlightFontColor": "rgba(245, 166, 35, 1)", "clickHighlight": true, "enableBorder": false, "onlyOuterBorder": false, "onlyHeaderBorder": false, "onlyHeaderHoriBorder": false, "onlyBodyBorder": false, "bodyBorderStyle": "solid", "onlyHoriBorder": false, "index": false, "enableCarousel": false, "waitTime": "2000ms", "hoverPause": false, "fontSize": 12, "headerFontSize": 14, "showDefaultValue": false, "showUnit": false, "nodeAlias": "表格", "rowNum": 5, "columnWidth": [ { "col": "1", "colW": 150 } ], "carousel": "single", "align": [ { "col": "1", "align": "center" } ], "headerFontColor": "#ffffff", "fontColor": "#ffffff", "borderColor": "#4a4a4a", "colNum": 3, "oddRowGradBG": "", "evenRowGradBG": "", "highlightGradBG": "", "dynamic": { "normalData": { "dataPoint": "", "compareType": "", "conditionVariables": [], "defaultValue": "", "unit": "", "renderIntervalEnabled": true, "customDatasource": true, "enableTestDatas": true, "testDatas": "", "renderInterval": "30000ms", "dataFilterFn": "// datas 数据处理\\r\\n// datas.forEach().....\\r\\n\\r\\n// 返回格式为:\\r\\nconst defaultHeader = [{ val: '列1', style: { color: 'red' } }, { val: '列2', style: { color: 'red' } }, { val: '列3', style: { color: 'red' } }]\\r\\nconst defaultDatas = [\\r\\n [{ val: '行1列1', style: { color: 'red' } }, { val: '行1列2', style: { color: 'green' } }, { val: '行1列3', style: { color: 'blue', cursor: 'pointer' } }],\\r\\n [{ val: '行2列1', style: { color: 'red' } }, { val: '行2列2', style: { color: 'green' } }, { val: '行2列3', style: { color: 'blue', cursor: 'pointer' } }],\\r\\n]\\r\\n// 或\\r\\n// const defaultHeader = ['列1', '列2', '列3']\\r\\n// const defaultDatas = [\\r\\n// ['行1列1', '行1列2', '行1列3'],\\r\\n// ['行2列1', '行2列2', '行2列3'],\\r\\n// ]\\r\\nreturn {\\r\\n headerDatas: defaultHeader,\\r\\n tableDatas: defaultDatas\\r\\n}", "requestMethod": "get", "requestParams": "return {};" }, "eventsData": { "eventCombo": [ { "eventType": "change", "enable": false, "config": "" } ] }, "uiData": { "dataPoint": "", "compareType": "", "conditionVariables": [] }, "animationData": { "animationCombo": [ { "dataPoint": "", "min": "", "max": "", "animationName": "旋转" } ] }, "hiddenData": { "hiddenCombo": [ { "dataPoint": "", "min": "", "max": "", "showOrHiddenName": "隐藏" } ] } } } } ]}`,javascript:`// 工具函数
/** * 精准判断对象类型 * @param obj */function typeOf(obj) { const toString = Object.prototype.toString const map = { '[object Boolean]': 'boolean', '[object Number]': 'number', '[object String]': 'string', '[object Function]': 'function', '[object Array]': 'array', '[object Date]': 'date', '[object RegExp]': 'regExp', '[object Undefined]': 'undefined', '[object Null]': 'null', '[object Object]': 'object', } return map[toString.call(obj)]}
/** * 深拷贝 * @param data */function deepCopy(data) { const t = typeOf(data) let o
if (t === 'array') { o = [] } else if (t === 'object') { o = {} } else { return data }
if (t === 'array') { for (let i = 0; i < data.length; i++) { o.push(deepCopy(data[i])) } } else if (t === 'object') { for (const i in data) { o[i] = deepCopy(data[i]) } } return o}
/** * 深覆盖 * @param target * @param merged */function deepMerge(target, merged) { for (const key in merged) { if (target[key] && typeof target[key] === 'object') { deepMerge(target[key], merged[key]) continue } if (typeof merged[key] === 'object') { target[key] = deepCopy(merged[key])
continue } target[key] = merged[key] }
return target}
/** * 节流函数,(限制函数的执行频率)返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param immediate 是否立即执行 true 则先调用,false不先调用 * @return {function} 返回客户调用函数 */function throttle(func, wait, immediate) { let timeoutID let lastExec = 0
function wrapper() { const self = this const elapsed = Number(new Date()) - lastExec const args = arguments
function clearExistingTimeout() { if (timeoutID) { clearTimeout(timeoutID) } }
function clear() { timeoutID = undefined }
function exec() { lastExec = Number(new Date()) func.apply(self, args) }
if (immediate && !timeoutID) { exec() } clearExistingTimeout() if (immediate === undefined && elapsed > wait) { exec() } else { timeoutID = setTimeout(immediate ? clear : exec, immediate === undefined ? wait - elapsed : wait) } }
return wrapper}
/** * 防抖函数,(限制函数的执行频率) 保证再一系列调用时间内,只调用一次 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @return {function} 返回客户调用函数 */function debounce(func, wait) { return throttle(func, wait, false)}
const { createApp, createVNode, render, nextTick, onBeforeUnmount, onUnmounted, onMounted, reactive, ref, toRefs, watch } = Vue;const app = createApp({})
function useAutoResize(props, afterResizeFun) { const domRef = ref(null) // dorm容器,默认设置为domRef
const status = reactive({ width: 0, height: 0, }) let __resizeHandler = null
function resize(resize = true) { nextTick().then(() => { const dom = domRef.value status.width = dom ? dom.clientWidth : 0 status.height = dom ? dom.clientHeight : 0
if (!dom) { console.warn('fei-datav: Failed to get dom node, component rendering may be abnormal!') } else if (!status.width || !status.height) { console.warn('fei-datav: Component width or height is 0px, rendering abnormality may occur!') }
if (typeof afterResizeFun === 'function' && resize) afterResizeFun() }) }
watch([() => props.containerWidth, () => props.containerHeight], () => { __resizeHandler && __resizeHandler(); })
onMounted(() => { setTimeout(() => { resize(); }, 500) __resizeHandler = debounce(resize, 100) })
return { domRef, ...toRefs(status), resize, }}
const ScrollTable = { template: \`
<div class="bv-scroll-table" ref="domRef" :style="tableStyle"> <div class="header" v-if="header.length && mergedConfig" :style="{'background-color': mergedConfig.headerBGC}"> <div class="header-item" v-for="(headerItem, i) in header" :key="(headerItem.val || headerItem) + '-' + i" :style="getHeaderItemStyle(widths, mergedConfig, i, header, enableBorder, borderColor, headerFontSize, headerFontColor, onlyOuterBorder, onlyHeaderBorder, onlyHeaderHoriBorder, headerItem, aligns[i])" :align="aligns[i]" v-html="headerItem.val || headerItem" /> </div> <div v-if="mergedConfig" class="rows" :style="getRowsHeight" > <div class="row-item" v-for="(row, ri) in rows" :key="row.toString() + '-' + row.scroll" :style="getRowItemStyle(heights, mergedConfig, row, ri, highlightGradBG, highlightBGC, rows, enableBorder, borderColor, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyOuterBorder)" > <div class="cell" v-for="(cell, ci) in row.cells" :key="(cell.val || cell) + '-' + ri + '-' + ci" :style="getCellStyle(widths, ci, row.cells, enableBorder, borderColor, fontSize, fontColor, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyOuterBorder, cell, row, highlightFontColor)" :align="aligns[ci]" v-html="getCellContent(cell)" @click="clickHandler('click', ri, ci, row, cell)" @mouseenter="handleHover(true, ri, ci, row, cell)" @mouseleave="handleHover(false)" @mousemove="(e) => handleMove(e, ri, ci, row, cell)" /> </div> </div> </div> \`,
name: 'BvScrollTable', props: { config: { type: Object, default: () => ({}), }, containerWidth: { type: Number, default: 500, }, containerHeight: { type: Number, default: 200, }, fontColor: { type: String, default: '' }, fontSize: { type: Number, default: 12 }, headerFontColor: { type: String, default: '' }, headerFontSize: { type: Number, default: 12 }, enableBorder: { type: Boolean, default: true }, borderColor: { type: String, default: '' }, enableCarousel: { type: Boolean, default: false }, onlyOuterBorder: { type: Boolean, default: false }, onlyHeaderBorder: { type: Boolean, default: false }, onlyBodyBorder: { type: Boolean, default: false }, bodyBorderStyle: { type: String, default: 'solid' }, onlyHoriBorder: { type: Boolean, default: false }, onlyHeaderHoriBorder: { type: Boolean, default: false }, tableBGC: { type: String, default: '' }, clickHighlight: { type: Boolean, default: false }, highlightBGC: { type: String, default: '' }, highlightGradBG: { type: String, default: '' }, highlightNumber: { type: Number, default: -1 }, highlightFontColor: { type: String, default: '' } }, computed: { tableStyle() { const style = {}; if (this.tableBGC) { style.background = this.tableBGC } if (this.enableBorder || this.onlyOuterBorder) { return { border: \`1px solid \${this.borderColor}\`,
'box-sizing': 'border-box', ...style } } else { return style } }, getHeaderItemStyle: () => (widths, mergedConfig, i, header, enableBorder, borderColor, headerFontSize, headerFontColor, onlyOuterBorder, onlyHeaderBorder, onlyHeaderHoriBorder, headerItem, align) => { const isLast = i === header.length - 1; const borderObj = {} if (enableBorder) { !isLast && (borderObj['border-right'] = \`1px solid \${borderColor}\`);
} if (!enableBorder && !onlyOuterBorder && onlyHeaderBorder && !onlyHeaderHoriBorder) { borderObj['border-top'] = \`1px solid \${borderColor}\`;
borderObj['border-bottom'] = \`1px solid \${borderColor}\`;
borderObj['border-right'] = \`1px solid \${borderColor}\`;
i === 0 && (borderObj['border-left'] = \`1px solid \${borderColor}\`);
} if (!enableBorder && !onlyOuterBorder && onlyHeaderBorder && onlyHeaderHoriBorder) { borderObj['border-top'] = \`1px solid \${borderColor}\`;
borderObj['border-bottom'] = \`1px solid \${borderColor}\`;
i === 0 && (borderObj['border-left'] = \`1px solid \${borderColor}\`);
isLast && (borderObj['border-right'] = \`1px solid \${borderColor}\`);
} if (!enableBorder && onlyOuterBorder && onlyHeaderBorder && !onlyHeaderHoriBorder) { borderObj['border-bottom'] = \`1px solid \${borderColor}\`;
!isLast && (borderObj['border-right'] = \`1px solid \${borderColor}\`);
} if (!enableBorder && onlyOuterBorder && onlyHeaderBorder && onlyHeaderHoriBorder) { borderObj['border-bottom'] = \`1px solid \${borderColor}\`;
}
let headItemStyle = {}; if (headerItem.style) { headItemStyle = { ...headerItem.style } } return { 'height': mergedConfig.headerHeight + 'px', 'width': widths[i] + 'px', 'font-size': headerFontSize + 'px', 'color': headerFontColor, ...borderObj, ...headItemStyle, display: 'flex', 'justify-content': align === 'center' ? 'center' : align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'left', 'align-items': 'center', 'text-align': align === 'center' ? 'center' : align === 'left' ? 'left' : align === 'right' ? 'right' : 'left', } }, getCellStyle: () => (widths, ci, cells, enableBorder, borderColor, fontSize, fontColor, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyOuterBorder, cell, row, highlightFontColor) => { const isLast = ci === cells.length - 1; const borderObj = {} if (enableBorder) { !isLast && (borderObj['border-right'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
} if (!enableBorder && !onlyOuterBorder && onlyBodyBorder && !onlyHoriBorder) { borderObj['border-right'] = \`1px \${bodyBorderStyle} \${borderColor}\`;
ci === 0 && (borderObj['border-left'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
} if (!enableBorder && onlyOuterBorder && onlyBodyBorder && !onlyHoriBorder) { !isLast && (borderObj['border-right'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
}
let cellStyle = {}; if (cell.style) { cellStyle = { ...cell.style } }
const isHighlight = row.isHighlight; let fontC = cellStyle.color || fontColor; if (isHighlight) { fontC = highlightFontColor; }
return { width: widths[ci] + 'px', 'font-size': fontSize + 'px', ...borderObj, ...cellStyle, 'color': fontC, } }, getRowsHeight() { const { height, header, mergedConfig, enableCarousel } = this; return { height: height - (header.length ? mergedConfig.headerHeight : 0) + 'px', overflow: enableCarousel ? 'hidden' : 'auto' } }, getRowItemStyle: () => (heights, mergedConfig, row, ri, highlightGradBG, highlightBGC, rows, enableBorder, borderColor, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyOuterBorder) => { const h = heights[ri]; const isHighlight = row.isHighlight; let background = mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']; if (background.includes('http') && !background.includes('url')) { background = \`url(\${background})\`;
} if (isHighlight) { background = highlightGradBG || highlightBGC; if (background.includes('http') && !background.includes('url')) { background = \`url(\${background})\`;
} }
const borderObj = {} const isLastRow = ri === rows.length - 1; if (enableBorder) { ri === 0 && (borderObj['border-top'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
borderObj['border-bottom'] = \`1px \${bodyBorderStyle} \${borderColor}\`;
} if (!enableBorder && !onlyOuterBorder && onlyBodyBorder && !onlyHoriBorder) { ri === 0 && (borderObj['border-top'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
borderObj['border-bottom'] = \`1px \${bodyBorderStyle} \${borderColor}\`;
} if (!enableBorder && !onlyOuterBorder && onlyBodyBorder && onlyHoriBorder) { ri === 0 && (borderObj['border-top'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
borderObj['border-bottom'] = \`1px \${bodyBorderStyle} \${borderColor}\`;
} if (!enableBorder && onlyOuterBorder && onlyBodyBorder && !onlyHoriBorder) { ri === 0 && (borderObj['border-top'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
!isLastRow && (borderObj['border-bottom'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
} if (!enableBorder && onlyOuterBorder && onlyBodyBorder && onlyHoriBorder) { ri === 0 && (borderObj['border-top'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
!isLastRow && (borderObj['border-bottom'] = \`1px \${bodyBorderStyle} \${borderColor}\`);
}
return { height: h + 'px', 'line-height': h + 'px', 'background': background, 'background-size': '100% 100%', ...borderObj } } }, emits: ['mouseover', 'click'], setup(props, { emit }) { const { onUnmounted, watch, reactive, ref, toRefs } = Vue;
const defaultConfig = ref({ /** * @description Board header * @type {Array<String>} * @default header = [] * @example header = ['column1', 'column2', 'column3'] */ header: [], /** * @description Board data * @type {Array<Array>} * @default data = [] */ data: [], /** * @description Row num * @type {Number} * @default rowNum = 5 */ rowNum: 5, /** * @description Header background color * @type {String} * @default headerBGC = '#00BAFF' */ headerBGC: '#00BAFF', /** * @description Odd row background color * @type {String} * @default oddRowBGC = '#003B51' */ oddRowBGC: '#003B51', /** * @description Even row background color * @type {String} * @default evenRowBGC = '#003B51' */ evenRowBGC: '#0A2732', /** * @description Scroll wait time * @type {Number} * @default waitTime = 2000 */ waitTime: 2000, /** * @description Header height * @type {Number} * @default headerHeight = 35 */ headerHeight: 35, /** * @description Column width * @type {Array<Number>} * @default columnWidth = [] */ columnWidth: [], /** * @description Column align * @type {Array<String>} * @default align = [] * @example align = ['left', 'center', 'right'] */ align: [], /** * @description Show index * @type {Boolean} * @default index = false */ index: false, /** * @description index Header * @type {String} * @default indexHeader = '#' */ indexHeader: '#', /** * @description Carousel type * @type {String} * @default carousel = 'single' * @example carousel = 'single' | 'page' */ carousel: 'single', /** * @description Pause scroll when mouse hovered * @type {Boolean} * @default hoverPause = true * @example hoverPause = true | false */ hoverPause: true, })
function calcData() { stopAnimation() mergeConfig() calcHeaderData() calcRowsData() calcWidths() calcHeights() calcAligns() animation(true) }
const { domRef, width, height, resize } = useAutoResize(props, calcData) const status = reactive({ mergedConfig: null, header: [], rowsData: [], rows: [], widths: [], heights: [], avgHeight: 0, aligns: [], animationIndex: 0, animationHandler: '', updater: 0, needCalc: false, })
function handleHover(enter, ri, ci, row, cell) { if (enter) emitEvent('mouseover', ri, ci, row, cell) if (!status.mergedConfig.hoverPause) return const tooltip = document.querySelector('.scroll-table-tooltip'); if (enter) { stopAnimation() } else { animation(true); } }
function onResize() { if (!status.mergedConfig) return stopAnimation() calcWidths() calcHeights(true) animation(true) }
function mergeConfig() { status.mergedConfig = deepMerge(deepCopy(defaultConfig.value), props.config || {}) }
function calcHeaderData() { let { header, index, indexHeader } = status.mergedConfig if (!header.length) { status.header = [] return } header = [...header] if (index) header.unshift(indexHeader) status.header = header }
function calcRowsData() { let { data, index, headerBGC, rowNum, enableCarousel } = status.mergedConfig if (index) { data = data.map((row, i) => { row = [...row] const indexTag = \`<span class="index" style="background-color: \${headerBGC};">\${i + 1}</span>\`;
row.unshift(indexTag) return row }) } data = data.map((cells, i) => ({ cells, rowIndex: i })) const rowLength = data.length if (enableCarousel && rowLength > rowNum && rowLength < 2 * rowNum) { data = [...data, ...data] } data = data.map((d, i) => ({ ...d, scroll: i })) status.rowsData = data status.rows = data }
function calcWidths() { const { mergedConfig, rowsData } = status const { columnWidth, header } = mergedConfig const usedWidth = columnWidth.reduce((all, w) => all + w, 0) let columnNum = 0 if (rowsData[0]) { columnNum = rowsData[0].cells.length } else if (header.length) { columnNum = header.length } const avgWidth = (width.value - usedWidth) / (columnNum - columnWidth.length) const widths = new Array(columnNum).fill(avgWidth) status.widths = deepMerge(widths, columnWidth) }
function calcHeights(onresize = false) { const { mergedConfig, header } = status const { headerHeight, rowNum, data } = mergedConfig let allHeight = height.value if (header.length) allHeight -= headerHeight const avgHeight = allHeight / rowNum status.avgHeight = avgHeight if (!onresize) status.heights = new Array(data.length).fill(avgHeight) }
function calcAligns() { const { header, mergedConfig } = status const columnNum = header.length let aligns = new Array(columnNum).fill('left') const { align } = mergedConfig status.aligns = deepMerge(aligns, align) }
async function animation(start = false) { if (!props.enableCarousel) return; const { needCalc, calcHeights, calcRowsData } = status if (needCalc) { calcRowsData() calcHeights() status.needCalc = false } let { avgHeight, animationIndex, mergedConfig, rowsData, updater } = status const { waitTime, carousel, rowNum } = mergedConfig const rowLength = rowsData.length if (rowNum >= rowLength) return if (start) { await new Promise(resolve => setTimeout(resolve, waitTime)) if (updater !== status.updater) return } const animationNum = carousel === 'single' ? 1 : rowNum let rows = rowsData.slice(animationIndex) rows.push(...rowsData.slice(0, animationIndex)) status.rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1) status.heights = new Array(rowLength).fill(avgHeight)
await new Promise(resolve => setTimeout(resolve, 300)) if (updater !== status.updater) return
status.heights.splice(0, animationNum, ...new Array(animationNum).fill(0))
animationIndex += animationNum
const back = animationIndex - rowLength if (back >= 0) animationIndex = back
status.animationIndex = animationIndex status.animationHandler = setTimeout(animation, waitTime - 300) }
function stopAnimation() { const { animationHandler, updater } = status status.updater = (updater + 1) % 999999 if (!animationHandler) return clearTimeout(animationHandler) }
function emitEvent(type, ri, ci, row, cell) { emit(type, ri, ci, row, cell) }
const clickHandler = (type, ri, ci, row, cell) => { if (props.clickHighlight) { status.rows.forEach((item, index) => { if (index === ri) { item['isHighlight'] = true } else { item['isHighlight'] = false } }) } emit(type, ri, ci, row, cell) }
function updateRows(rows, animationIndex) { const { mergedConfig, animationHandler } = { ...mergedConfig, data: [...rows], }
status.needCalc = true
if (typeof animationIndex === 'number') status.animationIndex = animationIndex if (!animationHandler) animation(true) }
watch(() => props.config, () => { stopAnimation() status.animationIndex = 0 calcData() }, { deep: true })
watch(() => props.highlightNumber, (num) => { if (props.enableCarousel) { if (num > -1) { status.animationIndex = num; animation(false); if (status.rows.length <= props.config.rowNum) { status.rows.forEach((row, index) => { if (index === num) { row.isHighlight = true } else { row.isHighlight = false } }) } else { status.rows.forEach((row, index) => { if (index === 0) { row.isHighlight = true } else { row.isHighlight = false } }) } stopAnimation(); setTimeout(() => { stopAnimation(); animation(true); }, 3000) } } else { const rows = document.querySelectorAll('.bv-scroll-table .rows .row-item'); if (rows[num]) { rows[num].scrollIntoView(); status.rows.forEach((row, index) => { if (index === num) { row.isHighlight = true } else { row.isHighlight = false } }) } } })
const moveHandler = (e) => { const target = e.target; const insideTable = target.closest('.bv-scroll-table'); if (!insideTable) { const tooltip = document.querySelector('.scroll-table-tooltip'); if (tooltip) { tooltip.remove(); } } }
const handleMove = window._.debounce(function (e, ri, ci, row, cell) { const x = e.pageX; const y = e.pageY; const offsetWidth = e.target.offsetWidth; const scrollWidth = e.target.scrollWidth; if (offsetWidth < scrollWidth) { const tooltip = document.querySelector('.scroll-table-tooltip'); if (!tooltip) { const span = document.createElement('span'); span.className = 'scroll-table-tooltip'; span.innerHTML = cell.val || cell; span.style.top = y + 10 + 'px'; span.style.left = x + 15 + 'px'; document.body.appendChild(span); } else { tooltip.innerHTML = cell.val || cell; tooltip.style.top = y + 10 + 'px'; tooltip.style.left = x + 15 + 'px'; } } else { const tooltip = document.querySelector('.scroll-table-tooltip'); tooltip && tooltip.remove(); } }, 200)
onMounted(() => { document.body.addEventListener('mousemove', moveHandler) })
onUnmounted(() => { stopAnimation(); document.body.removeEventListener('mousemove', moveHandler) })
const getCellContent = (cell) => { if (typeof cell === 'object') { return cell.val } else { return cell } }
return { defaultConfig, ...toRefs(status), domRef, width, height, resize, updateRows, handleHover, onResize, emitEvent, clickHandler, handleMove, getCellContent, } }}
const defaultHeader = ['列1', '列2', '列3']const defaultDatas = [ ['行1列1', '行1列2', '行1列3'], ['行2列1', '行2列2', '行2列3'], ['行3列1', '行3列2', '行3列3'], ['行4列1', '行4列2', '行4列3'], ['行5列1', '行5列2', '行5列3'], ['行6列1', '行6列2', '行6列3'], ['行7列1', '行7列2', '行7列3'], ['行8列1', '行8列2', '行8列3'], ['行9列1', '行9列2', '行9列3'], ['行10列1', '行10列2', '行10列3']]
class CustomScrollTableNode extends HtmlResize.view { headerDatas = defaultHeader tableDatas = defaultDatas oldProperties = {} chartRendered = false historyDatas = [] instance = null
setHtml(rootEl) { if (!rootEl) return; const { graphModel, model } = this.props; const { properties, width, height, } = this.props.model; const { rowNum, headerBGC, oddRowBGC, evenRowBGC, waitTime, headerHeight, columnWidth, align, index, carousel, hoverPause, fontColor, headerFontColor, oddRowGradBG, evenRowGradBG, enableBorder, borderColor, enableCarousel, fontSize, headerFontSize, onlyHeaderHoriBorder, onlyOuterBorder, onlyHeaderBorder, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, tableBGC, clickHighlight, highlightBGC, highlightGradBG, highlightNumber, highlightFontColor } = properties; const alignData = align.map(i => i.align); const colWidths = columnWidth.map(i => i.colW);
const clickHandler = (ri, ci, row, cell) => { graphModel.eventCenter.emit("node:change", { data: model, e: { ri, ci, row, cell }, }); }
if (this.instance) { // 实时数据不能推送一次就创建一次图表,可以在原有实例基础之上更改数据。
Object.assign(this.instance.component.props, { name: properties.nodeAlias, onClick: clickHandler, config: { header: this.headerDatas, data: this.tableDatas, rowNum, waitTime: parseInt(waitTime), headerBGC, oddRowBGC: oddRowBGC || oddRowGradBG, evenRowBGC: evenRowBGC || evenRowGradBG, headerHeight, columnWidth: colWidths, align: alignData, index, carousel, hoverPause, }, containerWidth: width, containerHeight: height, clickHighlight, highlightBGC, highlightGradBG, highlightNumber, highlightFontColor, fontColor, headerFontColor, enableBorder, borderColor, enableCarousel, fontSize, headerFontSize, onlyOuterBorder, onlyHeaderBorder, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyHeaderHoriBorder, tableBGC }) return } const el = document.createElement('div'); rootEl.innerHTML = ''; el.style.height = '100%'; const instance = createVNode(ScrollTable, { name: properties.nodeAlias, onClick: clickHandler, config: { header: this.headerDatas, data: this.tableDatas, rowNum, waitTime: parseInt(waitTime), headerBGC, oddRowBGC: oddRowBGC || oddRowGradBG, evenRowBGC: evenRowBGC || evenRowGradBG, headerHeight, columnWidth: colWidths, align: alignData, index, carousel, hoverPause, }, containerWidth: width, containerHeight: height, clickHighlight, highlightBGC, highlightGradBG, highlightNumber, highlightFontColor, fontColor, headerFontColor, enableBorder, borderColor, enableCarousel, fontSize, headerFontSize, onlyOuterBorder, onlyHeaderBorder, onlyBodyBorder, bodyBorderStyle, onlyHoriBorder, onlyHeaderHoriBorder, tableBGC }) instance.appContext = app._context render(instance, el) rootEl.appendChild(el); this.instance = instance; }
sameProps(properties) { const isSame = window._.isEqual(this.oldProperties, properties); if (isSame) return true; this.oldProperties = properties; return false }
filterDatasByGroup(datas, attrs) { let headers = []; let tableDatas = []; if (datas.length > 0) { headers = ['物编码', '物属性', '时间', '值', '单位']; const datasGrouped = window._.groupBy(datas, 'attrKey'); const unitMap = {}; for (const key in attrs) { unitMap[key] = attrs[key].unit; } for (const key in datasGrouped) { const serieData = datasGrouped[key]; serieData.forEach(i => { const time = window.dayjs(Number(i.ts)).format('YYYY-MM-DD HH:mm:ss'); tableDatas.push([i.thingCode, i.attrKey, time, i.val, unitMap[i.attrKey] || '']) }) } this.headerDatas = headers; this.tableDatas = tableDatas; } }
filterHistoryData(thingCodeArr, totalAttrs, dataPointArr, apiid, renderIntervalEnabled) { if (dataPointArr && dataPointArr.length > 0) { let datas = [] if (renderIntervalEnabled) { datas = window.totalHistoryDatas[apiid]; } else { datas = window.globalDashboardDatas[apiid].values; } if (datas && datas.length > 0) { const gotValues = datas.filter((val) => thingCodeArr.includes(val.thingCode) && dataPointArr.includes(val.attrKey)) this.filterDatasByGroup(gotValues, totalAttrs) this.chartRendered = true; } } }
renderEmpty(properties) { const { emptyTable, colNum, rowNum } = properties; if (emptyTable != undefined && emptyTable) { // 渲染空表格
this.headerDatas = new Array(colNum).fill(''); this.tableDatas = new Array(rowNum * 2).fill('').map(() => this.headerDatas.slice()); } else { this.headerDatas = defaultHeader; this.tableDatas = defaultDatas; } this.chartRendered = true; }
// 生命周期 支持重写内容, 但格式需一致
shouldUpdate() { const { properties } = this.props.model; const { apiid } = properties; const { normalData } = properties.dynamic || {}; const { dataPointArr, thingCodeArr, customApiDatas, enableTestDatas, testDatas } = normalData || {} let totalAttrs = {} if (normalData && normalData.dataPoint) { if (window.isJSON(normalData.dataPoint)) { const dataPointStrParsed = JSON.parse(normalData.dataPoint || '{}') const { attrs } = dataPointStrParsed; totalAttrs = attrs; } }
if (!dataPointArr || (dataPointArr && dataPointArr.length === 0)) { this.renderEmpty(properties); }
// 如果采用来自自定义数据源的数据
if (customApiDatas) { this.headerDatas = customApiDatas.headerDatas || defaultHeader; this.tableDatas = customApiDatas.tableDatas || defaultDatas; this.chartRendered = true; }
if (enableTestDatas && testDatas) { const fn = new Function('', testDatas); const ret = fn(); if (ret && ret.headerDatas && ret.tableDatas) { this.headerDatas = ret.headerDatas; this.tableDatas = ret.tableDatas; this.chartRendered = true; } }
const propertiesBack = window._.cloneDeep(properties); // 由于事件change 会给properties 增加一个 event 属性(见目录scadaDashboard/Diagram/useDynamicEventsHandler),会引发属性的改变,导致组件重渲染。
delete propertiesBack.event; if (propertiesBack.dynamic.normalData) { propertiesBack.dynamic.normalData.defaultValue = ''; const isSameProps = this.sameProps(propertiesBack); if (isSameProps && this.chartRendered) { return false } else { if (dataPointArr && apiid && !this.chartRendered) { this.filterHistoryData(thingCodeArr, totalAttrs, dataPointArr, apiid, normalData.renderIntervalEnabled); return true; } } return true } }
updateHtml() { this.setHtml(this.rootEl); }
componentDidMount() { const { properties } = this.props.model; const { normalData } = properties.dynamic || {}; const { renderInterval, dataPointArr, thingCodeArr } = normalData || {}; let totalAttrs = {} if (normalData && normalData.dataPoint) { if (window.isJSON(normalData.dataPoint)) { const dataPointStrParsed = JSON.parse(normalData.dataPoint || '{}') const { attrs } = dataPointStrParsed; totalAttrs = attrs; } }
if (this.shouldUpdate()) { this.setHtml(this.rootEl); }
let inters = parseInt(renderInterval || '300000') if (normalData && !normalData.renderIntervalEnabled) { inters = 1000 } setInterval(() => { if (window.totalHistoryDatas && window.totalHistoryDatas[properties.apiid]) { this.filterHistoryData(thingCodeArr, totalAttrs, dataPointArr, properties.apiid, normalData.renderIntervalEnabled); this.setHtml(this.rootEl); } }, inters) // 防止拖动时候频繁渲染图表
this.updateHtmlDebounced = window._.debounce(this.updateHtml.bind(this), 500); }
componentDidUpdate() { if (this.shouldUpdate()) { this.updateHtmlDebounced(); } }}
class CustomScrollTableModel extends HtmlResize.model { initNodeData(data) { // 自定义组件,需最开始重置一下text 。
data.text = { value: "", x: data.x, y: data.y, };
super.initNodeData(data); const { properties } = this; this.width = properties.width || 80; this.height = properties.height || 35; this.text.editable = false; // 不允许文本被编辑
}
setAttributes() { // 自定义组件需重置 text
const { x, y, properties } = this; const { textHorizontalMove = 0, textVerticalMove = 0 } = properties; this.text = { ...this.text, x: x + textHorizontalMove, y: y + textVerticalMove, value: "", } }}
lf.register({ type: 'custom-scrolltable-node', view: CustomScrollTableNode, model: CustomScrollTableModel,})`,css:`.bv-scroll-table {\r position: relative;\r width: 100%;\r height: 100%;\r color: #fff;\r}\r\r.bv-scroll-table .text {\r padding: 0 10px;\r box-sizing: border-box;\r white-space: nowrap;\r overflow: hidden;\r text-overflow: ellipsis;\r}\r\r.bv-scroll-table .header {\r display: flex;\r flex-direction: row;\r font-size: 15px;\r}\r\r.bv-scroll-table .header .header-item {\r padding: 0 10px;\r box-sizing: border-box;\r transition: all 0.3s;\r}\r\r.bv-scroll-table .rows {\r overflow: hidden;\r}\r\r.bv-scroll-table .rows::-webkit-scrollbar {\r width: 8px;\r}\r\r/* Track */\r.bv-scroll-table .rows::-webkit-scrollbar-track {\r box-shadow: inset 0 0 1px grey;\r border-radius: 4px;\r}\r\r/* Handle */\r.bv-scroll-table .rows::-webkit-scrollbar-thumb {\r background: #006FFF;\r border-radius: 4px;\r}\r\r/* Handle on hover */\r.bv-scroll-table .rows::-webkit-scrollbar-thumb:hover {\r background: #0090FF;\r}\r\r.bv-scroll-table .rows .row-item {\r display: flex;\r font-size: 14px;\r transition: all 0.3s;\r}\r\r.bv-scroll-table .rows .cell {\r padding: 0 10px;\r box-sizing: border-box;\r white-space: nowrap;\r overflow: hidden;\r text-overflow: ellipsis;\r}\r\r.bv-scroll-table .rows .index {\r border-radius: 3px;\r padding: 0 3px;\r}\r\r.scroll-table-tooltip {\r position: absolute;\r display: inline-block;\r background-color: rgba(0, 0, 0, 0.65);\r color: #ffffff;\r font-size: 12px;\r padding: 5px 5px;\r max-width: 120px;\r max-height: 120px;\r overflow-y: auto;\r z-index: 8888;\r transition: all 0.2s;\r}`,fakeData:""},m={id:e,name:n,aliasName:t,image:a,imageType:o,groupName:r,groupType:l,isRemote:!1,isDefault:!0,sectionType:i,config:d,files:s};export{t as aliasName,d as config,m as default,s as files,r as groupName,l as groupType,e as id,a as image,o as imageType,c as isDefault,u as isRemote,n as name,i as sectionType};
|