Widget:可视化推荐阅读编辑器
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core/dist/index.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/@logicflow/core/lib/style/index.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@logicflow/extension/dist/index.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/style/index.min.css" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<body>
</body>
<script>
const { HtmlResize, dynamicGroup, DndPanel, Menu, Control, MiniMap, DynamicGroup, SelectionSelect } = Extension; const { LogicFlow, h, BaseText } = Core;
// 暂定五种节点:主线、世界深处、约会剧情、活动剧情、手机信息 // 定义主线节点 class CommonModel extends HtmlResize.model { initNodeData(data) { super.initNodeData(data); this.width = 200; this.height = 50; } getTextStyle() { const style = super.getTextStyle(); style.color = this.properties.textColor || '#aa7952'; style.fontSize = 15 return style; } } class MainLineNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; // node_md.properties 获取节点属性 let el = document.createElement("div"); el.className = "main-line-node node-div"; el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作 el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const MainLineBox = { type: "main_line_node", view: MainLineNode, model: CommonModel, };
// 定义世界深处节点 class WorldDeepNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "world-deep-node node-div"; el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作 el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const WorldDeepBox = { type: "world_deep_node", view: WorldDeepNode, model: CommonModel, };
// 定义约会剧情节点 class DatePlotNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "date-plot-node node-div"; el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作 el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const DatePlotBox = { type: "date_plot_node", view: DatePlotNode, model: CommonModel, };
// 定义活动剧情节点 class ActivityPlotNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "activity-plot-node node-div"; el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作 el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const ActivityPlotBox = { type: "activity_plot_node", view: ActivityPlotNode, model: CommonModel, };
// 定义手机信息节点 class PhoneInfoNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "phone-info-node node-div"; el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作 el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const PhoneInfoBox = { type: "phone_info_node", view: PhoneInfoNode, model: CommonModel, };
// 定义分组节点 class GroupModel extends dynamicGroup.model { initNodeData(data) { super.initNodeData(data); // 设置默认样式 this.properties = { ...this.properties, collapsible: false, width: 300, height: 450 } this.collapsible = false; } getNodeStyle() { const style = super.getNodeStyle(); style.fill = "transparent"; style.strokeWidth = 0; return style; } // 处理高亮样式 getAddableOutlineStyle() { const style = super.getAddableOutlineStyle(); style.stroke = "#FF9800"; style.strokeDasharray = "5 5"; return style; } // 节点内文本样式 // getTextStyle() { // const style = super.getTextStyle(); // style.fontSize = 20; // style.color = this.properties.textColor || '#aa7952'; // 修改文字颜色 // return style; // } } class GroupView extends dynamicGroup.view { // 由于分组节点没有内置自定义html方法,因此重写getShape方法,加入自定义div元素 getShape() { const { model } = this.props const { width, height, x, y, radius } = model
const rectSvg = super.getShape(); return h('g', {}, [ rectSvg, h('g', {}, [ // 写入自定义内容-用于设置分组节点样式、背景图片 h('foreignObject', { x: x - width / 2, y: y - height / 2, width: width, height: height, radius: radius, }, [ // 注意:foreignObject内的HTML元素需要指定xhtml命名空间 h('div', { xmlns: 'http://www.w3.org/2000/xhtml', class: 'group-node' }) ]) ]) ]); }
// getText() { // const { model, graphModel } = this.props // const { editConfigModel } = graphModel
// // 当 节点文本模式非 TEXT 时,不显示文本 // if (editConfigModel.nodeTextMode !== 'text') return null // // 文本被编辑的时候,显示编辑框,不显示文本。 // if (model.state === 'TEXT_EDIT') return null
// if (model.text) { // let draggable = false // if (editConfigModel.nodeTextDraggable && model.text.draggable) { // draggable = true // } // return h('BaseText', { // editable: editConfigModel.nodeTextEdit && (model.text.editable ?? true), // model: model, // graphModel: graphModel, // draggable: draggable, // }) // // ( // // < // // editable={ // // editConfigModel.nodeTextEdit && (model.text.editable ?? true) // // } // // model={model} // // graphModel={graphModel} // // draggable={draggable} // // /> // // ) // } // return null // } } const GroupBox = { type: "group_node", model: GroupModel, view: GroupView, }
// 定义主标题 class MainTitleModel extends HtmlResize.model { initNodeData(data) { super.initNodeData(data); this.width = 200; this.height = 50; } getTextStyle() { const style = super.getTextStyle(); style.fontSize = 24; style.color = this.properties.textColor || '#aa7952'; return style; } } class MainTitleNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "main-title-node title-node"; el.id = node_md.id; el.setAttribute("data-text", node_md.text.value); el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const MainTitleBox = { type: "main_title", model: MainTitleModel, view: MainTitleNode, }
// 定义副标题 class SubTitleModel extends HtmlResize.model { initNodeData(data) { super.initNodeData(data); this.width = 200; this.height = 50; } getTextStyle() { const style = super.getTextStyle(); style.fontSize = 20; style.color = this.properties.textColor || '#aa7952'; // style.overflowMode = 'autoWrap' return style; } } class SubTitleNode extends HtmlResize.view { setHtml(rootEl) { let node_md = this.props.model; let el = document.createElement("div"); el.className = "sub-title-node title-node"; el.id = node_md.id; el.setAttribute("data-text", node_md.text.value); el.innerHTML = ""; // 需要先把之前渲染的子节点清除掉。 rootEl.innerHTML = ""; rootEl.appendChild(el); } } const SubTitleBox = { type: "sub_title", model: SubTitleModel, view: SubTitleNode, }
// 定义拓展阅读节点 class ExtendedReadModel extends dynamicGroup.model { initNodeData(data) { super.initNodeData(data); // 设置默认样式 this.properties = { ...this.properties, collapsible: false, width: 250, height: 150 } this.collapsible = false; } getNodeStyle() { const style = super.getNodeStyle(); style.fill = "transparent"; style.strokeWidth = 0; return style; } // 处理高亮样式 getAddableOutlineStyle() { const style = super.getAddableOutlineStyle(); style.stroke = "#FF9800"; style.strokeDasharray = "5 5"; return style; } } class ExtendedReadNode extends dynamicGroup.view { // 由于分组节点没有内置自定义html方法,因此重写getShape方法,加入自定义div元素 getShape() { const { model } = this.props const { width, height, x, y, radius } = model
const rectSvg = super.getShape(); return h('g', {}, [ rectSvg, h('g', {}, [ // 写入自定义内容-用于设置分组节点样式、背景图片 h('foreignObject', { x: x - width / 2, y: y - height / 2, width: width, height: height, radius: radius, }, [ // 注意:foreignObject内的HTML元素需要指定xhtml命名空间 h('div', { xmlns: 'http://www.w3.org/2000/xhtml', class: 'extended-read-node' }) ]) ]) ]); } } const ExtendedReadBox = { type: "extended_read_node", view: ExtendedReadNode, model: ExtendedReadModel, };
// 定义hover面板 class HoverNode { static pluginName = 'hoverNode'
constructor({ lf, LogicFlow }) { this.lf = lf; this.allowHover = false; // 默认关闭 }
render(lf, container) { this.container = container; lf.on("node:mouseenter", ({ data }) => { if (!this.allowHover) return; if (data.properties.recommond || data.properties.pageUrl) { // 创建一个div节点,将recommond内容渲染为html let panelDiv = document.createElement("div"); panelDiv.id = "node-page-panel"; // 计算悬浮框位置 panelDiv.style.left = this.lf.getTransform().SCALE_X * (data.x + data.properties.width / 2) + this.lf.getTransform().TRANSLATE_X + "px"; panelDiv.style.top = this.lf.getTransform().SCALE_Y * (data.y - data.properties.height / 2) + this.lf.getTransform().TRANSLATE_Y + "px"; panelDiv.innerHTML = `
<button class="node-button" ${data.properties.pageUrl ? : 'disabled'} style="${data.properties.pageUrl ? 'cursor: pointer; color: #aa7952;' : 'color: #aa79524d;'}" onclick="window.open('${data.properties.pageUrl}')">跳转</button> `; this.container.appendChild(panelDiv); panelDiv.addEventListener("mouseleave", () => { this.container.removeChild(panelDiv); }); let panelButton = $('.node-button')[0]; panelButton.addEventListener("mousedown", () => { panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/b/ba/8wn6pkomoljy6s809xc50g5gkt6aq6l.png)"; }); panelButton.addEventListener("mouseup", () => { panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png)"; }); panelButton.addEventListener("mouseleave", () => { panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png)"; }); }
}); lf.on("node:mouseleave", ({ data, e }) => { if (!this.allowHover) return; if (data.type == 'group_node') return; setTimeout(() => { let mouseX = e.offsetX; let mouseY = e.offsetY; let panelDiv = document.querySelector("#node-page-panel"); if (panelDiv) { // 查看鼠标是否在#node-page-panel节点上 if (mouseX >= panelDiv.offsetLeft && mouseX <= panelDiv.offsetLeft + panelDiv.offsetWidth && mouseY >= panelDiv.offsetTop && mouseY <= panelDiv.offsetTop + panelDiv.offsetHeight) { return; } else { this.container.removeChild(panelDiv); } } }, 100); }); } // 开启hover面板 openHoverPanel() { this.allowHover = true; } // 关闭hover面板 closeHoverPanel() { this.allowHover = false; } }
// TODO 虚线样式 // class SequenceModel extends PolylineEdgeModel { // // 设置边样式 // getEdgeStyle() { // const style = super.getEdgeStyle(); // const { properties } = this; // if (properties.isstrokeDashed) { // style.strokeDasharray = '4, 4'; // } // return style; // } // }
// export default { // type: 'sequence', // view: PolylineEdge, // model: SequenceModel, // };
// TODO 修改节点文本颜色 // graphModel.getNodeModelById(currentNodeId).setProperties({ textColor: '#fff' });
const lf = new LogicFlow({ container: document.querySelector("#container"), // grid: true, // edgeType: 'bezier', allowResize: true, disabledTools: ['multipleSelect'], // multipleSelectKey:"shift", keyboard: { enabled: true, }, background: { backgroundImage: "url(https://patchwiki.biligame.com/images/lysk/4/40/0ibuxl73pgr095hvneel8bf1kwxp6r7.jpg)", backgroundRepeat: "repeat", }, idGenerator: (type) => { //重写全局id let edgeTypes = ['line', 'polyline', 'bezier']; // 重写node节点id if (!edgeTypes.includes(type)) { let id_num = graphModel.nodes.length + 1; for (let i = 0; i < graphModel.nodes.length; i++) { if (id_num == graphModel.nodes[i].id.split('_')[1]) { id_num++; i--; } } return 'node_' + id_num; } }, plugins: [DndPanel, Menu, Control, DynamicGroup, SelectionSelect, HoverNode], //MiniMap pluginsOptions: { miniMap: { isShowCloseIcon: true, }, }, });
//注册自定义节点 lf.batchRegister([MainLineBox, WorldDeepBox, DatePlotBox, ActivityPlotBox, PhoneInfoBox, GroupBox, MainTitleBox, SubTitleBox, ExtendedReadBox]);//
lf.extension.dndPanel.setPatternItems([ { type: 'main_line_node', text: '主线节点', label: '主线节点', properties: {}, icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAP1JREFUaEPtmUEOgjAQRWfibfQS3oMmamLwGOIxMC5ctPfAQ+hxGANBorhg8RPaJp8tzPTPe10xKpk/mnl+4QCxDdJAMgacO27NVjtV28cONXP+RaRtvL813XfjFXKurETknHj4Pp6Z3kOoD9MBbHi5CaF+pThIUZzWqvbssnl/7eF/G+gH+LxIcYAuk3PlT04OsLQpGlia+PQ8GqABkACvEAgQLqcBGCHYgAZAgHA5DcAIwQY0AAKEy2kARgg2oAEQIFxOAzBCsAENgADhchqAEYINaAAECJfPGjDTbH+v577gyGbFVIm0j78VE3w5IzXgmjUS+PFYGqABkMAbG/IqQCYDb4cAAAAASUVORK5CYII=' }, { type: 'world_deep_node', text: '世界深处', label: '世界深处', properties: {}, icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQZJREFUaEPtmU0OgjAQhadlixfRS7jy5w5G5CTiSdDEOyg7PIRcBLZSUxMwYqKLl9A2eS6Rmb75vq4YJYH/VOD5hQO4NkgD3hiIV/lcmSgRMTvXoX6db0x7EC1lc01L+15/heJFniml9z6Hf2dTx7rYph8DTJYnYx8YHc2ay6bycZB4fZ6q9nG32eoiecHvDXQDdH/4OIDNNMzJAcY2RQNjEx+eRwM0ABLgFQIBwuU0ACMEG9AACBAupwEYIdiABkCAcDkNwAjBBjQAAoTLaQBGCDagARAgXE4DMEKwAQ2AAOHyvwaC/bwe/IIjnBWTZKLb29eKCb6cjhpwzeoIfH8sDdAASOAJUZEoQF1l6DYAAAAASUVORK5CYII=' }, { type: 'date_plot_node', text: '倾心之约', label: '倾心之约', properties: {}, icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQhJREFUaEPtmTEKwkAQRWciYqMX0VZiK8Z7qCcx3sSDGIhlxFYvEhsRsrJCIkbQ4kN2F75lzMz+eW+rjErgPw08v3AA1wZpwBsD5XI6V9NbiZi161C/zjdGdhJV+ehwzu17zRUqF3GqKlufw7+z6X6YFZuPAW5JbOyDvuhkkBVXHwe5J7PxQ8zFZhtmpxf8xkA9QP2HjwPYTO2cHKBrUzTQNfH2eTRAAyABXiEQIFxOAzBCsAENgADhchqAEYINaAAECJfTAIwQbEADIEC4nAZghGADGgABwuU0ACMEG9AACBAu/2sg2M/rwS84QlkxqZG0iqrj14oJvpyOGnDN6gh8cywN0ABI4Am8RCZADpxRJQAAAABJRU5ErkJggg==' }, { type: 'activity_plot_node', text: '活动剧情', label: '活动剧情', properties: {}, icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQZJREFUaEPtmUEOgkAMRds5jl5BjEvlEuodYC0uDR7CU6BL0DPocWYUEkjERBc/YQbzWcK0077XFVUZ+aMjr1/YgG+DNBCMgfy2XKiatXOy8V3Ut/utlb0xtkyjS1mf60boUMWZMbILufi2NlU5JbNi+9ZAfotd88K5aTI/P0Js5HhdTZzqva4tjYoGfmegbaD9EGIDdU39OtnA0KZoYGji/ftogAZAAhwhECAcTgMwQjABDYAA4XAagBGCCWgABAiH0wCMEExAAyBAOJwGYIRgAhoAAcLhNAAjBBPQAAgQDv9pYLS/1/9gwTGOFdNrBjMRW32smODh9JSAa1ZP4LtraYAGQAJPbn4mQI7cPW8AAAAASUVORK5CYII=' }, { type: 'phone_info_node', text: '手机信息', label: '手机信息', properties: {}, icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQhJREFUaEPtmdsJwkAQRWdiK6KiTVhCrMAHsQ5jHRGxg6QELUJRsRV3JUIWjKAfF7K7cP2Mmdk75+xXRiXyn0aeXziAb4M0EIyBYlRO1ehcVBa+Q/0631q7lUSO6+vsWL/nrlAxKHNV3YQc3mWzcsju6fJjgN2wsvWD5CmT1SO9hDjIvl+NTU/Odbbslr7hOwPNAM0fIQ5QZ2rn5ABdm6KBrom3z6MBGgAJ8AqBAOFyGoARgg1oAAQIl9MAjBBsQAMgQLicBmCEYAMaAAHC5TQAIwQb0AAIEC6nARgh2IAGQIBw+V8D0X5ej37BEcuKSa3mJjGnrxUTfDk9NeCa1RN4dywN0ABI4AXYnCZABsa7xAAAAABJRU5ErkJggg==' }, { type: 'group_node', label: '分组', text: , icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhRJREFUWEftlj+L1EAYxp8n6yoRPRDxrlG4YnFhk9nGyu4stRLhuFK/gKUifgBBEAU9bb3af5wIIoiCFtrvzBUWguB19wXuCvOYCcmx7q3ZjYSsxQ4M82Z4553fPO87SYgKLY7jKwBeVlhS5vpZ0j1WCdbr9a4HQfCwyhrvKwnkwa0kbVUC6Ha7x9vt9jkAh4suad8m2ZY0MSbJRQA3AByd6Fz1tNP4G2MuSdoAcKoUwBhzEcATSQGAgGRQ2P4ZgF+fzSdJko0jNot5SQ9ardb9wWCwHcfxCoBnEwGiKNogeXWaU436+Jz73I+07865ztQA3lHSNZKHAPjuc5yN+VzpM4cqL5V82cM455gDvABwspEaMMasSnrsJS8ASL6SdCIDMMacT6v5zr9I/bc1JG9ba7/m8T3AelpPiwWApNckF7wcHwFcqHPzoVifnHMruQJ/AAB4A+CYBzhQKXXC+BOPAyD5VlKYAaSF9iMIgpvW2ud1bD6a83EAkt6RPNIYAIB1Sfs1AOC9v1mzBPiQvcSaSkF65x9JWhq6BU9JLs8B5grMFZiJAp1OZyEMwy+SdmYCMPy6nwM0okC/3z+dJMlPANvOuTONp6DsE9+IAv8/AIAd/8daQvoLwK7vkvZIjrN3Se7lPt5eAxD6z2+pAlEUOZJRHb9iY2JY51y/FMAYczklvSvpbJ0QJL+latyy1m6Wxf0N485wXg1uXQsAAAAASUVORK5CYII=', }, { type: 'main_title', label: '主标题', text: '主标题', icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAP5JREFUWEftljFqw0AQRXcO4DIEgQ/jtOnlPpdIZbR/FlZF7hDcJwdIGV/AnXGVLo1BN1iBJggkiIRkFoMkDKNOMMM83kh/l8zCDy083yhAxwCAt3olAF5jVuO9T0IIOxEpnHOI6enXdACstVIXMHPUagBsROTbGHNg5icFUANqYMyA9/6xLMsVgJ/Zf8Msy1Ii+iCiLwDPswK0w9uhQ/kyWRD1h48F3CDADV/zhZmTtg/AWkR+m/dPY0w6K0BznpxE5MzM22sRP9kK/ltUADWgBtTAXRnYN5n9EnMe5Hn+EEJ4r6rq6JzjsR5r7Z6IiqHrftT1Owbm1hoFWNzAHxywbTDOHITTAAAAAElFTkSuQmCC', }, { type: 'sub_title', label: '副标题', text: '副标题', icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACA0lEQVR4AeyUP0vDQBjG026Ck4sg7m4ubg7STgVBcPHPIlqkOpTi5J/BtpcuFYUiKoJd7FoUKrh00g6Oboqji/0C/QC2/i4kJUmvmpZEpbTc0+e9uzf3PHlzd2Htj39DA/+rAtls9loIcex1W/Sar1rXXYGNVqu1q0rsMtZrfscybgMdCUEPDA0MKzC4FSgUCiOZTCbFXVEBB+l0ekZ1ogKpAJfZbKPRqIdCoTNEF0E+HA4/Y+SK2NF8N4D4KJfZDSpjoIaJbbgEZNuiKkkZWFAawGnLC6xF7NxsNlfoTyBc0XU9iqGirutxxgpAY3xTsgWlAWuyH0YgZj53Z7JBjJ8bgaZNmWyQ0gCOQ15grOD6o/xvDF2CB2BvEdnByItkC0oD1mQ/nMvlBOaTlL5ufx5jS7LPJ6pKtuC7AWthO2OmSH8evHMabuF2C9wA4ke8fcJUXKf/asYGBWoAsQji+1IJXuXTPMnYjsAMcN4Foo+m2AV7o2zGDgrEAOJr7PasVMIEL66nZKyC7wYo+zRCJ0BD/JA3FzLuBt8NIDTH248jfg9/Uo0dkOBmXYZjGJwkp918N4BwVK6O+AKch0+BPIZluMr8B+Pt5jZQIskoXzvj+6AjHwF509V4TAnmHZ/EYYDdEhdC7PGwp6bKl9+c8Wg3MK/bF3cYsE/8Vjz4Bn6q5BcAAAD//3mzGmkAAAAGSURBVAMA7QHjQec9SZUAAAAASUVORK5CYII=', }, { type: 'extended_read_node', label: '拓展阅读', text: , icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhRJREFUWEftlj+L1EAYxp8n6yoRPRDxrlG4YnFhk9nGyu4stRLhuFK/gKUifgBBEAU9bb3af5wIIoiCFtrvzBUWguB19wXuCvOYCcmx7q3ZjYSsxQ4M82Z4553fPO87SYgKLY7jKwBeVlhS5vpZ0j1WCdbr9a4HQfCwyhrvKwnkwa0kbVUC6Ha7x9vt9jkAh4suad8m2ZY0MSbJRQA3AByd6Fz1tNP4G2MuSdoAcKoUwBhzEcATSQGAgGRQ2P4ZgF+fzSdJko0jNot5SQ9ardb9wWCwHcfxCoBnEwGiKNogeXWaU436+Jz73I+07865ztQA3lHSNZKHAPjuc5yN+VzpM4cqL5V82cM455gDvABwspEaMMasSnrsJS8ASL6SdCIDMMacT6v5zr9I/bc1JG9ba7/m8T3AelpPiwWApNckF7wcHwFcqHPzoVifnHMruQJ/AAB4A+CYBzhQKXXC+BOPAyD5VlKYAaSF9iMIgpvW2ud1bD6a83EAkt6RPNIYAIB1Sfs1AOC9v1mzBPiQvcSaSkF65x9JWhq6BU9JLs8B5grMFZiJAp1OZyEMwy+SdmYCMPy6nwM0okC/3z+dJMlPANvOuTONp6DsE9+IAv8/AIAd/8daQvoLwK7vkvZIjrN3Se7lPt5eAxD6z2+pAlEUOZJRHb9iY2JY51y/FMAYczklvSvpbJ0QJL+latyy1m6Wxf0N485wXg1uXQsAAAAASUVORK5CYII=', }, ])
lf.extension.menu.setMenuConfig({ nodeMenu: [ { text: "删除", callback(node) { lf.deleteNode(node.id); }, }, ], // 覆盖默认的节点右键菜单 });
lf.extension.control.removeItem('zoom-out'); lf.extension.control.removeItem('zoom-in'); lf.extension.control.removeItem('reset'); lf.extension.control.addItem({ // 小地图导航 key: 'mini-map', iconClass: "custom-minimap", title: "", text: "导航", onClick: (lf, ev) => { lf.extension.miniMap.show(); lf.extension.miniMap.updatePosition('right-top'); }, }); lf.extension.control.addItem({ //导入json iconClass: 'input-json', title: "", text: "导入json", onClick: (lf, ev) => { $('#input-json')[0].style.display = ; $('#model-json-input')[0].value = ; }, }); lf.extension.control.addItem({ //打印json iconClass: 'log-json', title: "", text: "导出json", onClick: (lf, ev) => { const modelData = graphModel.modelToGraphData(); //图形->数据 $('#show-json')[0].style.display = ; $('.json-content')[0].innerText = JSON.stringify(modelData); }, }); lf.extension.control.addItem({ //保存 iconClass: 'save-json', title: "", text: "保存", onClick: (lf, ev) => { const request = indexedDB.open('readDatabase'); request.onerror = function (event) { console.error('Database error: ', event.target.errorCode); }; request.onsuccess = function (event) { const db = event.target.result; const newData = { id: 'readData', data: JSON.stringify(graphModel.modelToGraphData()) }; addData(db, newData); }; }, }); lf.extension.control.addItem({ // 开启框选 iconClass: "selection-open", title: "", text: "开启框选", onClick: (lf, ev) => { // 开启框选 graphModel.editConfigModel.updateEditConfig({ stopMoveGraph: true, }) lf.openSelectionSelect(); showCustomAlert('框选已开启!'); }, }); lf.extension.control.addItem({ // 关闭框选 iconClass: "selection-close", title: "", text: "关闭框选", onClick: (lf, ev) => { // 开启框选 graphModel.editConfigModel.updateEditConfig({ stopMoveGraph: false, }) lf.closeSelectionSelect(); showCustomAlert('框选已关闭!'); }, }); lf.extension.control.addItem({ // 开启悬浮框 iconClass: "hoverpanel-open", title: "", text: "开启悬浮框", onClick: (lf, ev) => { lf.extension.hoverNode.openHoverPanel(); showCustomAlert('节点悬浮框已开启!'); }, }); lf.extension.control.addItem({ // 关闭悬浮框 iconClass: "hoverpanel-close", title: "", text: "关闭悬浮框", onClick: (lf, ev) => { lf.extension.hoverNode.closeHoverPanel(); showCustomAlert('节点悬浮框已关闭!'); }, });
// 消息提示框 function showCustomAlert(message) { $('#buttons-tip')[0].innerText = message; $('#buttons-tip')[0].style.display = ; setTimeout(() => { $('#buttons-tip')[0].style.display = 'none'; }, 1000); }
// 复制json到剪贴板 $('.copy-json')[0].addEventListener('click', (event) => { async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); alert('已复制到剪贴板!'); } catch (err) { console.error('Failed to copy text: ', err); } } copyToClipboard($('.json-content')[0].innerText); }) // 关闭json弹窗 $('.close-json')[0].addEventListener('click', (event) => { $('#show-json')[0].style.display = 'none'; }) // 确认导入 $('.confirm-input')[0].addEventListener('click', (event) => { graphData = JSON.parse($('#model-json-input')[0].value); lf.render(graphData); $('#input-json')[0].style.display = 'none'; console.log('流程数据:', graphData) }) $('.cancle-input')[0].addEventListener('click', (event) => { $('#input-json')[0].style.display = 'none'; $('#model-json-input')[0].value = ; })
// 输入节点属性json $('#node-recommond-input')[0].addEventListener('input', (event) => { graphModel.getNodeModelById(currentNodeId).setProperties({ recommond: event.target.value }); }) $('#node-url-input')[0].addEventListener('input', (event) => { graphModel.getNodeModelById(currentNodeId).setProperties({ pageUrl: event.target.value }); }) // 关闭属性栏 $('.close-panel')[0].addEventListener('click', (event) => { $('.right-panel')[0].style.display = 'none'; })
let graphData, graphModel; const types = ['main_line_node', 'world_deep_node', 'date_plot_node', 'activity_plot_node', 'phone_info_node']
// 从indexedDB中读取数据 const loadDatafromDB = () => { const request = indexedDB.open('readDatabase'); request.onerror = function (event) { console.error('Database error: ', event.target.errorCode); }; request.onsuccess = function (event) { const db = request.result; const transaction = db.transaction(['readDataStore'], 'readonly'); const store = transaction.objectStore('readDataStore'); const result = store.get('readData'); result.onsuccess = function (event) { const data = result.result; if (data) { graphData = JSON.parse(data.data); } else { graphData = {}; } console.log('流程数据:', graphData); lf.render(graphData); //渲染 graphModel = lf.graphModel; }; result.onerror = function (event) { console.error('Error retrieving data.', event.target.errorCode); }; } request.onupgradeneeded = function (event) { let db = event.target.result; if (!db.objectStoreNames.contains("readDataStore")) { db.createObjectStore("readDataStore", { keyPath: "id" }); } }; } loadDatafromDB();
// 向indexedDB中写入数据readData function addData(db, data) { const transaction = db.transaction(['readDataStore'], 'readwrite'); const store = transaction.objectStore('readDataStore'); const request = store.put(data); request.onsuccess = function (event) { alert('数据缓存成功!'); }; request.onerror = function (event) { console.error('Error adding data.', event.target.errorCode); }; }
// 节点点击事件 var currentNodeId = ; lf.on("node:click", (node) => { console.log('节点数据:', node.data); currentNodeId = node.data.id; // 显示属性栏 if (types.includes(node.data.type)) { $('.right-panel')[0].style.display = ; const currentNodeModel = graphModel.getNodeModelById(currentNodeId); $('#current-node')[0].innerText = `${currentNodeModel.text.value}(${currentNodeModel.id})`; $('#node-recommond-input')[0].value = currentNodeModel.properties.recommond || ; $('#node-url-input')[0].value = currentNodeModel.properties.pageUrl || ; } });
// 节点添加事件 lf.on("node:dnd-add", ({ data }) => { // 修改分组节点的文本位置 setTimeout(() => { if (data.type == 'main_title') { let text = graphModel.getNodeModelById(data.id).text; text.x = data.x - data.properties.width / 2 + 20 + text.value.length * 17; text.y = data.y - data.properties.height / 2 + 20; } if (data.type == 'sub_title') { let text = graphModel.getNodeModelById(data.id).text; text.x = data.x - data.properties.width / 2 + 12 + text.value.length * 15; text.y = data.y - data.properties.height / 2 + 17; } changeLetterSpacing(); }, 100) })
// 更改文本 lf.on("text:update", ({ data }) => { if (data.type == 'main_title') { // 修改文本节点位置并更新文本内容 let oldText = $(`#${data.id}`)[0].getAttribute('data-text'); lf.getNodeModelById(data.id).moveText((data.text.length - oldText.length) * 17, 0) $(`#${data.id}`)[0].setAttribute('data-text', data.text); } if (data.type == 'sub_title') { // 修改文本节点位置并更新文本内容 let oldText = $(`#${data.id}`)[0].getAttribute('data-text'); lf.getNodeModelById(data.id).moveText((data.text.length - oldText.length) * 15, 0) $(`#${data.id}`)[0].setAttribute('data-text', data.text); } setTimeout(() => { changeLetterSpacing(); }, 100) }) const changeLetterSpacing = () => { let titleNodes = $('.title-node'); for (let i = 0; i < titleNodes.length; i++) { titleNodes[i].parentElement.parentElement.nextSibling.childNodes[0].setAttribute('letter-spacing', 10) } } // 节点缩放事件 lf.on("node:resize", (e) => { // 重写分组节点的文本位置 if (e.data.type == 'main_title') { let height = e.data.properties.height; let width = e.data.properties.width; let text = e.model.text text.x = e.data.x - width / 2 + 20 + text.value.length * 17; text.y = e.data.y - height / 2 + 20; } if (e.data.type == 'sub_title') { let height = e.data.properties.height; let width = e.data.properties.width; let text = e.model.text text.x = e.data.x - width / 2 + 12 + text.value.length * 15; text.y = e.data.y - height / 2 + 17; } })
lf.on('node:mouseenter', ({ data, e }) => { if (types.includes(data.type)) { $(`#${data.id}`)[0].style.backgroundImage = 'url(https://patchwiki.biligame.com/images/lysk/1/1b/sxyq7pmcdcwe6uazcy7uqk89losay2s.png)'; $(`#${data.id}`)[0].style.backgroundSize = '100% 110%'; } }) lf.on('node:mouseleave', ({ data, e }) => { if (types.includes(data.type)) { $(`#${data.id}`)[0].style.backgroundImage = 'url(https://patchwiki.biligame.com/images/lysk/5/51/241yaplcz3ortw7m1n5o2fn37sotr7o.png)'; $(`#${data.id}`)[0].style.backgroundSize = '100% 90%'; } })
</script>
<style>
/* .main-line-node, .world-deep-node, .date-plot-node, .activity-plot-node, .phone-info-node */ .node-div { width: 100%; height: 100%; background-image: url(https://patchwiki.biligame.com/images/lysk/5/51/241yaplcz3ortw7m1n5o2fn37sotr7o.png); background-size: 100% 90%; background-repeat: no-repeat; background-position: center; cursor: pointer; box-sizing: border-box; }
.main-title-node::after { content: attr(data-text); position: absolute; line-height: 100%; left: 20px; top: 55px; font-size: 24px; letter-spacing: 10px; transform: scaleY(-1); transform-origin: top; background: linear-gradient(to bottom, transparent 30%, #aa79525c); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.sub-title-node { background-image: url(https://patchwiki.biligame.com/images/lysk/c/c1/c2xu5ggcepuj340avoyfrf782dxudm7.png); background-size: contain; background-repeat: no-repeat; width: 100%; height: 100%; }
.sub-title-node::after { content: attr(data-text); position: absolute; line-height: 100%; left: 12px; top: 50px; font-size: 20px; letter-spacing: 10px; transform: scaleY(-1); transform-origin: top; background: linear-gradient(to bottom, transparent 30%, #aa79525c); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.group-node { width: 100%; height: 100%; border-width: 19px 20px 16px 10px; border-style: solid; border-image-source: url(https://patchwiki.biligame.com/images/lysk/0/00/5xlyzapkfe69krklaup1dwxxsod8f67.png); border-image-slice: 4% 6% 2% 3% fill; border-image-repeat: round; cursor: pointer; box-sizing: border-box; }
.extended-read-node { width: 100%; height: 100%; background-image: url(https://patchwiki.biligame.com/images/lysk/5/57/cne0haqqiyjdpkww3qg54mxwfq2d3mo.png); background-size: 100% 100%; }
#node-page-panel { position: absolute; width: 250px; height: 150px; padding: 20px 25px; background-image: url(https://patchwiki.biligame.com/images/lysk/c/cb/cfp9bf0jkzu4ffyxeubrf4csbmbcxq4.png); background-size: 100% 100%; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; }
#node-page-panel>.node-recommond { width: 100%; flex: 1; color: #aa7952; font-weight: bold; letter-spacing: 2px; }
#node-page-panel>.node-button { width: 90px; height: 25px; border: none; font-size: 13px; font-weight: bold; padding: 0; background-image: url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png); background-size: 100% 113%; background-position: -2px 0px; background-repeat: no-repeat; background-color: transparent; letter-spacing: 4px; }
#container { width: 100%; height: calc(100vh - 20px); }
#buttons-tip { position: absolute; width: 500px; background-color: #fdaeae; border-radius: 5px; text-align: center; padding: 10px; top: 0; left: calc((100vw - 500px) / 2); }
.overlay { position: absolute; width: 80%; height: 80%; font-size: 10px; overflow: auto; top: 10%; left: 10%; background: #e1dddd; padding: 20px; }
.right-panel { position: absolute; height: 450px; width: 200px; right: 0; bottom: 0; background-color: #ddd; padding: 10px; }
.lf-dndpanel { top: 60px; }
.lf-control { right: unset !important; }
.custom-minimap { background: url(data:image/png;base64,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); }
.input-json { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAi1JREFUWEftl7+LE0EUx98bUsQfjWAjWJizTCOolQg5rGyuEYP+A1eIlkqKzM6bHSFoICJ4hfFfuOaws/G0EUTt9ErlLCysUkSIxHk6snvEsLOzm+RMk22W3Tfv+z77nbezswhLPnDJ9WEF8NeBVqt1olqt1qy1NSFEDQDWmNmd95n5gxBibzQa7XU6ne9FpoyIGsysfGO11utp7J8p6Ha7x4bD4U0AuMHMVzIE+tbavjHmfR5IAvAyB+CgrrcHlFL3AOCBR6SPiI+J6FNW3AFk3WfmRwBwTmsdBnACUsrzQoh3WWKI+BEAmj6IrByllHOlURjAiRDRSWbOnPuyEDMBJE5cFkK89jlRqVSuttvtr6EGnRkgceIhM9/1FHmitb5z2ABnmPmzrwgi1ojoSx5EKQeklBenxYQQESKe8nT4N2ttPBE7aox5NTm2FAARHWdm17UXQtZOxxHxjet2Ivo5M4BLNMacHY/HuwBwuiiEmyZEdK/a/nROKQfSZCnlJSGEg6gUgPiBiOtE9HZh64ATiqLoGiJuhwD+fDc24jh+7hsXdCCKIkJElTyFe+qDg4huMfOWT5yZN+M4fjbXW5AHkKwFMTPLjKaTRHQ/5NBcDqTiSqmnALCZXiPiFhHdDhV38YUAJEI7ALABANta6+tFii8UoNfrHRkMBi/q9Xqj2Wz++u8ARQuWXgdCTThr4Yn+yd8PrABSB+a1OpTv3ZItHSBEfhjx1a/Z0h34DcmTODBHFVvwAAAAAElFTkSuQmCC); }
.log-json { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAjpJREFUWEftlr9rFEEUx9/b4xAE/wBt1D6Faa3UWhEbvVJQiD8a/4C7me/MsmCnnRgICjYBIRKxNwgBA3baWGgUYhMEKyEc5zxvjl3Z29u9u73duE2mnJ157/O+78csU8OLG/ZPRwC1KwBAAbDzpnYMAMC+iORCOecuhWH4cZZhrbU/884Yc3/WWf89C/BLRI4R0Z/sZefc+RIAS0S0wcw3AAymgYwBaK2FiLaMMRfnoc87A+CriJwdRce8TUQdAHtF9moDAHCZiB4S0fEEIIb4EkN8yIOoDADgHBFZEbkSO9xNA/g9EfkdBIFX4k0WohIAgAtE9FxETieGmXkCIPkmIrettWtpiIUBlFJXiegFM58YMzgFIFaja62N/gGnL89bhFEUnez3+z6npyYknQHAzCsAVisBKKXAzDq3qIoBDuK2fF0pBdOiLypCIvrmnOuEYbhTuQgLot8nonXn3HoQBF5eP4iS9b7dbne63e73WtpQa/1sGNHNVNVvt1qtW71e77Pfi0fxCICZN+MZcFDbINJavx1OS99+fq0aY1YyhezfgiVmfgrgTvItvkfZKVu6DbXWu8PeP0NEL40x17ORxQpsGGPGirSowxYB8O/F3lDeZQA/swBKqbvW2ic5YLnvTGkAAJ+cc2vW2kdFec3br1OBzcFgcC+Koh+NAAB4DOBBGedxd9STAqXUNWvtq8YAyjpOtWE9CvwXgEWdzLjnh9fE715eGx6S/5HZ6QCH6Xmut+AIoAkF/gI/+G0wykYAmgAAAABJRU5ErkJggg==); }
.save-json { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAATtJREFUWEftl8uRgzAMQCO1k2O2gKSAXLcbQDat5JYilgKy15y2FmvXDPYQxiAR2DCZMRcwsqRn+SMZdhs/sLH/3QNAURQHADgvgULECxH9aG1EACI6MvOXVnGqHzN/WmuvGlsRoCxLAoCKmU1Q9G3/3f/Xydi/AYCG8p6OCiIJYK1tDVdV1ToyxiTXSkoeBtKBiRCrA3jHcyD+BWAOhApAWkxjU6SJhATgd8VRAGiMMaexPhLEJIA0cq18CuIlAMM1AQB7Irq3WzmMon8OhG2oHaG2X8pHBsgRyBHIEVgjF0jnUMwVsw+iUHBIHiR5yJZPA4ylW8nxsGLKAO8TAUVZLk1/K0+V9aH0Dyk/bkN/K0LEm8rywk7OuY+6rr8fChLf8Lcj55xUAy5yj4jNn58mRnmRtRWUN78d/wJKgfAwcdVvgAAAAABJRU5ErkJggg==); }
.selection-open { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAnpJREFUWEfNV7+rE0EQngkW+QssRbS0tX/vYW8haCc8bSwiiFgFye7sBn+QwkL0FeJTEGy0U0vBWFjZKogiYmFh9QolNsl9Zs7dcO+Su+RCzryFwN5kd+bb2W9mZ5jWPHjN9mkCwFr7hog2mXlLRPrj3yYAlREA570XnVtrEUD3nXNbQZbu1blzLtVpjBFmtjqfoXOydx8AAG8bjYYaTwFkvaMy/c7KF5WFfanOJEn0kBsR/MG5gnVxYeIBvTMFEe+6LkB5O1Mc+B8AZnJAo0BJuDYAkd2R2XVdQd7OwYmCKh4QkYcA9prN5s12u71XxVuFHqjCAWvtYyLaJqLfAG57728sCiJkyOlEVAWAiGwDUBDpAPCDiG557+/PA1IIoEoeEJFjAL7mjTHzRwDeOfesCEhhHpiHPP+/tfYbER2dtY+Z3yVJ0vHep49Z2Vg6CjI8KNP/gpmvi8iHokVLZ0IRuQhgd94Jw3PcEpGdmd6KwiokDG/GidFoVHiyjLE+M18Skc8rBaDKyngQjO0451q1cECVjqPhyTgazhfeL/MVEblbGwBjTIuZ75UY+MPM50Tk1cpJGDxwEsD7jPLnRHQ2a0xzAxGdqYUDgQffiegIAPHeO2vtTyI6nAPxUkROr5yEqjBkti/e+6f63el0NrSwnWHsjnPuWl6+dCIqI5YxpsPMfsrYv3B8sM87iySSZdaIyGsAp/J7Y98Q5bUVpd1u9/hwOPxERIdCNlQyXs5XXPnOSLsYl+mM0s4mWyuGDio9QGwuijxkjLnAzI+YeXcwGFzt9Xq/CjkQn8lsZ6RdTNwQi9WwTtszXqSA1QqorM6shYRVOLN2AH8BG2CPMA76kPQAAAAASUVORK5CYII=); }
.selection-close { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAUlJREFUWEftlj1Ow0AQhef5DkGipE1NTzgEOQSipPbOrvER4BCROIPhADQU1KkocgQXO2TR2jjrRIpEJm7WraV938y8+QFN/GFifcoAOxkwxjyLyB2AmVJp1kT0aq197N7vAeq6vmzb9ltEbFEUbxoA3vsFAAPgipkDzJ8HmHkhIo21VtUXxhgBcLvV+w2yF8sAOQM5AzkDZ8zACsATM39OMojSEX/2SagOEEpJRBtm/tq30OJ/UtsFxpgmrvNlClGWJcdtqLeMmHlORKsYfQ/RiYsIO+fs6B44ZRekEN77ZYg8FVftggHEhYjM9omrAoTHw4m3NeQ9gA0RhbqPjKnWhl3NiegFwE3qCVUPDAxnnXN8yJg7JfjPUdr1dHgwFe8iPQRxkrPce39dVdVHrHsjIu8h8nQQDSAeRoNI4ww/5k3VEzwDHJOBH1GDWDAXw4uYAAAAAElFTkSuQmCC); }
.hoverpanel-open { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAPFJREFUWEftlzEOgkAQRWcIBYl4E1t7OYKewNpWQkWYmRvYWHsCE28gl7CytvcGo1ssQSTGggWiu9UmS/Y/XrKzswgDDxw4H8YFkGXZNIqirUsrQRDsmPluM14MEJG6DLd7i0iVW02YeaGqZ0RMXELYjGdeaXJaAexi1yD1n/QA3sDvGkjTdBLH8bx+gsIwvOZ5fuvlFDDzRlX3dQBEvDDzrBeAoigYEalZQ0z18wDegDfgDfyHAWZeqeqxUQlLEUl6MfCpjfsKQFWl616wcTGRaXzf+gHzEREdAGDtEgAATiKybH0XOA5u3X5cT7O/NPAAJmqjMJ+vpdYAAAAASUVORK5CYII=); }
.hoverpanel-close { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAYdJREFUWEftV7tOw0AQ3LGUkh6Jno4aIZqkpQfRoIBA4h9QdLfnAv8DAtEDLaIMBeFR09FS05PGAxclkhMO8rIdEcWSC/t8O3O7nr0biIgYYw6jKDoguemfi74AtNI0vYzj+AKqWiXZLBo0FB9AzRN4JLkhIvdDSFRH+GacdVQBPMFaSz+LpIvjWEMRrLU+Qx0CzrnaOCihb40xCsD6sQWBRQb+dwZ8D/m+h8lXVHVVVd96ishFBcaYbQBXInIDYE9VP4dI7sw5d9ztvNPLMENAANy22+16kiQfWRLZlYpIvgQGVyIizUqlUm80Gu+BsWvn3E6uJQgFI/kSRdF+mqa7vU4nIn3guZXgt1QDeCW51h3/AV4IgUDK/asg+HwS6NN12SXIgpf+Ew7ovFwZzrwRzbwVeznNdDOa5lyYy264IDB/GQBwp6pbZRgTVX0mud4xJqr6kDGlf53v8rBmyyRXACx1wIEWvDMGcD5NTSedS/IIvYYiIqddkzppvJHneVMqIif+RP0Fs9tU/h3esY0AAAAASUVORK5CYII=); }
.lf-dnd-item>.lf-dnd-shape { background-size: 100%; }
.lf-node-text--auto-wrap-inner { font-weight: bold; font-size: 18px; }
</style>