掌握Tiles框架 (二)-- Tiles布局和定义

1. Tile 布局

构建第一个 tile 布局

如果站点能够重用相同的布局(使用 HTML 表格来实现)和图像,而不必重复相同的 HTML 代码,这样不是很好吗?

Tile 在为站点创建共同的外观方面特别出色。话虽这样说,许多开发人员并没有认识到 Tiles 在创建用 JSP 实现的可重用组件方面同样也很出色。

如果您发现自己在多个页面上重复相同的 HTML 代码,就可考虑对那些页面使用 tile 布局。类似地,如果在不同页面上的不同地方使用相同的 HTML 或 JSP 标签,这种情形也很适合使用 tile 来创建小型可视组件。

作为 Tiles 框架的一个例子,下面将重构一个简单的股票报价应用程序来利用 tile 布局,如图 3 所示。


示例应用程序

这个简单的示例应用程序主要包含一个股票报价页面,它具有一个接受单个参数(即股票代码)的表单(index.jsp)。 另一个页面显示股票报价的值(quote.jsp)。

研究一下下面这两个代码清单。您将重构它们以使用各种各样的 tile 布局。

index.jsp

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
<head>
<title>Stock Quote</title>
</head>

<body>
<table width="500" border="0" cellspacing="0" cellpadding="0">
<tr>
<td> </td>
</tr>
<tr bgcolor="#36566E">
<td height="68" width="48%">
<div align="left">
<img src="images/hp_logo_rickhightower.gif"
width
="220"
height
="74">
</div>
</td>
</tr>
<tr>
<td> </td>
</tr>
</table>

<html:form action="Lookup">
<table width="45%" border="0">
<tr>
<td><bean:message key="app.symbol" />:</td>
<td><html:text property="symbol" /></td>
</tr>
<tr>
<td colspan="2" align="center"><html:submit /></td>
</tr>
</table>
</html:form>

</body>
</html>

quote.jsp

ContractedBlock.gif ExpandedBlockStart.gif View Code
 <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
<head>
<title>Stock Quote</title>
</head>
<body>

<table width="500" border="0" cellspacing="0" cellpadding="0">
<tr>
<td> </td>
</tr>
<tr bgcolor="#36566E">
<td height="68" width="48%">
<div align="left">
<img src="images/hp_logo_rickhightower.gif" width="220" height="74">
</div>
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
<bean:message key="app.price" />: <%= request.getAttribute("PRICE") %>
</td>
</tr>
<tr>
<td> </td>
</tr>
</table>
</body>
</html>

欲学习如何使用 Tiles 框架,您首先必须编写一个 tile 布局,然后重构上述两个例子页面,以便它们不必重复如此多的 HTML 代码。


逐步创建 tile 布局

为了创建一个 tile 布局,您需要做以下事情:

  1. 找出两个页面的相似之处。
  2. 创建一个新的布局页面。
  3. 创建两个新的内容页面,它们仅包含 和 之间的不同之处。EmployeeListing.jspDeptListing.jsp
  4. 将 tile 布局插入页面 ―― 也就是让 和 在它们的页面中插入 tile 布局,并将内容作为参数传递,同时传递其他必要的参数(比如标题)。EmployeeListing.jspDeptListing.jsp

由于找出两个页面之间的相似之处需要 HTML 布局和 Web 站点适用性方面的技能,事实证明这项工作更像一门艺术,而不是像一门科学。由于某些原因,拥有紫色的头发和纹身是有所帮助的。如果您不这样认为,可以问我的朋友 Boo。

本教程重点集中于 Struts,而不是 HTML 布局和 Web 站点适用性方面的必要技能。 因此,您不会了解关于纹身和紫色头发方面的内容。事实上,例子中的 HTML 布局是刻意简单化的,以防分散我们对 Tiles 框架的注意力。


创建 tile 布局

一旦找出了页面之间的相似之处(这是困难的部分),您就能够创建新的布局页面(这是容易的部分)。为了创建一个 tile 布局,您必须做以下事情:

  • 使用标签库指令将 tile 标签库导入 JSP,同时导入需要的其他任何标签库。
  • 使用字符串参数来显示像页面这样使用 标签的内容。tiles:getAsString
  • 使用 标签将 tile 插入布局的适当区域。tiles:insert
  • 使用 tiles:put 标签向内部 tile 传递任何需要的参数 ―― 这个标签是 tiles:insert 标签的子标签。

将 tile 标签库导入 JSP,同时导入需要的其他任何标签库,如下所示(siteLayout.jsp):

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %

注意:要使用 tile 标签库,不要忘了包括 web.xml文件中的标签库:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<taglib>
<taglib-uri>/WEB-INF/struts-tiles.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-tiles.tld
</taglib-location>
</taglib>

接下来使用字符串参数显示诸如页面标题之类的内容。您不仅需要更改页面的内容,而且还需要更改出现在浏览器中的标题。为此,需要传入 tile 布局将要使用的标题:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<html>
<head>
<title>
<tiles:getAsString name="title" ignore="true"/>
</title>
</head>

注意该代码中使用了tiles:getAsString 标签来显示字符串参数。您不仅能够传递字符串参数,而且能够传递要插入这个页面的其他页面。这里假设调用 JSP 页面向这个 tile 布局传递了一个标题;否则,标题将是空白。

注意: ignore 属性:

ignore 属性如果为 true,这意味着在缺失该属性的情况下忽略它。否则,如果 ignore 属性为 false,那么在没有传递该参数的情况下,Tiles 框架将抛出异常,页面将不会显示出来(false 是默认值)。

要插入内容 JSP,可使用 tiles:insert标签,它插入该框架作为 tile 来引用的任何页面或 Web 资源。这个标签实际上在 tile 布局中定义了一个区域。 记住,tile 布局的目标是将 tile 布置到该布局中。下面是向该布局插入一个 tile 的例子:

<tiles:insert attribute="content"/>

上面这个例子非常简单。如果想要插入一个 tile,并向它传递当前页面范围内的项,那该怎么办呢?例如,使用 Tiles 框架给 header.jsp 传递一个标题参数(在 tile 范围内)是可以做到的。


调用其他 tile(传递属性)

在插入 tile 的任何时候,您都可以选择性地给它传递参数。传递给 tile 的参数将被放入该 tile 的标题范围(称为“标题属性”)。例如,除了让标题显示在浏览器的标题栏之外,可能还希望该标题出现在页面的页眉区域。

header.jsp文件将完成这个任务。虽然标题变量在该 tile 布局页面范围之内,但它不在该 tile 布局所插入的 tile 的范围之内。脆弱方法每个 tile 和 tile 布局都获取它自己的环境 ―― 也就是它自己的 tile 范围。因而,您必须像下面这样给页眉 tile 传递该 tile 变量:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<tiles:insert attribute="header" ignore="true">
<tiles:put name="title"
beanName
="title" beanScope="tile"/>
</tiles:insert>

tiles:put 标签将这个 tile 布局范围内的 tile 参数放进页眉 tile 的范围。然后页眉 tile 就能够像 tile 布局所做的那样,通过 tiles:getAsString 标签来使用这个参数。参数名称就是页眉的 tile 范围内的属性名称。 bean 参数是当前范围内(siteLayout.jsp)的 bean 的名称。 beanScope 是您在其中查找这个属性的范围(可能的值是页面、tile、请求、会话和应用程序)。 您可以从任何范围向该 tile 传递 bean。


这个 tile 布局的完整清单

接下来,您会看到 quote.jspindex.jsp 将要使用的这个新布局页面(siteLayout.jsp)的完整清单:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<html>

<head>
<title>
<tiles:getAsString name="title" ignore="true"/>
</title>
</head>

<body>
<table width="500" border="0" cellspacing="0" cellpadding="0">

<tr bgcolor="#36566E">
<td height="68" width="48%">
<div align="left">
<img src="images/hp_logo_rickhightower.gif"
width
="220" height="74">
</div>
</td>
</tr>

<tr>
<td height="68" width="2000">
<tiles:insert attribute="header" ignore="true">
<tiles:put name="title"
beanName
="title" beanScope="tile"/>
</tiles:insert>
</td>
</tr>
<tr>
<td>
<div align="center">
<tiles:insert attribute="content"/>
</div>
</td>
</tr>
<tr>
<td>
<tiles:insert attribute="footer" ignore="true"/>
</td>
</tr>

</table>

</body>
</html>

请花点时间研究一下上面的代码。注意 tile 是如何插入不同区域的(页眉、页脚、内容),以及如何利用 HTML 布局来为 tile 定义区域,从而为应用程序定义完整的布局。


使用 tile 布局

现在已经定义好了使用 tiles 的 tile 布局,您需要使用该布局。 index.jspquote.jsp 都将使用同一个布局。虽然这对两个页面来说似乎是大量的工作,但是对于真实的 Web 应用程序,你可能会对 20 个或更多的页面使用同一个布局。 通过这种方式,您不必在 20 个位置重复 HTML 或包括 JSP 片断。

注意:为什么不就使用 jsp:include 呢?

在适当的位置包括 JSP 片断是重用 HTML 的脆弱方法。设想一下包括相同的 5 个 JSP 片断的 20 个页面 ―― 您必须重复 100 次。

为了使用 tile,您需要执行以下步骤:

  1. 使用taglib指令导入 tile 标签库。
  2. 使用 tiles:insert标签来将 tile 布局插入当前页面。
  3. 使用 tiles:put来传递字符串参数。
  4. 使用 tiles:put 来传入参数 tile。

通过使用 tile 布局,您能够在一个位置中将站点布局所需要的整个 HTML 外部化,然后只需将它插入每个页面。观察一下下面的例子,它显示了如何把 tile 布局插入 index.jsp

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<tiles:insert page="/siteLayout.jsp" flush="true">

<tiles:put name="title" type="string"
value
="Get Rick Hightower Stock Quote" />
<tiles:put name="header" value="/header.jsp" />
<tiles:put name="footer" value="/footer.jsp" />
<tiles:put name="content" value="/indexContent.jsp"/>
</tiles:insert>

现在,当您想要在 quote.jsp 中做相同的事情时,只需更改内容和页眉。

您需要使用插入标签来调用 tile 布局(显示函数)。(注意用来将 tile 布局插入当前页面的 tiles:insert标签):

ContractedBlock.gif ExpandedBlockStart.gif View Code
<tiles:insert page="/siteLayout.jsp" flush="true">

page 属性指定了上一节中定义的 tile 布局。如果 flush 属性被设置为 true,这个 tile(以及到目前为止的页面)将在页面的其余部分之前(或在缓冲区满而迫使执行刷新时)写到浏览器。

要更改 quote.jspheader.jsp 之间的页面 tile,可使用子标签 tiles:put

ContractedBlock.gif ExpandedBlockStart.gif View Code
<tiles:put name="title" type="string"
value
="Get Stock Quote" />

注意 tiles:put 是如何向 tile 布局传递字符串参数的。 tiles:putname 属性标签指定了参数名称。tiles:puttype 属性指定了参数的类型。最后,value 参数用于传递 title 属性的值。这允许您在使用 tiles:insert 标签来调用 tile 布局(显示函数)时,把简单的字符串作为参数来传递。这些参数将成为该 tile 布局属性;也就是被插入该 tile 布局的 tile 范围。

注意您是如何将三个 tile 作为页眉、页脚和内容参数来传递的( header.jspfooter.jspindexContent.jsp):

ContractedBlock.gif ExpandedBlockStart.gif View Code
<tiles:put name="header" value="/header.jsp" />
<tiles:put name="footer" value="/footer.jsp" />
<tiles:put name="content" value="/indexContent.jsp"/>

header.jsp 页面将被插入该 tile 布局的页眉区域。 footer.jsp 页面将被插入该 tile 布局的页脚区域。indexContent.jsp 页面将被插入该 tile 布局的内容区域。 如果想插入不同的内容和 tile,只需改变内容参数的值。

注意用于 index.jsp 的表单不再驻留在 index.jsp 中。该表单现在驻留在 indexContent.jsp中,如下面所列出的:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html:form action="Lookup">
<table width="45%" border="0">
<tr>
<td><bean:message key="app.symbol" />:</td>
<td><html:text property="symbol" /></td>
</tr>
<tr>
<td colspan="2" align="center"><html:submit /></td>
</tr>
</table>
</html:form>

除了将 tile 指定为 JSP 页面外,您还能够在 tiles:put 标签的正文内将文本作为 tile 来传递。quote.jsp所做的正好就是这样:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<tiles:insert page="/siteLayout.jsp" flush="true">
<tiles:put name="title" type="string" value="Rick Hightower Stock Quote" />
<tiles:put name="header" value="/header.jsp" />
<tiles:put name="footer" value="/footer.jsp" />
<tiles:put name="content" type="string">
<bean:message key="app.price"/>
<%= request.getAttribute("PRICE") %>
</tiles:put>
</tiles:insert

注意 tiles:put 标签的标签体包含 quote.jsp 的内容。其他每项内容都是该布局所定义的,都与上一个例子中使用的 tile 相同。这样的优点是能够减少系统中的 JSP 页面的数目。 关于哪种方法工作得最好,很久以来一直存在争议,我的结论是它取决于 put 标签体中有多少代码。

您看到这里存在的问题了吗?存在这样一条规则:不要重复您自己(Don't repeat yourself,DRY),而您已经违背了这点规则。您知道为什么吗?

2. Tile 定义

创建定义

遗憾的是,quote.jspindex.jsp 都违背了 DRY 规则,它们都重复定义了页眉和页脚参数。由于它们都使用相同的参数值,因此不必在两个页面上重复相同的参数是很理想的。

设想有这样一个真实的应用程序,其中的 tile 布局包括更多的区域(比如说 8 个)和使用该 tile 布局的更多页面。您会发现每次想使用某个 tile 布局时都要重复每个参数是一件很痛苦的事情。既然大多数页面都将使用相同的页眉和页脚,那么在单个位置而不是在每个页面定义它们将会带来好处。

回顾一下前面的显示函数类比,tile 布局在某些方面类似一个显示函数。 您使用 tiles:insert 来调用 tile 布局,并且使用 tiles:put 来传入参数。参数是能够插入 tile 布局区域的其他 JSP 页面或字符串。

您现在需要定义对应于页眉和页脚区域的默认参数的能力。Tile 框架还允许您使用定义(definition)来给 tile 布局传递默认参数。

在本节中,您将学习如何创建和使用定义。定义(definition)定义了 tile 布局的默认参数。定义(definition)可以在 JSP 代码或 XML 中定义。在结束本节的学习时,您将能够同时使用这两种方法创建定义。


创建和使用 JSP 定义

您将发现使用 JSP 页面来创建定义是最容易的方法,因为它需要最少的配置。

要创建一个 JSP 定义,请执行以下步骤:

  1. 使用 taglib指令导入 tile 标签库。
  2. 使用 logic:notPresent标签来确保该定义仅被定义一次。
  3. 使用 tiles:definition标签来定义该定义,同时传递定义了 tile 布局的 JSP 页面以及新创建的定义的范围。
  4. 使用 tiles:put 标签定义默认参数。

在下面的清单中,siteLayoutDefinition.jsp 定义了这样一个定义,它使用 siteLayout.jsp 作为 tile 布局,并定义了页眉和页脚的默认参数(以及其他参数):

ContractedBlock.gif ExpandedBlockStart.gif View Code
 <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<logic:notPresent name="siteLayoutDef" scope="application">
<tiles:definition id="siteLayoutDef" page="/siteLayout.jsp" scope="application">
<tiles:put name="title" type="string" value="Rick Hightower Stock Quote System" />
<tiles:put name="header" value="/header.jsp" />
<tiles:put name="footer" value="/footer.jsp" />
<tiles:put name="content" type="string">
Content goes here
</tiles:put>
</tiles:definition>
</logic:notPresent>

tiles:definition 标签定义了一个ComponentDefinition(org.apache.struts.tiles.ComponentDefinition) 类型的 JavaBeanComponentDefinition 具有用于传递给它的所有属性的 getter 和 setter 方法。logic:notPresent 标签通过在定义之前检查它是否已经在范围中,从而确保 ComponentDefinition 对每个应用程序仅创建一次。

注意: 默认设置可能会带来麻烦。

注意您还要为内容和标题定义默认参数。然而,这被认为是很糟糕的做法。为什么这样说呢?如果有人忘了使用这个标题,他们将取得默认值。由于标题应该随每个页面而改变,您不应该为它定义默认值。那样的话,如果有人忘了传递这个标题,tile 布局就会失败。为了使 tile 在这种情况下失败,您需要做以下两件事情:

  • 不要在定义中定义默认值。
  • 在使用 tiles:insert 标签定义 tile 布局中的区域时,不要将 ignore 设置为 true。

 使用 JSP tile 定义

Tile 定义的使用类似于直接使用 tile 布局。唯一的区别:您将指定定义而不是指定 tile 布局 JSP 页面,并且您将使用 tiles:put 传入更少的参数。

要使用 tile 定义,请执行以下步骤:

  1. 使用 taglib 指令导入 tile 标签库。
  2. 使用 jsp:include 来包含定义该定义的 JSP 页面。
  3. 使用 tiles:insert标签,不过要指定“定义 bean(definition bean)”名称和范围而不是指定 tile 布局页面。
  4. 使用 tiles:put属性来仅指定标题和内容(不指定页眉和页脚)。

 下面是一个使用 tile 定义的例子(index2.jsp):

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<jsp:include page="siteLayoutDefinition.jsp"/>

<tiles:insert beanName="siteLayoutDef" beanScope="application">
<tiles:put name="title" type="string"
value
="Get Rick Hightower Stock Quote 2" />
<tiles:put name="content" value="indexContent2.jsp"/>
</tiles:insert>

注意代码中使用了 beanName 属性 siteLayoutDef 来指定上一节定义的定义。beanName 属性值对应于上一节中的 bean 定义的 id 属性值。请注意例子中使用 tiles:put 来指定两个参数而不是四个参数,这意味着更少的键入工作和维护更少的代码 ―― 这就是 DRY 的乐趣。

您看到这里存在的问题了吗?您必须为定义创建一个 JSP 页面(siteLayoutDefinition.jsp),为内容创建一个页面(indexContent.jsp),为 index.jsp 本身创建一个页面,为布局创建一个页面(siteLayout.jsp),为页眉创建一个页面,以及为页脚创建一个页面。吆! 您总计要创建 6 个而不是 1 个 JSP 页面(而这还只是一个 简单 的例子)。就算您获得了可重用性,但这是以牺牲简单性为代价的。

关于这个例子的另一个不可思议之处在于该定义本身。JSP 页面的本意是为了以文档为中心的方式表达可视内容。然而,该定义没有任何内容本质上是可视的。事实上,它主要不过就是配置。一个布局可能有多组定义,因此您会发现每个定义都有一个 JSP 页面真是一件麻烦事情。将所有配置保留在单个位置很不错,但是如何做到这点呢?

注意:对定义使用 jsp:include 而不是 @page include

如果以前使用过 tile,您也许已经看到过使用包含指令(@page include)而不是使用动态包含操作(jsp:include)的例子。 我更喜欢 jsp:include,因为包含指令在编译时执行,而且,除非包含它的页面改变了,否则新的 JSP 定义就不会重新定义。使用 jsp:include 操作吧,它会帮您省去一些开发方面的麻烦。事实证明两者之间的性能差别是可以忽略的(指令要稍微快一点),但是过时的 JSP 定义的痛苦使人们躲避去使用它。


XML 定义解决了非可视化的 JSPpage 暴露的问题。与其为每个 JSPpage 定义一个定义,您可以在单个配置文件中定义所有配置。然而在能够开始使用 XML 定义之前,您需要首先使用对应的 Struts 的 Tiles 插件:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<plug-in className="org.apache.struts.tiles.TilesPlugin" >
<set-property property="definitions-config"
value
="/WEB-INF/tiles-defs.xml" />
<set-property property="moduleAware" value="true" />
<set-property property="definitions-parser-validate" value="true" />
</plug-in>

您需要向 struts 配置文件添加上述代码。注意 definition-config 属性指定了将包含基于 XML 的定义的 XML 文件。这段代码还指定这个 tile 引擎是模块感知的(module-aware),并指定它验证 XML 文件:


使用 XML tile 定义

一旦定义了该插件,创建 XML 定义就变得容易了。 您只需在 tile 定义文件(例如 tiles-def.xml)中添加另一个条目:

ContractedBlock.gif ExpandedBlockStart.gif View Code
<tiles-definitions>

<definition name="siteLayoutDef" path="/siteLayout.jsp">
<put name="title" value="Rick Hightower Stock Quote System" />
<put name="header" value="/header.jsp" />
<put name="footer" value="/footer.jsp" />
<put name="content" type="string">
Content goes here
</put>
</definition>

根元素是 tiles-definition; ―― 这个模块的所有 tile 定义都将定义在 tiles-definition 元素内。

definition 元素指定一个 tile 定义。上面定义的定义在功能上等价于前面定义的 JSP 版本。注意该定义的属性稍有区别:使用 name 而不是 id,以及使用 path 而不是 page。(很气人,不是吗?)如果您知道如何定义一个基于 JSP 的定义,那么定义基于 XML 的定义将证明只是小孩子玩的游戏,因为它们在形式和功能上几乎是完全相同的。


使用 XML tile 定义

现在已经定义好了 XML 定义,您需要更改 quote.jspindex.jsp 以使用它。事实证明该定义的使用和以前几乎没有区别:唯一的区别是传递给 tiles:insert 标签的属性,如下所示(index3.jsp):

ContractedBlock.gif ExpandedBlockStart.gif View Code
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<tiles:insert definition="siteLayoutDef">
<tiles:put name="title" type="string"
value
="Get Rick Hightower Stock Quote 3" />
<tiles:put name="content" value="indexContent3.jsp"/>
</tiles:insert>

注意您现在使用 definition 属性来指定 tile 定义文件(tiles-def.xml)中创建的定义,而不是使用 beanNamebeanScope。还要注意您需要在定义 JSP 中使用 jsp:includelogic:notPresent

一旦您转变思路,开始使用 XML 定义而不是使用 JSP 定义,那么 tile 的使用将变得更容易一些。您将只需编写更少的代码和维护更少的非可视化 JSP 页面。

                                   (前一篇) 掌握Tiles 框架 (一)---Tiles入门和Tiles 框架和体系结构   (后一篇) 掌握Tiles 框架 (三)—高级 tile 主题和列表

转载于:https://www.cnblogs.com/laoyangHJ/articles/tilesframe2.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值