65.9K
CodeProject 正在变化。 阅读更多。
Home

自动过滤 GridView 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (20投票s)

2014年4月28日

CPOL

8分钟阅读

viewsIcon

47659

downloadIcon

2101

标准的GridView控件的组件替换,可在标题行中添加过滤器,且无需更改页面上的代码。

引言

我接触ASP.NET只有几年时间,之前是在Classic ASP背景下工作的。我一直很需要一个GridView控件,能够对列进行过滤。我曾寻找一个简单且免费的控件来满足我的需求,但大多数找到的都是用户手动在标题行或页脚行中放置过滤器,并编写后台代码来处理过滤。我最终也这样做了,但总觉得应该有更简单的方法,不必为我制作的每个页面都重写后台代码,或者为了获得过滤功能而修改每个页面上的GridView本身。

如果您宁愿直接将控件集成到您自己的项目中,请下载Bin.zip,将DLL放入您项目的bin文件夹,添加对DLL的引用,在您的aspx页面上注册它,并将您的GridView替换为AutoFilterGridView

背景

我一直在重用相同的代码,在太多的ASP.NET页面上为GridView的标题行添加过滤。这确实有效,但我厌倦了每次想要过滤器时都要重写后台代码。我最终决定使用我现有的一些后台代码、一些新代码,并创建一个新的GridView控件,我可以简单地将其拖放到页面上就能工作,或者替换页面上的Microsoft GridView控件,而无需任何其他特殊编码即可为列添加过滤功能。然后我意识到标准过滤器有时是一个缺点,于是我决定更进一步。我意识到对于日期/时间、数字和布尔类型需要最小/最大范围,因此我添加了它们。最终,我为GridView做了一个简单的替换,当被告知时就会添加过滤器。不需要额外的编码,只需用新的控件替换GridView。我称之为“AutoFilterGridView”。我已用SQL Server 2005和2008测试了该控件。请注意,这是一个基本的组件,旨在满足我当时的需求。如果您决定使用它,仍有很大的改进和个性化空间。

Using the Code

为了创建该控件,我继承了**System.Web.UI.WebControls.GridView**,并添加了一个名为“AutoFilterGridView.vb”的类,然后创建了一个将成为新组件的Shell。

Imports System.Text.RegularExpressions
Imports System.Web.UI.HtmlControls

<Assembly: TagPrefix("MyCustomControl.CustomsControls", "asp")>

<ToolboxData("<{0}:AutoFilterGridView runat=""server""></{0}:AutoFilterGridView>")> _
Partial Public Class AutoFilterGridView
    Inherits GridView 

End Class

控件还需要一个位置来存储有关它将过滤的字段的信息。为了快速制作组件,我选择将信息存储在一个private类中,然后将这个类存储在ViewState中。我意识到这可能不是存储信息的最佳方式,但您完全可以随意重写这个组件。

    'used to hold information needed for filtering the fields
    ' needs to be serializable so we can store it in the viewstate
    <Serializable()> _
 Private Class FilterInfo
        Public Name As String
        Public PlaceHolder As String
        Public DataFieldType As System.Type
        Public DataFieldName As String
        Public [Operator] As String
    End Class

    Private Filters As New List(Of FilterInfo)

接下来,我需要一种通过属性与组件进行通信的方式。我想要一种控制是否启用过滤器、放置应用过滤器按钮和移除过滤器按钮的位置,以及是否希望控件提供对过滤器字段的基本验证。验证是为了防止用户尝试用“Frank”过滤日期,或用“@@#!”过滤数字。这些属性将是:

  • IncludeFiltersTrue表示添加过滤器,False表示不添加。
  • FilterButtonsColumnIndex:告诉GridView将应用和清除按钮放置在哪一列的标题行。
  • ClientCalidateFiltersTrue表示自动验证过滤器,False表示不验证(这样您就可以在页面上自行处理)。
    <Category("Behavior")> _
    <Description("Add filters the gridview.")> _
    <DefaultValue(False)> _
 Public Property IncludeFilters() As Boolean
        Get
            If String.IsNullOrEmpty(ViewState("IncludeFilters")) Then
                Return False
            Else
                Return DirectCast(ViewState("IncludeFilters"), Boolean)
            End If
        End Get
        Set(ByVal Value As Boolean)
            ViewState("IncludeFilters") = Value
        End Set
    End Property

    <Category("Behavior")> _
     <Description("Add filter button to which column position (0=first empty)?")> _
     <DefaultValue(-1)> _
    Public Property FilterButtonsColumnIndex() As Integer
        Get
            If String.IsNullOrEmpty(ViewState("FilterButtonsColumnIndex")) Then
                Return 0
            Else
                Return DirectCast(ViewState("FilterButtonsColumnIndex"), Integer)
            End If
        End Get
        Set(ByVal Value As Integer)
            ViewState("FilterButtonsColumnIndex") = Value
        End Set
    End Property

    <Category("Behavior")> _
     <Description("Basic client validation on filters")> _
     <DefaultValue(True)> _
    Public Property ClientValidateFilters() As Boolean
        Get
            If String.IsNullOrEmpty(ViewState("ClientValidateFilters")) Then
                Return True
            Else
                Return DirectCast(ViewState("ClientValidateFilters"), Boolean)
            End If
        End Get
        Set(ByVal Value As Boolean)
            ViewState("ClientValidateFilters") = Value
        End Set
    End Property

显然,我们无法在没有一些客户端代码的情况下进行基本的客户端验证。我知道我的一些页面会包含JQuery,而有些则不会,所以我编写了所需的客户端脚本来独立处理这两种情况。我意识到这并非必需,因为编写成不使用JQuery就足够了,但我希望向人们展示如何处理这两种情况。我不会详细介绍这段代码,因为我相信您能够读懂它。我将展示的是我如何将文件包含在组件中。

    ' Adds the needed js file for validation and clearing the filters
    Protected Overrides Sub OnPreRender(e As EventArgs)
        MyBase.OnPreRender(e)
        Dim resourceName As String = "MyCustomControls.AutoFilterGridView.js"
        Dim cs As ClientScriptManager = Me.Page.ClientScript
        cs.RegisterClientScriptResource(GetType(MyCustomControls.AutoFilterGridView), resourceName)
    End Sub

现在我们进入主要部分。当控件DataBound(数据绑定)并且*如果我们*希望在标题行中添加过滤器时,我们就需要做一些工作。DataBound事件是创建过滤器控件以及在ViewState中存储字段数据信息的地方。

   Private Sub AutoFilterGridView_DataBound(sender As Object, e As System.EventArgs) Handles Me.DataBound
      ' if the control is not told to add filters leave
      If Not IncludeFilters Then Return

      If Me.Controls.Count > 0 Then
         If Not AddFilterHeader() Then Return
      End If

      ' store the filter list in the viewstate
      Using sw As New IO.StringWriter()
         Dim los = New LosFormatter
         los.Serialize(sw, Filters)
         ViewState("CustomGridFilters") = sw.GetStringBuilder().ToString()
      End Using

      Return
   End Sub

AddFilterHeader例程用于遍历gridview的列,以确定其中的数据类型,创建列过滤器,并将列映射到过滤器列表中。对于每一列,只有三种可能的操作。

  1. 添加过滤器字段
  2. 添加相关的过滤器按钮
  3. 什么都不做

例程首先检查是否需要添加过滤器字段。这会发生在*如果*该列没有通过前面添加的属性指定用于存放过滤器按钮,标题可见,列可见,并且列中的字段是或与BoundField控件相关。如果该列将获得过滤器,则会调用MakeFilterCell函数。

如果该列的标题行不接收过滤器,则例程检查该列是否被指定用于接收过滤器按钮。如果是,则添加按钮并设置一个标志,以便不再添加按钮。

如果该列既不接收过滤器也不接收按钮,则创建一个空单元格。

   Private Function AddFilterHeader() As Boolean
      ' get a view of the table
      Dim myTable = DirectCast(Me.Controls(0), Table)
      Dim myNewRow = New GridViewRow(0, -1, DataControlRowType.Header, DataControlRowState.Normal)
      Dim boolFilterDropped As Boolean = False
      Filters.Clear()
      ' Get a view of the data columns
      Dim columns As DataColumnCollection
      If Not IsNothing(Me.DataSource) AndAlso Me.DataSource.GetType() Is GetType(DataTable) Then
        columns = DirectCast(Me.DataSource, DataTable).Columns
      ElseIf Not IsNothing(Me.DataSourceObject) AndAlso Me.DataSourceObject.GetType() Is GetType(SqlDataSource) Then
        columns = CType(CType(Me.DataSourceObject, SqlDataSource).Select(DataSourceSelectArguments.Empty), DataView).Table.Columns
      Else
        Return False
      End If

      'For each column, process it
      For x = 1 To Me.Columns.Count
         With Me.Columns(x - 1)
            If (FilterButtonsColumnIndex <> x) AndAlso _
            (.ShowHeader AndAlso .Visible AndAlso GetType(BoundField).IsAssignableFrom(.GetType()) _
            AndAlso Not String.IsNullOrEmpty(CType(Me.Columns(x - 1), BoundField).DataField)) Then
            Using tc As New TableHeaderCell
               AddFilterControls(tc, columns(CType(Me.Columns(x - 1), _
               BoundField).DataField.ToString).DataType, CType(Me.Columns(x - 1), _
               BoundField).DataField.ToString, .GetType())
               tc.CssClass = "filterHeader"
            End Using
            ElseIf FilterButtonsColumnIndex = x OrElse (FilterButtonsColumnIndex = 0 _
            AndAlso .Visible And Not boolFilterDropped) Then
               Using tc As New TableHeaderCell
                  tc.CssClass = "filterButtons"
                  tc.Controls.Add(New Button() With {.ID = "btnApplyFilters", _
                  .CommandName = "ApplyFilters", _
                  .Text = "Filter", .CssClass = "filterButton"})
                  Using b As New Button
                     b.ID = "btnClearFilters"
                     b.CommandName = "ClearFilters"
                     b.Text = "Clear"
                     b.CssClass = "filterButton"
                     b.Attributes.Add("onclick", "ClearAllFilters()")
                     tc.Controls.Add(b)
                  End Using
                  myNewRow.Cells.Add(tc)
               End Using
               boolFilterDropped = True
            ElseIf .Visible Then   ' just make an empty cell
               myNewRow.Cells.Add(New TableHeaderCell())
            End If
         End With

      Next

      myTable.Rows.AddAt(0, myNewRow)
      Return True
   End Sub

AddFilterControls例程是创建过滤器并将其添加到标题单元格的地方。为了决定需要哪种类型的过滤器,例程需要查看列中BoundFieldDataField类型关联的DataFieldType,然后按需处理它们。用于正确过滤此字段数据的*信息*也将存储在filters列表中以备后续使用。

它处理的第一种类型是Boolean。由于该组件允许布尔数据作为复选框或其他类型,我们必须检查列中使用的控件。如果它是一个复选框,那么将使用一个三态复选框来进行过滤(选中、未选中、不确定)。
复选框的状态将由我们之前加载的JavaScript代码处理。
复选框还会被赋予一个idnamedata-indeterminate属性、如果之前被选中则赋予checked属性,然后是一个class,以便我们可以在客户端按需设置样式。

   Private Sub AddFilterControls(ByRef hc As TableHeaderCell, DataFieldType As System.Type, _
DataFieldName As String, BoundFieldType As System.Type)
      ' Based on the datatype we will need to make different controls and set the values
      Select Case Type.GetTypeCode(DataFieldType)
         Case TypeCode.Boolean
            If BoundFieldType Is GetType(CheckBoxField) Then
               ' create a tristate checkbox
               Using i As New HtmlGenericControl("input")
                  i.Attributes.Add("id", "filter1_" & DataFieldName)
                  i.Attributes.Add("name", i.Attributes("id"))
                  i.Attributes.Add("type", "checkbox")
                  i.Attributes.Add("data-indeterminate", _
                  String.IsNullOrEmpty(If(Page.Request(i.Attributes("name")), String.Empty)))
                  If String.Compare(If(Page.Request(i.Attributes("name")), _
                  String.Empty), "True", True) = 0 Then
                     i.Attributes.Add("checked", String.Compare_
                     (If(Page.Request(i.Attributes("name")), String.Empty), "True", True) = 0)
                  End If
                  i.Attributes.Add("class", "autoFilter tri " & DataFieldType.Name.ToLower)

                  hc.Controls.Add(i)

                  Filters.Add(New FilterInfo() With {.Name = i.Attributes("name"), _
                  .DataFieldType = DataFieldType, .DataFieldName = DataFieldName, .Operator = "="})
               End Using

如果Boolean不是checkbox类型,那么我们将使其成为一个select,带有truefalse和空选项。
select会被赋予一个idname和一个class,以便我们可以在客户端按需设置样式。如果需要,其中一个选项将设置为selected

            Else
               ' create a true/false/any dropdownlist
               Using i As New HtmlGenericControl("select")
                  i.Attributes.Add("id", "filter1_" & DataFieldName)
                  i.Attributes.Add("name", i.Attributes("id"))
                  Using o As New HtmlGenericControl("option")
                     o.Attributes.Add("value", "")
                     o.InnerText = ""
                     If (If(Page.Request(i.Attributes("name")), String.Empty)) = o.Attributes("value") Then
                        o.Attributes.Add("selected", "selected")
                     End If
                     i.Controls.Add(o)
                  End Using
                  Using o As New HtmlGenericControl("option")
                     o.Attributes.Add("value", "false")
                     o.InnerText = "False"
                     If (If(Page.Request(i.Attributes("name")), String.Empty)) = o.Attributes("value") Then
                        o.Attributes.Add("selected", "selected")
                     End If
                     i.Controls.Add(o)
                  End Using
                  Using o As New HtmlGenericControl("option")
                     o.Attributes.Add("value", "true")
                     o.InnerText = "True"
                     If (If(Page.Request(i.Attributes("name")), String.Empty)) = o.Attributes("value") Then
                        o.Attributes.Add("selected", "selected")
                     End If
                     i.Controls.Add(o)
                  End Using

                  i.Attributes.Add("class", "autoFilter " & DataFieldType.Name.ToLower)

                  hc.Controls.Add(i)

                  Filters.Add(New FilterInfo() With {.Name = i.Attributes("name"), _
                  .DataFieldType = DataFieldType, .DataFieldName = DataFieldName, .Operator = "="})
               End Using
            End If 

例程处理的下一个数据类型是数字类型。由于数字字段提供了一个范围用于过滤,因此会放置2个输入字段。第一个是最小值,第二个是最大值。
这两个input字段将被赋予属性:idnameclassplaceholder(以便用户了解该字段的作用)、maxlength,如果设置了ClientValidateFilters,则会添加一个onblur事件调用来进行基本验证。

         Case TypeCode.Byte, TypeCode.Decimal, TypeCode.Double, TypeCode.Int16, _
TypeCode.Int32, TypeCode.Int64, TypeCode.SByte, TypeCode.Single, TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64
            ' This is a range control, add min then max
            Dim mm As String() = {"min", "max", ">=", "<="}
            For x = 1 To 2
               Using i As New HtmlGenericControl("input")
                  i.Attributes.Add("id", "filter" & x.ToString & "_" & DataFieldName)
                  i.Attributes.Add("name", i.Attributes("id"))
                  i.Attributes.Add("placeholder", mm(x - 1))
                  If Type.GetTypeCode(DataFieldType) = TypeCode.Byte Then
                     i.Attributes.Add("maxlength", 4)
                  Else
                     i.Attributes.Add("maxlength", 20)
                  End If
                  i.Attributes.Add("class", "autoFilter " & mm(x - 1) & _
                  "Value numericValue " & DataFieldType.Name.ToLower)
                  If ClientValidateFilters Then
                     Select Case Type.GetTypeCode(DataFieldType)
                        Case TypeCode.Byte, TypeCode.Int16, TypeCode.Int32, TypeCode.Int64, TypeCode.SByte
                           i.Attributes.Add("onblur", "ValidateAutoFilter(this,/^([\-+]?\d+)?$/)")

                        Case TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64
                           i.Attributes.Add("onblur", "ValidateAutoFilter(this,/^(\d+)?$/)")

                        Case TypeCode.Decimal, TypeCode.Double, TypeCode.Single
                           i.Attributes.Add("onblur", "ValidateAutoFilter(this,/^([-+]?[0-9]*\.?[0-9]+)?$/)")
                     End Select
                  End If

                  i.Attributes.Add("value", If(Page.Request(i.Attributes("id")), String.Empty))
                  hc.Controls.Add(i)

                  Filters.Add(New FilterInfo() With {.Name = i.Attributes("id"), _
                  .DataFieldType = DataFieldType, .DataFieldName = DataFieldName, .Operator = mm(x + 1), _
                  .PlaceHolder = i.Attributes("placeholder")})
               End Using
            Next

例程处理的下一个数据类型是日期/时间类型。由于这些字段类型提供了一个范围用于过滤,因此会放置2个输入字段。第一个是最小值,第二个是最大值。

这两个input字段将被赋予属性:idnameclassplaceholder(以便用户了解该字段的作用)、maxlength,如果设置了ClientValidateFilters,则会添加一个onblur事件调用来进行基本验证。

         Case TypeCode.DateTime
            ' This is a range control, add min then max
            Dim mm As String() = {"min", "max", ">=", "<="}
            For x = 1 To 2
               Using i As New HtmlGenericControl("input")
                  i.Attributes.Add("id", "filter" & x.ToString & "_" & DataFieldName)
                  i.Attributes.Add("name", i.Attributes("id"))
                  i.Attributes.Add("placeholder", mm(x - 1))
                  i.Attributes.Add("maxlength", 22)
                  i.Attributes.Add("class", "autoFilter " _
                  & mm(x - 1) & "Value " & DataFieldType.Name.ToLower)

                  If ClientValidateFilters Then
                     i.Attributes.Add("onblur", "ValidateAutoFilter_
                     (this, /^((0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)[0-9]{2}_
                     ( ((2[0-3]|[0-1]?[0-9]):[0-5][0-9](:[0-5][0-9])?|(1[0-2]|[1-9]):[0-5][0-9]_
                     (:[0-5][0-9])?( (am|pm))?))?)?$/i)")
                  End If

                  i.Attributes.Add("value", If(Page.Request(i.Attributes("id")), String.Empty))
                  hc.Controls.Add(i)

                  Filters.Add(New FilterInfo() With {.Name = i.Attributes("id"), _
                  .DataFieldType = DataFieldType, .DataFieldName = DataFieldName, _
                  .Operator = mm(x + 1), .PlaceHolder = i.Attributes("placeholder")})
               End Using
            Next

该例程将剩余的类型作为文本处理。这些类型只提供一个input
input字段然后会被赋予属性:idnameclassplaceholder(以便用户了解该字段的作用)和maxlength

         Case Else
            Using i As New HtmlGenericControl("input")
               i.Attributes.Add("name", "filter_" & DataFieldName)
               i.Attributes.Add("id", "filter_" & DataFieldName)
               i.Attributes.Add("placeholder", "contains")
               If DataFieldType.Name = "Char" Then
                  i.Attributes.Add("maxlength", 1)
               Else
                  i.Attributes.Add("maxlength", 255)
               End If

               i.Attributes.Add("class", "autoFilter textValue " & DataFieldType.Name.ToLower)

               i.Attributes.Add("value", If(Page.Request(i.Attributes("id")), String.Empty))
               hc.Controls.Add(i)

               Filters.Add(New FilterInfo() With {.Name = i.Attributes("id"), _
               .DataFieldType = DataFieldType, .DataFieldName = DataFieldName, _
               .Operator = "LIKE", .PlaceHolder = i.Attributes("placeholder")})
            End Using

      End Select

   End Sub

下一个重要的逻辑分支发生在页面加载时。这是数据将被过滤的地方。如果控件设置为包含过滤功能,并且Page.IsPostBacktrue,则调用过滤例程。

    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        If IncludeFilters AndAlso Page.IsPostBack Then
            FilterTheData()
        End If
    End Sub

过滤功能相当基础,只需决定如何应用过滤器。这里唯一的特殊代码是针对日期过滤器,并且仅在最大值*不*包含时间的情况下。由于最大值是包含性的,并且没有时间假定为午夜,我们必须通过加一天来调整该值。对于我们从网页上获得的每个有值的过滤器,都会创建一个FormParameter并添加到SqlDataSourceFilterParameters中。然后,将要使用的过滤器表达式添加到字符串列表中。最后,构建过滤器表达式列表并将其分配给SqlDataSource.FilterExpression,然后对数据源和组件调用.DataBind

    Private Sub FilterTheData()
        ' retrieve the latest filter information from the viewstate
        Filters = (New LosFormatter()).Deserialize(ViewState("CustomGridFilters"))
        Using ds As SqlDataSource = CType(Me.DataSourceObject, SqlDataSource)
            Dim FilterExpressions As New List(Of String)()

            With ds
                ' remove parameters and recreate the needed ones
                .FilterParameters.Clear()
                ' cycle through each filterable column set in the filters list
                For Each filter As FilterInfo In Filters
                    ' if there is a request value for the column 
                    '(that is not equal to the placeholder) then add the filter to the datasource
                    If Not String.IsNullOrEmpty(If(Page.Request(filter.Name), String.Empty)) _
                    AndAlso Page.Request(filter.Name) <> If(filter.PlaceHolder, String.Empty) Then
                        Dim p As New FormParameter(filter.Name, filter.Name)
                        With p
                            .Type = System.Type.GetTypeCode(filter.DataFieldType)
                            .DefaultValue = Page.Request(filter.Name)
                            ' If this is a datetime filter and the max value, 
                            ' we need to make an adjustment if there is no time. 
                            ' Since the filter should be =< the date and a date only 
                            ' is treated as midnight, add a dat to the value
                            If .Type = TypeCode.DateTime AndAlso Regex.IsMatch(filter.Name, "^filter2_") _
                            AndAlso Regex.IsMatch(.DefaultValue, "^((0?[1-9]|1[012])[- /.]_
                            (0?[1-9]|[12][0-9]|3[01])[- /.](19|20)[0-9]{2})?$") Then
                                .DefaultValue = DateAdd(DateInterval.Second, -1, _
                                DateAdd(DateInterval.Day, 1, DateTime.Parse(.DefaultValue)))
                            End If
                        End With
                        .FilterParameters.Add(p)

                        ' create each expression for the parameter
                        Select Case System.Type.GetTypeCode(filter.DataFieldType)
                            Case TypeCode.Boolean
                                FilterExpressions.Add(filter.DataFieldName & _
                                filter.Operator & " '{" & (.FilterParameters.Count - 1) & "}'")

                            Case TypeCode.Byte, TypeCode.Decimal, TypeCode.Double, TypeCode.Int16, _
                            TypeCode.Int32, TypeCode.Int64, TypeCode.SByte, TypeCode.Single, _
                            TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64
                                FilterExpressions.Add(filter.DataFieldName & filter.Operator & _
                                "{" & (.FilterParameters.Count - 1) & "}")

                            Case TypeCode.DateTime
                                FilterExpressions.Add(filter.DataFieldName & filter.Operator & _
                                "#{" & (.FilterParameters.Count - 1) & "}#")

                            Case Else
                                FilterExpressions.Add(filter.DataFieldName & _
                                " " _& filter.Operator & " '%{" & _
                                (.FilterParameters.Count - 1) & "}%'")

                        End Select

                    End If
                Next

                ' convert filter expressions into one expression and assign it to the filterexpression
                .FilterExpression = [String].Join(" AND ", FilterExpressions.ToArray())
                .DataBind()
            End With

            Me.DataBind()
        End Using
    End Sub

我忘记提到的变化

根据Simon在下面留下的消息,我认为我应该解释控件图像中的“编辑”按钮和过滤器按钮。 “编辑”列用于编辑与该行关联的记录。后台代码接收记录ID,您可以根据需要进行处理。它有一个额外的好处,就是留下一个空标题单元格,我让控件将此单元格用于过滤器按钮。如果您不想要GridView中的命令列,*或者*只是没有一个空的标题区域来让控件放置过滤器按钮,您可以将AutoFilterGridViewFilterButtonsColumnIndex设置为-1(一个不存在的列),这样控件就不会放置按钮。然后,在页面的某个地方,您自己添加2个按钮。它们甚至不必是ASP.NET按钮。您可以将其放在gridview的后面或前面,

<input class="filterButton" type="submit" value="Filter">
<input class="filterButton" onclick="ClearAllFilters();" type="submit" value="Clear">

它们都能正常工作。

© . All rights reserved.