WPF实现关系图

embedded/2024/9/23 12:11:57/

该文档用于:WPF内嵌VIS.JS实现关系图,交互通过调用JS实现

1 安装

1.1 WPF 端安装以下包

<package id="CefSharp.Common"/>
<package id="CefSharp.Wpf"/>

1.2 WPF 框架使用Prism

<package id="Prism.Core" />
<package id="Prism.Unity" />
<package id="Prism.Wpf" />

1.3 关系图使用 VIS.JS
VIS.JS官方文档
VIS.JS官方示例

2 使用(部分代码)

2.1 XAML

<cefSharp:ChromiumWebBrowser x:Name="browser"Address="{Binding Address, Mode=TwoWay}"AllowDrop="True" />

2.2 XAML.CS

private ViewModel _vm;
public View()
{InitializeComponent();this.DataContext = _vm = (ViewModel)ServiceLocator.Current.GetInstance<ViewModel>(); ;browser.LoadError += Browser_LoadError;browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
}private void Browser_IsBrowserInitializedChanged(object sender, DependencyPropertyChangedEventArgs args)
{if (args.NewValue is bool isInitialized && isInitialized == true){//browser.ShowDevTools();_vm.OnBrowserInitialized(browser);}
}private void Browser_LoadError(object sender, LoadErrorEventArgs args)
{if (args.ErrorCode == CefErrorCode.Aborted)return;args.Frame.LoadHtml($"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body><h2>无法展示该网页</h2><br><h3>错误代码:{args.ErrorCode}</h3></body></html>");
}
}

2.3 ViewModel
建议ViewModel注册为单例


public class ViewModel: BindableBase{private ChromiumWebBrowser _webBrowser;private string _address;public string Address{get { return _address; }set{if (_address == value)return;SetProperty(ref _address, value, "Address");}}//初始化CEFprivate void InitBrowser(){try{var setting = new CefSettingsBase();setting.RegisterScheme(new CefCustomScheme{SchemeName = CefSharpSchemeHandlerFactory.SchemeName,SchemeHandlerFactory = new CefSharpSchemeHandlerFactory()});setting.WindowlessRenderingEnabled = true;setting.Locale = "zh-CN";setting.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400";if (!Cef.IsInitialized)Cef.Initialize(setting, true);}catch (Exception ex){}}//public void OnBrowserInitialized(ChromiumWebBrowser webBrowser){try{_webBrowser = webBrowser;_webBrowser.Load("index.html");_webBrowser.FrameLoadEnd += (sender, e) =>{if (e.Frame.IsMain){var str = "(function(){CefSharp.BindObjectAsync('boundAsync');})()";_webBrowser.GetFocusedFrame().EvaluateScriptAsync(str);}};_webBrowser.JavascriptObjectRepository.Register("boundAsync", ServiceLocator.Current.GetInstance<ViewModel>(), true, BindingOptions.DefaultBinder);}catch (Exception ex){}}
}

2.4 WPF调用JS

//传参
if (_webBrowser != null && _webBrowser.IsBrowserInitialized)var result = _webBrowser.EvaluateScriptAsync($"addNodes({json});");
//不传参      
if (_webBrowser != null && _webBrowser.IsBrowserInitialized)var result = _webBrowser.EvaluateScriptAsync($"clearNetwork();");

2.5 JS 调用C#方法

//传参
window.boundAsync.downloadCommand(id);
//不传参
window.boundAsync.iterationsDoneCommand("");

2.6 C# 端方法

public void DownloadAppCommand(object obj)
{//
}

2.7 HTML
index.html

 <!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><scripttype="text/javascript"src="standalone/umd/vis-network.min.js"></script><script src="js/jquery-3.7.0.min.js"></script><link rel="stylesheet" href="css/index.css"><script>document.addEventListener("contextmenu", function (e) {e.preventDefault();});</script><title>HomologyView V1.0</title></head><body oncontextmenu="return false;"><div><div id="mynetwork"></div><div id="context-menu"><ul id="context-menu-list"></ul></div></div><script src="js/index.js"></script></body>
</html>

2.8 JS
index.js

var container = document.getElementById("mynetwork");
var network = null;
var options;
var nodes_data = [];
var edges_data = [];options = {nodes: {brokenImage: "images/app.svg",chosen: true,borderWidth: 0, // 默认边框宽度borderWidthSelected: 2, // 选中时的边框宽度opacity: 1,fixed: {x: false,y: false,},font: {size: 14, // pxstrokeWidth: 0, // pxalign: "center",multi: false,},image: "images/app.svg",imagePadding: {left: 1,top: 1,bottom: 1,right: 1,},labelHighlightBold: true,level: undefined,mass: 0.8,physics: true,shape: "image",shapeProperties: {borderDashes: false, // only for bordersborderRadius: 6, // only for box shapeinterpolation: false, // only for image and circularImage shapesuseImageSize: false, // only for image and circularImage shapesuseBorderWithImage: true, // only for image shapecoordinateOrigin: "center", // only for image and circularImage shapes},size: 30,},edges: {arrows: {to: {enabled: true,},},endPointOffset: {from: 0,to: 0,},arrowStrikethrough: true,chosen: true,color: {inherit: "from",},dashes: false,font: {vadjust: 0,size: 14, // pxalign: "middle", //horizontal,top,middle,bottommulti: false,},hidden: false,hoverWidth: 2,labelHighlightBold: true,physics: true,selectionWidth: 2,smooth: {enabled: false,},},physics: {enabled: true,// stabilization: false, //动态加载// timestep: 0.8, // 时间步长,越大越快timestep: 0.5, // 时间步长,越大越快solver: "barnesHut", // 使用 barnesHut 算法提高性能forceAtlas2Based、hierarchicalRepulsionbarnesHut: {// gravitationalConstant: -50000, //引力吸引。所以该值为负。如果你想要更强的排斥力,请减小该值(因此为 -10000、-50000)gravitationalConstant: -30000, //-30000springConstant: 0.002, //这就是弹簧的“坚固程度”。值越高,弹簧越坚固0.001springLength: 80, //弹簧的静止长度。80damping: 0.1, //减小阻尼的值可以增加节点的移动速度avoidOverlap: 0.8, //[0-1]。当大于 0 时,值 1 表示最大重叠避免。//centralGravity: 0.3,},hierarchicalRepulsion: {centralGravity: 0.3,springLength: 100,springConstant: 0.005,nodeDistance: 320,damping: 0.1,avoidOverlap: 0.8},forceAtlas2Based: {gravitationalConstant: -2000,   // 减小节点间引力// centralGravity: 0.08,            // 增强中心引力springConstant: 0.005,           // 增加弹簧常数以平衡吸引力springLength: 40,               // 保持弹簧长度damping: 0.5,                   // 增加阻尼来减速停止振荡avoidOverlap: 0.8               // 避免节点重叠},adaptiveTimestep: true, // 启用自适应时间步长stabilization: {fit: true,enabled: true,iterations: 2000, //updateInterval: 500, // 调整更新间隔,以更快地看到布局变化50onlyDynamicEdges: false,},},interaction: {//相互作用dragNodes: true, //拖拽节点dragView: true, //是否可拖拽tooltipDelay: 200, //title延迟显示时间hideEdgesOnDrag: false, //拖拽隐藏线条hideEdgesOnZoom: false, //缩放隐藏线条hideNodesOnDrag: false, //拖拽隐藏节点hover: true, //悬停时显示颜色hoverConnectedEdges: true, //悬停突出显示边multiselect: false, //多选selectable: true, //可选节点和边selectConnectedEdges: true, //选中节点突出显示边zoomSpeed: 1, //缩放速度有多快/粗略或多慢/精确zoomView: true, //可放大navigationButtons: false, // 如果为真,则在网络画布上绘制导航按钮。这些是HTML按钮,可以使用CSS完全自定义。},layout: {randomSeed: 1, //布局种子improvedLayout: false, //执行聚类以减少节点数量clusterThreshold: 150,hierarchical: false,},
};$(function () {clearNetwork();
});Init();function Init() {var data = {nodes: nodes_data,edges: edges_data,};network = new vis.Network(container, data, options);nodes_data = network.body.data.nodes;edges_data = network.body.data.edges;// 监听数据加载完成事件network.on("afterDrawing", function (e) {// 获取当前缩放比例var scale = network.getScale();// 自己添加的DOM元素跟随缩放和移动var imageElements = document.querySelectorAll('[id^="tag_"]');if (imageElements.length === 0) return;imageElements.forEach(function (imageElement) {var nodeId = imageElement.id.replace("tag_", "");var nodePosition = network.getPositions([nodeId])[nodeId];var convertPoint = network.canvasToDOM(nodePosition);var position = network.getBoundingBox(nodeId);imageElement.style.width = scale * 18 + "px";imageElement.style.height = scale * 18 + "px";imageElement.style.top =convertPoint.y -((position.bottom - position.top) / 3) * scale +"px";imageElement.style.left =convertPoint.x +((position.right - position.left) / 4) * scale +"px";});});//动画稳定后的处理事件var stabilizedTimer;network.on("stabilized", function (params) {// 会调用两次?console.log("动画稳定后的处理事件");window.clearTimeout(stabilizedTimer);stabilizedTimer = setTimeout(function () {exportNetworkPosition(network);options.physics.enabled = false; // 关闭物理系统network.setOptions(options);}, 2000);});network.on("stabilizationIterationsDone", function () {//通知C#端,节点迭代完成window.boundAsync.iterationsDoneCommand("");});//拦截系统右键菜单,显示自定义菜单network.on("oncontext", function (e) {e.event.preventDefault();var nodeId = network.getNodeAt(e.pointer.DOM);if (nodeId !== undefined) {var nodeData = nodes_data.get(nodeId);if (nodeData === undefined) return;showCustomMenu(e.pointer.DOM.x, e.pointer.DOM.y, nodeData);} else {HideCustomMenu();}});//选中节点network.on("selectNode", function (event) {});//双击节点 隐藏或者显示子节点network.on("doubleClick", function (params) {if (params.nodes.length !== 0) {var nodeId = params.nodes[0];var nodeData = nodes_data.get(nodeId);var nodeName = nodeData.title;var allChild = getAllChilds(network, nodeId, []);if (allChild.length > 0) {// 存在子节点if (!nodeData.ishidden) {// 当前节点未隐藏nodes_data.update([{id: nodeId,label: nodeName + " " + allChild.length,ishidden: true,},]);for (var i = 0; i < allChild.length; i++) {nodes_data.update([{ id: allChild[i], hidden: true }]);}} else {// 当前节点已隐藏nodes_data.update([{ id: nodeId, label: nodeName, ishidden: false },]);for (var j = 0; j < allChild.length; j++) {nodes_data.update([{ id: allChild[j], hidden: false }]);}}}}});//单击节点network.on("click", function (params) {HideCustomMenu();});//拖动结束事件network.on("dragEnd", function (params) {HideCustomMenu();if (params.nodes.length != 0) {var arr = nodeMoveFun(params);exportNetworkPosition(network, arr);}});//拖动节点network.on("dragging", function (params) {//拖动进行中事件HideCustomMenu();if (params.nodes.length != 0) {nodeMoveFun(params);}});
}//绘制节点
function drawNodes(jsonData) {options.physics.enabled = true; // 开启物理系统options.physics.stabilization.iterations = calculateIterations(0, jsonData.nodes.length);network.setOptions(options);var newData = {nodes: jsonData.nodes,edges: jsonData.edges,};// // 更新网络实例的数据并重新绘制network.setData(newData);nodes_data = network.body.data.nodes;edges_data = network.body.data.edges;
}// 添加节点
function addNodes(jsonData) {options.physics.enabled = true; // 开启物理系统options.physics.stabilization.iterations = calculateIterations(nodes_data.length, jsonData.nodes.length);network.setOptions(options);// 更新网络实例中的数据nodes_data.add(jsonData.nodes);edges_data.add(jsonData.edges);var newData = {nodes: nodes_data,edges: edges_data,};network.setData(newData);
}//设置节点被选中
function selectedNodeCommand(selectedNodeId) {if (selectedNodeId === undefined) return;network.selectNodes([selectedNodeId]);var selectedNode = nodes_data.get(selectedNodeId);if (selectedNode === undefined || selectedNode === null) return;//移至屏幕中间var options = {// scale: 1.0,animation: {duration: 1000, // 动画持续时间 (毫秒)easingFunction: "easeInOutQuad", // 缓动函数},};network.focus(selectedNode.id, options);
}//获取当前所有节点
function GetAllNode() {var allNodes = network.body.data.nodes.get();var allCurrentNodes = allNodes.filter(function (node) {return !node.ishidden;});return JSON.stringify(allCurrentNodes);
}//清空
function clearNetwork() {//关闭卡片var customMenu = document.querySelector(".card");customMenu.style.display = "none";if (network === null || network === undefined) return;// 更新网络实例中的数据var newData = {nodes: [],edges: [],};// // 更新网络实例的数据并重新绘制network.setData(newData);var allElements = document.querySelectorAll('[id^="tag_"]');// 从 DOM 中删除选定的元素allElements.forEach(function (element) {element.parentNode.removeChild(element);});console.log("初始化完成...");
}//过滤节点
function filterNodes(jsonData) {console.log("过滤节点:");console.log(jsonData);var nodesToUpdate = jsonData.map(node => ({id: node.id,hidden: node.ishidden,ishidden: node.ishidden,}));nodes_data.update(nodesToUpdate);
}//重绘
function redraw() {if (network === null || network === undefined) return;// network.stabilize()console.log("重置网络");options.physics.enabled = true; // 开启物理系统network.setOptions(options);network.redraw();
}//显示自定义菜单
function showCustomMenu(x, y, nodeData) {if (nodeData === undefined) return;var customMenu = document.getElementById("context-menu");var contextMenuList = document.getElementById("context-menu-list");contextMenuList.innerHTML = "";var menuOptions = [{ id: "addNodeTag", text: "添加标记" },{ id: "deleteNodeTag", text: "删除标记" },{ id: "deleteNode", text: "删除节点" },];menuOptions.forEach(function (item) {var li = document.createElement("li"); // 创建 <li> 元素li.id = item.id; // 设置 <li> 的 idli.textContent = item.text; // 设置 <li> 的文本内容contextMenuList.appendChild(li); // 将 <li> 插入到 <ul> 中});customMenu.style.left = x + "px";customMenu.style.top = y + "px";customMenu.style.display = "block";customMenu.setAttribute("data-selectednodes", JSON.stringify(nodeData));
}//隐藏菜单
function HideCustomMenu() {var customMenu = document.getElementById("context-menu");customMenu.style.display = "none";
}document.getElementById("context-menu-list").addEventListener("click", function (e) {if (e.target && e.target.id) {handleMenuClick(e.target.id);}});function handleMenuClick(action) {switch (action) {case "addNodeTag":addNodeTag();break;case "deleteNodeTag":deleteNodeTag();break;case "deleteNode":deleteNode();break;}
}// //添加标签
function addNodeTag() {HideCustomMenu();var nodeInfo = document.getElementById("context-menu").getAttribute("data-selectednodes");var selectedNodes = JSON.parse(nodeInfo);if (selectedNodes.isMarked) return;addMarkedNode(selectedNodes);
}// //删除标签
function deleteNodeTag() {HideCustomMenu();var nodeInfo = document.getElementById("context-menu").getAttribute("data-selectednodes");var selectedNodes = JSON.parse(nodeInfo);if (!selectedNodes.isMarked) return;console.log("删除标记:" + selectedNodes.id);var imageElement = document.getElementById("tag_" + selectedNodes.id);if (imageElement) {var parentNode = imageElement.parentNode;parentNode.removeChild(imageElement);nodes_data.update([{id: selectedNodes.id,isMarked: false,},]);if (currentCardNode != null &&selectedNodes.id === currentCardNode.id) {changeCardMarked(false);network.emit("selectNode", {nodes: [currentCardNode.id],});}} else {console.log("Image not found");}
}// //添加标记
function addMarkedNode(nodeInfo) {//添加标记前先校验是否已经标记var imageId = "tag_" + nodeInfo.id;var imageElements = document.querySelector('[id="' + imageId + '"]');if (imageElements != null) {console.log("存在标记:" + nodeInfo.id);return;}var nodePosition = network.getPositions([nodeInfo.id])[nodeInfo.id];var convertPoint = network.canvasToDOM(nodePosition);var scale = network.getScale();var position = network.getBoundingBox(nodeInfo.id);var newImage = document.createElement("img");newImage.id = "tag_" + nodeInfo.id;newImage.src = "images/star.svg";newImage.style.width = scale * 18 + "px";newImage.style.height = scale * 18 + "px";newImage.style.position = "absolute";newImage.style.top =convertPoint.y - ((position.bottom - position.top) / 3) * scale + "px";newImage.style.left =convertPoint.x + ((position.right - position.left) / 4) * scale + "px";container.appendChild(newImage);nodeInfo.isMarked = true;nodes_data.update([{id: nodeInfo.id,isMarked: true,},]);if (currentCardNode != null && nodeInfo.id === currentCardNode.id) {changeCardMarked(true);network.emit("selectNode", {nodes: [currentCardNode.id],});}console.log("添加标记成功:" + nodeInfo.id + " ==" + nodeInfo.isMarked);
}//删除节点及其子节点
function deleteNode() {HideCustomMenu();var customMenu = document.querySelector(".card");customMenu.style.display = "none";var nodeInfo = document.getElementById("context-menu").getAttribute("data-selectednodes");console.log(nodeInfo);var selectedNodes = JSON.parse(nodeInfo);if (selectedNodes.isMarked) {var imageElement = document.getElementById("tag_" + selectedNodes.id);if (imageElement) {var parentNode = imageElement.parentNode;parentNode.removeChild(imageElement);}}var edgesToRemove = [];var childNodess = [];var childNodes = removeNodeAndChildren(selectedNodes.id, childNodess);childNodes.forEach((element) => {var deleteNode = nodes_data.get(element);if (deleteNode.isMarked) {var imageElement = document.getElementById("tag_" + deleteNode.id);if (imageElement) {var parentNode = imageElement.parentNode;parentNode.removeChild(imageElement);}}var rootElement = document.getElementById("root_" + deleteNode.id);if (rootElement) {var parentNode = rootElement.parentNode;parentNode.removeChild(rootElement);}network.body.data.edges.forEach(function (edge) {if (edge.from === selectedNodes.id || edge.to === element) {edgesToRemove.push(edge.id);}});});network.body.data.nodes.remove(childNodes); network.body.data.edges.remove(edgesToRemove); console.log(JSON.stringify(childNodes));nodes_data = network.body.data.nodes;edges_data = network.body.data.edges;window.boundAsync.deleteNodesCommand(JSON.stringify(childNodes));
}//大小改变事件
window.addEventListener("resize", function () {var customMenu = document.getElementById("context-menu");if (customMenu.style.display === "block") {customMenu.style.display = "none";}
});function removeNodeAndChildren(nodeId, childNodes = []) {childNodes.push(nodeId);var connectedNodes = network.getConnectedNodes(nodeId, "to");connectedNodes.forEach(function (childNodeId) {removeNodeAndChildren(childNodeId, childNodes); // 递归调用自身来删除子节点及其子节点});return childNodes;
}//计算迭代次数
function calculateIterations(initialNodeCount, additionalNodes, iterationFactor = 10, incrementFactor = 5, minIterations = 1000, maxIterations = 3000) {// 计算初始迭代次数let initialIterations = initialNodeCount * iterationFactor;// 计算新增节点的额外迭代次数let additionalIterations = additionalNodes * incrementFactor;// 总迭代次数let totalIterations = initialIterations + additionalIterations;// 限制最大迭代次数console.log("节点数量:" + (initialNodeCount + additionalNodes));if(totalIterations < minIterations){console.log("迭代次数:"+ minIterations);return minIterations;}console.log("迭代次数:"+ Math.min(totalIterations, maxIterations))return Math.min(totalIterations, maxIterations);
}/**获取所有子节点* network :图形对象* _thisNode :单击的节点(父节点)* _Allnodes :用来装子节点ID的数组* */
function getAllChilds(network, _thisNode, _Allnodes) {var _nodes = network.getConnectedNodes(_thisNode, "to");if (_nodes.length > 0) {for (var i = 0; i < _nodes.length; i++) {getAllChilds(network, _nodes[i], _Allnodes);_Allnodes.push(_nodes[i]);}}return _Allnodes;
}/**节点位置设置* network :图形对象* arr :本次移动的节点位置信息* */
function exportNetworkPosition(network, arr) {if (arr) {// 折叠过后  getPositions() 获取的位置信息里不包含隐藏的节点位置信息,这时候调用上次存储的全部节点位置,并修改这次移动的节点位置,最后保存var localtionPosition = JSON.parse(localStorage.getItem("position"));for (let index in arr) {localtionPosition[index] = {x: arr[index].x,y: arr[index].y,};}setLocal(localtionPosition);} else {var position = network.getPositions();setLocal(position);}
}//处理本地存储,这里仅仅只能作为高级浏览器使用,ie9以下不能处理
function setLocal(position) {localStorage.removeItem("position");localStorage.setItem("position", JSON.stringify(position));
}// 节点移动
function nodeMoveFun(params) {var click_node_id = params.nodes[0];var allsubidsarr = getAllChilds(network, click_node_id, []); // 获取所有的子节点if (allsubidsarr != null && allsubidsarr.length > 0) {// 如果存在子节点var positionThis = network.getPositions(click_node_id);var clickNodePosition = positionThis[click_node_id]; // 记录拖动后,被拖动节点的位置var position = JSON.parse(localStorage.getItem("position"));var startNodeX, startNodeY; // 记录被拖动节点的子节点,拖动前的位置var numNetx, numNety; // 记录被拖动节点移动的相对距离var positionObj = {}; // 记录移动的节点位置信息, 用于返回positionObj[click_node_id] = {x: clickNodePosition.x,y: clickNodePosition.y,}; // 记录被拖动节点位置信息numNetx = clickNodePosition.x - position[click_node_id].x; // 拖动的距离numNety = clickNodePosition.y - position[click_node_id].y;for (var j = 0; j < allsubidsarr.length; j++) {if (position[allsubidsarr[j]]) {startNodeX = position[allsubidsarr[j]].x; // 子节点开始的位置startNodeY = position[allsubidsarr[j]].y;network.moveNode(allsubidsarr[j],startNodeX + numNetx,startNodeY + numNety); // 在视图上移动子节点positionObj[allsubidsarr[j]] = {x: startNodeX + numNetx,y: startNodeY + numNety,}; // 记录子节点位置信息}}}return positionObj;
}

2.9 CSS
index.css

html,
body {margin: 0;padding: 0;width: 100%;height: 100%;background-color: #f4f8fa;
}#mynetwork {position: fixed;width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;border: 0px solid lightgray;
}
#context-menu {display: none;position: absolute;z-index: 999;background: white;box-shadow: 0px 4px 16px 0px rgba(16, 47, 94, 0.16);
}#context-menu-list {list-style: none;width: 120px;padding: 0;margin: 0;
}
#context-menu-list li {padding: 10px;cursor: pointer;
}#context-menu-list li:hover {background: #f0f0f0;
}

2.10 JSON格式

{"nodes": [{"id": "04c53dc6-297e-406c-bee2-022811f7a9b0","label": "04c53dc6-297e-406c-bee2-022811f7a9b0","image": "app.png","title": "04c53dc6-297e-406c-bee2-022811f7a9b0","hidden": false,"ishidden": false,"isMarked": false,"group": "0","parents": []},{"id": "803b96e0-2033-4fc2-95f8-699fa1daae3f","label": "803b96e0-2033-4fc2-95f8-699fa1daae3f","image": "app.svg","title": "803b96e0-2033-4fc2-95f8-699fa1daae3f","hidden": false,"ishidden": false,"isMarked": false,"group": "5","parents": ["04c53dc6-297e-406c-bee2-022811f7a9b0"]},{"id": "e134e243-6d02-4c88-beba-899d89e97eb4","label": "e134e243-6d02-4c88-beba-899d89e97eb4","image": "app.svg","title": "e134e243-6d02-4c88-beba-899d89e97eb4","hidden": false,"ishidden": false,"isMarked": false,"group": "5","parents": ["803b96e0-2033-4fc2-95f8-699fa1daae3f"]}],"edges": [{"title": "title","label": "label","from": "803b96e0-2033-4fc2-95f8-699fa1daae3f","to": "e134e243-6d02-4c88-beba-899d89e97eb4","ishidden": false},{"title": "title","label": "label","from": "04c53dc6-297e-406c-bee2-022811f7a9b0","to": "803b96e0-2033-4fc2-95f8-699fa1daae3f","ishidden": false}]
}

http://www.ppmy.cn/embedded/115580.html

相关文章

php调用Gpt 执行 shell curl输出聊天结果实现简单聊天机器人

<?php header("Access-Control-Allow-Origin: *");// return 1; //$command ls -l; // 要执行的shell命令$command "curl http://192.168.124.27:11434/api/chat -d {\"model\": \"openchat:latest\",\"messages\": [{\&qu…

Linux中环境变量设置及查看方法(临时环境变量和用户级别长期环境变量)

设置环境变量的方式&#xff1a;&#xff08;三种&#xff09; Linux中通常来说设置环境变量分为三种&#xff1a;临时设置环境变量&#xff08;只在当前用户的当前终端会话中有效&#xff09;&#xff0c;将环境变量添加到 Shell 启动文件&#xff08;对当前用户有效&#xf…

反序列化- Jackson...

Jackson库 Jackson库的核心功能是将Java对象转换为JSON字符串&#xff08;序列化&#xff09;以及将JSON字符串转换为Java对象&#xff08;反序列化&#xff09; 反序列化器及序列化器 JSR310DateTimeDeserializerBase和JSR310FormattedSerializerBase抽象类 当你创建这些子…

前端工程化4:从0到1构建完整的前端监控平台

前言 一套完整的前端监控系统的主要部分&#xff1a; 数据上报方式数据上送时机性能数据采集错误数据采集用户行为采集定制化指标监控sdk 监控的目的&#xff1a; 一、数据上报方式 本文的方案是&#xff0c;优先navigator.sendBeacon&#xff0c;降级使用1x1像素gif图片…

开源模型应用落地-qwen模型小试-调用Qwen2-VL-7B-Instruct-更清晰地看世界-集成vLLM(二)

一、前言 学习Qwen2-VL ,为我们打开了一扇通往先进人工智能技术的大门。让我们能够深入了解当今最前沿的视觉语言模型的工作原理和强大能力。这不仅拓宽了我们的知识视野,更让我们站在科技发展的潮头,紧跟时代的步伐。 Qwen2-VL 具有卓越的图像和视频理解能力,以及多语言支…

面试爱考 | 设计模式

一、概述二、创建型 1. 单例&#xff08;Singleton&#xff09; IntentClass DiagramImplementationExamplesJDK 2. 简单工厂&#xff08;Simple Factory&#xff09; IntentClass DiagramImplementation 3. 工厂方法&#xff08;Factory Method&#xff09; IntentClass Diagr…

WPF DataGrid 单元格居中,头部居中,点击行改变背景色。

我得全局样式都写在了App.XAML文件下的ResourceDictionary里&#xff0c;方便全局引用 DataGrid样式和点击改变行背景色的触发器(BasedOn继承的是UI框架的样式&#xff0c;若无则删除&#xff0c;触发器还有鼠标移动事件等&#xff0c;按需自行修改添加) <Style x:Key&quo…

word-break和word-wrap

1&#xff0c;word-break:break-all 例如div宽200px&#xff0c;它的内容就会到200px自动换行&#xff0c;如果该行末端有个英文单词很长&#xff08;congratulation等&#xff09;&#xff0c;它会把单词截断&#xff0c;变成该行末端为conra(congratulation的前端部分)&#…