文章前,先聊点啥吧。
最近元宇宙炒的挺火热,在所有人都争相定义元宇宙的时候,资本就开始着手入场了。当定义明确,全民皆懂之后,风口也就过去了。
前两天看到新闻,新世界CEO宣布购入最大的数字地块,这块虚拟土地的投资金额约为 3200 万元人民币。在这之前新世界以及他购买的虚拟土地持有公司名称也都是鲜为人知的。这就是资本的力量,即打了广告,又占了风口坑位,一举多得,好像现在没个几千万上亿的动态,都不足以博得大众眼球。
那么何为元宇宙,为何不是元世界、元地球,而是疯狂到了宇宙这个范围。如果真的虚拟到了宇宙的无穷无尽,试想一下,一块虚拟土地能卖到几千万?所以重点不在虚拟土地的价格。重点是资本想让你看到什么。正所谓,见山不是山,见水不是水......
元宇宙,无论是使用还是创造,只有落实到具体应用,普及到全民参与,才能真正体现它的价值。试想一下,全球大部分公司都参与到元宇宙的研发,全球很多人都为元宇宙添砖加瓦,真正做到全民参与。那么元宇宙才真正的发展起来。
所以解决问题的痛点在工具,为全民提供创造元宇宙的工具,才是方向....
序:
前段时间,做了一个数字孪生的项目,所谓数字孪生,百度百科解释说,就是充分利用物理模型、传感器更新、运行历史等数据,集成多学科、多物理量、多尺度、多概率的仿真过程,在虚拟空间中完成映射,从而反映相对应的实体装备的全生命周期过程。数字孪生是一种超越现实的概念,可以被视为一个或多个重要的、彼此依赖的装备系统的数字映射系统。是个普遍适应的理论技术体系,可以在众多领域应用,在产品设计、产品制造、医学分析、工程建设等领域应用较多。在国内应用最深入的是工程建设领域,关注度最高、研究最热的是智能制造领域。
通俗的讲,就是利用三维技术,将生产过程、数据监测监控展示出来。
随着物联网技术发展,数据展示、大屏展示在IT界也是越发的流行与时尚,而这其中所应用到了关键的技术WebGL(three.js),也就理所当然的成为了集万千宠爱于一生的前端焦点。
技术交流 1203193731@qq.com
交流微信:
如果你有什么要交流的心得 可邮件我
一、解决方案
数字孪生,解决主要在三大步骤:
1、数据采集:主要在传感器方面,如何选用传感器,适用不同场景,比如炼钢厂,需要采用耐高温传感器,煤矿采用防静电传感器等
2、数据传输与存储:跟传感器配套方案,是否采用无线方案,或者是有线传输,都需要根据实际场景来判断,还有这中间是否采用中间网关,做数据转发等。然后就是数据如何存储与处理。这涉及到平台侧的具体框架方案。
3、数据展示:展示方面,为了配合数字孪生这个名词,只能采用3D显示方案。对于3D的方案,光是展时端,我们就可以选择CS、BS两种方式的一种。当然,本篇我们选择BS(webgl)方案。
前两个部分,后面考虑用较详细的文章篇幅详细介绍。本文主要讲解数据展示这一层的方案。
二、展示效果与代码实现
2.1、工业园区整体效果与实现
这里主要是建模,与流光效果的实现。对于非重点监控车间,我们可以用虚拟建筑模型方块代替。
代码实现:
var initOption = { far:100000000, antialias: true, // 启用平滑、抗锯齿效果 loadSyn: false, // 是否启用异步加载 showHelpGrid: false, // 是否显示网格线 clearCoolr: 0x4068b0, // 背景色 clearColorOp: 0, // 透明度 }; var AllModelJsons = []; //获取园区模型; httpGetSyn("../js/models/build.json", function (rs) { AllModelJsons = rs; });
WT3DModel.initWT3D('canvas-frame', initOption, Aobjects);
2.2、双击重点展示工作车间,进入车间
这里采用最简单的页面跳转方式,显示摄像头推进,然后直接跳转到另一个页面,简单而不失流程。还能充分回收资源。
代码实现:
if (_obj.name.indexOf("b1_112_out") >= 0) {//通过模型名称判断 setTimeout(function () { window.location = ("room.html"); }, 2000); modelBussiness.nearCameraPostion(_obj, _face); }
2.3、双击加热炉,展示加热炉内部锻件的详细信息,与位置
这里采用虚化周边的方案。
modelBussiness.currentState = 10; modelBussiness.removeAllMsgs(); modelBussiness.commonFunc.hideAllModels(function () { if (_obj.name.indexOf("OBJCREN") > 0) { _obj = WT3DObj.commonFunc.findObject(_obj.name.split("OBJCREN")[0]); } if (_obj.oldPositionY) { _obj.position.y = _obj.oldPositionY; } _obj.visible = true; // modelBussiness.commonFunc.showJRLDetailMsg(_obj, function () { }); var tkname = "jlr_tiekuai_1"; if (_obj.name.indexOf("xsjrl_1135") >= 0) { tkname = "jlr_tiekuai_1"; } else if (_obj.name.indexOf("xsjrl_1136") >= 0) { tkname = "jlr_tiekuai_2"; } else if (_obj.name.indexOf("xsjrl_1137") >= 0) { tkname = "jlr_tiekuai_3"; } tk = WT3DObj.commonFunc.findObject(tkname); tk.visible = true; if (tk.oldPositionY) { tk.position.y = tk.oldPositionY; } WT3DObj.commonFunc.changeObjsOpacity([tk], 0, 1, 100, function () { }); WT3DObj.commonFunc.changeObjsOpacity([_obj], 0, 0.2, 1000, function () { modelBussiness.commonFunc.showNetDetail(WT3DObj.commonFunc.findObject(config.Mrelation[_obj.name + "_tiekuai"])); }); });
2.4、明显标注传感器,并且查看各部位传感器信息。
这里注意绑定传感器数据,并且使用突显方案,显示传感器标签,在任何角度,都能看到传感器位置。
代码实现:
if (_obj.name.indexOf("wycgq_") >= 0) { $(".jrlDetail").hide(); // name = "位移传感器"; var screenPostion = WT3DObj.commonFunc.transToScreenCoord(_obj.position); $("#MarkMessageHelper").remove(); $("body").append("<div id='MarkMessageHelper' style='position:absolute;left:" + (screenPostion.x) + "px;top:" + screenPostion.y + "px;height:2px;width:2px;z-index:1000;'></div>"); $("#airConfig2Detail").show(); $("#airConfig2Detail").css("left", $("#MarkMessageHelper").css("left")); $("#airConfig2Detail").css("top", (parseFloat($("#MarkMessageHelper").css("top")) - 170) + "px"); } else if (_obj.name.indexOf("dajiliybp") >= 0) { $(".jrlDetail").hide(); // name = "打击力应变片"; var screenPostion = WT3DObj.commonFunc.transToScreenCoord(_obj.position); $("#MarkMessageHelper").remove(); $("body").append("<div id='MarkMessageHelper' style='position:absolute;left:" + (screenPostion.x) + "px;top:" + screenPostion.y + "px;height:2px;width:2px;z-index:1000;'></div>"); $("#rackDetail").show(); $("#rackDetail").css("left", $("#MarkMessageHelper").css("left")); $("#rackDetail").css("top", (parseFloat($("#MarkMessageHelper").css("top")) - 170) + "px"); } else if (_obj.name.indexOf("_qiya") >= 0) { $(".jrlDetail").hide(); var screenPostion = WT3DObj.commonFunc.transToScreenCoord(_obj.position); //name = "气压传感器"; $("#MarkMessageHelper").remove(); $("body").append("<div id='MarkMessageHelper' style='position:absolute;left:" + (screenPostion.x) + "px;top:" + screenPostion.y + "px;height:2px;width:2px;z-index:1000;'></div>"); $("#upsDetail").show(); $("#upsDetail").css("left", $("#MarkMessageHelper").css("left")); $("#upsDetail").css("top", (parseFloat($("#MarkMessageHelper").css("top")) - 170) + "px"); } else if (_obj.name.indexOf("yeyacgq_") >= 0) { $(".jrlDetail").hide(); var screenPostion = WT3DObj.commonFunc.transToScreenCoord(_obj.position); //name = "液压传感器"; $("#MarkMessageHelper").remove(); $("body").append("<div id='MarkMessageHelper' style='position:absolute;left:" + (screenPostion.x) + "px;top:" + screenPostion.y + "px;height:2px;width:2px;z-index:1000;'></div>"); $("#upsDetail").show(); $("#upsDetail").css("left", $("#MarkMessageHelper").css("left")); $("#upsDetail").css("top", (parseFloat($("#MarkMessageHelper").css("top")) - 170) + "px"); } else { $(".jrlDetail").show(); }
三、数据驱动,根据车间实时动作,在三维内展示实时动作
3.1、轨道机械臂,从加热炉中取料
实现代码:
isDoAction = true; var jxb = WT3DObj.commonFunc.findObject("jxbct_369"); var jxbdp = WT3DObj.commonFunc.findObject("jxbdp_373"); if(name=="xsjrl_1135"){ new TWEEN.Tween(jxb.position).to({ x: 7054.110 }, 1000).onComplete(function () { }).start(); new TWEEN.Tween(jxbdp.position).to({ x: 7054.110 }, 1000).onComplete(function () { WT3DObj.commonFunc.changeCameraPosition( {x: 11987.945516911437, y: 5821.875816333479, z: 639.598300399881}, {x: 5418.917462960544, y: -132.7956226536168, z: 2029.241082946989}, 500, function () { jrlAnimation(700, name) setTimeout(function () { jxbAnimation("sc", null, 500); setTimeout(function () { jxbAnimation("showtk"); setTimeout(function () { jxbAnimation("ss"); setTimeout(function () { WT3DObj.commonFunc.changeCameraPosition( { x: 10739.533256704372, y: 4071.9019928732682, z: 4924.279861011403 }, { x: 6579.256577472461, y: 55.12686192176127, z: 1749.5443318108976 }, 500, function () { jxbAnimation("xz"); setTimeout(function () { jxbAnimation("sc"); setTimeout(function () { jxbAnimation("hidetk"); WT3DObj.commonFunc.findObject("tiekuai_0").visible = true; setTimeout(function () { isDoAction = false; jxbAnimation("ss"); jxbAnimation("backxz"); }, 200); }, 600); }, 600); }); }, 600); }, 300); }, 800); }, 1000); }); }).start(); }
加热炉动画:
//加热炉 function jrlAnimation(moveLength,name) { var ylj_1 = WT3DObj.commonFunc.findObject(name?name:"xsjrl_1135").children[19];//394 var ylj_2 = WT3DObj.commonFunc.findObject(name ? name : "xsjrl_1135").children[11];//-112 var ylj_3 = WT3DObj.commonFunc.findObject(name ? name : "xsjrl_1135").children[12];//-112 if (!ylj_1.oldPostionY) { ylj_1.oldPostionY = ylj_1.position.y; } if (!ylj_2.oldPostionY) { ylj_2.oldPostionY = ylj_2.position.y; } if (!ylj_3.oldPostionY) { ylj_3.oldPostionY = ylj_3.position.y; } ylj_1.currentPostionY = ylj_1.position.y; ylj_2.currentPostionY = ylj_2.position.y; ylj_3.currentPostionY = ylj_3.position.y; var movePosition = { y: 0 } new TWEEN.Tween(movePosition).to({ y: moveLength }, 500).onUpdate(function () { ylj_1.position.y = ylj_1.currentPostionY + this.y; if (ylj_1.position.y < ylj_1.oldPostionY - moveLength) { ylj_1.position.y =ylj_1.oldPostionY - moveLength } ylj_1.matrixAutoUpdate = true; ylj_2.position.y = ylj_2.currentPostionY + this.y; if (ylj_2.position.y < ylj_2.oldPostionY - moveLength) { ylj_2.position.y = ylj_2.oldPostionY - moveLength } ylj_2.matrixAutoUpdate = true; ylj_3.position.y = ylj_3.currentPostionY + this.y; if (ylj_3.position.y < ylj_3.oldPostionY - moveLength) { ylj_3.position.y = ylj_3.oldPostionY - moveLength } ylj_3.matrixAutoUpdate = true; }).onComplete(function () { }).start(); }
3.2、机械臂从中间台上取料
实现代码:
case "ql"://取料 isDoAction = true; WT3DObj.commonFunc.findObject("tiekuai_1_1").visible = false; WT3DObj.commonFunc.findObject("tiekuai_1").visible = false; WT3DObj.commonFunc.changeCameraPosition( { x: 7131.978477911106, y: 2829.502314368439, z: 2197.1946670176567 }, { x: 6411.024615439089, y: 528.9277768783592, z: 856.9774806044239 }, 500, function() { var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = true; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = false; jxb_zz_6_3A0(function() { jxb_zz_6_3A01(function() { isDoAction = false; }) }) }); break;
3.3、机械臂将取到的料放置到压力机下面
实现代码:
case "2": case "fl"://放料 isDoAction = true; WT3DObj.commonFunc.changeCameraPosition( { x: 7131.978477911106, y: 2829.502314368439, z: 2197.1946670176567 }, { x: 6411.024615439089, y: 528.9277768783592, z: 856.9774806044239 }, 500, function() { jxb_zz_6_3A2(function () { WT3DObj.commonFunc.findObject("tiekuai_1_1").visible = false; WT3DObj.commonFunc.findObject("tiekuai_1").visible = true; jxb_zz_6_3A3(function () { var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = false; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = true; jxb_zz_6_3_1.children[4].children[2].children[4].children[10].children[2].children[5].visible = false; isDoAction = false; }) }); });
3.4、压力机摧压货料。
代码实现:
case "3": case "ylj"://压力机打击 isDoAction = true; var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = false; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = true; jxb_zz_6_3_1.children[4].children[2].children[4].children[10].children[2].children[5].visible = false; WT3DObj.commonFunc.changeCameraPosition( { x: 7193.942917022709, y: 1335.12023764365, z: 1186.3353004352634 }, { x: 6790.381940272481, y: 763.1919527866438, z: -44.54061049048587 }, 500, function() { yaliji_1126_A1(function () { isDoAction = false; }, 0) }); break;
3.5、机械臂喷雾
这里实现比较简单,本可以用粒子方案,后来为了省事,直接用流体方案实现
case "4": case "qbjqbcp"://喷雾 isDoAction = true; var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = false; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = true; jxb_zz_6_3_1.children[4].children[2].children[4].children[10].children[2].children[5].visible = false; WT3DObj.commonFunc.changeCameraPosition( { x: 6992.126352231225, y: 548.5464812898373, z: 1331.9129698522017 }, { x: 6795.575361553676, y: 631.1769732337358, z: -557.178527158633 }, 500, function() { pqj_6_1266A2(function() { WT3DObj.commonFunc.findObject("flowtube_1368").visible = true; setTimeout(function(){ WT3DObj.commonFunc.findObject("flowtube_1368").visible = false; pqj_6_1266A3(function() { isDoAction = false; }); },3000); }); }); break;
3.6、机械臂取出锻件半成品
代码实现:
case "qcyljdj"://取出压力机锻件 WT3DObj.commonFunc.findObject("tiekuai_3").visible = false; isDoAction = true; var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = false; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = true; jxb_zz_6_3_1.children[4].children[2].children[4].children[10].children[2].children[5].visible = false; WT3DObj.commonFunc.changeCameraPosition( { x: 7131.978477911106, y: 2829.502314368439, z: 2197.1946670176567 }, { x: 6411.024615439089, y: 528.9277768783592, z: 856.9774806044239 }, 500, function() { // jxb_zz_6_3_1A2(function () { WT3DObj.commonFunc.findObject("tiekuai_1").visible = false; WT3DObj.commonFunc.findObject("tiekuai_1_1").visible = false; jxb_zz_6_3_1A3(true,function () { isDoAction = false; }) }) }); break;
case "6": case "fxyljdj"://放下压力机锻件 WT3DObj.commonFunc.findObject("tiekuai_3").visible = false; isDoAction = true; var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = false; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = true; jxb_zz_6_3_1A4(function () { isDoAction = false; WT3DObj.commonFunc.findObject("tiekuai_3").visible = true; jxb_zz_6_3_1A1(function () { isDoAction = false; }) }) break;
3.7、机械臂将锻件放置到传送带上
代码实现:
case "7": case "yd"://移动 var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = true; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = false; /* else if (data.conveyorToCut==1) {//传送到切边机 state = "7"; } */ isDoAction = true; WT3DObj.commonFunc.findObject("tiekuai_3").visible = true; WT3DObj.commonFunc.changeCameraPosition( { x: 3088.224325931938, y: 2460.282161815149, z: 3740.1275634657304 }, { x: 4081.729182608692, y: 203.35568280176042, z: 654.5133357334003 }, 500, function() { tiekuai3_A1(function () { isDoAction = false; }); }); break;
3.8、传送带,传送锻件至切边机一侧
case "7": case "yd"://移动 var jxb_zz_6_3 = WT3DObj.commonFunc.findObject("jxb_zz_6_3"); jxb_zz_6_3.visible = true; var jxb_zz_6_3_1 = WT3DObj.commonFunc.findObject("jxb_zz_6_3_1"); jxb_zz_6_3_1.visible = false; /* else if (data.conveyorToCut==1) {//传送到切边机 state = "7"; } */ isDoAction = true; WT3DObj.commonFunc.findObject("tiekuai_3").visible = true; WT3DObj.commonFunc.changeCameraPosition( { x: 3088.224325931938, y: 2460.282161815149, z: 3740.1275634657304 }, { x: 4081.729182608692, y: 203.35568280176042, z: 654.5133357334003 }, 500, function() { tiekuai3_A1(function () { isDoAction = false; }); }); break;
3.9、机械臂取出锻件,放置到切边机上
case "8": case "qbjqj"://切边机机取料 /* else if (data.conveyorToPress == 1 || data.r2ConveyorCatch == 1) {//从传送带抓料子并复原传送带 state = "8"; } */ WT3DObj.commonFunc.changeCameraPosition( { x: 1176.799334050281, y: 2261.502847148386, z: -3176.64624829932 }, { x: 2172.7062907541617, y: -1082.7207373607675, z: 549.2316313972628 }, 500, function() { jxb_zz_6_795A4(function () { jxb_zz_6_795A1(function () { csddzsbbf_1212A2();//复原传送带 isDoAction = false; }); }); }); break; case "9": case "qbjfl"://切边机机放料 /* if (data.r2SendCut == 1) {//机械臂往切边机送料 state = "9"; } else if (data.cutStrike == 1) {//切边 state = "10"; } else if (data.r2CutCatch == 1) {//取料 state = "11"; } else if (data.r2CutOverlap == 1) {//取飞边 state = "12"; } */ WT3DObj.commonFunc.findObject("tiekuai_2").visible = false; WT3DObj.commonFunc.findObject("tiekuai_2_1").visible = false; isDoAction = true; WT3DObj.commonFunc.changeCameraPosition( { x: 1176.799334050281, y: 2261.502847148386, z: -3176.64624829932 }, { x: 2172.7062907541617, y: -1082.7207373607675, z: 549.2316313972628 }, 500, function() { jxb_zz_6_795A2(function () { WT3DObj.commonFunc.findObject("tiekuai_2").visible = true; WT3DObj.commonFunc.findObject("tiekuai_2_1").visible = true; jxb_zz_6_795A3(function() { isDoAction = false; }); }); }); break;
3.10、切边机,切边
case "10": case "qbjqb"://切边机切边 isDoAction = true; WT3DObj.commonFunc.changeCameraPosition( { x: 1268.7356763452408, y: 1568.1044843402083, z: -2963.5457047272607 }, { x: 1348.071262611549, y: 377.4064330677588, z: 594.0594238388968 }, 500, function() { qiebianji_1293_A1(function() { isDoAction = false; }); }); break;
3.11、取出切好的锻件,放到观测台上观测
case "11": case "qbjqbcp"://取出成品 WT3DObj.commonFunc.findObject("duanjian_1_2355").visible = false; isDoAction = true; WT3DObj.commonFunc.changeCameraPosition( { x: 1242.5999280327703, y: 3110.0888878135233, z: -6003.8189588224295 }, { x: 127.35800599694372, y: -857.7840251121656, z: -414.9639298471293 }, 500, function () { WT3DObj.commonFunc.findObject("tiekuai_2").visible = false; jxb_zz_6_795A6(function () { WT3DObj.commonFunc.findObject("duanjian_1_2355").visible = true; var duanjian_1_2355 = WT3DObj.commonFunc.findObject("duanjian_1_2355"); //duanjian_1_2355.position.y = 577.047; //duanjian_1_2355.position.x = 1109.776; duanjian_1_2355.visible = true; setTimeout(function () { jxb_zz_6_795A3(); setTimeout(function () { isDoAction = false; }, 1000); }, 1000); //new TWEEN.Tween(duanjian_1_2355.position).to({ // x: -522.392, //}, 1500).onUpdate(function () { //}).onComplete(function () { // jxb_zz_6_795A3(); // new TWEEN.Tween(duanjian_1_2355.position).to({ // x: -691.733, // y: 33.214, // }, 1500).onUpdate(function () { // }).onComplete(function() { // isDoAction = false; // }).start(); //}).start(); }); }); break;
3.12、取出飞边毛料,放到对应的框中
case "12": case "qbjqbfb"://取出飞边 isDoAction = true; WT3DObj.commonFunc.findObject("tiekuai_2_1").visible = false; WT3DObj.commonFunc.findObject("tiekuai_2_123").visible = false; WT3DObj.commonFunc.changeCameraPosition( { x: 1242.5999280327703, y: 3110.0888878135233, z: -6003.8189588224295 }, { x: 127.35800599694372, y: -857.7840251121656, z: -414.9639298471293 }, 500, function() { jxb_zz_6_795A5(function() { isDoAction = false; WT3DObj.commonFunc.findObject("tiekuai_2_123").visible = true; jxb_zz_6_795A3(); }); }); break;
技术交流 1203193731@qq.com
交流微信:
如果你有什么要交流的心得 可邮件我
其它相关文章:
使用webgl(three.js)创建3D机房,3D机房微模块详细介绍(升级版二)
如何用webgl(three.js)搭建一个3D库房-第一课
如何用webgl(three.js)搭建一个3D库房,3D密集架,3D档案室,-第二课
使用webgl(three.js)搭建一个3D建筑,3D消防模拟——第三课
使用webgl(three.js)搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课
如何用webgl(three.js)搭建不规则建筑模型,客流量热力图模拟
使用webgl(three.js)搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课(炫酷版一)
使用webgl(three.js)搭建3D智慧园区、3D大屏,3D楼宇,智慧灯杆三维展示,3D灯杆,web版3D,bim管理系统——第六课
如何用webgl(three.js)搭建处理3D园区、3D楼层、3D机房管线问题(机房升级版)-第九课(一)
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!