领先技术—使用 AJAX 的模式对话框(MSDN 杂志)

领先技术
使用 AJAX 的模式对话框
Dino Esposito

代码下载位置: CuttingEdge2008_Launch.exe (419 KB)
Browse the Code Online
对话框在 Windows ® 中已使用了很长时间,它们确实具有自己的优势。但是,如果您希望 Web 应用程序拥有对话框,它们基本上是弹出框,您知道,大多数用户都会使用拦截程序禁用弹出框。那么,如果您需要弹出框将要怎么办?
使用 Microsoft ® ASP.NET AJAX 时,如果没有页面重新加载或新页面,对话框对于显示上下文相关信息尤其重要。因此,非常有必要设计这样一种对话框,它与模式弹出框等效,并且让用户使用起来十分方便。
ASP.NET 和 AJAX 扩展都不具有对弹出框或 Web 对话框的内置支持,但 AJAX 控件工具包却具有。AJAX 控件工具包是 ASP.NET 扩展程序控件的共享源码库。它最有用的一个控件是 ModalPopupExtender 控件,我在 2008 年 1 月的“领先技术安装”( msdn.microsoft.com/msdnmag/issues/08/01/CuttingEdge) 中对其进行了简要介绍。ModalPopup 扩展程序采用服务器端 ASP.NET 面板所生成的标记,并在用户单击关联的 HTML 元素时显示或隐藏此标记。对话框一开始是隐藏的,在加载页面时下载到客户端上,然后根据需要显示或隐藏。那么,模态是如何保证的呢?请看一下 图 1 中的代码段。
// Code excerpted from modalpopupbehavior.js.
// Method initialize()
//
// Download the source code of the AJAX Control Toolkit from 
// http://www.codeplex.com/atlascontroltoolkit.
//

// The panel defined as the popup is saved as the foreground element.
this._foregroundElement = this._popupElement;

// A new DIV tag is created as the background element for the panel.
// The panel is invisible and placed at 0,0 coordinates (fixed position).
// In addition, the background element is given a high z-index so that it
// sits above everything else.
this._backgroundElement = document.createElement('div');
this._backgroundElement.id = this.get_id() + '_backgroundElement';
this._backgroundElement.style.display = 'none';
this._backgroundElement.style.position = 'fixed';
this._backgroundElement.style.left = '0px';
this._backgroundElement.style.top = '0px';
this._backgroundElement.style.zIndex = 10000;

// The background element is styled as specified.
if (this._BackgroundCssClass) 
{
     this._backgroundElement.className = this._BackgroundCssClass;
}

// The background element is appended to the parent of the foreground 
// element.
this._foregroundElement.parentNode.appendChild(this._backgroundElement);

// The foreground element is programmatically hidden from view. In 
// addition, it is given absolute positioning and an higher z-index than 
// the background element.
this._foregroundElement.style.display = 'none';
this._foregroundElement.style.position = 'fixed';
this._foregroundElement.style.zIndex = $common.getCurrentStyle(
this._backgroundElement, 'zIndex', this._backgroundElement.style.zIndex) + 1;

// A click handler is added to the target element of the extender. In
// this case, It is the DOM element whose ID is passed on as the 
// TargetControlID property.
this._showHandler = Function.createDelegate(this, this._onShow);
$addHandler(this.get_element(), 'click', this._showHandler);

该代码显示用于在客户端上初始化 ModalPopup 扩展程序的脚本的摘录。请注意,ASP.NET AJAX 扩展程序通常包含一个服务器控件(扩展程序)和一个以 JavaScript 编写的客户端行为类。 图 1 中的代码源自 ModalPopup 扩展程序的客户端行为类。如果您从 codeplex.com/atlascontroltoolkit 下载 AJAX 控件工具包的源代码,则会在 modalpopupbehavior.js 文件中发现上述代码。
如您在 图 1 中所见,对话框标记树的根元素被指定为前台元素并以编程方式从视图中隐藏。同时,会动态创建一个 DIV 标记并将其指定为后台元素。该标记固定在坐标 0,0 处,并具有合适的样式。还为 DIV 标记提供了一个极高(但任意)的 z 索引,这实际上可确保该标记位于文档中所有其他元素之上,因为 z 索引属性设置 HTML 元素的堆栈顺序,而且堆栈顺序最高的元素始终显示在堆栈顺序较低的元素之上。
DIV 随后作为前台元素父项的一个子项添加到“文档对象模型”(DOM) 中。最后,为前台元素(对话框内容)得到一个 z 索引,它恰好高于后台元素的 z 索引。
此中间 DIV 的实际效果是新的透明元素位于所有页面元素(弹出面板除外)之上。单击处理程序还会动态应用于扩展程序控件目标,以确保在弹出面板外部所执行的任何用户输入(在前台元素上)丢失,永远也达不到想要的目标。这正是确保模态的行为。
您在源 ASPX 页面定义的服务器面板内容作为模式化窗口弹出,就像典型的 Windows 消息框一样。同时,它的 UI 灵活性也是最高的—它毕竟是一个 ASP.NET 面板,因此您可以使用所需的任何控件组合来填充它,而且可以根据自己的需要设计样式。

ModalPopupExtender 控件
使用 AJAX 控件工具包库来设置模式弹出框是一项相对简单的任务。首先,定义一个提供 UI 的面板,然后添加一个触发对话框显示的按钮控件:
<asp:Button runat="server" ID="btnEditCustomer"
    Text="Edit text" />
<asp:Panel runat="server" ID="pnlEditCustomer">
  ...
</asp:Panel>
接下来,设置扩展程序并指定目标控件 ID 和弹出框控件 ID:
<act:ModalPopupExtender ID="ModalPopupExtender1"
    runat="server"  
    TargetControlID="btnEditCustomer"
    PopupControlID="pnlEditCustomer"
    BackgroundCssClass="modalBackground"
    OkControlID="editBox_OK"
    OnOkScript="yes()" />
ModalPopup 扩展程序的目标控件 ID 是服务器控件的 ID,当您单击它时,会弹出对话框。弹出框控件 ID 是为该对话框提供内容的服务器控件的 ID。
AJAX 控件工具包框架不为对话框提供任何默认样式。用户必须使用面板中的可视元素来退出对话框。 图 2 显示了工作对话框的示例。它允许进行确认并提供某些信息。但是,如果您希望上下文相关对话框具备进一步的用户交互功能,则需做更多工作。
图 2  工作状态 Web 模式对话框的示例 (单击该图像获得较大视图)
要使 ModalPopup 扩展程序与 Windows 对话框的扩展程序更加相像,可以添加多项功能。例如,只要按 Esc 键便可退出对话框的功能(Windows 中的一个常用功能,但 AJAX 尚不支持)就是一个很好的想法。
在实现任何这些功能之前,让我们先简单回顾一下 ModalPopupExtender 控件的方法和属性。 图 3 列出了它的属性,但未包括从基类自动继承的所有属性。

属性说明
BackgroundCssClass一个链接到主机应用程序的 CSS 类,在显示弹出框时应用于模式弹出框下的任何内容。
DropShadow一个布尔属性,用于指示扩展程序是否自动将投影添加到模式弹出框。默认情况下为 True。
OkCancelID某个 DOM 元素的 ID,该元素位于使用“取消”操作关闭模式弹出框时所显示的 UI 中。
OkControlID某个 DOM 元素的 ID,该元素位于使用“确定”操作关闭模式弹出框时所显示的 UI 中。
OkCancelScript某个 JavaScript 函数的名称,该函数链接到要在使用“取消”操作关闭模式弹出框时运行的页面。
OnOkScript某个 JavaScript 函数的名称,该函数链接到要在使用“确定”操作关闭模式弹出框时运行的页面。
PopupControlID作为模式弹出框内容显示的 DOM 树中根元素的 ID。
PopupDragHandleControlID某个嵌入元素的 ID,该元素包含将作为整个弹出框的拖动句柄使用的标头/标题。
RepositionMode指示在调整浏览器窗口大小或滚动浏览器窗口时是否需要重新定位弹出框。可用值属于 ModalPopupRepositionMode 枚举类型。
TargetControlID某个 DOM 元素的 ID,单击该元素时将激活模式弹出框。
X模式弹出框左上角的 X 坐标。
Y模式弹出框左上角的 Y 坐标。
以下是 ModalPopupExtender 控件类的签名:
    public class ModalPopupExtender :
        DynamicPopulateExtenderControlBase 
此处的基类是库定义的扩展程序,它提供对多个扩展程序的 DynamicPopulate 支持。DynamicPopulate 是 AJAX 控件工具包中的另一个扩展程序,它使用 Web 服务调用所返回的文本替换 DOM 元素的标记。ModalPopup 扩展程序与其他 AJAX 控件工具包扩展程序存在依赖关系,其中包括 DropShadow 和 DragPanel。
对话框的内容完全由某个元素(通过 PopupControlID 属性指定)中的 DOM 子树决定。此元素最常见的是一个 ASP.NET 服务器控件,例如面板。弹出框的位置由 X 和 Y 属性决定,但如果未指定坐标,弹出框将水平居中。
如同在任何典型的 Windows 对话框中一样,Web 模式弹出框可以四处拖动。为启用此功能,请在 PopupDragHandleControlID 属性中指定要用作拖动句柄的元素的 ID,之后嵌入脚本便会执行其余操作。请注意,您可以四处拖动模式弹出框,但这仅限于 DOM 所覆盖的页面区域内。换句话说,如果您的页面区域由高度为 100 像素的 DIV 定义,且您在 1600 × 1024 像素的浏览器窗口中显示该页面,则您仅可以垂直拖动弹出框 100 像素,无论浏览器窗口的实际高度如何。
只要不把 RepositionMode 属性设置为默认值“None”(无),您就可以使该行为的脚本在用户滚动 Web 浏览器窗口或调整其大小时更新弹出框的位置(请参阅 图 4)。
图 4  重新定位模式对话框 (单击该图像获得较大视图)

使用 Esc 键关闭弹出框
在 Windows 中,用户可以通过按 Esc 键来退出消息或对话框。在 AJAX 控件工具包中,模式弹出框行为本身不会提供此功能。稍后您会看见,检测和处理 Esc 键并不是一个大问题。请考虑下列 JavaScript 代码,它附加到页面中 DOM 的 KeyDown 事件:
   function OnKeyPress(args)
   {
     if(args.keyCode == Sys.UI.Key.esc)
     {
       $find("ModalPopupExtender1").hide();
     }
   }
该代码由影响整个文档的按键触发。处理程序通过 args 参数接收 Sys.UI.DomEvent 对象。该对象描述捕获 DOM 级别事件时的鼠标和键盘状态。,keyCode 属性专门指示已按下的键的 ASCII 代码。并提供了 Ctrl、Shift 和 Alt 键的其他信息,以及有关鼠标位置和按钮状态的信息。Sys.UI.Key 枚举列出了最常使用的键(包括 Esc、Enter 和 Del 按钮)的代码的某些预定义常量。使用这些工具,检测 Esc 键是否已按下是一件轻而易举的事。现在,让我们看一下您如何能够以编程方式隐藏弹出框。
AJAX 控件工具包扩展程序具有一个客户端和服务器编程接口。服务器接口是一个 ASP.NET 服务器控件;客户端接口是一个 JavaScript 行为类。此类会公开镜像服务器端编程模型的 JavaScript 编程模型,因此 图 3 中的每个属性都具有它自己对应的 JavaScript 属性。这些属性的容器是具有 ModalPopup 扩展程序控件 ID 的对象。此控件不是 DOM 的一部分,因为它是由 Microsoft AJAX 库的基础结构创建的。所以,您不能使用 $get 函数获取行为类的实例;必须改用 $find 函数。模式弹出框的服务器和客户端 API 均具有控制可见性的显示和隐藏方法。
请注意,只有先使用 Microsoft AJAX 库注册 OnKeyPress 事件处理程序,它才能随后发挥作用。运行此注册代码的最佳位置是 pageLoad 函数,如下所示:
function pageLoad(sender, args) 
{
  $addHandler(document, "keydown", OnKeyPress);
}
在 Microsoft AJAX 库的所有启动任务均已完成且库中及页面上所有内容均已初始化后,它会调用 pageLoad 函数。该库还自动将其加载阶段关联到其名称可在页面中找到的任何 JavaScript 函数中。现在,可使用 Esc 键退出通过 AJAX 控件工具包的 ModalPopup 扩展程序管理的任何模式弹出框。

添加动画显示
此工作完成后,我们可以继续进行其他工作。您是否愿意让您的 Web 应用程序具有与 Windows Vista ® 相同的淡入效果?该效果内置用于常规 Windows 托管窗口,例如调用 window.alert 方法时所弹出的窗口。在自定义模式弹出框上(就象我在此所构建的弹出框),您必须通过自己设置才能有此效果。
动画可能很快就会成为 ModalPopup 扩展程序的自有功能;目前,由于在 AJAX 控件工具包库中附带了动画 API,也可相对容易地设置其发挥功效。下列代码引用 Animation 扩展程序,该程序支持对多个常用的预定义 DOM 事件(例如 OnLoad、OnClick、OnMouseOver 和 OnMouseOut)应用动画。TargetControlID 指向其事件将触发动画的 DOM 元素:
<act:AnimationExtender ID="popUpAnimation" runat="server" 
  TargetControlID="btnViewMore">
  <Animations>
    <OnClick>
      <Parallel AnimationTarget="pnlViewCustomer" 
        Duration=".3" Fps="25">
        <FadeIn />
      </Parallel>                    
    </OnClick>
  </Animations>
</act:AnimationExtender>
动画可按序列组合,甚至可以几个并行播放。上述代码只是在单击按钮后,按指定的持续时间和帧数淡入与模式弹出框相关联的面板。下面的代码显示了更为复杂的动画,有多种效果:
<act:AnimationExtender ID="popUpAnimation" runat="server" 
  TargetControlID="btnViewMore">
  <Animations>
    <OnClick>
      <Parallel AnimationTarget="pnlViewCustomer" 
        Duration=".3" Fps="25">
        <Move Horizontal="100" Vertical="100" />
        <Resize Width="280" Height="180" />
        <Color PropertyKey="backgroundColor" 
          StartValue="#FFFFFF" 
          EndValue="#FFFF00" />
      </Parallel>                    
    </OnClick>
  </Animations>
</act:AnimationExtender>
此处,首先将模式弹出框移至新的相对位置,然后调整其大小和着色。 asp.net/AJAX/AjaxControlToolkit/Samples/Animation/Animation.aspx 中的示例库显示了某些演示;但是,它们中的大部分必须先修改源代码,然后才能应用于模式弹出框。造成这种情况的主要问题是模式弹出框在动画启动前显示。虽然它可能对简单的淡入效果奏效,但如果您希望创建爆炸或消失效果,则还需要多下一翻功夫。
ModalPopup 扩展程序使客户端新增了几个有用的事件,其中包括 showing 事件。其订阅方法如下:
function pageLoad(sender, args) 
{
    $find("ModalPopupExtender1").add_showing(onModalShowing);
}
与 showing 事件关联的任何代码恰好在显示弹出框之前运行。使用此事件可执行任何客户端初始化任务。您可以绑定的其他客户端事件包括 hiding、hidden 和 shown。
初始化弹出框的元素
在客户端,ModalPopup 扩展程序切换 DOM 树的可见性,就象 PopupControlID 属性标识的那样。在针对客户端行为的源代码中,您还会看到其他代码,用于截获从服务器下载而来、作为主机页面构成部分的标记,并且您可以设计它的显示样式。
因此,一旦已提供了页面,您便不能更改弹出框的模板或内容。请考虑以下方案。您的用户从下拉列表中选择一个客户,然后查看某些详细信息。接下来,他可能希望编辑一些客户信息。在传统的 Web 应用程序中,您只需将用户重定向到新页面,或者进行回发加载不同视图即可。在 ASP.NET 2.0 和更新版本中,这是 DetailsView 控件所做的工作。在桌面应用程序中,您将使用模式对话框。对于启用 AJAX 的应用程序,Web 对话框现在是一个选件。
但是,用于编辑客户的对话框具有固定布局,每次在弹出之前都使用新内容进行填充。假设在选择客户时您发起完全回发。在选择发生更改的事件中,您更新客户视图并且有选择地更新模式弹出框中的控件。以此方式,在单击按钮弹出对话框时,最新信息已经加载。
但是,AJAX 应用程序并不正常执行完全回发。 图 5 显示了一个改用部分呈现来更新客户视图的 ASP.NET AJAX 页面段。可更新的区域链接到子下拉列表控件上的 SelectedIndexChanged 事件。而下拉列表又将 AutoPostBack 属性设置为 true。实际效果为:无论何时用户更改下拉列表中的选项, 图 5 中的表格都会更新而无需重新加载完整页面。到目前为止一切顺利。
<table>
  <tr>
    <td valign="top">
      <b>Customers</b><br />
      <asp:DropDownList id="ddlCustomers" runat="server" 
        DataSourceID="odsCustomers" 
        DataTextField="CompanyName" 
        DataValueField="ID" 
        AutoPostBack="true" 
        OnSelectedIndexChanged="ddlCustomers_SelectedIndexChanged" 
        ondatabound="ddlCustomers_DataBound" />
      <asp:ObjectDataSource ID="odsCustomers" runat="server"
        TypeName="IntroAjax.CustomerManager"
        SelectMethod="LoadAll">
      </asp:ObjectDataSource>
    </td>
    <td valign="top">
      <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
          <table>            
            <tr>
              <td><b>Customer ID:</b></td>
              <td>
                <asp:Label runat="server" id="lblCustomerID" />
              </td>
            </tr>            
            <tr>
              <td><b>Company Name:</b></td>
              <td>
                <asp:Label runat="server" id="lblCompanyName" />
              </td>
            </tr>
            <tr>
              <td><b>Contact Name:</b></td>
              <td>
                <asp:Label runat="server" id="lblContactName" />
              </td>
            </tr>
            <tr>
              <td><b>Country:</b></td>
              <td><asp:Label runat="server" id="lblCountry" /></td>
            </tr>
          </table>         
        </ContentTemplate>     
        <Triggers>
          <asp:AsyncPostBackTrigger ControlID="ddlCustomers"
            EventName="SelectedIndexChanged" />
        </Triggers>  
      </asp:UpdatePanel>
    </td>
  </tr>
</table>                   

下一步需要更新 图 6 中的面板,该图显示模式弹出框的内容。该面板必须与客户视图同步。
<asp:Panel ID="pnlEditCustomer" runat="server" 
  CssClass="modalPopup" style="display:none">
  <div style="margin:10px">
    <asp:UpdatePanel runat="server" ID="ModalPanel1" 
      RenderMode="Inline" UpdateMode="Conditional"> 
      <ContentTemplate>
        <table>            
          <tr>
            <td><b>Customer ID:</b></td>
            <td>
              <asp:Label runat="server" id="editCustomerID" />
            </td>
          </tr>            
          <tr>
            <td><b>Company Name:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtCompanyName" />
            </td>
          </tr>
          <tr>
            <td><b>Contact Name:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtContactName" />
            </td>
          </tr>
          <tr>
            <td><b>Country:</b></td>
            <td>
              <asp:TextBox runat="server" id="editTxtCountry" />
            </td>
          </tr>
        </table>   
        <hr />
        <asp:Button ID="btnApply" runat="server" 
          Text="Apply" OnClick="btnApply_Click" />
      </ContentTemplate>
    </asp:UpdatePanel>              
   
    <asp:Button ID="editBox_OK" runat="server" 
       Text="OK" OnClick="editBox_OK_Click" />
    <asp:Button ID="editBox_Cancel" runat="server" Text="Cancel" />
  </div>
</asp:Panel>

您可以在显示新客户时或恰好在显示弹出框之前更新弹出框中所显示的面板:
<act:ModalPopupExtender ID="ModalPopupExtender1" runat="server"  
  TargetControlID="hiddenTargetControlForModalPopup"
  PopupControlID="pnlEditCustomer"
  BackgroundCssClass="modalBackground"
  DropShadow="false"
  OkControlID="editBox_OK"
  OnOkScript="ok()"
  OnCancelScript="cancel()"
  CancelControlID="editBox_Cancel" />
如果您恰好在显示之前更新弹出内容(我推荐的方法),则需在您的服务器上运行对话框初始化代码,但您不会从启动模式对话框的任何控件得到任何回发事件。即使您具有回发事件,对而您言,编辑对话框中的控件也已经太迟。实际上,ModalPopup 扩展程序将客户端 onclick 事件处理程序添加到目标控件,并防止发生默认操作—在本例中为回发。
其思路是使用 ModalPopup 扩展程序的伪目标控件,这样扩展程序永远不会在您的控件外部自动启动。那么,您如何触发模式弹出框呢?示例如下。单击下面的按钮时,它通过部分呈现操作执行其服务器端 OnClick 处理程序:
<asp:UpdatePanel runat="server" ID="DialogBoxUpdatePanel" 
  UpdateMode="Conditional">
  <ContentTemplate>
    <asp:Button runat="server" ID="btnEditText" 
      Text="Edit text" 
      OnClick="btnEditText_Click" />
  </ContentTemplate>
</asp:UpdatePanel>
这具有两方面的好处。首先,完全刷新对页面无影响。其次,OnClick 服务器代码可用于正确地初始化弹出面板,然后命令扩展程序显示弹出框:
protected void btnEditText_Click(object sender, EventArgs e)
{
    InitDialog();
    ModalPanel1.Update();
    ModalPopupExtender1.Show();
}
InitDialog 方法包含初始化 图 6 中面板的所有控件所需的内部代码。此代码足以更改所包含控件的状态,但不能修改它们的标记。这是因为您正在执行支配部分呈现回发的代码。因此,在下一步骤中,您要明确刷新可更新的面板。最后,调用 ModalPopup 扩展程序上的 Show 方法。这个最终调用确保将加载页面时显示对话框的脚本正确地下载到浏览器。 图 7 显示了遵从这些原则所设计的工作页面示例。
图 7  具备当前上下文数据的模式对话框 (单击该图像获得较大视图)
您在页面中确实需要所有这些部分呈现区域吗?如果您的唯一目的是找出如何使用服务器代码来初始化对话框,则可能需要保存某些可更新的区域。然而,如果您的对话框需要服务器端初始化工作,则它可能要求某些工作使用所收集的数据更新基础页面。在此情况下,您需要将数据返回到服务器。

将数据返回到服务器
ModalPopup 扩展程序允许您确定充当“确定”和“取消”按钮的控件,方法是使用属性 OkControlID 和 CancelControlID。单击这些按钮中的任意一个后,弹出框关闭,并有选择地执行某些 JavaScript 代码。您可以使用 OnOkScript 和 OnCancelScript,定义一个在单击“确定”和“取消”按钮时运行的 JavaScript 函数。在单击任何预定义的“确定”和“取消”按钮时,弹出框并不回发。取自 modalpopupbehavior.js 源文件的下列代码摘录对此进行了解释。此代码属于“确定”和“取消”按钮单击事件的内置处理程序:
var element = $get(this._OkControlID);
if (element && !element.disabled) {
   if (this.hide() && this._OnOkScript) {
      window.setTimeout(this._OnOkScript, 0);
   }
   e.preventDefault();
   return false;
}
下面是“确定”按钮的示例处理程序:
function onOK(sender, e) 
{
  // refresh the UI

  // if you need to run server code, you
  // can invoke a Web service method
}

请您登场
对于刷新,我并不是通过将代码添加到现有控件或客户端行为来完成本示例的。我只是使用了现有成员组(客户端和服务器)来改善对话框的初始化,以及与主机页面的数据交换。为避免刷新整个页面,我在必要时使用了部分呈现。
要将此处所讨论的示例代码中的技巧用于您自己的应用程序中,请安装 AJAX 控件工具包的最新版本,然后将模式面板封装在 UpdatePanel 区域中,请注意不要将“确定”和“取消”按钮包括在部分区域中。接下来,将 ModalPopup 扩展程序绑定至不可见控件,并以编程方式显示和隐藏弹出框。最后,如果您需要在弹出框中张贴其他按钮而不退出该对话框,您只需将这些按钮添加到封装弹出框的 UpdatePanel 中即可。有关详细信息,请查看源代码;一行代码胜过千言万语。

请将您想向 Dino 询问的问题和提出的意见发送至 cutting@microsoft.com。 cutting@microsoft.com.


Dino Esposito 是“Programming ASP.NET 3.5 Core Reference”(ASP.NET 3.5 编程核心参考) (Microsoft Press, 2008) 一书的作者。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos

转载于:https://www.cnblogs.com/eduask0114/archive/2009/04/06/1430563.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值