流程管理,可视化一棵树
useTreeLight.js:
import React, { useEffect, useState, useMemo } from 'react'
import TreeCard from './TreeCard'
import { deepClone } from '../../../utils/tools'
import TreeLine from './TreeLine'
import useTreeLightList from './useTreeLightList'
let timer
let historyPosition = {}
export default function useTreeLight(props) {
const { dataSource, isToCenter, onAddChild, onDelete, onEdit } = props
const [scaleValue, setScaleValue] = useState(1)
const [isDrag, setIsDrag] = useState(false)
const [domHistoryPositon, setDomHistoryPositon] = useState({
clientX: 0,
clientY: 0,
})
//添加position和lines
const { treeData, treeBoundary, processEndNode, endNodeLines } =
useTreeLightList({ dataSource })
//查找行列值和position值一致的元素
const findTreeNode = ({ treeData, position }) => {
let result
const findTreeNodeLoop = (arr, parentId = '') => {
for (let i = 0; i < arr.length; i++) {
if (
position.rolIndex === arr[i].position.rolIndex &&
position.colIndex === arr[i].position.colIndex
) {
result = arr[i]
}
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
findTreeNodeLoop(arr[i].children, `${parentId}${i + 1}`)
}
}
}
const treeDataCopy = deepClone(treeData)
findTreeNodeLoop(treeDataCopy)
return result
}
//查找行列值等于lines数组包含的行列值,对应的type
const findLineType = ({ treeData, position }) => {
let result
const findLineTypeLoop = (arr, parentId = '') => {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i].lines)) {
const lineItem = arr[i].lines.find(
(item) =>
item.rolIndex === position.rolIndex &&
item.colIndex === position.colIndex
)
if (lineItem) {
result = lineItem.lineType
}
}
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
findLineTypeLoop(arr[i].children, `${parentId}${i + 1}`)
}
}
}
const treeDataCopy = deepClone(treeData)
findLineTypeLoop(treeDataCopy)
return result
}
//根据树结构判断是否应该渲染TreeCard组件
const renderTreeCol = ({ rolIndex, colIndex }) => {
const treeNode = findTreeNode({
treeData,
position: { rolIndex, colIndex },
})
const lineType = findLineType({
treeData,
position: { rolIndex, colIndex },
})
const endNodeLine = endNodeLines.find(
(item) => item.rolIndex === rolIndex && item.colIndex === colIndex
)
return (
<>
{treeNode && (
<TreeCard
title={treeNode.name}
color={treeNode.color}
item={treeNode}
treeNodeContent={treeNode.content}
onAddChild={onAddChild}
onDelete={onDelete}
onEdit={onEdit}
></TreeCard>
)}
{lineType && <TreeLine lineType={lineType}></TreeLine>}
{processEndNode.rolIndex === rolIndex &&
processEndNode.colIndex === colIndex && (
<div className="m-tree-end-node">流程结束</div>
)}
{endNodeLine && <TreeLine lineType={endNodeLine.lineType}></TreeLine>}
</>
)
}
//根节点回到可视区域内
const handleResetTreeToCenter = () => {
setDomHistoryPositon({
clientX: 0,
clientY: 0,
})
setTimeout(() => {
const treeRoot = document.getElementById('m-tree-root')
if (treeRoot) {
treeRoot.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
}
}, 0)
}
//调解大小
const handleScale = (value) => {
setScaleValue((value / 100).toFixed(2))
clearTimeout(timer)
timer = setTimeout(() => {
handleResetTreeToCenter()
}, 200)
}
//鼠标按下
const handleMouseDown = (e) => {
console.log('down', e.clientX, e.clientY, e)
historyPosition = {
clientX: e.clientX,
clientY: e.clientY,
}
setIsDrag(true)
// clearTimeout(timer2)
// timer2 = setTimeout(function () {
// setIsDrag(true)
// }, 500)
}
//鼠标抬起
const handleMouseUp = (e) => {
console.log('up', e.clientX, e.clientY, e)
setIsDrag(false)
}
//拖拽
const handleMouseMove = (e) => {
if (e.buttons === 1) {
//console.log('move', e.clientX, e.clientY, e)
setIsDrag(true)
setDomHistoryPositon({
clientX:
domHistoryPositon.clientX + (e.clientX - historyPosition.clientX),
clientY:
domHistoryPositon.clientY + (e.clientY - historyPosition.clientY),
})
historyPosition = {
clientX: e.clientX,
clientY: e.clientY,
}
} else {
setIsDrag(false)
}
}
const renderTree = useMemo(() => {
console.log('tree')
const dataArr = []
const { rolIndexEnd = 10, colIndexEnd = 10 } = treeBoundary
const rolCount = rolIndexEnd + 6 < 10 ? 10 : rolIndexEnd + 6
const colCount = colIndexEnd + 2 < 10 ? 10 : colIndexEnd + 2
for (let i = 0; i < rolCount; i++) {
let dataRow = []
for (let j = 0; j < colCount; j++) {
dataRow.push({
row: i,
col: j,
})
}
dataArr.push(dataRow)
}
const isDev = localStorage.getItem('isDev') === 'true' ? true : false
return (
<>
{dataArr.map((colList, rolIndex) => (
<div className="m-tree-row" key={rolIndex}>
{colList.map((item, colIndex) => (
<div
key={`${rolIndex}-${colIndex}`}
className={`m-tree-col ${isDev ? 'active' : ''}`}
>
{renderTreeCol({ rolIndex, colIndex })}
</div>
))}
</div>
))}
</>
)
// eslint-disable-next-line
}, [dataSource])
//渲染dom
const renderDom = () => {
return (
<div className={`m-tree-wrap`}>
<div className="m-tree-container" onMouseDown={handleMouseDown}>
<div
className="m-tree-inner"
style={{
transform: `scale(${scaleValue})`,
left: domHistoryPositon.clientX + 'px',
top: domHistoryPositon.clientY + 'px',
}}
>
{renderTree}
</div>
</div>
<div
className={`m-drag-level ${isDrag ? 'active' : ''}`}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
></div>
</div>
)
}
useEffect(() => {
if (isToCenter) {
handleResetTreeToCenter()
}
// eslint-disable-next-line
}, [isToCenter])
return {
getTreeDom: renderDom,
handleResetTreeToCenter,
handleScale,
}
}
useTreeLightList.js:
import { deepClone } from '../../../utils/tools'
export default function useTreeLightList({ dataSource }) {
//统计一个棵树各层的元素的个数,并求出最大值
const getMaxTreeLevelNodeCount = ({ treeDataSource }) => {
let levelObj = {}
const find = (arr, level = 0) => {
for (let i = 0; i < arr.length; i++) {
arr[i].level = level
levelObj[level] = levelObj[level] ? levelObj[level] + 1 : 1
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
find(arr[i].children, level + 1)
}
}
}
const treeDataSourceCopy = deepClone(treeDataSource)
find(treeDataSourceCopy)
let levelCountArr = []
for (let key in levelObj) {
levelCountArr.push(levelObj[key])
}
let maxLevelCount = 0
if (Array.isArray(levelCountArr) && levelCountArr.length > 0) {
maxLevelCount = Math.max.apply(null, levelCountArr)
}
return maxLevelCount
}
// const count = getMaxTreeLevelNodeCount({ treeDataSource: dataSource })
// console.log(count)
//添加position
const handleAddPosition = ({ treeDataSource }) => {
//rolIndex计算方式:孩子节点rolIndex = 父节点rolIndex + 2
//colIndex计算方式:
//(1)startColIndex标识起始位置,
//(2)如果是第一个节点,需要加上一个levelMove偏移量,这个levelMove偏移量是
//根据该节点的children计算出来的,和最大层节点数有关
//(3)如果不是第一个节点,除了和levelMove偏移量有关外,和紧邻的上一个兄弟节点的位置也有关,
//和紧邻的上一个兄弟节点的levelMove也有关,
const setPosition = (arr, { rolIndex, startColIndex, isRoot }) => {
for (let i = 0; i < arr.length; i++) {
const maxTreeLevelNodeCount = getMaxTreeLevelNodeCount({
treeDataSource: [arr[i]],
})
let levelMove = 0
if (maxTreeLevelNodeCount % 2 === 0) {
levelMove = ((maxTreeLevelNodeCount - 2) / 2) * 2 + 1
} else {
levelMove = ((maxTreeLevelNodeCount - 1) / 2) * 2
}
let fatherColIndex
if (i === 0) {
fatherColIndex = startColIndex + (isRoot ? levelMove : 0)
} else if (i > 0 && arr[i - 1].position) {
fatherColIndex =
arr[i - 1].position.colIndex +
2 +
arr[i - 1].position.levelMove +
levelMove
}
arr[i].position = {
rolIndex: rolIndex + 2,
colIndex: fatherColIndex,
levelMove,
}
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
let childrenLength = arr[i].children.length
//奇数偶数处理方式不同
let startColIndex
if (childrenLength % 2 === 1) {
startColIndex = fatherColIndex - ((childrenLength - 1) / 2) * 2
} else {
startColIndex = fatherColIndex - ((childrenLength - 2) / 2) * 2 - 1
}
setPosition(arr[i].children, {
rolIndex: rolIndex + 2,
startColIndex,
})
}
}
}
const treeDataSourceCopy = deepClone(treeDataSource)
//起始行数: -1 + 2 = 1
//起始列: 2
//初步设置position
setPosition(treeDataSourceCopy, {
rolIndex: -1,
startColIndex: 2,
isRoot: true,
})
//找出最小的colIndex
//平移这个树
return treeDataSourceCopy
}
//添加lines
const handleAddLines = ({ treeDataSource }) => {
const setLines = (arr) => {
for (let i = 0; i < arr.length; i++) {
const rolIndex = arr[i].position.rolIndex + 1
arr[i].lines = []
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
const fatherColIndex = arr[i].position.colIndex
const childrenColIndexArr = arr[i].children.map(
(item) => item.position.colIndex
)
for (
let j = arr[i].children[0].position.colIndex;
j < arr[i].children[arr[i].children.length - 1].position.colIndex;
j++
) {
//设置不合孩子节点及父节点在同一列的lineType
if (!childrenColIndexArr.includes(j) && j !== fatherColIndex) {
arr[i].lines.push({
rolIndex,
colIndex: j,
lineType: [2, 4],
})
}
//设置父节点正下方的lineType
if (!childrenColIndexArr.includes(j) && j === fatherColIndex) {
if (arr[i].children.length > 1) {
arr[i].lines.push({
rolIndex,
colIndex: j,
lineType: [1, 2, 4],
})
}
}
}
//设置孩子节点正上方的lineType
for (let j = 0; j < arr[i].children.length; j++) {
const tempColIndex = arr[i].children[j].position.colIndex
if (tempColIndex < fatherColIndex && j === 0) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [2, 3],
})
} else if (
tempColIndex > fatherColIndex &&
j === arr[i].children.length - 1
) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [4, 3],
})
} else if (
arr[i].children.length === 1 &&
fatherColIndex === tempColIndex
) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [1, 3],
})
} else if (
arr[i].children.length > 1 &&
fatherColIndex === tempColIndex
) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [1, 2, 3, 4],
})
} else if (
arr[i].children.length > 1 &&
fatherColIndex > tempColIndex &&
j !== 0
) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [2, 3, 4],
})
} else if (
arr[i].children.length > 1 &&
fatherColIndex < tempColIndex &&
j !== arr[i].children.length - 1
) {
arr[i].lines.push({
rolIndex,
colIndex: tempColIndex,
lineType: [2, 3, 4],
})
}
}
setLines(arr[i].children)
}
}
}
const treeDataSourceCopy = deepClone(treeDataSource)
setLines(treeDataSourceCopy)
return treeDataSourceCopy
}
//求树坐标的边界值
const getTreeBoundary = ({ treeDataSource }) => {
let rolIndexArr = []
let colIndexArr = []
const find = (arr, level = 0) => {
for (let i = 0; i < arr.length; i++) {
rolIndexArr.push(arr[i].position.rolIndex)
colIndexArr.push(arr[i].position.colIndex)
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
find(arr[i].children, level + 1)
}
}
}
const treeDataSourceCopy = deepClone(treeDataSource)
find(treeDataSourceCopy)
rolIndexArr.sort((a, b) => a - b)
colIndexArr.sort((a, b) => a - b)
return {
rolIndexStart: rolIndexArr[0],
rolIndexEnd: rolIndexArr[rolIndexArr.length - 1],
colIndexStart: colIndexArr[0],
colIndexEnd: colIndexArr[colIndexArr.length - 1],
}
}
//流程结束节点位置
const getPorcessEndNode = ({ treeDataSource, treeBoundary }) => {
return {
rolIndex: treeBoundary.rolIndexEnd + 4,
colIndex: treeDataSource[0].position.colIndex,
}
}
//叶子节点连接到流程结束节点的线
const getEndNodeLines = ({ treeDataSource, processEndNode }) => {
let leafNodeArr = []
let endNodeLines = []
const find = (arr, level = 0) => {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
find(arr[i].children, level + 1)
} else {
leafNodeArr.push(arr[i].position)
}
}
}
const treeDataSourceCopy = deepClone(treeDataSource)
find(treeDataSourceCopy)
leafNodeArr.forEach((item) => {
for (let i = item.rolIndex + 1; i < processEndNode.rolIndex - 1; i++) {
endNodeLines.push({
rolIndex: i,
colIndex: item.colIndex,
lineType: [1, 3],
})
}
})
for (
let i = leafNodeArr[0].colIndex;
i < leafNodeArr[leafNodeArr.length - 1].colIndex + 1;
i++
) {
if (leafNodeArr.length === 1) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [1, 3],
})
} else if (i === leafNodeArr[0].colIndex) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [1, 2],
})
} else if (i === leafNodeArr[leafNodeArr.length - 1].colIndex) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [1, 4],
})
} else if (i === processEndNode.colIndex && leafNodeArr.find(item => item.colIndex === i)) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [1, 2, 3, 4],
})
} else if (i === processEndNode.colIndex && !leafNodeArr.find(item => item.colIndex === i)) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [2, 3, 4],
})
} else if (i !== processEndNode.colIndex && leafNodeArr.find(item => item.colIndex === i)) {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [ 1, 2, 4],
})
} else {
endNodeLines.push({
rolIndex: processEndNode.rolIndex - 1,
colIndex: i,
lineType: [2, 4],
})
}
}
return endNodeLines
}
//切换数据源
let treeDataResult
let treeDataSource = dataSource
let treeBoundary = {}
let processEndNode = []
let endNodeLines = []
if (Array.isArray(treeDataSource) && treeDataSource.length > 0) {
treeDataResult = handleAddPosition({ treeDataSource })
treeDataResult = handleAddLines({ treeDataSource: treeDataResult })
treeBoundary = getTreeBoundary({ treeDataSource: treeDataResult })
processEndNode = getPorcessEndNode({
treeDataSource: treeDataResult,
treeBoundary,
})
endNodeLines = getEndNodeLines({
treeDataSource: treeDataResult,
processEndNode,
})
}
//切换真正用于渲染的treeData
let treeData = treeDataResult ? treeDataResult : treeDataSource
return {
treeData,
treeBoundary,
processEndNode,
endNodeLines,
}
}
TreeCard.js:
import React from 'react'
import Icon from '../Icon'
export default function TreeCard(props) {
const { title, treeNodeContent, color = 'gray', item } = props
const handleAdd = (e) => {
e.stopPropagation()
props.onAddChild(item)
}
const handleDelete = (e) => {
e.stopPropagation()
props.onDelete(item)
}
const handleMouseDown = (e) => {
e.stopPropagation()
}
const getShortContent = () => {
let shortContent
if (treeNodeContent && treeNodeContent.length > 50) {
shortContent = treeNodeContent.slice(0, 50) + '...'
} else {
shortContent = treeNodeContent
}
return shortContent
}
return (
<div
className={`m-tree-card ${color}`}
onClick={() => props.onEdit(item)}
id={`${item.belongCategory === '0' ? 'm-tree-root' : ''}`}
onMouseDown={handleMouseDown}
>
<div className={`m-tree-card-header ${color}`}>
<div className="m-tree-card-header-title" title={title}>{title}</div>
{item.belongCategory !== '0' && (
<Icon
className="m-tree-card-header-delete"
name="delete"
onClick={handleDelete}
></Icon>
)}
</div>
<div className="m-tree-card-content">
<div className="m-tree-card-content-text" title={treeNodeContent}>
{getShortContent()}
</div>
<span className="m-tree-card-add-wrap" >
<Icon
name="bg-add"
onClick={handleAdd}
className="m-tree-card-add"
></Icon>
<span className="m-tree-card-add-bg"></span>
</span>
</div>
</div>
)
}
TreeLight.js:
import React from 'react'
export default function TreeLine(props) {
const { lineType } = props
return (
<div className="m-tree-line-wrap">
{/* {JSON.stringify(lineType)} */}
<div className="m-tree-line-row">
<div className={`m-tree-line-item ${lineType.includes(1) ? 'right' : ' '} ${lineType.includes(4) ? 'bottom' : ' '}`}></div>
<div className={`m-tree-line-item ${lineType.includes(1) ? 'left' : ' '} ${lineType.includes(2) ? 'bottom' : ' '}`}></div>
</div>
<div className="m-tree-line-row">
<div className={`m-tree-line-item ${lineType.includes(3) ? 'right' : ' '} ${lineType.includes(4) ? 'top' : ' '}`}></div>
<div className={`m-tree-line-item ${lineType.includes(2) ? 'top' : ' '} ${lineType.includes(3) ? 'left' : ' '}` }></div>
</div>
</div>
)
}
css:
.m-tree-wrap{display: flex; position: relative; flex: 1; margin: 5px 10px 5px; }
.m-tree-container{position: relative; flex: 1;padding: 16px;background: white;overflow: auto;}
.m-drag-level{position: absolute;top: 0;left: 0;right: 0;bottom: 0; pointer-events: none;}
.m-drag-level.active{pointer-events: unset;}
.m-tree-inner{white-space: nowrap;position: absolute;top: 0;left: 0;user-select: none;}
.m-tree-col{display: inline-block;position: relative;width: 240px;height: 120px;vertical-align: top;}
.m-tree-col.active{border: 1px solid #ddd;}
.m-tree-card{display: flex; height: 100%; flex-direction: column; border-radius: 4px;cursor: pointer;box-shadow: 0 1px 8px 0 #ddd;}
.m-tree-card-header{display: flex; padding: 5px 0 5px 5px;}
.m-tree-card-header-title{flex:1;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
.m-tree-card-header-delete{opacity: 0;;width: 30px;cursor: pointer;text-align: center;vertical-align: top;}
.m-tree-card:hover .m-tree-card-header-delete{opacity: 1;}
.m-tree-card-header-delete:hover{color: #1890ff;font-weight: bold;}
.m-tree-card-content{display: flex; flex:1;flex-direction: column; padding: 5px; background: white;border-bottom-left-radius: 4px;border-bottom-right-radius: 4px;}
.m-tree-card-content-text{flex:1;word-wrap:break-word;word-break:break-all;white-space: normal}
.m-tree-card.gray{border-top: 4px solid #88939F;background: #EEF4FB;}
.m-tree-card.gray:hover{box-shadow: 0 1px 8px 0 #88939F;}
.m-tree-card.blue{border-top: 4px solid #4A94FF;background: #E9F2FF;}
.m-tree-card.blue:hover{box-shadow: 0 1px 8px 0 #4a94ff;}
.m-tree-card.orange{border-top: 4px solid #FCAD22;background: #FFF9EE;}
.m-tree-card.orange:hover{box-shadow: 0 1px 8px 0 #FCAD22;}
.m-tree-card.green{border-top: 4px solid #3CB4B2;background: #E6F8F8;}
.m-tree-card.green:hover{box-shadow: 0 1px 8px 0 #3CB4B2;}
.m-tree-card-add-wrap{position: absolute;display: inline-block; transform: translateY(115px) translateX(99px);width: 30px;height: 30px;z-index: 999;}
.m-tree-card-add{position: absolute;top: 0;left: 0; display: inline-block;width: 30px;height: 30px;font-size: 30px;color: #A0B5C8;cursor: pointer;z-index: 99;}
.m-tree-card-add:hover{color: #8599AA;}
.m-tree-card-add-bg{position: absolute;top: 16px; left: 8px; width: 15px; height: 15px; background: white;}
.m-tree-line-wrap{display: flex;flex-direction: column;height: 100%;}
.m-tree-line-row{display: flex; flex:1;}
.m-tree-line-item{flex:1;}
.m-tree-line-item.top{border-top: 1px solid #333}
.m-tree-line-item.right{border-right: 1px solid #333}
.m-tree-line-item.bottom{border-bottom: 1px solid #333}
.m-tree-line-item.left{border-left: 1px solid #333}
.m-tree-slider{display: inline-block; width: 100px;}
.m-tree-end-node{display: flex; align-items: center;justify-content: center;height:60px;border-radius: 4px;box-shadow: 0 1px 8px 0 #ddd;}
后端node.js:
data.js:
let dataArr = [
{
id: '0',
key: '0',
path: '/light/index/content?id=0',
title: '请假',
tree: [
{
name: '申请人',
content: '商之讯',
belongCategory: '0',
status: true,
id: 1622771045562,
color: 'blue',
position: {
rolIndex: 1,
colIndex: 2
},
children: [
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052842,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 2
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052842,
status: false,
id: 1636424256035,
color: 'green',
position: {
rolIndex: 5,
colIndex: 2
},
}
]
}
]
}
]
},
{
id: '1',
key: '1',
path: '/light/index/content?id=1',
title: '报销',
tree: [
{
name: '申请人',
content: '商之讯',
belongCategory: '0',
status: true,
id: 1622771045562,
color: 'blue',
position: {
rolIndex: 1,
colIndex: 2
},
children: [
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052842,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 1
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052842,
status: false,
id: 1636424256035,
color: 'green',
position: {
rolIndex: 5,
colIndex: 1
},
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052843,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 3
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052843,
status: false,
id: 1636424256034,
color: 'green',
position: {
rolIndex: 5,
colIndex: 3
}
}
]
}
]
}
]
},
{
id: '2',
key: '2',
path: '/light/index/content?id=2',
title: '出差',
tree: [
{
name: '申请人',
content: '商之讯',
belongCategory: '0',
status: true,
id: 1622771045562,
color: 'blue',
position: {
rolIndex: 1,
colIndex: 4
},
children: [
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052842,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 2
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052842,
status: false,
id: 1636424256035,
color: 'green',
position: {
rolIndex: 5,
colIndex: 2
}
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052843,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 4
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052843,
status: false,
id: 1636424256034,
color: 'green',
position: {
rolIndex: 5,
colIndex: 4
}
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052844,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 6
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052844,
status: false,
id: 1636424256036,
color: 'green',
position: {
rolIndex: 5,
colIndex: 6
}
}
]
}
]
}
]
},
{
id: '3',
key: '3',
path: '/light/index/content?id=3',
title: '活动',
tree: [
{
name: '申请人',
content: '商之讯',
belongCategory: '0',
status: true,
id: 1622771045562,
color: 'blue',
position: {
rolIndex: 1,
colIndex: 6
},
children: [
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052842,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 3
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052842,
status: false,
id: 1636424256066,
color: 'green',
position: {
rolIndex: 5,
colIndex: 3
}
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052843,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 5
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052843,
status: false,
id: 1636424256034,
color: 'green',
position: {
rolIndex: 5,
colIndex: 5
}
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052844,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 7
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052844,
status: false,
id: 1636424256036,
color: 'green',
position: {
rolIndex: 5,
colIndex: 7
}
}
]
},
{
name: '审批人',
content: '申请人自选 不限范围 多选 依次审批',
belongCategory: 1622771045562,
status: false,
id: 1622771052847,
color: 'orange',
position: {
rolIndex: 3,
colIndex: 9
},
children: [
{
name: '抄送人',
content: '申请人自选',
belongCategory: 1622771052847,
status: false,
id: 1636424256038,
color: 'green',
position: {
rolIndex: 5,
colIndex: 9
}
}
]
}
]
}
]
}
]
module.exports = { dataArr }
list.js:
let { dataArr } = require('../data')
const { v4: uuidv4 } = require('uuid')
//调试用,返回全部数据
const dataSearchAll = (req, res) => {
console.log('air dataSearchAll')
res.send({
code: 200,
data: dataArr,
message: '搜索成功'
})
}
//搜索
const dataSearch = (req, res) => {
const data = dataArr.map((item) => {
return {
id: item.id,
key: item.key,
path: item.path,
title: item.title
}
})
console.log('air dataSearch')
res.send({
code: 200,
data,
message: '搜索成功'
})
}
//添加
const dataAdd = (req, res) => {
const { dataItem } = req.body
dataItem.id = uuidv4()
dataItem.path += `?id=${dataItem.id}`
dataItem.key = dataItem.id
dataItem.tree = [
{
name: '流程起点',
belongCategory: '0',
status: true,
id: 1622771045569
}
]
dataArr.push({ ...dataItem })
res.send({
code: 200,
data: dataItem,
message: '添加成功'
})
}
//删除
const dataDelete = (req, res) => {
let { ids } = req.body
console.log(ids)
dataArr = dataArr.filter((item) => !ids.includes(item.id))
res.send({
code: 200,
data: ids,
message: '删除成功'
})
}
//编辑
const dataEdit = (req, res) => {
let { id, dataItem } = req.body
let index = dataArr.findIndex((item) => item.id === id)
if (index >= 0) {
dataArr[index].title = dataItem.title
//dataArr[index] = { id, ...dataItem }
res.send({
code: 200,
data: dataItem,
message: '编辑成功'
})
} else {
res.send({
code: 400,
data: dataItem,
message: '编辑失败,id不存在'
})
}
}
module.exports = {
processListSearchAll: dataSearchAll,
processListSearch: dataSearch,
processListAdd: dataAdd,
processListDelete: dataDelete,
processListEdit: dataEdit
}
fields.js:
let { dataArr } = require('../data')
//添加函数,如果不是一级分类,就递归遍历所有children,找到他的所属分类并添加
const addFunWrap = (dataItem) => {
//belongCategory==='0'代表一级分类
if (dataItem.belongCategory === '0') {
dataArr.push({ ...dataItem })
return () => {}
} else {
const addFun = (arr, belongCategory) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
addFun(arr[i].children, belongCategory)
}
if (arr[i].id === belongCategory) {
if (arr[i].children && arr[i].children.length > 0) {
arr[i].children.push(dataItem)
} else {
arr[i].children = [dataItem]
}
}
}
}
return addFun
}
}
//编辑或删除后,children可能为空,及时删除children
const deleteEmptyChildrenFunWrap = (tree) => {
const deleteEmptyChildrenFun = (arr) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
deleteEmptyChildrenFun(arr[i].children)
}
if (arr[i].children && arr[i].children.length === 0) {
delete arr[i].children
}
}
}
return deleteEmptyChildrenFun(tree)
}
//编辑函数,递归遍历所有children,找到他的对应的id并编辑
const editFunWrap = (dataItem, tree) => {
const editFun = (arr, id) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
editFun(arr[i].children, id)
}
if (arr[i].id === id) {
//编辑时没有修改所属分类
if (arr[i].belongCategory === dataItem.belongCategory) {
arr[i] = { id, ...dataItem, updateTime: Date.now(), children: arr[i].children }
} else {
//删除当前的,这块需要改一改,不能把children删了
const deleteItem = arr.splice(i, 1)
if (arr.length === 0) {
deleteEmptyChildrenFunWrap(tree)
}
if (Array.isArray(deleteItem) && deleteItem.length > 0 && Array.isArray(deleteItem[0].children) && deleteItem[0].children.length > 0) {
dataItem.children = deleteItem[0].children
}
//重新添加一个新的
addFunWrap(dataItem)(tree, dataItem.belongCategory)
}
}
}
}
return editFun
}
//删除函数,递归遍历所有children,找到他的对应的id并编辑
const deleteFunWrap = (id, tree) => {
const deleteFun = (arr) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
deleteFun(arr[i].children)
}
if (arr[i].id === id) {
//删除当前的
arr.splice(i, 1)
if (arr.length === 0) {
deleteEmptyChildrenFunWrap(tree)
}
}
}
}
deleteFun(tree)
}
//调试用,返回全部数据
const dataSearchAll = (req, res) => {
res.send({
code: 200,
data: dataArr,
message: '搜索成功',
})
}
//搜索
const dataSearch = (req, res) => {
const { tableId } = req.body
const application = dataArr.find((item) => item.id === tableId)
res.send({
code: 200,
data: {
...application
},
message: '搜索成功',
})
}
//添加
const dataAdd = (req, res) => {
const { tableId, dataItem } = req.body
console.log('add')
const index = dataArr.findIndex((item) => item.id === tableId)
dataItem.id = Date.now()
const tree = dataArr[index].tree
addFunWrap(dataItem)(tree, dataItem.belongCategory)
res.send({
code: 200,
data: dataItem,
message: '添加成功',
})
}
//删除
const dataDelete = (req, res) => {
let { tableId, id } = req.body
const tableIndex = dataArr.findIndex((item) => item.id === tableId)
console.log(id)
// dataArr[tableIndex].table.fields = dataArr[tableIndex].table.fields.filter(
// (item) => !ids.includes(item.id)
// )
deleteFunWrap(id, dataArr[tableIndex].tree)
res.send({
code: 200,
data: id,
message: '删除成功',
})
}
//编辑
const dataEdit = (req, res) => {
let { tableId, id, dataItem } = req.body
const tableIndex = dataArr.findIndex((item) => item.id === tableId)
const tree = dataArr[tableIndex].tree
editFunWrap(dataItem, tree)(tree, id)
res.send({
code: 200,
data: dataItem,
message: '编辑成功',
})
}
//编辑全部
const dataEditAll = (req, res) => {
const { tableId, dataItem, skin } = req.body
const tableIndex = dataArr.findIndex((item) => item.id === tableId)
dataArr[tableIndex].table.fields = [
...dataArr[tableIndex].table.fields.filter((item) => item.isSystem),
...dataItem,
]
dataArr[tableIndex].table.skin = skin ? skin : {}
res.send({
code: 200,
data: dataArr,
message: '保存成功',
})
}
module.exports = {
processFieldsSearchAll: dataSearchAll,
processFieldsSearch: dataSearch,
processFieldsAdd: dataAdd,
processFieldsDelete: dataDelete,
processFieldsEdit: dataEdit,
processFieldsEditAll: dataEditAll,
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步