ASP.NET - 使用 Microsoft AJAX Library 创建自定义客户端脚本

当您要执行下列操作时,可以使用 Microsoft AJAX Library 的功能:

·向 JavaScript 代码中添加面向对象的功能,以提高代码的重用性、灵活性和可维护性。

·使用反射在运行时检查客户端脚本的结构和组件。

·使用枚举提供不同于整数的另一种易读的表示形式。

·使用 JavaScript 基类型的扩展缩短常规脚本任务的开发时间。

·使用调试扩展和跟踪功能,实现比传统 JavaScript 调试技术更快、信息更丰富的调试。

 

一、使用类型系统

Microsoft AJAX Library 增加了一个类型系统以及一系列对 JavaScript 对象的扩展,可提供与 .NET Framework 功能类似的面向对象的常用功能。利用这些功能,可按一种结构化方式编写支持 AJAX ASP.NET 应用程序,这不仅能提高可维护性,还简化了添加功能以及对功能分层的操作。Microsoft AJAX Library 扩展为 JavaScript 添加了下列功能:

类、命名空间、继承、接口、枚举、反射

 

该库还提供了针对字符串和数组的 Helper 函数。

 

1.1、类、成员和命名空间

Microsoft AJAX Library 包括基类及其派生的对象和组件。通过所有这些类,您可以使用面向对象的编程模型来编写客户端脚本。

Type 类为 JavaScript 编程添加了命名空间、类和继承等面向对象的功能。任何使用 Type 类注册的 JavaScript 对象都会自动获得访问此功能的权限。下面的示例演示如何使用 Type 类在 JavaScript 文件中创建并注册一个命名空间和类:

Type.registerNamespace("Demo");

 

Demo.Person = function(firstName, lastName, emailAddress) {

    this._firstName = firstName;

    this._lastName = lastName;

    this._emailAddress = emailAddress;

}

 

Demo.Person.prototype = {

 

    getFirstName: function() {

        return this._firstName;

    },

 

    getLastName: function() {

        return this._lastName;

    },

 

    getName: function() {

        return this._firstName + ' ' + this._lastName;

    },

 

    dispose: function() {

        alert('bye ' + this.getName());

    }

}

Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

 

// Notify ScriptManager that this is the end of the script.

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

      类有四种成员:字段、属性、方法和事件。字段和属性是名称/值对,用于描述类实例的特征。字段由基元类型组成,可直接进行访问,如下面的示例所示:

myClassInstance.name="Fred"

      属性可以表示任何基元类型或引用类型。属性值需通过 get set 访问器方法进行访问。在 Microsoft AJAX Library 中,get set 访问器都是函数。按照约定,这些函数的名称中应使用前缀“get_”或“set_”。例如,若要获取或设置属性 cancel 的值,需要调用 get_cancel set_cancel 方法。

对于在 AJAX 客户端应用程序生命周期中发生的操作,Microsoft AJAX Library 将引发相应的事件进行响应。Microsoft AJAX Library 还提供一种为 AJAX 客户端组件创建自定义事件的标准方式。

Microsoft AJAX Library 提供一种有助于对常用功能进行分组的命名空间注册方式。下面的示例演示如何使用 Type.registerNamespace .registerClass 方法向 Demo 命名空间中添加 Person 类。

若要对 ASP.NET 网页启用 AJAX 功能,必须向该页面添加 ScriptManager 控件。呈现该页面时,将自动生成对 AJAX 客户端脚本库的相应脚本引用。下面的示例演示一个包含 ScriptManager 控件的页面。

<asp:ScriptManager runat="server" ID="scriptManager" />

      下面的示例演示如何完成以下过程:注册命名空间,创建类,然后重新注册该类。

Type.registerNamespace("Demo");

 

Demo.Person = function(firstName, lastName, emailAddress) {

    this._firstName = firstName;

    this._lastName = lastName;

    this._emailAddress = emailAddress;

}

 

Demo.Person.prototype = {

 

    getFirstName: function() {

        return this._firstName;

    },

 

    getLastName: function() {

        return this._lastName;

    },

 

    getName: function() {

        return this._firstName + ' ' + this._lastName;

    },

 

    dispose: function() {

        alert('bye ' + this.getName());

    }

}

Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

 

// Notify ScriptManager that this is the end of the script.

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

 

Type.registerNamespace("Demo");

 

Demo.Person = function(firstName, lastName, emailAddress) {

    this._firstName = firstName;

    this._lastName = lastName;

    this._emailAddress = emailAddress;

}

 

Demo.Person.prototype = {

 

    getFirstName: function() {

        return this._firstName;

    },

 

    getLastName: function() {

        return this._lastName;

    },

 

    getName: function() {

        return this._firstName + ' ' + this._lastName;

    },

 

    dispose: function() {

        alert('bye ' + this.getName());

    }

}

Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

 

// Notify ScriptManager that this is the end of the script.

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

 

1.2、访问修饰符

大多数面向对象的编程语言都包括“访问修饰符”这一概念。通过访问修饰符,可以指定类或成员可用的上下文,例如是对外部程序可用,还是对同一命名空间中的内部类可用,抑或是仅在特定的代码块中可用。JavaScript 中没有访问修饰符。但是,Microsoft AJAX Library 遵循以下约定:名称以下划线字符(“_”)开头的成员视为私有成员,不能从成员所属类的外部访问它们。

 

1.3、继承

继承是指一个类从另一个类派生的能力。派生类可自动继承基类的所有字段、属性、方法和事件。派生类可以添加新成员或者重写基类的现有成员,以更改这些成员的行为。

下面的示例包含两个在脚本中定义的类:Person Employee,其中 Employee 派生自 Person。这两个类演示私有字段的用法,并且它们都具有公共属性和方法。此外,Employee 还重写 Person 类的 toString 实现并使用基类的功能。

Type.registerNamespace("Demo");

 

Demo.Person = function(firstName, lastName, emailAddress) {

    this._firstName = firstName;

    this._lastName = lastName;

    this._emailAddress = emailAddress;

}

 

Demo.Person.prototype = {

    getFirstName: function() {

        return this._firstName;

    },

 

    getLastName: function() {

        return this._lastName;

    },

 

    getEmailAddress: function() {

        return this._emailAddress;

    },

    setEmailAddress: function(emailAddress) {

        this._emailAddress = emailAddress;

    },

 

    getName: function() {

        return this._firstName + ' ' + this._lastName;

    },

 

    dispose: function() {

        alert('bye ' + this.getName());

    },

 

    sendMail: function() {

        var emailAddress = this.getEmailAddress();

 

        if (emailAddress.indexOf('@') < 0) {

            emailAddress = emailAddress + '@example.com';

        }

        alert('Sending mail to ' + emailAddress + ' ...');

    },

 

    toString: function() {

        return this.getName() + ' (' + this.getEmailAddress() + ')';

    }

}

 

Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

Demo.Employee = function(firstName, lastName, emailAddress, team, title) {

    Demo.Employee.initializeBase(this, [firstName, lastName, emailAddress]);

 

    this._team = team;

    this._title = title;

}

 

Demo.Employee.prototype = {

 

    getTeam: function() {

        return this._team;

    },

    setTeam: function(team) {

        this._team = team;

    },

 

    getTitle: function() {

        return this._title;

    },

    setTitle: function(title) {

        this._title = title;

    },

    toString: function() {

        return Demo.Employee.callBaseMethod(this, 'toString') + '/r/n' + this.getTitle() + '/r/n' + this.getTeam();

    }

}

Demo.Employee.registerClass('Demo.Employee', Demo.Person);

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html >

<head>

    <title>Inheritance</title>

</head>

 

<body>

    <form id="Main" runat="server">

        <asp:ScriptManager runat="server" ID="scriptManager" />

    <script type="text/javascript" src="Inheritance.js"></script>

    </form>

 

    <h2>Inheritance</h2>

    <p />

 

    <div>

        This file contains two classes defined in script: Person and Employee, where

        Employee derives from Person.

        <p />

 

        Each class has private fields, and public properties and methods. In addition,

        Employee overrides the toString implementation, and in doing so, it uses the

        base class functionality.

        <p />

 

        This example puts the Person class in the "Demo" namespace.

        <p />

    </div>

 

 

    <div>

        <ul>

            <li><a href="#" οnclick="return OnTestNewClick()">Object Creation</a></li>

            <li><a href="#" οnclick="return OnTestDisposeClick()">Object Dispose</a></li>

            <li><a href="#" οnclick="return OnTestPrivatePropertyClick()">Public vs. Private Properties</a></li>

            <li><a href="#" οnclick="return OnTestInstanceMethodClick()">Instance Methods</a></li>

            <li><a href="#" οnclick="return OnTestOverrideMethodClick()">Overriden Methods</a></li>

            <li><a href="#" οnclick="return OnTestInstanceOfClick()">Instance Of Check</a></li>

        </ul>

    </div>

 

    <script type="text/javascript" language="JavaScript">

 

    function GetTestPerson()

    {

        return new Demo.Person('Jane', 'Doe', 'jane.doe@example.com');

    }

 

    function GetTestEmployee()

    {

        return new Demo.Employee('John', 'Doe', 'john.doe@example.com', 'Platform', 'Programmer');

    }

 

    function OnTestNewClick() {

        var aPerson = GetTestPerson();

 

        alert(aPerson.getFirstName());

        alert(aPerson);

        alert(Object.getType(aPerson).getName());

 

        var testPerson = GetTestPerson();

        alert(testPerson.getFirstName());

        alert(testPerson);

 

        return false;

    }

 

    function OnTestDisposeClick() {

        var aPerson = GetTestEmployee();

        alert(aPerson.getFirstName());

        aPerson.dispose();

    }

 

    function OnTestPrivatePropertyClick() {

        var aPerson = GetTestEmployee();

        alert('aPerson._firstName = ' + aPerson._firstName);

        alert('aPersona.getFirstName() = ' + aPerson.getFirstName());

 

        return false;

    }

 

    function OnTestInstanceMethodClick() {

        var aPerson = GetTestEmployee();

        aPerson.sendMail('Hello', 'This is a test mail.');

 

        return false;

    }

 

    function OnTestOverrideMethodClick() {

        var testPerson = GetTestEmployee();

        alert(testPerson);

 

        return false;

    }

 

    function OnTestInstanceOfClick() {

        var aPerson = GetTestEmployee();

        if (Demo.Employee.isInstanceOfType(aPerson)) {

            alert(aPerson.getName() + ' is an Employee instance./r/nTitle property: ' + aPerson.getTitle());

        }

 

        return false;

    }

 

    </script>

</body>

</html>

 

1.4、接口

接口用于定义实现它的类的输入和输出要求。这样,函数可以和实现同一接口的类进行交互,而不用考虑该类还实现哪些其他功能。

下面的示例定义一个 Tree 基类和一个 IFruitTree 接口。两个派生类 Apple Banana 可实现 IFruitTree 接口,但 Pine 类不实现该接口。实现 IFruitTree 接口的任何类都可确保 bearFruit 方法是该类的成员。

Type.registerNamespace("Demo.Trees");

 

Demo.Trees.IFruitTree = function() {}

Demo.Trees.IFruitTree.Prototype = {

    bearFruit: function(){}

}

Demo.Trees.IFruitTree.registerInterface('Demo.Trees.IFruitTree');

 

 

Demo.Trees.Tree = function(name) {

    this._name = name;

}

Demo.Trees.Tree.prototype = {

    returnName: function() {

        return this._name;

    },

 

    toStringCustom: function() {

        return this.returnName();

    },

 

    makeLeaves: function() {}

}

Demo.Trees.Tree.registerClass('Demo.Trees.Tree');

 

 

Demo.Trees.FruitTree = function(name, description) {

    Demo.Trees.FruitTree.initializeBase(this, [name]);

    this._description = description;

}

Demo.Trees.FruitTree.prototype.bearFruit = function() {

        return this._description;

}

Demo.Trees.FruitTree.registerClass('Demo.Trees.FruitTree', Demo.Trees.Tree, Demo.Trees.IFruitTree);

 

Demo.Trees.Apple = function() {

    Demo.Trees.Apple.initializeBase(this, ['Apple', 'red and crunchy']);

}

Demo.Trees.Apple.prototype = {

    makeLeaves: function() {

        alert('Medium-sized and desiduous');

    },

    toStringCustom: function() {

        return 'FruitTree ' + Demo.Trees.Apple.callBaseMethod(this, 'toStringCustom');

    }

}

Demo.Trees.Apple.registerClass('Demo.Trees.Apple', Demo.Trees.FruitTree);

 

Demo.Trees.GreenApple = function() {

    Demo.Trees.GreenApple.initializeBase(this);

    // You must set the _description feild after initializeBase

    // or you will get the base value.

    this._description = 'green and sour';

}

Demo.Trees.GreenApple.prototype.toStringCustom = function() {

    return Demo.Trees.GreenApple.callBaseMethod(this, 'toStringCustom') + ' ... its GreenApple!';

}

Demo.Trees.GreenApple.registerClass('Demo.Trees.GreenApple', Demo.Trees.Apple);

 

 

Demo.Trees.Banana = function(description) {

    Demo.Trees.Banana.initializeBase(this, ['Banana', 'yellow and squishy']);

}

Demo.Trees.Banana.prototype.makeLeaves = function() {

    alert('Big and green');

}

Demo.Trees.Banana.registerClass('Demo.Trees.Banana', Demo.Trees.FruitTree);

 

 

 

Demo.Trees.Pine = function() {

    Demo.Trees.Pine.initializeBase(this, ['Pine']);

}

Demo.Trees.Pine.prototype.makeLeaves = function() {

    alert('Needles in clusters');

}

Demo.Trees.Pine.registerClass('Demo.Trees.Pine', Demo.Trees.Tree);

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html >

<head>

    <title>Interface</title>

</head>

 

<body>

    <form id="Main" runat="server">

        <asp:ScriptManager runat="server" ID="scriptManager" />

    </form>

 

    <h2>Interface</h2>

    <p />

 

    <div>

        This file contains a Tree base class, and an IFruitTree interface.

        Apple and Banana, two derived classes implement that interface, whereas,

        Pine does not implement that interface.

        <p />

    </div>

 

    <script type="text/javascript" src="Interface.js"></script>

 

    <div>

        <ul>

                <li><a href="#" οnclick="return OnTestNewClick()">Object Creation</a></li>

                <li><a href="#" οnclick="return OnTestImplementsClick()">Implements Check</a></li>

                <li><a href="#" οnclick="return OnTestInterfaceMethodClick()">Call interface method</a></li>

        </ul>

    </div>

 

    <script type="text/javascript" language="JavaScript">

 

    function OnTestNewClick() {

        var apple = new Demo.Trees.Apple('Apple');

        alert(apple.returnName());

        apple.makeLeaves();

 

        return false;

    }

 

    function OnTestImplementsClick() {

        var apple = new Demo.Trees.Apple();

        if (Demo.Trees.IFruitTree.isImplementedBy(apple)) {

            alert('Apple implements IFruitTree');

        }

        else {

            alert('Apple does not implement IFruitTree');

        }

 

        var pine = new Demo.Trees.Pine();

        if (Demo.Trees.IFruitTree.isImplementedBy(pine)) {

            alert('Pine implements IFruitTree');

        }

        else {

            alert('Pine does not implement IFruitTree');

        }

 

        return false;

    }

 

    function OnTestInterfaceMethodClick() {

        var apple = new Demo.Trees.Apple();

        ProcessTree(apple);

 

        var pine = new Demo.Trees.Pine();

        ProcessTree(pine);

 

        var banana = new Demo.Trees.Banana();

        ProcessTree(banana);

 

        var g = new Demo.Trees.GreenApple();

        ProcessTree(g);

 

        return false;

    }

 

    function ProcessTree(tree) {

        alert('Current Tree ' + tree.returnName());

        alert(tree.toStringCustom());

        if (Demo.Trees.IFruitTree.isImplementedBy(tree)) {

            alert(tree.returnName() + ' implements IFruitTree; Fruit is ' + tree.bearFruit());

        }

    }

    </script>

</body>

</html>

 

1.5、枚举

枚举是指包含一组命名整数常量的类。您可以像访问属性那样访问这些值,如下面的示例所示:

myObject.color = myColorEnum.red

枚举提供不同于整数的另一种易读的表示形式。有关 Microsoft AJAX Library 中的枚举的更多信息,请参见 Type.registerEnum 方法 (ASP.NET AJAX)

下面的示例定义一个命名颜色的枚举,这些命名颜色用于表示十六进制的值。

Type.registerNamespace("Demo");

 

// Define an enumeration type and register it.

Demo.Color = function(){};

Demo.Color.prototype =

{

    Red:    0xFF0000,

    Blue:   0x0000FF,

    Green:  0x00FF00,

    White:  0xFFFFFF

}

Demo.Color.registerEnum("Demo.Color");

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html >

<head>

    <title>Enumeration</title>

</head>

 

<body>

    <form id="Main" runat="server">

        <asp:ScriptManager runat="server" ID="scriptManager" />

    </form>

 

    <div>

        <p>This example creates an Enumeration of colors

            and applies them to page background.</p>

 

        <select id="ColorPicker"

            οnchange="ChangeColor(options[selectedIndex].value)">

            <option value="Red" label="Red" />

            <option value="Blue" label="Blue" />

            <option value="Green" label="Green" />

            <option value="White" label="White" />

        </select>

 

    </div>

 

    <script type="text/javascript" src="Enumeration.js"></script>

    <script type="text/javascript" language="JavaScript">

 

    function ChangeColor(value)

    {

         document.body.bgColor = eval("Demo.Color." + value + ";");

    }

 

    </script>

 

</body>

</html>

 

1.6、反射

反射是指在运行时检查程序的结构和组件的能力。实现反射的 API 是对 Type 类的扩展。通过这些方法,可以收集有关对象的信息,例如该对象继承自谁,它是否实现特定的接口,以及它是否是特定类的实例等。

下面的示例使用反射 API 对前面接口示例中的 GreenApple 类进行测试。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html >

<head>

    <title>Reflection</title>

</head>

 

<body>

    <form id="Main" runat="server">

        <asp:ScriptManager runat="server" ID="scriptManager" />

    </form>

 

    <div>

        <p>This example tests the Demo.Trees.GreenApple class

            against various reflection APIs.</p>

 

        <input id="Button1" value="Check Type"

            type="button" οnclick="return OnButton1Click()" />

        <input id="Button2" value="Check Inheritance"

            type="button" οnclick="return OnButton2Click()" />

        <input id="Button3" value="Check Interface"

            type="button" οnclick="return OnButton3Click()" />

 

    </div>

 

    <script type="text/javascript" src="Interface.js"></script>

    <script type="text/javascript" language="JavaScript">

 

    var g = new Demo.Trees.GreenApple();

    var gt = Demo.Trees.GreenApple;

    var a = new Array(

        Demo.Trees.Apple,

        Demo.Trees.Tree,

        Demo.Trees.Pine,

        Demo.Trees.IFruitTree,

        Sys.IContainer);

 

    function OnButton1Click()

    {

        for (var i = 0; i < a.length; i ++)

        {

            if (a[i].isInstanceOfType(g))

            {

                alert(gt.getName() + " is a " + a[i].getName() + ".");

            }

            else alert(gt.getName() + " is not a " + a[i].getName() + ".");

        }

    }

 

    function OnButton2Click()

    {

        for (var i = 0; i < a.length; i ++)

        {

            if (gt.inheritsFrom(a[i]))

            {

                alert(gt.getName() + " inherits from " + a[i].getName() + ".");

            }

            else alert(gt.getName() + " does not inherit from " + a[i].getName() + ".");

        }

    }

 

    function OnButton3Click()

    {

        for (var i = 0; i < a.length; i ++)

        {

            if (Type.isInterface(a[i]))

            {

                if (gt.implementsInterface(a[i]))

                {

                    alert(gt.getName() + " implements the " + a[i].getName() + " interface.");

                }

                else alert(gt.getName() + " does not implement the " + a[i].getName() + " interface.");

            }

            else alert(a[i].getName() + " is not an interface.");

        }

    }

    </script>

</body>

</html>

 

二、使用 JavaScript 基类型的扩展

JavaScript 基类型的扩展可为这些类型提供附加功能。

·Array 类型扩展

·Boolean 类型扩展

·Date 类型扩展

·Error 类型扩展

·Number 类型扩展

·Object 类型扩展

·String 类型扩展

Sys.Debug 类可提供丰富的调试功能。

如果基于 Microsoft AJAX Library 创建组件,则可以创建由 ScriptManager 控件自动管理的脚本文件的调试版本和发行版本。通过在脚本文件名中添加“.debug”部分,可以标识脚本文件的调试版本。例如,下面的脚本文件名标识同一文件的零售版本和调试版本:

·MyScript.js(零售版)

·MyScript.debug.js(调试版)

 

三、将客户端脚本集成到 ASP.NET Web 应用程序中

任何 ASP.NET 网页均可以通过在 <script> 块中引用脚本文件来对其进行访问,如下面的示例所示:

<script type="text/javascript" src="MyScript.js"></script>

但是,以此方式调用的脚本将不能参与部分页呈现,或无法访问 Microsoft AJAX Library 的某些组件。若要使脚本文件可在支持 AJAX ASP.NET Web 应用程序中用于部分页呈现,必须在该页面的 ScriptManager 控件中注册该脚本。若要注册脚本文件,请创建一个指向相关文件的 ScriptReference 对象,并使之将该文件添加到 Scripts 集合中。下面的示例演示如何在标记中执行此操作:

<asp:ScriptManager ID="SMgr" runat="server">

  <Scripts>

    <asp:ScriptReference path="MyScript.js" />

  </Scripts>

</asp:ScriptManager>

      若要使脚本文件得到 ScriptManager 控件的正确处理,每个文件都必须在末尾包含对 Sys.Application.notifyScriptLoaded 方法的调用。此调用可通知应用程序,已完成文件加载。下面的示例演示用于实现此目的的代码:

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

      此外,您还可以将 .js 文件作为资源嵌入在托管代码程序集中(如果创建的 ASP.NET 服务器控件在客户端脚本中实现其 AJAX 功能,则可能需要这样做)。将脚本嵌入在程序集中时,脚本中便无需包括通知语句。此外,您也不必在脚本引用中指定 path 属性。但是,必须提供不带文件扩展名的程序集名称,如下面的示例所示:

<asp:ScriptManager ID="SMgr" runat="server">

  <Scripts>

    <asp:ScriptReference

        Name="MyScript.js" Assembly="MyScriptAssembly"/>

  </Scripts>

</asp:ScriptManager>

      此外,通过在代码中创建脚本引用并将它们添加到 Scripts 集合中,还可以用编程方式注册脚本。有关更多信息,请参见 动态分配脚本引用。

使用 ScriptManager 控件的注册方法,可以注册部分页更新所需的脚本。您可以按下列方式使用这些方法:

·若要在代码中生成客户端脚本,请以字符串形式生成一个脚本块,然后将其传递给 RegisterClientScriptBlock 方法。

·若要添加没有 Microsoft AJAX Library 依赖项的独立脚本文件,请使用 RegisterClientScriptInclude 方法。

·若要添加嵌入在程序集中的脚本文件,请使用 RegisterClientScriptInclude 方法。

      任何要注册的脚本块或内联脚本都必须位于页面的 <form> 元素中。否则,该脚本将不能在 ScriptManager 控件中注册,从而无法访问 ASP.NET AJAX 功能。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值