vueflow
自定义节点,自定义线,具体细节还未完善,实现效果:
1.安装vueflow
2.目录如下
3.
index.vue
<script setup>
import { ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { ControlButton, Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
import { MarkerType } from '@vue-flow/core'
import useDragAndDrop from './components/useDnD'
import Sidebar from './components/Sidebar.vue'
const { onInit, onNodeDragStop, onConnect, addEdges, setViewport, toObject, addNodes, project } = useVueFlow()
const { onDragStart, onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
import EdgeWithButton from './components/EdgeWithButton.vue'
import { toPng, toJpeg, toBlob } from 'html-to-image'
// const flowContainer = ref(null)
// 导入自定义节点
import DataSetNode from './components/DataSetNode.vue'//数据集
import ConditionNode from './components/ConditionNode.vue'//条件
import AlgorithmsLibraryNode from './components/AlgorithmsLibraryNode.vue'//算法
// 节点
const nodes = ref([])
// 线
const edges = ref([])
// var drawer = ref(false)
// 线的默认颜色
const edgesStyle = {style: {// stroke: '#6366f1',strokeWidth: 1, // 设置线宽 },markerEnd: {type: MarkerType.ArrowClosed,// color: '#6366f1',// width: 6, // 箭头宽度// height: 12, // 箭头高度}}
// 初始化
onInit((vueFlowInstance) => {vueFlowInstance.fitView()
})
// 链接线
onConnect((connection) => {addEdges({...connection, // 保留原始连接属性type: 'button',...edgesStyle})
})
// 双击事件
// const handleNodeDoubleClick = (event, node) => {
// drawer.value = true
// }
// 阻止右键事件
const showContextMenu = (e) => {// e.preventDefault()
}
// 保存按钮
const saveNodes = () => {console.log(nodes.value)console.log(edges.value)edges.value.map(val => {val.type = null})console.log("保存")
}
</script><template><div class="dndflow" @drop="onDrop" @click.right.native="showContextMenu($event)"><!-- 顶部的按钮 --><div class="top-title-button"><div class="top-title">算法流程编辑</div><el-button type="primary" class="ybutton">运行</el-button><el-button type="success" class="ybutton" @click="saveNodes">保存</el-button></div><div ref="flowContainer" class="flow-container"><!-- @node-double-click="handleNodeDoubleClick" --><VueFlow v-model:nodes="nodes" v-model:edges="edges" class="basic-flow" :default-viewport="{ zoom: 1.5 }":min-zoom="0.2" :max-zoom="4" @dragover="onDragOver" @dragleave="onDragLeave"><template #edge-button="buttonEdgeProps"><!-- 删除线的删除按钮 --><EdgeWithButton :id="buttonEdgeProps.id" :source-x="buttonEdgeProps.sourceX":source-y="buttonEdgeProps.sourceY" :target-x="buttonEdgeProps.targetX" :target-y="buttonEdgeProps.targetY":source-position="buttonEdgeProps.sourcePosition" :target-position="buttonEdgeProps.targetPosition":marker-end="buttonEdgeProps.markerEnd" :style="buttonEdgeProps.style" /></template><template #node-data-set="props"><!-- 数据集节点 --><DataSetNode :id="props.id" :data="props.data"></DataSetNode></template><template #node-algorithms-library="props"><!-- 算法库节点 --><AlgorithmsLibraryNode :id="props.id" :data="props.data"></AlgorithmsLibraryNode></template><template #node-condition="props"><!-- 条件节点 --><ConditionNode :id="props.id" :data="props.data"></ConditionNode></template><!-- 背景 --><Background :gap="16" /><!-- 小地图 --><MiniMap /><!-- 小按钮 --><Controls position="bottom-center" /></VueFlow></div><!-- 左侧拖动面板 --><Sidebar /></div>
</template>
<style>
@import './main.css';
</style>
main.css
/* import the necessary styles for Vue Flow to work */
@import "@vue-flow/core/dist/style.css";/* import the default theme, this is optional but generally recommended */
@import "@vue-flow/core/dist/theme-default.css";html,
body,
#app {margin: 0;height: 100%;
}#app {text-transform: uppercase;font-family: 'JetBrains Mono', monospace;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}.clearfix:after {content: "";display: block;clear: both;
}/* 最外层div样式 */
.dndflow {flex-direction: column;display: flex;height: 100%;width: calc(100% - 200px);position: absolute;left: 200px;
}.flow-container {width: 100%;height: calc(100% - 60px);background: white;border: 1px solid #ddd;
}/* 小地图 */
.vue-flow__minimap {transform: scale(75%);transform-origin: bottom right;
}/* 顶部标题及运行和保存按钮 */
.top-title-button {height: 60px;text-align: left;line-height: 60px;
}.top-title {display: inline-block;font-size: 30px;font-weight: 800;padding-left: 20px;font-weight: bold;/* color: #0f6cd6; */text-shadow:-2px -2px 0 #000;/* 1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000; */background-image: -webkit-linear-gradient(bottom, red, #fd8403, yellow);-webkit-background-clip: text;-webkit-text-fill-color: transparent;
}.ybutton {margin: 20px 10px 0;float: right;
}/* 工具行样式 */
.basic-flow .vue-flow__controls .vue-flow__controls-button svg {height: 16px;width: 16px;padding: 2px;
}/* 在 handle 内部添加 + 号 */
.vue-flow__handle {height: 12px;width: 12px;border-radius: 50%;
}.vue-flow__handle::after {content: "+";position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);font-size: 14px;color: #fff;pointer-events: none;/* 避免干扰拖拽事件 */
}/* 左侧面板 */
.left-panal {position: fixed;bottom: 0;left: 0;top: 0;margin: 0;background: linear-gradient(to left, #ba8beb, #c1e9e9);z-index: 5;width: 200px;
}.left-panal>div {margin: 10px auto;cursor: grab;
}/*左侧按钮 */
.vue-flow__node-default {/* border-width: 3px; */padding: 0;border: 1px solid #ca9fed;padding: 5px 10px;font-size: 16px;display: flex;align-items: center;justify-content: center;
}.vue-flow__node-default .el-icon {margin-right: 5px;
}/* 删除按钮 */
.edgebutton {width:15px;height:15px;line-height:15px;font-size: 12px;border: 1px solid #b0dee7;background: #ffffff;border-radius: 50%;cursor: pointer;color: #aaa;
}.edgebutton:hover {transform: scale(1.1);transition: all ease .5s;box-shadow: 0 0 0 1px #a8ddcb80, 0 0 0 2px #c0e4e4
}/* 节点样式 */
.custom-node {width: 180px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);position: relative;text-align: left;border: 1px solid #ddd;background: #fff;border-radius: 5px;padding:10px;
}.node-header {font-weight: bold;/* border-bottom: 1px solid #eee; */padding-bottom: 4px;
}.vue-flow__node.selected .custom-node {box-shadow: 0 1px 3px #6366f1 !important;border: 1px solid #6366f1 !important;
}.deletebtn {position: absolute;right: 5px;top: 0;cursor: pointer;
}.deletebtn .el-icon {margin: 5px 5px;width: 12px;height: 12px;
}.del-icon {color: #f00;
}.copy-icon {color: rgb(13, 67, 227);
}.edit-icon {color: rgb(10, 236, 232);
}.yxjgbtn {float: right;color: #6366f1;font-size: 14px;cursor: pointer;
}/* .btnList{cursor: pointer;}.btnList>p{cursor: pointer;text-align: center;font-size: 16px;border-bottom:1px solid #eee;margin: 0;padding: 5px 0;}.btnList>p:last-child{border: 0;} */
Sidebar.vue
<!-- 左侧拖动节点栏 -->
<script setup>
import useDragAndDrop from './useDnD'
const { onDragStart } = useDragAndDrop()
</script><template><aside class="left-panal"><!-- <div class="vue-flow__node-input" :draggable="true"@dragstart="(event) => onDragStart(event, { type: 'input', label: '开始' })">开始</div><div class="vue-flow__node-output" :draggable="true"@dragstart="(event) => onDragStart(event, { type: 'output', label: '结束' })">结束</div> --><div class="vue-flow__node-default" :draggable="true"@dragstart="(event) => onDragStart(event, 'algorithms-library')"><el-icon style="color: #532ff3;"><Memo /></el-icon>算法</div><div class="vue-flow__node-default" :draggable="true" @dragstart="(event) => onDragStart(event, 'data-set')"><el-icon style="color: #f34033;"><Files /></el-icon>数据集</div><div class="vue-flow__node-default" :draggable="true" @dragstart="(event) => onDragStart(event, 'condition')"><el-icon style="color: #077215;"><Connection /></el-icon>条件</div></aside>
</template>
useDnD.js
import { useVueFlow } from '@vue-flow/core'
import { ref, watch } from 'vue'/*** @returns {string} - A unique id.*/
function getId() {let id = Date.now();return `dndnode_${id}`
}/*** In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.* @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}*/
const state = {/*** The type of the node being dragged.*/draggedType: ref(null),isDragOver: ref(false),isDragging: ref(false),
}export default function useDragAndDrop() {const { draggedType, isDragOver, isDragging } = stateconst { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()watch(isDragging, (dragging) => {document.body.style.userSelect = dragging ? 'none' : ''})function onDragStart(event, type) {if (event.dataTransfer) {event.dataTransfer.setData('application/vueflow', type)event.dataTransfer.effectAllowed = 'move'}draggedType.value = typeisDragging.value = truedocument.addEventListener('drop', onDragEnd)}/*** Handles the drag over event.** @param {DragEvent} event*/function onDragOver(event) {event.preventDefault()if (draggedType.value) {isDragOver.value = trueif (event.dataTransfer) {event.dataTransfer.dropEffect = 'move'}}}function onDragLeave() {isDragOver.value = false}function onDragEnd() {isDragging.value = falseisDragOver.value = falsedraggedType.value = nulldocument.removeEventListener('drop', onDragEnd)}/*** Handles the drop event.** @param {DragEvent} event*/function onDrop(event) {const position = screenToFlowCoordinate({x: event.clientX,y: event.clientY,})const nodeId = getId()const newNode = {id: nodeId,type: draggedType.value,position,data: { label: nodeId },}/*** Align node position after drop, so it's centered to the mouse** We can hook into events even in a callback, and we can remove the event listener after it's been called.*/const { off } = onNodesInitialized(() => {updateNode(nodeId, (node) => ({position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 },}))off()})addNodes(newNode)}return {draggedType,isDragOver,isDragging,onDragStart,onDragLeave,onDragOver,onDrop,}
}
AlgorithmsLibraryNode.vue
<!-- CustomNode.vue -->
<template><div class="custom-node clearfix"><div class="deletebtn"><el-popconfirm class="box-item" title="确定删除该节点吗?" placement="top-start" @confirm="deleteNode(id)"><template #reference><el-icon class="del-icon"><Delete /></el-icon></template></el-popconfirm><el-icon class="copy-icon" @click="duplicateNode(id)"><DocumentCopy /></el-icon><el-icon class="edit-icon" @click="xgjd(id)"><EditPen /></el-icon><!-- <el-popover class="box-item" placement="top-start"><template #reference><el-icon><MoreFilled /></el-icon></template><div class="btnList"><p @click="deleteNode">删除</p><p>复制</p></div></el-popover> --></div><div class="node-header">算法</div><div @click="yxjg()" class="yxjgbtn">运行结果</div><Handle type="source" position="right" /><Handle type="target" position="left" /></div><!-- 运行结果 --><el-drawer v-model="draweryx" :with-header="false" size="20%" append-to-body><span>运行结果</span></el-drawer><!-- 点击节点弹出的弹出框 --><el-drawer v-model="drawerjd" :with-header="false" size="20%" append-to-body><span>修改节点</span></el-drawer></template><script setup>import { Handle } from '@vue-flow/core'import { useVueFlow } from '@vue-flow/core'const { removeNodes, getNodes, addNodes } = useVueFlow()var draweryx = ref(false)var drawerjd = ref(false)const props = defineProps({id: String,data: Object,selected: Boolean})// 运行结果事件const yxjg = (id) => {draweryx.value = true}// 修改节点事件const xgjd = (id) => {drawerjd.value = true}// 删除单个节点const deleteNode = (nodeId) => {removeNodes(nodeId)}// 复制指定节点const duplicateNode = (nodeId) => {const originalNode = getNodes.value.find(n => n.id === nodeId)if (!originalNode) return// 创建新节点(修改ID和位置)const newNode = {...originalNode,id: `${originalNode.id}-copy-${Date.now()}`, // 确保ID唯一position: {x: originalNode.position.x + 50, // 偏移位置y: originalNode.position.y + 50},selected: false // 取消选中状态}addNodes(newNode)}</script>
ConditionNode.vue
<!-- CustomNode.vue -->
<template><div class="custom-node clearfix" ><div class="deletebtn"><el-popconfirm class="box-item" title="确定删除该节点吗?" placement="top-start" @confirm="deleteNode(id)"><template #reference><el-icon class="del-icon"><Delete /></el-icon></template></el-popconfirm><el-icon class="copy-icon" @click="duplicateNode(id)"><DocumentCopy /></el-icon><el-icon class="edit-icon" @click="xgjd(id)"><EditPen /></el-icon></div><div class="node-header">条件</div><div v-for="(item, index) in data.conditions" v-if="data.conditions" class="conditionsNode"><p v-if="index == 0"><span>Case{{ index + 1 }}</span> <span class="caseif">If</span></p><p v-if="index != 0 && index != data.conditions.length - 1"><span>Case{{ index + 1 }}</span><spanclass="caseif">ElseIf</span></p><p v-if="index == data.conditions.length - 1"><span class="caseif">Else</span></p><div class="paramList" v-if="index != data.conditions.length - 1"><div v-for="(d, num) in item.rules"><p class="param"> {{ d.param }}{{ d.operator }}{{ d.value }}</p><p v-if="item.rules.length > 1 && item.rules.length - 1 != num" class="operator">{{ item.operator }}</p></div></div><Handle :position="Position.Right" type="source" :id="item.id + 'right_' + index"class="conditionsHandleNode"></Handle></div><Handle type="target" position="left" /><!-- <Handle v-for="(item, index) in conditions" :position="Position.Right" type="source" :id="'right_' + index":style="getDynamicHandlePos(item, index)"></Handle> --></div><!-- 点击节点弹出的弹出框 --><el-drawer v-model="drawerjd" size="20%" append-to-body :with-header="false"><div class="drawerTitle"><el-icon style="color: blueviolet;margin-right: 5px;"><Edit /></el-icon>条件节点</div><p class="nodedescribe">该组件用于根据前面的组件输出相应的引导执行流程,通过定义各种情况并指定操作,或不满足条件时采取默认操作,实现复杂的分支逻辑</p><div v-for="(item, index) in data.conditions" class="drawerCase" v-show="index!=data.conditions.length-1"><el-select v-model="item.operator" placeholder="选择" size="large"><el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /></el-select><div></div></div><el-button type="success">Add Condition</el-button><!-- <div class="addcondition">Add Condition</div> --><el-button type="primary">Add Case</el-button><!-- <div class="addcase">Add Case</div> --></el-drawer>
</template><script setup>
import { Position, Handle } from '@vue-flow/core'
import { useVueFlow } from '@vue-flow/core'
import { onMounted } from 'vue'
const { removeNodes, getNodes, addNodes, updateNode } = useVueFlow()
var draweryx = ref(false)
var drawerjd = ref(false)
const options = [{value: 'AND',label: '与',},{value: 'OR',label: '或',},
]const props = defineProps({id: String,data: Object,selected: Boolean
})
const initconditions = () => {if (props.data.conditions) returnprops.data.conditions = [{operator: 'AND',rules: [{param: 'ceshi',operator: '>',value: '13',}, {param: 'ceshi',operator: '>',value: '13',}, {param: null,operator: null,value: null,}]}, {operator: null,rules: null}]
}
// 修改节点事件
const xgjd = (id) => {drawerjd.value = true
}
// 删除单个节点
const deleteNode = (nodeId) => {removeNodes(nodeId)
}
// 复制指定节点
const duplicateNode = (nodeId) => {const originalNode = getNodes.value.find(n => n.id === nodeId)if (!originalNode) return// 创建新节点(修改ID和位置)const newNode = {...originalNode,id: `${originalNode.id}-copy-${Date.now()}`, // 确保ID唯一position: {x: originalNode.position.x + 50, // 偏移位置y: originalNode.position.y + 50},selected: false // 取消选中状态}addNodes(newNode)
}
onMounted(() => {initconditions()const originalNode = getNodes.value.find(n => n.id === props.id)console.log(originalNode)
})
</script>
<style scoped>
.conditionsNode {width: 100%;position: relative;/* text-align: right; */
}.conditionsNode p {font-size: 14px;margin: 5px 0;
}.conditionsHandleNode {position: absolute;top: 10px;right: -10px;
}.caseif {float: right;
}.paramList {padding: 5px;background: #f8f6fe;
}.paramList .param {padding: 5px;background: #e2d6ff;
}.paramList .operator {text-align: center;font-size: 12px;font-weight: 800;
}.drawerTitle {font-size: 16px;font-weight: 800;display: flex;align-items: center;
}.nodedescribe {color: #666;font-size: 12px;
}
.drawerCase{background: #f8f6fe;padding: 5px;
}
</style>
DataSetNode.vue
<!-- CustomNode.vue -->
<template><div class="custom-node clearfix" ><div class="deletebtn"><el-popconfirm class="box-item" title="确定删除该节点吗?" placement="top-start" @confirm="deleteNode(id)"><template #reference><el-icon class="del-icon"><Delete /></el-icon></template></el-popconfirm><el-icon class="copy-icon" @click="duplicateNode(id)"><DocumentCopy /></el-icon><el-icon class="edit-icon" @click="xgjd(id)"><EditPen /></el-icon><!-- <el-popover class="box-item" placement="top-start"><template #reference><el-icon><MoreFilled /></el-icon></template>
<div class="btnList"><p @click="deleteNode">删除</p><p>复制</p>
</div>
</el-popover> --></div><div class="node-header">数据集</div><div @click="yxjg()" class="yxjgbtn">运行结果</div><Handle type="source" position="right" /><Handle type="target" position="left" /></div><!-- 运行结果 --><el-drawer v-model="draweryx" :with-header="false" size="20%" append-to-body><span>运行结果</span></el-drawer><!-- 点击节点弹出的弹出框 --><el-drawer v-model="drawerjd" :with-header="false" size="20%" append-to-body><span>修改节点</span></el-drawer>
</template><script setup>
import { Handle } from '@vue-flow/core'
import { useVueFlow } from '@vue-flow/core'
const { removeNodes, getNodes, addNodes } = useVueFlow()
var draweryx = ref(false)
var drawerjd = ref(false)const props = defineProps({id: String,data: Object,selected: Boolean
})// 运行结果事件
const yxjg = (id) => {draweryx.value = true
}
// 修改节点事件
const xgjd = (id) => {drawerjd.value = true
}
// 删除单个节点
const deleteNode = (nodeId) => {removeNodes(nodeId)
}
// 复制指定节点
const duplicateNode = (nodeId) => {const originalNode = getNodes.value.find(n => n.id === nodeId)if (!originalNode) return// 创建新节点(修改ID和位置)const newNode = {...originalNode,id: `${originalNode.id}-copy-${Date.now()}`, // 确保ID唯一position: {x: originalNode.position.x + 50, // 偏移位置y: originalNode.position.y + 50},selected: false // 取消选中状态}addNodes(newNode)
}</script>
EdgeWithButton.vue
<script setup>
import { BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow } from '@vue-flow/core'
import { computed } from 'vue'const props = defineProps({id: {type: String,required: true,},sourceX: {type: Number,required: true,},sourceY: {type: Number,required: true,},targetX: {type: Number,required: true,},targetY: {type: Number,required: true,},sourcePosition: {type: String,required: true,},targetPosition: {type: String,required: true,},markerEnd: {type: String,required: false,},style: {type: Object,required: false,},
})const { removeEdges } = useVueFlow()const path = computed(() => getBezierPath(props))
</script><script>
export default {inheritAttrs: false,
}
</script><template><!-- You can use the `BaseEdge` component to create your own custom edge more easily --><BaseEdge :id="id" :style="style" :path="path[0]" :marker-end="markerEnd" /><!-- Use the `EdgeLabelRenderer` to escape the SVG world of edges and render your own custom label in a `<div>` ctx --><EdgeLabelRenderer><div :style="{pointerEvents: 'all',position: 'absolute',transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,}" class="nodrag nopan"><div class="edgebutton" @click="removeEdges(id)">×</div></div></EdgeLabelRenderer>
</template>
<style scoped></style>
相关文章:

vueflow
自定义节点,自定义线,具体细节还未完善,实现效果: 1.安装vueflow 2.目录如下 3. index.vue <script setup> import { ref } from vue import { VueFlow, useVueFlow } from vue-flow/core import { Background } from vue-…...

LearnOpenGL-笔记-其十一
Normal Mapping 又到了介绍法线贴图的地方,我感觉我已经写了很多遍了... 法线贴图用最简单的话来介绍的话,就是通过修改贴图对应物体表面的法线来修改光照效果,从而在不修改物体实际几何形状的前提下实现不同于物体几何形状的视觉效果。 因…...
@Docker Compose 部署 Prometheus
文章目录 Docker Compose 部署 Prometheus1. 环境准备2. 配置文件准备3. 编写 Docker Compose 文件4. 启动服务5. 验证部署6. 常用操作7. 生产环境增强建议8. 扩展监控对象 Docker Compose 部署 Prometheus 1. 环境准备 安装 Docker(版本 ≥ 20.10)和 …...

openppp2 -- 1.0.0.25225 优化多线接入运营商路由调配
本文涉及到的内容,涉及到上个发行版本相关内容,人们在阅读本文之前,建议应当详细阅读上个版本之中的VBGP技术相关的介绍。 openppp2 -- 1.0.0.25196 版本新增的VBGP技术-CSDN博客 我们知道在现代大型的 Internet 网络服务商,很多…...
二次封装 Vuex for Uniapp 微信小程序开发
作为高级前端开发工程师,我将为你提供一个针对 Uniapp Vue2 Vuex 的 Store 二次封装方案,使团队成员能够更便捷地使用和管理状态。 封装目标 模块化管理状态 简化调用方式 提供类型提示(在 Vue2 中尽可能实现) 便于维护和查…...

详细到用手撕transformer下半部分
之前我们讨论了如何实现 Transformer 的核心多头注意力机制,那么这期我们来完整地实现整个 Transformer 的编码器和解码器。 Transformer 架构最初由 Vaswani 等人在 2017 年的论文《Attention Is All You Need》中提出,专为序列到序列(seq2s…...
Spring Boot 整合 Spring Data JPA、strategy 的策略区别、什么是 Spring Data JPA
DAY29.2 Java核心基础 Spring Boot 整合 Spring Data JPA Spring Data JPA根据具体的数据库分为不同的子模块,无论是关系型数据库和非关系型数据库,Spring Data都提供了支持 Mysql:Spring Data JPA Redis:Spring Data Redis …...
Vue 3.0 中的路由导航守卫详解
1. 路由导航守卫 1.1. 全局前置守卫 Vue-Router 提供的导航守卫主要用来守卫路由的跳转或取消。它们可以植入到全局、单个路由或组件级别。 全局前置守卫可以使用 router.beforeEach 注册: const router createRouter({... });router.beforeEach((to, from) &g…...

【Sqoop基础】Sqoop生态集成:与HDFS、Hive、HBase等组件的协同关系深度解析
目录 1 Sqoop概述与大数据生态定位 2 Sqoop与HDFS的深度集成 2.1 技术实现原理 2.2 详细工作流程 2.3 性能优化实践 3 Sqoop与Hive的高效协同 3.1 集成架构设计 3.2 数据类型映射处理 3.3 案例演示 4 Sqoop与HBase的实时集成 4.1 数据模型转换挑战 4.2 详细集成流程…...

MySQL + CloudCanal + Iceberg + StarRocks 构建全栈数据服务
简述 在业务数据快速膨胀的今天,企业对 低成本存储 与 实时查询分析能力 的需求愈发迫切。 本文将带你实战构建一条 MySQL 到 Iceberg 的数据链路,借助 CloudCanal 快速完成数据迁移与同步,并使用 StarRocks 完成数据查询等操作,…...
MSVC支持但是Clang会报错的C++行为
MSVC的非标 目的友元别名模板类显式特例化的命名空间限制 目的 因为在使用clang进行ast分析msvc项目的时候,出现了爆红现象,了解到msvc会有一些不严格按照c标准但是允许的语法,在这点上clang就很严格,所以本文以clang为基准&…...

截屏精灵:轻松截屏,高效编辑
在移动互联网时代,截图已经成为我们日常使用手机时的一项基本操作。无论是记录重要信息、分享有趣内容,还是进行学习和工作,一款好用的截图工具都能极大地提升我们的效率。截屏精灵就是这样一款功能强大、操作简单的截图工具,它不…...

【JavaWeb】基本概念、web服务器、Tomcat、HTTP协议
目录 1. 基本概念1.1 基本概念1.2 web应用程序1.3 静态web1.4 动态web 2. web服务器3. tomcat详解3.1 安装3.2 启动3.3 配置3.3.1 配置启动的端口号3.3.2 配置主机的名称3.3.3 其他常用配置项日志配置数据源配置安全配置 3.4 发布一个网站 4. Http协议4.1 什么是http4.2 http的…...
黑马程序员C++核心编程笔记--4 类和对象--封装
C面向对象三大特征:封装、继承、多态 C认为万事万物皆对象,对象有其属性和行为,具有相同性质的对象可以抽象称为类 4.1 封装 4.1.1 封装的意义 将属性和行为作为一个整体,表现生活中的事物将属性和行为加以权限控制 在设计类…...
Debian:自由操作系统的精神图腾与技术基石
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 ——解码Linux世界最纯粹的开源哲学 一、Debian的诞生:从个人实验到全球协作 1993年,一位名为Ian Murdock的程序员在开源社区的启…...

云计算Linux Rocky day02(安装Linux系统、设备表示方式、Linux基本操作)
云计算Linux Rocky day02(安装Linux系统、设备表示方式、Linux基本操作) 目录 云计算Linux Rocky day02(安装Linux系统、设备表示方式、Linux基本操作)1、虚拟机VMware安装Rocky2、Linux命令行3、Linux Rocky修改字体大小和背景颜…...

在 ODROID-H3+ 上安装 Win11 系统
在 ODROID-H3 上安装 Windows 11 系统。 以下是完整的步骤,包括 BIOS 设置、U 盘制作、安装和驱动处理,全程不保留之前的系统数据。 ✅ 准备工作 1. 准备一个 ≥8GB 的 USB 启动盘 用另一台电脑制作 Windows 11 安装盘。 👉 推荐工具&…...
Docker常用命令操作指南(一)
Docker常用命令操作指南-1 一、Docker镜像相关命令1.1 搜索镜像(docker search)1.2 拉取镜像(docker pull)1.3 查看本地镜像(docker images)1.4 删除镜像(docker rmi) 二、Docker容器…...
什么是 SQL 注入?如何防范?
什么是 SQL 注入?如何防范? 1. SQL 注入概述 1.1 基本定义 SQL 注入(SQL Injection)是一种通过将恶意SQL 语句插入到应用程序的输入参数中,从而欺骗服务器执行非预期SQL命令的攻击技术。攻击者可以利用此漏洞绕过认证、窃取数据甚至破坏数据库。 关键结论:SQL 注入是O…...

使用el-input数字校验,输入汉字之后校验取消不掉
先说说复现方式 本来input是只能输入数字的,然后你不小心输入了汉字,触发校验了,然后这时候,你发现校验取消不掉了 就这样了 咋办啊,你一看校验没错啊,各种number啥的也写了,发现没问题啊 <el-inputv…...

Docker容器启动失败的常见原因分析
我们在开发部署的时候,用 Docker 打包环境,理论上是“我装好了你就能跑”。但理想很丰满,现实往往一 docker run 下去就翻车了。 今天来盘点一下我实际工作中经常遇到的 Docker 容器启动失败的常见原因,顺便给点 debug 的小技巧&a…...
Java提取markdown中的表格
Java提取markdown中的表格 说明 这篇博文是一个舍近求远的操作,如果只需要要对markdown中的表格数据进行提取,完全可以通过正在表达式或者字符串切分来完成。但是鉴于学习的目的,这次采用了commonmark包中的工具来完成。具体实现过程如下 实…...

立志成为一名优秀测试开发工程师(第七天)——unittest框架的学习
目录 unittest框架的学习 一、测试类的编写 创建相关测试类cal.py、CountTest.py 二、常见断言方法 使用unittest单元测试框架编写测试用例CountTest.py 注意:执行的时候光标一定要放在括号后面,鼠标右键运行 三、对测试环境的初始化和清除模块…...
精益数据分析(85/126):营收阶段的核心指标与盈利模型优化——从数据到商业决策的落地
精益数据分析(85/126):营收阶段的核心指标与盈利模型优化——从数据到商业决策的落地 c。 一、营收健康度的核心指标:投资回报率模型 (一)季度再发性营收增长率(QRR) 该指标衡量…...

论坛系统(4)
用户详情 获取用户信息 实现逻辑 ⽤⼾提交请求,服务器根据是否传⼊Id参数决定返回哪个⽤⼾的详情 1. 不传⽤⼾Id,返回当前登录⽤⼾的详情(从session获取) 2. 传⼊⽤⼾Id,返回指定Id的⽤⼾详情(根据用户id去查) 俩种方式获得用户信息 参…...
本地Markdown开源知识库选型指南
本地Markdown开源知识库选型指南 以下是几款优秀的本地Markdown开源知识库解决方案,适合不同需求场景: 1. Obsidian (非完全开源但免费) 特点:基于Markdown的本地优先知识管理,丰富的插件生态优势:双向链接、图形视…...
【.net core】SkiaSharp 如何在Linux上实现
1. 安装依赖库 首先需要安装 SkiaSharp 运行时依赖: # Ubuntu/Debian sudo apt-get update sudo apt-get install -y libfontconfig1 libfreetype6 libx11-6 libx11-xcb1 libxcb1 \libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 \libnss3 libcups2 lib…...
后端项目中静态文案国际化语言包构建选型
这是一个很关键的问题。在做国际化(i18n)时,不同语言包格式如 .resx、.properties 和 .json 都可用,但各自有适用场景、特性与限制,你在选择时可以根据你的开发语言、生态和维护成本权衡。 ✅ 一张对比表:.…...
前端面经 React常见的生命周期
初始化阶段 constructor state的初始化,防抖节流的绑定getDerivedStateFromProps 静态函数 当作纯函数使用 传入props和state,合并成一个新的statecomponentWillMount 组件如果有getDrivedStatefromprops不会执行 针对一些接口的预请求时使用rendercomp…...

力扣面试150题--二叉树的层平均值
Day 54 题目描述 思路 初次做法(笨):使用两个队列,一个队列存放树的节点,一个队列存放对应节点的高度,使用x存放上一个节点,highb存放上一个节点的高度,sum存放当前层的节点值之和…...