官网介绍 zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。
1、zTree 是利用 JQuery 的核心代码,实现一套能完成大部分常用功能的 Tree 插件
2、zTree v3.0 将核心代码按照功能进行了分割,不需要的代码可以不用加载
3、采用了 延迟加载 技术,上万节点轻松加载,即使在 IE6 下也能基本做到秒杀
4、兼容 IE、FireFox、Chrome、Opera、Safari 等浏览器
5、支持 JSON 数据
6、支持静态 和 Ajax 异步加载节点数据
7、支持任意更换皮肤 / 自定义图标(依靠css)
8、支持极其灵活的 checkbox 或 radio 选择功能
9、提供多种事件响应回调
10、灵活的编辑(增/删/改/查)功能,可随意拖拽节点,还可以多节点拖拽哟
11、在一个页面内可同时生成多个 Tree 实例
12、简单的参数配置实现 灵活多变的功能
ztree是一个很好的树形结构组件,依赖Jquery,以其优异和完备的api可以自定义树形的多种实现,做到灵活配置,最近看了淘宝的图片空间,用的也恰好是ztree,所已就打算copy下来...
先看下效果图:
继续贴代码:
首先引入相关的配置依赖文件
1 <link href="$!webPath/resources/js/zTree/css/zTreeStyle/zTreeStyle.css" type="text/css" rel="stylesheet"/> 2 <script type="text/javascript" charset="utf-8" src="$!webPath/resources/js/zTree/js/jquery.ztree.core-3.5.js"></script> 3 <script type="text/javascript" charset="utf-8" src="$!webPath/resources/js/zTree/js/jquery.ztree.excheck-3.5.js"></script> 4 <script type="text/javascript" charset="utf-8" src="$!webPath/resources/js/zTree/js/jquery.ztree.exedit-3.5.js"></script>
相关json数据
1 [ 2 { 3 "id": "1", 4 "name": "我的图片", 5 "isParent": true, 6 "checked": false, 7 "nocheck": false, 8 "open": true, 9 "click": "photo_manage.accessory_list('1','common')", 10 "children": [ 11 { 12 "id": "1753", 13 "name": "测试_123kl", 14 "isParent": true, 15 "checked": false, 16 "nocheck": false, 17 "open": false, 18 "click": "photo_manage.accessory_list('1753','common')" 19 }, 20 { 21 "id": "1478", 22 "name": "日化家居", 23 "isParent": true, 24 "checked": false, 25 "nocheck": false, 26 "open": false, 27 "click": "photo_manage.accessory_list('1478','common')" 28 }, 29 { 30 "id": "1477", 31 "name": "美容彩妆", 32 "isParent": true, 33 "checked": false, 34 "nocheck": false, 35 "open": false, 36 "click": "photo_manage.accessory_list('1477','common')" 37 }, 38 { 39 "id": "1476", 40 "name": "母婴用品", 41 "isParent": true, 42 "checked": false, 43 "nocheck": false, 44 "open": false, 45 "click": "photo_manage.accessory_list('1476','common')" 46 }, 47 { 48 "id": "1475", 49 "name": "营养保健", 50 "isParent": true, 51 "checked": false, 52 "nocheck": false, 53 "open": false, 54 "click": "photo_manage.accessory_list('1475','common')" 55 }, 56 { 57 "id": "1474", 58 "name": "进口食品", 59 "isParent": true, 60 "checked": false, 61 "nocheck": false, 62 "open": false, 63 "click": "photo_manage.accessory_list('1474','common')" 64 } 65 ] 66 } 67 ]
在script脚本中定义setting和zTreeNodes
1 var curStatus = "init", curAsyncCount = 0, asyncForAll = false, goAsync = false; 2 var setting = { 3 async: { 4 enable: true, 5 autoParam: ["id=ids"],//, "name=n", "level=lv" 6 url: "$!webPath/xxx/xxxTreeData.htm", 7 dataFilter: filter, 8 type: "post" 9 }, 10 view: { 11 fontCss: getFont, 12 showLine: true, 13 expandSpeed: "", 14 addHoverDom: addHoverDom, 15 //removeHoverDom: removeHoverDom, 16 selectedMulti: false 17 }, 18 edit: { 19 drag: { 20 isCopy: true, 21 isMove: true, 22 prev:true, 23 next:true 24 }, 25 enable: true, 26 showRemoveBtn: false, 27 //removeTitle: "删除菜单", 28 renameTitle: "编辑菜单名称" 29 }, 30 data: { 31 keep: { 32 parent: true 33 }, 34 simpleData: { 35 enable: false 36 } 37 }, 38 callback: { 39 onRightClick: onRightClick, 40 //beforeRemove: beforeRemove, //节点被删除之前的事件,并且根据返回值确定是否允许删除操作 41 beforeRename: beforeRename, //用于捕获节点编辑名称结束 42 43 beforeAsync: beforeAsync, //用于捕获异步加载之前的事件回调函数,zTree 根据返回值确定是否允许进行异步加载 44 onAsyncSuccess: onAsyncSuccess, //用于捕获异步加载出现异常错误的事件回调函数 45 onAsyncError: onAsyncError, //用于捕获异步加载正常结束的事件回调函数 46 47 beforeDrag: beforeDrag, //用于捕获节点被拖拽之前的事件回调函数,并且根据返回值确定是否允许开启拖拽操作 48 beforeDrop: beforeDrop, //用于捕获节点拖拽操作结束之前的事件回调函数,并且根据返回值确定是否允许此拖拽操作 49 beforeDragOpen: beforeDragOpen, //用于捕获拖拽节点移动到折叠状态的父节点后,即将自动展开该父节点之前的事件回调函数,并且根据返回值确定是否允许自动展开操作 50 onDrag: onDrag, //用于捕获节点被拖拽的事件回调函数 51 onDrop: onDrop, //用于捕获节点拖拽操作结束的事件回调函数 52 onExpand: onExpand //用于捕获节点被展开的事件回调函数 53 } 54 };
相关的html和css
1 <div > 2 <div class="zTreeDemoBackground left" > 3 <!--<ul id="treeDemo" class="ztree"></ul>--> 4 <ul id="zTreeMenuContent" class="ztree" style="height: 790px;"></ul> 5 </div> 6 </div> 7 <style type="text/css"> 8 div#rMenu{ 9 position: fixed; 10 visibility:hidden; 11 top:0; 12 background-color: #fff; 13 text-align: left; 14 border: 1px solid #c9caca; 15 box-shadow: 0 0 2px #c9caca; 16 padding: 2px 0; 17 z-index: 999; 18 } 19 20 div#rMenu ul li{ 21 margin: 1px 0; 22 padding: 0 5px; 23 cursor: pointer; 24 list-style: none; 25 height: 30px; 26 background-color: #fff; 27 28 } 29 div#rMenu ul li:hover{ 30 background: #ddd; 31 } 32 </style> 33 34 <div id="rMenu" style="width: 120px;height: 110px;font-size: 12px;" > 35 <ul> 36 <li id="m_add" > 37 <i class="fa fa-plus fa-lg" aria-hidden="true"></i> 38 <span style="color:#1681ff;"> 39 添加相册 40 </span> 41 42 </li> 43 <li id="m_rename" onclick="editAlbumName();"> 44 <i class="fa fa-edit fa-lg" aria-hidden="true"></i> 45 <span style="color:#1681ff;"> 46 重新命名 47 </span> 48 </li> 49 <li id="m_del" > 50 <i class="fa fa-close fa-lg" aria-hidden="true"></i> 51 <span style="color:#1681ff;"> 52  删除相册 53 </span> 54 </li> 55 </ul> 56 </div>
基本增删改和右键功能
1 //节点数据过滤 2 function filter(treeId, parentNode, childNodes) { 3 if (!childNodes) { 4 return null; 5 } 6 for (var i = 0, l = childNodes.length; i < l; i++) { 7 childNodes[i].name = childNodes[i].name.replace(/\.n/g, '.'); 8 } 9 return childNodes; 10 } 11 12 function expandNodes(nodes) { 13 if (!nodes) { 14 return; 15 } 16 17 curStatus = "expand"; 18 var zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 19 if (zTree != null) { 20 for (var i = 0, l = nodes.length; i < l; i++) { 21 zTree.expandNode(nodes[i], true, false, false); 22 if (nodes[i].isParent && nodes[i].zAsync) { 23 expandNodes(nodes[i].children); 24 } else { 25 goAsync = true; 26 } 27 } 28 } 29 } 30 31 //字体设置 32 function getFont(treeId, node) { 33 return node.font ? node.font : {}; 34 } 35 36 ////////////////////下面是处理增删改节点////////////////// 37 38 //节点被删除之前的事件,并且根据返回值确定是否允许删除操作 39 function beforeRemove(treeId, treeNode) { 40 41 } 42 43 //用于捕获节点编辑名称 44 function beforeRename(treeId, treeNode, newName, isCancel) { 45 46 if (treeNode.id === "1") { 47 layer.msg('根目录不能编辑!', {icon: 3}); 48 return true; 49 } else { 50 if (treeNode.name == newName) { 51 return true; 52 } else if (newName.length == 0) { 53 layer.msg('菜单名称不能为空!', {icon: 3}); 54 treeNode.name = treeNode.name; 55 return false; 56 } else { 57 58 var cn_pattern= /[\u4e00-\u9fa5]/g; 59 var g = newName.match(cn_pattern); 60 var cn_length = newName.length; 61 if(g!=null){ 62 var cn_numbers = g.length; 63 cn_length = cn_numbers*2+(cn_length-cn_numbers); 64 } 65 66 if(cn_length>99){ 67 layer.msg('名称不能超过99个字符(1个汉字为2个字符)', {icon: 7}); 68 return false; 69 }else{ 70 var param_data = {"album_id": treeNode.id, "album_name": newName}; 71 var reData = ''; 72 jQuery.ajax({ 73 type: "post", 74 async: false, 75 url: "$!webPath/xxx/xxx_ajax_update.htm", 76 data: param_data, 77 success: function (save_id) { 78 reData = save_id; 79 } 80 }); 81 if (reData == treeNode.id) { 82 return true; 83 } else { 84 layer.msg("修改<" + treeNode.name + ">菜单名称失败!", {icon: 3}); 85 return false; 86 } 87 } 88 89 } 90 } 91 } 92 93 //添加菜单 94 var newCount = 1; 95 function addHoverDom(treeId, treeNode) { 96 var sObj = $("#" + treeNode.tId + "_span"); 97 if (treeNode.editNameFlag || $("#addBtn_" + treeNode.tId).length > 0) { 98 return; 99 } 100 }; 101 102 103 function dropNext(treeId, nodes, targetNode) { 104 var pNode = targetNode.getParentNode(); 105 if (pNode && pNode.dropInner === false) { 106 return false; 107 } else { 108 for (var i = 0, l = curDragNodes.length; i < l; i++) { 109 var curPNode = curDragNodes[i].getParentNode(); 110 if (curPNode && curPNode !== targetNode.getParentNode() && curPNode.childOuter === false) { 111 return false; 112 } 113 } 114 } 115 return true; 116 } 117 118 function dropPrev(treeId, nodes, targetNode) { 119 var pNode = targetNode.getParentNode(); 120 if (pNode && pNode.dropInner === false) { 121 return false; 122 } else { 123 for (var i = 0, l = curDragNodes.length; i < l; i++) { 124 var curPNode = curDragNodes[i].getParentNode(); 125 if (curPNode && curPNode !== targetNode.getParentNode() && curPNode.childOuter === false) { 126 return false; 127 } 128 } 129 } 130 return true; 131 } 132 133 function dropInner(treeId, nodes, targetNode) { 134 if (targetNode && targetNode.dropInner === false) { 135 return false; 136 } else { 137 for (var i = 0, l = curDragNodes.length; i < l; i++) { 138 if (!targetNode && curDragNodes[i].dropRoot === false) { 139 return false; 140 } else if (curDragNodes[i].parentTId && curDragNodes[i].getParentNode() !== targetNode && curDragNodes[i].getParentNode().childOuter === false) { 141 return false; 142 } 143 } 144 } 145 return true; 146 } 147 148 //className = "dark", 149 //用于捕获节点被拖拽之前的事件回调函数,并且根据返回值确定是否允许开启拖拽操作 150 var log, curDragNodes, autoExpandNode; 151 function beforeDrag(treeId, treeNodes) { 152 //className = (className === "dark" ? "" : "dark"); 153 for (var i = 0, l = treeNodes.length; i < l; i++) { 154 if (treeNodes[i].drag === false) { 155 curDragNodes = null; 156 return false; 157 } else if (treeNodes[i].parentTId && treeNodes[i].getParentNode().childDrag === false) { 158 curDragNodes = null; 159 return false; 160 } 161 } 162 curDragNodes = treeNodes; 163 return true; 164 } 165 166 167 //用于捕获节点被拖拽的事件回调函数 168 function onDrag(event, treeId, treeNodes) { 169 //className = (className === "dark" ? "" : "dark"); 170 } 171 172 //用于捕获节点拖拽操作结束的事件回调函数 173 function onDrop(event, treeId, treeNodes, targetNode, moveType, isCopy) { 174 175 if (treeNodes.length > 0 && targetNode) { 176 var dragId = treeNodes[0].id;//被拖拽菜单 177 var targetId = targetNode.id;//拖拽到的目标菜单 178 179 //判断是否同级拖动 180 var treeNodes_parentId = treeNodes[0].parentTId; 181 var targetNode_parentId = targetNode.parentTId; 182 var is_child_move = false; 183 if(treeNodes_parentId==targetNode_parentId && moveType!="inner"){ 184 is_child_move = true; 185 } 186 var data = { "album_id" : dragId, "target_id" : targetId ,"moveType":moveType , "is_child_move" : is_child_move}; 187 jQuery.ajax({ 188 type: "post", 189 async: false, 190 url: "$!webPath/xxx/xxx_ajax_drag_update.htm", 191 data: data, 192 success: function(save_id){ 193 layer.msg('移动成功!', {icon: 1}); 194 }, 195 error: function () { 196 layer.msg('服务器内部异常!', {icon: 1}); 197 } 198 }); 199 } 200 } 201 202 //用于捕获节点拖拽操作结束之前的事件回调函数,并且根据返回值确定是否允许此拖拽操作 203 function beforeDrop(treeId, treeNodes, targetNode, moveType, isCopy) { 204 if(targetNode){ 205 var targetNode_parentId = targetNode.parentTId; 206 if(targetNode_parentId==null){ 207 return false; 208 }else{ 209 return true; 210 } 211 }else{ 212 return false; 213 } 214 } 215 216 //用于捕获节点被展开的事件回调函数 217 function onExpand(event, treeId, treeNode) { 218 if (treeNode === autoExpandNode) { 219 //className = (className === "dark" ? "" : "dark"); 220 } 221 } 222 223 function beforeAsync() { 224 layer.msg('加载中', {time: 6000,icon: 16}); 225 curAsyncCount++; 226 } 227 228 function onAsyncSuccess(event, treeId, treeNode, msg) { 229 230 curAsyncCount--; 231 if (curStatus == "expand") { 232 expandNodes(treeNode.children); 233 } else if (curStatus == "async") { 234 asyncNodes(treeNode.children); 235 } 236 //console.info(curStatus); 237 if (curAsyncCount <= 0) { 238 if (curStatus != "init" && curStatus != "") { 239 asyncForAll = true; 240 } 241 curStatus = ""; 242 } 243 layer.closeAll(); 244 } 245 246 function asyncNodes(nodes) { 247 if (!nodes) { 248 return; 249 } 250 curStatus = "async"; 251 var zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 252 for (var i = 0, l = nodes.length; i < l; i++) { 253 if (nodes[i].isParent && nodes[i].zAsync) { 254 asyncNodes(nodes[i].children); 255 } else { 256 goAsync = true; 257 zTree.reAsyncChildNodes(nodes[i], "refresh", true); 258 } 259 } 260 } 261 262 function asyncAll() { 263 if (!check()) { 264 return; 265 } 266 var zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 267 if (asyncForAll) { 268 269 } else { 270 asyncNodes(zTree.getNodes()); 271 if (!goAsync) { 272 curStatus = ""; 273 } 274 } 275 } 276 277 //用于捕获异步加载正常结束的事件回调函数 278 function onAsyncError(event, treeId, treeNode, XMLHttpRequest, textStatus, errorThrown) { 279 curAsyncCount--; 280 if (curAsyncCount <= 0) { 281 curStatus = ""; 282 if (treeNode != null) asyncForAll = true; 283 } 284 } 285 286 //用于捕获拖拽节点移动到折叠状态的父节点后,即将自动展开该父节点之前的事件回调函数,并且根据返回值确定是否允许自动展开操作 287 function beforeDragOpen(treeId, treeNode) { 288 autoExpandNode = treeNode; 289 return true; 290 } 291 292 293 //字体设置 294 function getFont(treeId, node) { 295 return node.font ? node.font : {}; 296 } 297 298 //初始化 299 var rMenu,zTree; 300 $(document).ready(function () { 301 zTree = jQuery.fn.zTree.init($("#zTreeMenuContent"), setting); 302 $("#callbackTrigger").bind("change", {}, setTrigger); //拖拽节点时自动展开父节点是否触发 303 rMenu = $("#rMenu"); 304 }); 305 306 function reset() { 307 if (!check()) { 308 return; 309 } 310 asyncForAll = false; 311 goAsync = false; 312 jQuery.fn.zTree.init($("#zTreeMenuContent"), setting); 313 } 314 315 function check() { 316 if (curAsyncCount > 0) { 317 //$("#demoMsg").text("正在进行异步加载,请等一会儿再点击..."); 318 return false; 319 } 320 return true; 321 } 322 323 324 function expandAll() { 325 if (!check()) { 326 return; 327 } 328 var zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 329 if (zTree == null) 330 return false; 331 332 if (asyncForAll) { 333 zTree.expandAll(true); 334 } else { 335 expandNodes(zTree.getNodes()); 336 if (!goAsync) { 337 curStatus = ""; 338 } 339 } 340 } 341 function setTrigger() { 342 var zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 343 zTree.setting.edit.drag.autoExpandTrigger = $("#callbackTrigger").attr("checked"); 344 } 345 //默认打开所有子级 346 // setTimeout(function () { 347 // expandAll(); 348 // }, 500); 349 350 //初始化关闭所有子级 351 expandAll(); 352 353 354 355 //鼠标右键功能 356 function onRightClick(event, treeId, treeNode) { 357 358 //var x = event.clientX+48; 359 //var y = event.clientY-132; 360 var x = event.clientX; 361 var y = event.clientY; 362 if (!treeNode && event.target.tagName.toLowerCase() != "button" && $(event.target).parents("a").length == 0) { 363 zTree.cancelSelectedNode(); 364 showRMenu("root", x, y); 365 } else if (treeNode && !treeNode.noR) { 366 zTree.selectNode(treeNode); 367 showRMenu("node", x, y); 368 } 369 } 370 371 function editAlbumName(){ 372 hideRMenu(); 373 zTree = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 374 var nodes = zTree.getSelectedNodes(); 375 zTree.editName(nodes[0]); 376 return true; 377 } 378 379 380 381 function showRMenu(type, x, y) { 382 $("#rMenu ul").show(); 383 if (type=="root") { 384 $("#m_del").hide(); 385 $("#m_rename").hide(); 386 } else { 387 $("#m_del").show(); 388 $("#m_rename").show(); 389 $("#m_add").show(); 390 } 391 rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"}); 392 $("body").bind("mousedown", onBodyMouseDown); 393 } 394 395 function hideRMenu() { 396 if (rMenu) rMenu.css({"visibility": "hidden"}); 397 jQuery("body").unbind("mousedown", onBodyMouseDown); 398 } 399 function onBodyMouseDown(event){ 400 if (!(event.target.id == "rMenu" || $(event.target).parents("#rMenu").length>0)) { 401 rMenu.css({"visibility" : "hidden"}); 402 } 403 }
1 photo_del:function(){ /*删除相册*/ 2 $("#syncBtndel,#m_del").unbind("click").click(function(){ 3 4 var treeObj = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 5 var nodes = treeObj.getSelectedNodes(true); 6 if(nodes.length<=0){ 7 layer.msg('请选择一个相册', {icon: 3}); 8 return false; 9 } 10 var node = nodes[0]; 11 if(node.id==1){ 12 layer.msg('根目录不能删除', {icon: 3}); 13 return false; 14 } 15 16 layer.confirm('确定要删除('+node.name+')相册吗,该相册下的图片不会被删除?', { 17 btn: ['确定','取消'] //按钮 18 }, function(){ 19 20 var data = { "album_id" : node.id }; 21 var reData = ''; 22 jQuery.ajax({ 23 type: "post", 24 async: false, 25 url: web_Path+"/xxx/xxx_album_del.htm", 26 data: data, 27 success: function(delete_id){ 28 reData = delete_id; 29 //刷新树节点 30 //treeObj.reAsyncChildNodes(null, "refresh"); 31 32 //移除删除的节点 33 treeObj.removeNode(node); 34 35 //刷新相册 36 photo_manage.accessory_list(null,"delce"); 37 38 } 39 }); 40 if(reData != node.id){ 41 layer.msg("删除<" + node.name + ">菜单失败!", {icon: 3}); 42 } 43 layer.closeAll(); 44 }, function(){ 45 layer.msg('已取消', {time: 2000, }); 46 }); 47 }); 48 },
1 photo_add:function(){ /*添加相册*/ 2 3 var photo_name = $("#groupTitle").val(); 4 if(isEmpty(photo_name)){ 5 layer.msg('相册名称不能为空', {icon: 7}); 6 return false; 7 } 8 9 //相册名称格式验证 10 //var pattern = new RegExp("^[\u4E00-\u9FA5A-Za-z0-9]{1,20}$"); 11 //if(!pattern.test(photo_name)){ 12 // layer.msg('请输入正确的格式(中文、英文、数字)', {icon: 7}); 13 // return false; 14 //} 15 16 //相册名称长度验证{名称不能超过20个字符(1个汉字为2个字符)} 17 var cn_pattern= /[\u4e00-\u9fa5]/g; 18 var g = photo_name.match(cn_pattern); 19 var cn_length = photo_name.length; 20 if(g!=null){ 21 var cn_numbers = g.length; 22 cn_length = cn_numbers*2+(cn_length-cn_numbers); 23 } 24 25 if(cn_length>99){ 26 layer.msg('名称不能超过99个字符(1个汉字为2个字符)', {icon: 7}); 27 return false; 28 } 29 30 //获取当前选中节点 31 var treeObj = jQuery.fn.zTree.getZTreeObj("zTreeMenuContent"); 32 var nodes = treeObj.getSelectedNodes(true); 33 var node = null; 34 var orderIds = null; 35 if(nodes.length>0){ 36 node = nodes[0]; 37 orderIds = (node.children == undefined ? 1 : (node.children.length + 1)); 38 }else{ 39 node = treeObj.getNodes()[0]; 40 orderIds = treeObj.getNodes()[0].children.length+1; 41 } 42 var addName = photo_name;//菜单初始化名称 43 var param_data = {"parentid" : node.id, "album_name" : addName, "album_sequence" : orderIds }; 44 var reData = ''; 45 jQuery.ajax({ 46 type: "post", 47 async: false, 48 url: web_Path+"/xxx/xxx_ajax_save.htm", 49 data: param_data, 50 success: function(save_id){ 51 reData = save_id; 52 $('#syncBtn').popover('hide'); 53 $('#m_add').popover('hide'); 54 $(".popover").css("display","none"); 55 $("#groupTitle").val(''); 56 }, 57 error: function(){ 58 layer.msg('服务器内部异常', {icon: 3}); 59 } 60 }); 61 if(reData != ""){ 62 //强制刷新该节点下的子节点 63 if(node.id==1){ 64 //刷新整棵树 65 treeObj.reAsyncChildNodes(null, "refresh"); 66 }else{ 67 //刷新选中节点下的子节点 68 treeObj.reAsyncChildNodes(nodes[0], "refresh"); 69 } 70 layer.closeAll(); 71 } 72 //隐藏树结构右键菜单 73 hideRMenu(); 74 },
到这里就差不多了,服务器代码就不贴了,就节点之间的移动和排序稍微复杂些,其他的实现起来不难。
春风如贵客,一到便繁华