先看看完成后效果
- 上个版本实现了跟公司业务关联 实现了联系人库可连线、点击详情、删除等功能
- 这个版本需要拓展一些功能 如联系人节点添加标识 添加文本框等
节点打标
需要在联系人三个角落添加标识 颜色走后端控制 基于原来的FloatingNode 更改 FloatingNode.tsx
export default memo((node: NodeProps) => {
//connectable:控制连接点是否显隐 tag:标识文案 tagColor:标识背景色
// 因为业务需要 只弄了三个角
const { data: { label, connectable, item } } = node
const { tagColor = {}, tag = {} } = item
return (
<div className={styles.flowNode} >
<div className={styles.flowNodeContent}>
{label}
</div>
<div className={classNames(styles.flowNodeLabel, styles.leftTop, tagColor.decisionRole ? '' : styles.noneLabel)}
style={tagColor.decisionRole ? { backgroundColor: tagColor.decisionRole } : {}} title={tag.decisionRole}>{tag.decisionRole}</div>
<div className={classNames(styles.flowNodeLabel, styles.rightTop, tagColor.customerAttitude ? '' : styles.noneLabel)}
style={tagColor.customerAttitude ? { backgroundColor: tagColor.customerAttitude } : {}} title={tag.customerAttitude}>{tag.customerAttitude}</div>
<div className={classNames(styles.flowNodeLabel, styles.rightBtm, tagColor.competeRelation ? '' : styles.noneLabel)}
style={tagColor.competeRelation ? { backgroundColor: tagColor.competeRelation } : {}} title={tag.competeRelation}>{tag.competeRelation}</div>
{
!connectable && <><Handle type="target" position={Position.Top} id="a" />
<Handle type="source" position={Position.Bottom} id="b" />
<Handle type="source" position={Position.Right} id="c" />
<Handle type="target" position={Position.Left} id="d" /></>
}
</div>
);
})
组件库
const getComLibModal = () => {
// 拖拽功能基本和联系人列表一致
return <div className={styles.topModal}>
<div className={styles.headTitle}>
组件库
<CloseOutlined onClick={() => setIsComLibShow(false)} />
</div>
<div className={styles.modalContent}>
<div className={styles.comContainer} onDragStart={(event) => onDragStart(event, { label: '双击编辑文本', nodeType: 'labelText' }, 'customNode')} draggable>
<div>
<span className="icon iconfont icon-ziti"></span>
</div>
<p>文本</p>
</div>
<div className={styles.comContainer} onDragStart={(event) => onDragStart(event, { label: '', nodeType: 'rectangle' }, 'customNode')} draggable>
<div>
<span className={styles.comRectangle}></span>
</div>
<p>矩形</p>
</div>
<div className={styles.comContainer} onDragStart={(event) => onDragStart(event, { label: '', nodeType: 'circular' }, 'customNode')} draggable>
<div>
<span className={styles.comCircular}></span>
</div>
<p>圆形</p>
</div>
</div>
</div>
}
接下来难点就是如何实现文本编辑 采用给dom添加双击事件
const addTextEvnt = () => {
// 获取所有拖拽的组件 遍历出来 依次添加事件
let dom: any = document.querySelectorAll(".react-flow__node-customNode")
dom.forEach((el: HTMLDivElement) => {
if (!el.ondblclick ) {
el.ondblclick = ((event: MouseEvent) => {
const id = el.getAttribute('data-id')
const oldHtml = el.children[0].innerHTML;
if (!oldHtml.includes('<textarea')) {
// 触发双击事件时 创建一个文本域 插入当前dom中
let textarea = document.createElement('textarea')
textarea.maxLength = 30
el.children[0].innerHTML = '';
el.children[0].appendChild(textarea);
textarea.focus();
textarea.onblur = (evt: FocusEvent) => {
el.children[0].innerHTML = (evt.target as HTMLInputElement).value
setLabel(id, (evt.target as HTMLInputElement).value)
}
}
})
}
})
}
// 文本编辑后 Flow同步更改
const setLabel = (labelId: string, labelVal: string) => {
if (labelId) {
setNodes((es: Node[]) =>
es.map((el) => {
if (el.id === labelId) {
el.data.label = labelVal;
}
return el;
})
);
}
}
建立一个组件库node
CustomNode.tsx
export default memo((node: NodeProps) => {
const { data: { label, item } } = node
const { nodeType = '' } = item
const styleMap = {
"labelText": styles.textLabel,
"rectangle": styles.rectangle,
"circular": styles.circular
}
return (
<div className={styleMap[nodeType]} title={label}>
{label}
</div>
);
})
完成后送个产品看 产品举起大拇指 说还是有点瑕疵 就是文本框不支持换行、并且没考虑小屏用户画图体验 明天要给上面演示 今晚加班努力下~
作为一个每天雷打不动摸鱼的前端 知道今天没得鱼摸了~~
第一步 换行问题 由于是使用innerHTML 所以文本域带的\n转行符是没用的
// 取值和存值的时候 把\n 和<br> 互换即可
let textarea = document.createElement('textarea')
textarea.maxLength = 30
textarea.value = oldHtml.replaceAll("<br>", "\n") //控制换行 innerHTML不识别\n 必须使用br
el.children[0].innerHTML = '';
el.children[0].appendChild(textarea);
textarea.focus();
textarea.onblur = (evt: FocusEvent) => {
let textHTML = (evt.target as HTMLInputElement).value.replaceAll("\n", "<br />")
el.children[0].innerHTML = textHTML
setLabel(id, (evt.target as HTMLInputElement).value)
}
第二步 本来是想用大弹窗解决菜单栏、导航栏占地面积过多导致小屏的内容区域过小问题 但是想着既然是画图 何不弄个画图区域全屏功能呢?
添加全屏功能按钮
// ControlButton flow对外开放的自定义工具图标
<Controls showFitView={false} showInteractive={false} >
<ControlButton onClick={() => onFullScreen()}>
<FullscreenOutlined />
</ControlButton>
</Controls>
const onFullScreen = () => {
try {
// 获取需要全屏的dom
let fullarea = document.querySelectorAll('.container')[0]
if (!document.fullscreenElement) {
// 全屏
fullarea.requestFullscreen();
} else {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
} catch (err) {
console.log("err>>", err)
}
}