经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
可视化—gojs 超多超实用经验分享(一)
来源:cnblogs  作者:Echoyya、  时间:2023/5/8 10:22:38  对本文有异议

可视化的库非常多,如:echarts、highcharts、antv 系列、d3、gojs.....

按照可自定义绘图的程度排序: gojs、d3js > antv > echarts 、highcharts

如果需求简单,不需要自定义图元素,那么 echarts 、highcharts 看中哪个 demo 效果就选用哪个库。

如果有一定程度需要自定义图元素,那么可以看 antv g2/g6 demo 是否能满足需求,可自定义大部分图元素。

如果上面的都不能解决你的需求,那么就是高可定制的,可以考虑 d3js、gojs,还是先去看 demo,看哪个更接近你的需求就采用哪个。

gojs 是一个用于构建交互式可视化图的 js 库,使用可自定义的模板和布局构建复杂节点、链接和组,从而构建出简单到复杂的各类图,如流程图、脑图、组织图、甘特图等。而且提供了许多用于用户交互的高级功能,例如拖放、复制和粘贴、就地文本编辑......

本文是关于如何使用可视化库 gojs 的介绍及使用时的小技巧。gojs 的高可自定义性,非常适合需求复杂的图交互。

绘制基本流程简单介绍,

  • 引入库,可以下载,也可以引入 cdn 地址
  • html 文件创建画布容器,并设置宽高
  • 创建实例,定义布局,样式,交互,属性,事件等
  • 绑定节点和连线数据,渲染图表

先绘制出基本的实例,让后续的学习,有个大致的轮廓

  1. <template
  2. >>
  3. <div>
  4. <div id="myDiagramDiv" style="height: 1000px;"></div>
  5. </div>
  6. </template>
  7. <script lang="ts" setup>
  8. import go from "@/assets/js/go";
  9. import { onMounted } from "vue";
  10. let diagram: any = null;
  11. const $ = go.GraphObject.make;
  12. onMounted(() => {
  13. init();
  14. });
  15. function init() {
  16. // 创建diagram实例,
  17. diagram = $(go.Diagram, "myDiagramDiv");
  18. // 分组模板
  19. diagram.groupTemplate = $(go.Group, "Auto", {
  20. /* options 后期主要学习部分 */
  21. });
  22. // 连线模板
  23. diagram.linkTemplate = $(go.Link, {
  24. /* options 后期主要学习部分 */
  25. });
  26. // 节点模板
  27. diagram.nodeTemplate = $(go.Node, "Auto", {
  28. /* options 后期主要学习部分 */
  29. });
  30. // 绘制节点模板 追加新的 自定义的模板类型
  31. diagram.nodeTemplateMap.add();
  32. diagram.layout = $(go.LayeredDigraphLayout, {
  33. direction: 0, // 布局方向,0 水平 90 垂直
  34. layerSpacing: 120, // 节点间隔
  35. isOngoing: false,
  36. });
  37. var nodeDataArray = []; // 节点集合
  38. var linkDataArray = []; // 分组集合
  39. diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
  40. }
  41. </script>

1. 设置分组模板,默认样式,统一最小宽度,展开收起状态监听

  1. // 分组模板
  2. diagram.groupTemplate = $(
  3. go.Group,
  4. "Auto",
  5. {
  6. layout: $(go.LayeredDigraphLayout, { direction: 0, columnSpacing: 5 }),
  7. isSubGraphExpanded: false, // 默认展开 true 、折叠false.
  8. subGraphExpandedChanged: function (group) {}, // 功能 小 ## 4
  9. },
  10. $(
  11. go.Shape,
  12. "RoundedRectangle", // 分组形状,圆角矩形
  13. { parameter1: 5, opacity: 0.7, minSize: new go.Size(120, NaN) }, // 圆角 透明度 统一最小宽度
  14. new go.Binding("fill", "color"), // 绑定填充色,如果是固定颜色,可以直接在上述对象中,填写对应的属性,如 fill:"#ccc"
  15. new go.Binding("stroke", "color") // 绑定描边色,同上
  16. ),
  17. $(
  18. go.Panel,
  19. "Vertical",
  20. { defaultAlignment: go.Spot.Left, margin: 4 },
  21. $(
  22. go.Panel,
  23. "Horizontal",
  24. { defaultAlignment: go.Spot.Top, margin: 4, padding: new go.Margin(5, 5, 5, 2) },
  25. // 设置收缩按钮,用于展开折叠子图 +/-
  26. $("SubGraphExpanderButton", { padding: new go.Margin(0, 5, 0, 0) }),
  27. $(
  28. go.TextBlock,
  29. { font: "bold 12px Sans-Serif", stroke: "white" },
  30. new go.Binding("text", "", (node) => node.key + `(${node.children})`) // 分组名称+成员个数: name(children)
  31. )
  32. ),
  33. // 分组展开后的 面板占位
  34. $(
  35. go.Placeholder,
  36. { background: "white" },
  37. new go.Binding("padding", "", function (node) {
  38. // 每组背景色和边距
  39. return node.children ? new go.Margin(10, 10) : new go.Margin(0, 10);
  40. })
  41. )
  42. )
  43. );

2. 分组名称显示成员个数: 分组名称+成员个数: name(children)

  1. $(
  2. go.TextBlock,
  3. { font: "bold 12px Sans-Serif", stroke: "white" },
  4. new go.Binding("text", "", (node) => node.key + `(${node.children})`) // 分组名称+成员个数: name(children)
  5. );

3. 分组成员为空时,不显示 placeholder 占位留白

  1. // 分组展开后的 面板占位
  2. $(
  3. go.Placeholder,
  4. { background: "white" },
  5. new go.Binding("padding", "", function (node) {
  6. // 每组背景色和边距
  7. return node.children ? new go.Margin(10, 10) : new go.Margin(0, 10);
  8. })
  9. );

4. 分组第一次展开请求获取成员接口,监听展开收起状态 subGraphExpandedChanged:fn

  1. subGraphExpandedChanged: function (group) {
  2. // 子图展开或收起的状态 group.isSubGraphExpanded
  3. var groupData = group.part.data; // 获取分组 数据
  4. if (!groupData.isRequested) {
  5. // 设置一个标识位,标明该分组数据是否 有请求过接口,
  6. // 未请求过,可以编写请求接口代码,或者添加已知节点代码,
  7. groupData.isRequested = true;
  8. diagram.model.addNodeData({
  9. key: "任意要显示的node节点名称",
  10. group: groupData.key,
  11. color: groupData.color,
  12. icon: groupData.icon,
  13. });
  14. // diagram.model.addLinkData({ from: "", to: "" }) // 新增节点 有连线关系则添加连线对象
  15. diagram.animationManager.stopAnimation(); // 取消动画
  16. }
  17. // 请求过 就直接 展开或收起分组 isOngoing 属性true会自动布局,但是会影响用户拖拽效果,因此分组自行布局后,需要在改为false
  18. diagram.layout.isOngoing = true;
  19. setTimeout(() => {
  20. diagram.layout.isOngoing = false;
  21. });
  22. }

5. 设置节点模板

  1. // 节点模板
  2. diagram.nodeTemplate = $(
  3. go.Node,
  4. "Auto",
  5. {
  6. mouseEnter: mouseEnter,
  7. mouseLeave: mouseLeave,
  8. click: nodeclick,
  9. },
  10. $(
  11. go.Shape,
  12. "Rectangle",
  13. { strokeWidth: 1, stroke: "white", fill: "white" },
  14. new go.Binding("stroke", "isHighlighted", (sel) => (sel ? "#1E90FF" : "white")).ofObject(), // 鼠标选中高亮样式
  15. new go.Binding("strokeWidth", "isHighlighted", (sel) => (sel ? 3 : 1)).ofObject()
  16. ),
  17. $(
  18. go.Panel,
  19. "Horizontal",
  20. { width: 280, padding: new go.Margin(5, 5, 5, 2) },
  21. $(
  22. go.TextBlock, // 设置 icon
  23. { font: "bold 12px Sans-Serif", stroke: "white", width: 24, textAlign: "center" },
  24. new go.Binding("text", "icon"), // 绑定icon 图表文案
  25. new go.Binding("background", "color") // 绑定 背景色
  26. ),
  27. $(
  28. go.TextBlock,
  29. {
  30. margin: 5,
  31. width: 240,
  32. font: "15px Verdana",
  33. stroke: "#444",
  34. maxSize: new go.Size(260, NaN),
  35. maxLines: 1,
  36. overflow: go.TextBlock.OverflowEllipsis, // maxSize maxLines overflow 属性联合使用,用于文案截断 显示...
  37. name: "TEXT", // 命名随意,用于后期 鼠标状态事件,节点成员的获取
  38. },
  39. new go.Binding("text", "key")
  40. ),
  41. {
  42. toolTip: $(
  43. // 鼠标悬浮显示全部文案 ,默认触发时间比较长,可以通过属性来修改
  44. "ToolTip",
  45. $(go.TextBlock, { margin: 4 }, new go.Binding("text", "key"))
  46. ),
  47. }
  48. )
  49. );

6. 设置节点的 icon,此处以文字为例

  1. $(
  2. go.TextBlock, // 设置 icon
  3. { font: "bold 12px Sans-Serif", stroke: "white", width: 24, textAlign: "center" },
  4. new go.Binding("text", "icon"), // 绑定icon 图表文案
  5. new go.Binding("background", "color") // 绑定 背景色
  6. ),

7. 文案太长截取显示...,鼠标悬浮显示全部

  1. {
  2. toolTip: $(
  3. // 鼠标悬浮显示全部文案 ,默认触发时间比较长,可以通过属性来修改
  4. "ToolTip",
  5. $(go.TextBlock, { margin: 4 }, new go.Binding("text", "key"))
  6. );
  7. }

8. 设置鼠标移入、移出状态,点击节点高亮相关联节点

  1. function mouseEnter(e, obj) {
  2. var text = obj.findObject("TEXT");
  3. text.stroke = "#1E90FF";
  4. }
  5. function mouseLeave(e, obj) {
  6. var text = obj.findObject("TEXT");
  7. text.stroke = "#444";
  8. }
  9. function nodeclick(e, node) {
  10. var diagram = node.diagram;
  11. diagram.clearHighlighteds();
  12. node.findNodesConnected().each(function (l) {
  13. l.isHighlighted = true;
  14. });
  15. node.linksConnected.each(function (n) {
  16. n.isHighlighted = true;
  17. });
  18. }

9. 动态追加节点

  1. diagram.model.addNodeData({
  2. key: "任意要显示的node节点名称",
  3. group: "分组名",
  4. color: "节点背景颜色",
  5. icon: "icon文字",
  6. });

10. 节点众多需要不同表现样式时,可定义不同的节点模板

  • 仅有一种不同形式时,可使用 diagram.nodeTemplateMap.add(node)调用不同的节点模板
  • 多种不同模板时,是封装一个方法,生成模板,在调用diagram.nodeTemplateMap.set(typename, node)

11. 每个节点前后追加 input/output spot 的两种方式

方法一: 该方法需要重点关注的方法是 makePort,函数调用位置及返回值

  1. var nodeDataArray = [{ key: "A", category: "Start", text: "节点设置左右连接点" }];
  2. var linkDataArray = [
  3. { from: "A", to: "B", frompid: "1", topid: "1" }, // createPort方法 portId
  4. { from: "B", to: "C", frompid: "2", topid: "2" },
  5. ];
  6. diagram.model.linkFromPortIdProperty = "frompid"; // 连接点对应名称
  7. diagram.model.linkToPortIdProperty = "topid";
  8. diagram.nodeTemplateMap.add(
  9. "Start", // nodeDataArray 中的 category
  10. $(
  11. go.Node,
  12. "Auto",
  13. { width: 260, height: 80 },
  14. $(go.Shape, "Rectangle", { fill: "white", stroke: "white", strokeWidth: 1 }),
  15. $(
  16. go.Panel,
  17. "Vertical",
  18. { padding: new go.Margin(5, 5, 5, 2) },
  19. $(
  20. go.TextBlock,
  21. "节点设置左右连接点", // 1. 节点文本也可以直接写在这
  22. { font: "18px Sans-Serif", stroke: "#444", textAlign: "center" },
  23. new go.Binding("text") // 2.文本也可以通过绑定 nodeDataArray 中的 text, 或者其他任意字段
  24. )
  25. ),
  26. $(
  27. go.Panel,
  28. "Vertical",
  29. {
  30. alignment: go.Spot.Left,
  31. alignmentFocus: new go.Spot(0, 0.5, 8, 0),
  32. },
  33. makePort(2, 3).inSpotList // 需要返回一个数组,表示 2个 入边连接点
  34. ),
  35. $(
  36. go.Panel,
  37. "Vertical",
  38. {
  39. alignment: go.Spot.Right,
  40. alignmentFocus: new go.Spot(1, 0.5, -8, 0),
  41. },
  42. makePort(2, 3).outSpotList // 需要返回一个数组,表示 3个 出边连接点
  43. )
  44. )
  45. );
  46. function makePort(inCount, outCount) {
  47. let inSpot = inCount;
  48. let outSpot = outCount;
  49. let inSpotList: any = [];
  50. let outSpotList: any = [];
  51. for (let i = 1; i <= inSpot; i++) {
  52. inSpotList.push(createPort(i, "Left"));
  53. }
  54. for (let i = 1; i <= outSpot; i++) {
  55. outSpotList.push(createPort(i, "Right"));
  56. }
  57. function createPort(portId, pos) {
  58. var port = $(go.Shape, "Rectangle", {
  59. fill: "gray",
  60. stroke: null,
  61. desiredSize: new go.Size(8, 8),
  62. portId: String(portId), // 该属性比较重要,用于给每一个连接点 命名,
  63. toMaxLinks: 3,
  64. cursor: "pointer",
  65. });
  66. var panel = $(go.Panel, "Horizontal", { margin: new go.Margin(2, 0) });
  67. port.fromSpot = go.Spot[pos];
  68. port.fromLinkable = true;
  69. panel.alignment = go.Spot["Top" + pos];
  70. panel.add(port);
  71. return panel;
  72. }
  73. return { inSpotList, outSpotList };
  74. }

方法二: 该方法需要重点关注的方法itemArray,在数据中分别定义了 leftArrayrightArray,用于循环显示子元素

  1. diagram.nodeTemplate = $(
  2. go.Node,
  3. "Table",
  4. $(
  5. go.Panel,
  6. "Horizontal",
  7. { row: 1, column: 2 },
  8. $(
  9. go.TextBlock, // 资产名称
  10. {
  11. margin: 5,
  12. width: 240,
  13. font: "15px Verdana",
  14. stroke: "#444",
  15. }
  16. )
  17. ),
  18. $(go.Panel, "Vertical", new go.Binding("itemArray", "leftArray"), {
  19. // 节点 左侧 入边连接点 循环显示
  20. row: 1,
  21. column: 0,
  22. itemTemplate: $(
  23. go.Panel,
  24. {
  25. _side: "left",
  26. fromSpot: go.Spot.Left,
  27. toSpot: go.Spot.Left,
  28. cursor: "pointer",
  29. },
  30. new go.Binding("portId", "portId"),
  31. $(
  32. go.Shape,
  33. "Rectangle",
  34. {
  35. stroke: null,
  36. strokeWidth: 1,
  37. desiredSize: new go.Size(8, 8),
  38. margin: new go.Margin(1, 5, 1, 0),
  39. },
  40. new go.Binding("fill", "portColor")
  41. )
  42. ),
  43. }),
  44. $(go.Panel, "Vertical", new go.Binding("itemArray", "rightArray"), {
  45. // 节点 右侧 出边连接点 循环显示
  46. row: 1,
  47. column: 3,
  48. itemTemplate: $(
  49. go.Panel,
  50. {
  51. _side: "right",
  52. fromSpot: go.Spot.Right,
  53. toSpot: go.Spot.Right,
  54. cursor: "pointer",
  55. },
  56. new go.Binding("portId", "portId"),
  57. $(
  58. go.Shape,
  59. "Rectangle",
  60. {
  61. stroke: null,
  62. strokeWidth: 0,
  63. desiredSize: new go.Size(8, 8),
  64. margin: new go.Margin(1, 0),
  65. },
  66. new go.Binding("fill", "portColor")
  67. )
  68. ),
  69. })
  70. );
  71. var nodeDataArray = [
  72. { key: "A", rightArray: [{ portColor: "#33B12C", portId: "left0" }], rightArray: [{ portColor: "#33B12C", portId: "right0" }] },
  73. { key: "B", rightArray: [{ portColor: "#F29941", portId: "left0" }], rightArray: [{ portColor: "#F29941", portId: "right0" }] },
  74. { key: "C", rightArray: [{ portColor: "#11C67B", portId: "left0" }], rightArray: [{ portColor: "#11C67B", portId: "right0" }] },
  75. ];
  76. var linkDataArray = [
  77. { from: "A", to: "B", frompid: "right0", topid: "left0" },
  78. { from: "B", to: "C", frompid: "right0", topid: "left0" },
  79. ];
  80. diagram.model.linkFromPortIdProperty = "frompid"; // 连接点对应名称
  81. diagram.model.linkToPortIdProperty = "topid";

12. 设置连线模板,箭头样式,连线上添加关系文字

  1. // 连线模板
  2. diagram.linkTemplate = $(
  3. go.Link,
  4. {
  5. routing: go.Link.Orthogonal,
  6. corner: 25,
  7. relinkableFrom: true,
  8. relinkableTo: true,
  9. },
  10. $(go.Shape, { isPanelMain: true, stroke: "transparent" }),
  11. $(go.Shape, { isPanelMain: true, stroke: "#ccc", strokeWidth: 2 }, new go.Binding("stroke", "color"), new go.Binding("strokeWidth", "strokeWidth")),
  12. $(
  13. go.Shape,
  14. { toArrow: "standard", strokeWidth: 1, fill: "#ccc" }, // 箭头
  15. new go.Binding("stroke", "color"),
  16. new go.Binding("fill", "color")
  17. ),
  18. $(
  19. go.Panel,
  20. "Auto", // 连线上的文字
  21. $(go.Shape, { fill: "white", stroke: "white" }),
  22. $(go.TextBlock, { stroke: "#ff6600", visible: false }, new go.Binding("text", "linkText"), new go.Binding("visible", "linkText", (a) => (a ? true : false)), new go.Binding("stroke", "isHighlighted", (sel) => (sel ? "#1E90FF" : "#ff6600")).ofObject())
  23. )
  24. );

13. 动态追加连线 addLinkData()

  1. diagram.model.addLinkData({ from: "节点key", to: "节点key", color: "线的颜色", linkText: "连线上的文字" }); // 不指定连接点,直接连
  2. diagram.model.addLinkData({ from: "节点key", to: "节点key", color: "线的颜色", linkText: "连线上的文字", frompid: "right0", topid: "left0" }); // 设置入边和出边的连接点
  3. var nodeDataArray = [
  4. { key: "A", rightArray: [{ portColor: "#33B12C", portId: "left0" }], rightArray: [{ portColor: "#33B12C", portId: "right0" }] },
  5. { key: "B", rightArray: [{ portColor: "#F29941", portId: "left0" }], rightArray: [{ portColor: "#F29941", portId: "right0" }] },
  6. { key: "C", rightArray: [{ portColor: "#11C67B", portId: "left0" }], rightArray: [{ portColor: "#11C67B", portId: "right0" }] },
  7. ];
  8. var linkDataArray = [
  9. { from: "A", to: "B", frompid: "right0", topid: "left0" },
  10. { from: "B", to: "C", frompid: "right0", topid: "left0" },
  11. ];
  12. diagram.model.linkFromPortIdProperty = "frompid"; // 连接点对应名称
  13. diagram.model.linkToPortIdProperty = "topid";

14. 点击节点使其相关联节点与连线及文字高亮显示

  1. function nodeclick(e, node) {
  2. var diagram = node.diagram;
  3. diagram.clearHighlighteds();
  4. node.findNodesConnected().each(function (l) {
  5. l.isHighlighted = true;
  6. });
  7. node.linksConnected.each(function (n) {
  8. n.isHighlighted = true;
  9. });
  10. }

15. 点击画布清空当前高亮状态元素

  1. diagram.click = function (e) {
  2. e.diagram.commit(function (d) {
  3. d.clearHighlighteds();
  4. }, "no highlighteds");
  5. };

16. 删除连线的方法

删除一条: diagram.model.removeLinkData(linkData); ,这个方法,我试了几个都没有成功,可能是linkData获取的不对,又由于我是要全部删除,因此使用了 diagram.model.removeLinkDataCollection方法,进行批量删除,但是在实际过程中发现,调用这个方法只能删除一半,(也不知道是什么原因,如果有耐心的有缘人,读到此处并解决了问题,欢迎留言帮我解惑),但是呢办法总比困难多,写一个 while 循环就可以搞定了

  1. while (diagram.model.linkDataArray.length) {
  2. diagram.model.removeLinkDataCollection(diagram.model.linkDataArray);
  3. }

17. 动态根据需求 展开和收起某一个或全部的分组

展开或收起某一个分组:

  1. // groupKey 在 nodeDataArray节点列表中的, 分组节点的 Key值
  2. diagram.findNodeForKey(groupKey).isSubGraphExpanded = true; // 展开
  3. diagram.findNodeForKey(groupKey).isSubGraphExpanded = false; // 收起

展开或收起全部分组: 这个功能我用在了,在每次展开某种特定条件的分组时,先关闭之前所有的分组,在进行新一轮的展开操作

  1. function graphExpandCollaps() {
  2. const { nodeDataArray } = diagram.model;
  3. nodeDataArray.forEach((v) => {
  4. if (v.isGroup) {
  5. diagram.findNodeForKey(v.groupName).isSubGraphExpanded = false; // 分组全部收起
  6. }
  7. });
  8. }

18. 关闭画布重新渲染时的动画

  1. diagram.animationManager.stopAnimation(); // 取消动画

19. 画布无限拖拽功能

画布固定宽度和高度之后,对拖拽功能很不友好,当内容比较多时,容易拖拽不动,再则在使用 mac 浏览器时,会触发浏览器的前进后退事件。设置画布可以无限滚动后,解决

  1. diagram.scrollMode = go.Diagram.InfiniteScroll;

20. 图禁止复制,禁止删除,开启鼠标滚轮缩放,toolTip 触发 hoverDelay 初始定位

  1. diagram = $(go.Diagram, "myDiagramDiv", {
  2. "toolManager.hoverDelay": 200, // toolTip触发
  3. "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, // 开启鼠标滚轮缩放
  4. // initialContentAlignment: go.Spot.Center, // 居中:第一次 不好使
  5. // contentAlignment: go.Spot.Center, // 可以居中,但是居中之后不可以拖动
  6. initialPosition: new go.Point(-150, -300), // 初始坐标
  7. allowCopy: false, // 禁止复制
  8. allowDelete: false, // 禁止删除
  9. scale: 1, // 缩放会恢复默认值 diagram.scale = 1
  10. minScale: 0.4, // 缩小 diagram.scale -= 0.1
  11. maxScale: 1.4, // 放大 diagram.scale += 0.1
  12. });

21. 消除水印

在其他文章中看到要删除一个函数的方法,但是由于 gojs 是压缩过的,每个版本的变量都不一样,因此查到另外一个方式,亲测有效,
全文搜索 String.fromCharCode(a.charCodeAt(g)^b[(b[c]+b[d])%256]),大家再搜索时,需要注意一个空格,不然可能会导致搜索不到结果。

在 for 循环的后面 加上一个 判断, 需要跟画布上水印的文字进行比对一下,我觉得其实写一个条件语句,应该就可以命中了。

  1. if (f.indexOf("GoJS 2.2 evaluation") > -1 || f.indexOf("(c) 1998-2022 Northwoods Software") > -1 || f.indexOf("Not for distribution or production use") > -1 || f.indexOf("gojs.net") > -1) {
  2. return "";
  3. } else {
  4. return f;
  5. }

更多分享 详见下一篇博文

gojs 超多超实用经验分享(二)

原文链接:https://www.cnblogs.com/echoyya/p/17238222.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号