树和自联表(一)

Author:水如烟 


自联表我们经常用到,它总是跟树联结在一起。
对于它们的处理,.NET没有专门的处理类。
控件类TreeNode,也没有直接跟自联表挂上钩。

所以,我也尝试一下写写这方面的代码。
如我以往所写的一样,仅提供一种方法,至于更好的方法,更好的效率,鉴于自己学识所限,不深究。

通常的,要做成泛型类才能通用。所以,若还是使用.Net FrameWork1.1的话,无法使用下面的类了。

下面是为应用自联表做的树类,只有两个文件:
Node.vb

Namespace  LzmTW.uSystem.uCollection

    
< Serializable() >  _
    
Public   Class  Node( Of  T)

        
Friend  gIsRoot  As   Boolean   =   True
        
Friend  gParent  As  Node( Of  T)

        
' '' <summary>
         ' '' 当前节点的父节点
         ' '' </summary>
         Public   ReadOnly   Property  Parent()  As  Node( Of  T)
            
Get
                
If   Me .IsRoot  Then
                    
Return   Nothing
                
End   If
                
Return  gParent
            
End   Get
        
End Property

        
' '' <summary>
         ' '' 树的深度
         ' '' </summary>
         Public   ReadOnly   Property  Level()  As   Integer
            
Get
                
If   Me .IsRoot  Then
                    
Return   0
                
End   If
                
Return   Me .Parent.Level  +   1
            
End   Get
        
End Property


        
' '' <summary>
         ' '' 当前节点是否是根节点
         ' '' </summary>
         Public   ReadOnly   Property  IsRoot()  As   Boolean
            
Get
                
Return  gIsRoot
            
End   Get
        
End Property

        
Private  gUserData  As   Object

        
' '' <summary>
         ' '' 获取或设置包含树节点有关数据的对象
         ' '' </summary>
         Public   Property  Tag()  As   Object
            
Get
                
Return  gUserData
            
End   Get
            
Set ( ByVal  value  As   Object )
                gUserData 
=  value
            
End   Set
        
End Property

        
Private  gItem  As  T
        
Public   Property  Item()  As  T
            
Get
                
Return  gItem
            
End   Get
            
Set ( ByVal  value  As  T)
                gItem 
=  value
            
End   Set
        
End Property


        
Friend  gChildren  As  NodeCollection( Of  T)

        
' '' <summary>
         ' '' 获取第一个子树节点
         ' '' </summary>
         Public   ReadOnly   Property  FirstNode()  As  Node( Of  T)
            
Get
                
If  gChildren.Count  =   0   Then
                    
Return   Nothing
                
End   If
                
Return  gChildren( 0 )
            
End   Get
        
End Property

        
' '' <summary>
         ' '' 获取最后一个子树节点
         ' '' </summary>
         Public   ReadOnly   Property  LastNode()  As  Node( Of  T)
            
Get
                
If  gChildren.Count  =   0   Then
                    
Return   Nothing
                
End   If
                
Return  gChildren(gChildren.Count  -   1 )
            
End   Get
        
End Property


        
Private  gNodes  As  NodeCollection( Of  T)

        
' '' <summary>
         ' '' 当前节点的节点集合
         ' '' </summary>
         Public   ReadOnly   Property  Nodes()  As  NodeCollection( Of  T)
            
Get
                
Return  gNodes
            
End   Get
        
End Property

        
' '' <summary>
         ' '' 当前节点在节点集合中的位置
         ' '' </summary>
         Public   ReadOnly   Property  Index()  As   Integer
            
Get
                
Return  GetIndex()
            
End   Get
        
End Property

        
Private   Function  GetIndex()  As   Integer
            
If   Me .IsRoot  Then
                
Return   0
            
End   If

            
Return   Me .Parent.Nodes.IndexOf( Me )
        
End Function

        
' '' <summary>
         ' '' 获取下一个同级树节点
         ' '' </summary>
         Public   ReadOnly   Property  NextNode()  As  Node( Of  T)
            
Get
                
If   Me .IsRoot  OrElse   Me .Index  +   1   >   Me .Parent.Nodes.Count  Then
                    
Return   Nothing
                
End   If

                
Return   Me .Parent.Nodes.Item( Me .Index  +   1 )
            
End   Get
        
End Property

        
' '' <summary>
         ' '' 获取上一个同级树节点
         ' '' </summary>
         Public   ReadOnly   Property  PrevNode()  As  Node( Of  T)
            
Get
                
If   Me .IsRoot  OrElse   Me .Index  -   1   <   0   Then
                    
Return   Nothing
                
End   If

                
Return   Me .Parent.Nodes.Item( Me .Index  -   1 )
            
End   Get
        
End Property

        
Private   Sub  Initialzie()
            gNodes 
=   New  NodeCollection( Of  T)( Me )
            gChildren 
=   New  NodeCollection( Of  T)( Me )
            gByProperty 
=   Not  uSystem.uReflection.CommonFunction.TypeHasFields( GetType (T))
        
End Sub

        
Sub   New ()
            Initialzie()
        
End Sub

        
Sub   New ( ByVal  item  As  T)
            gItem 
=  item

            Initialzie()
        
End Sub

        
Public   Function  GetNodeCount( ByVal  includeSubNodes  As   Boolean As   Integer
            
Dim  mCount  As   Integer   =  gChildren.Count
            
If  includeSubNodes  Then
                
Dim  mIndex  As   Integer   =   0
                
Do   While  mIndex  <  gChildren.Count
                    mCount 
+=  gChildren(mIndex).GetNodeCount( True )
                    mIndex 
+=   1
                
Loop
            
End   If

            
Return  mCount
        
End Function

        
Public   Sub  Remove()
            
If   Me .IsRoot  Then
                
Throw   New  Exception( " 不能移除根节点 " )
            
End   If
            
Me .Parent.Nodes.RemoveAt( Me .Index)
        
End Sub


        
Private  gTable  As  DataTable
        
Private  gByProperty  As   Boolean

        
' '' <summary>
         ' '' 将当前节点树转换为表
         ' '' </summary>
         ' '' <param name="includeSubNodes">是否包括子节点的T对象</param>
         Public   Function  ConvertToDataTable( ByVal  includeSubNodes  As   Boolean As  DataTable
            gTable 
=  uSystem.uReflection.CommonFunction.CreateTableFromType( GetType (T))

            
If  gTable.Columns.Count  =   0   Then
                
If  gByProperty  Then
                    
Throw   New  Exception( " 对象无属性列 " )
                
Else
                    
Throw   New  Exception( " 对象无字段列 " )
                
End   If
            
End   If

            
Me .ForEach( New  Action( Of  T)( AddressOf  GetDataTableDatasAction), includeSubNodes)

            gTable.AcceptChanges()

            
Return  gTable
        
End Function


        
Private   Sub  GetDataTableDatasAction( ByVal  item  As  T)
            uSystem.uReflection.CommonFunction.ItemAppendToTable(
Of  T)(item, gTable)
        
End Sub

        
' '' <summary>
         ' '' 将当前节点树转换为TreeNode
         ' '' </summary>
         ' '' <param name="NameOfTreeNodeText">TreeNode的Text值对应的T对象属性名或字段名</param>
         ' '' <param name="includeSubNodes">是否包括子节点</param>
         ' '' <remarks>TreeNode的Tag存T对象值</remarks>
         Public   Function  ConvertToTreeNode( ByVal  nameOfTreeNodeText  As   String ByVal  includeSubNodes  As   Boolean As  Windows.Forms.TreeNode
            CheckValid(gByProperty, nameOfTreeNodeText)

            
Dim  mTreeNode  As  System.Windows.Forms.TreeNode  =  ConvertToTreeNode( Me , gByProperty, nameOfTreeNodeText)

            
If  includeSubNodes  Then  AppendTreeNode(mTreeNode,  Me , gByProperty, nameOfTreeNodeText)

            
Return  mTreeNode
        
End Function

        
Private   Shared   Sub  AppendTreeNode( ByVal  treeNode  As  Windows.Forms.TreeNode,  ByVal  node  As  Node( Of  T),  ByVal  byProperty  As   Boolean ByVal  nameOfTreeNodeText  As   String )
            
For   Each  n  As  Node( Of  T)  In  node.gChildren

                
Dim  mCurrentTreeNode  As  Windows.Forms.TreeNode  =  ConvertToTreeNode(n, byProperty, nameOfTreeNodeText)

                treeNode.Nodes.Add(mCurrentTreeNode)

                AppendTreeNode(mCurrentTreeNode, n, byProperty, nameOfTreeNodeText)

            
Next

        
End Sub

        
Private   Shared   Function  ConvertToTreeNode( ByVal  node  As  Node( Of  T),  ByVal  byProperty  As   Boolean ByVal  nameOfTreeNodeText  As   String As  System.Windows.Forms.TreeNode
            
Dim  mTextValue  As   Object

            
If  byProperty  Then
                mTextValue 
=   GetType (T).GetProperty(nameOfTreeNodeText).GetValue(node.Item,  Nothing )
            
Else
                mTextValue 
=   GetType (T).GetField(nameOfTreeNodeText).GetValue(node.Item)
            
End   If

            
If  mTextValue  Is   Nothing   Then
                mTextValue 
=   " Root "
            
End   If

            
Dim  mTreeNode  As   New  System.Windows.Forms.TreeNode(mTextValue.ToString)
            mTreeNode.Tag 
=  node.Item

            
Return  mTreeNode
        
End Function

        
Private   Sub  CheckValid( ByVal  byProperty  As   Boolean ByVal  nameOfTreeNodeText  As   String )
            
If  byProperty  Then
                
Dim  mPropertyInfo  As  System.Reflection.PropertyInfo  =   GetType (T).GetProperty(nameOfTreeNodeText)
                
If  mPropertyInfo  Is   Nothing   Then
                    
Throw   New  Exception( " 属性名无效 " )
                    
If   Not  mPropertyInfo.CanRead  Then
                        
Throw   New  Exception( " 属性名不可读 " )
                    
End   If
                
End   If
            
Else
                
Dim  mFieldInfo  As  System.Reflection.FieldInfo  =   GetType (T).GetField(nameOfTreeNodeText)
                
If  mFieldInfo  Is   Nothing   Then
                    
Throw   New  Exception( " 字段名无效 " )
                
End   If
            
End   If
        
End Sub


        
' '' <summary>
         ' '' 对每个节点执行指定操作
         ' '' </summary>
         ' '' <param name="action">对指定的对象执行操作的方法</param>
         ' '' <param name="includeSubNodes">是否包括子节点</param>
         Public   Sub  ForEach( ByVal  action  As  Action( Of  Node( Of  T)),  ByVal  includeSubNodes  As   Boolean )
            Node(
Of  T).ForEach( Me , action, includeSubNodes)
        
End Sub

        
Public   Shared   Sub  ForEach( ByVal  node  As  Node( Of  T),  ByVal  action  As  Action( Of  Node( Of  T)),  ByVal  includeSubNodes  As   Boolean )
            
For   Each  n  As  Node( Of  T)  In  node.gChildren
                action.Invoke(n)

                
If  includeSubNodes  Then  ForEach(n, action,  True )
            
Next
        
End Sub

        
' '' <summary>
         ' '' 对每个T对象执行指定操作
         ' '' </summary>
         ' '' <param name="action">对指定的对象执行操作的方法</param>
         ' '' <param name="includeSubNodes">是否包括子节点的T对象</param>
         Public   Sub  ForEach( ByVal  action  As  Action( Of  T),  ByVal  includeSubNodes  As   Boolean )
            Node(
Of  T).ForEach( Me , action, includeSubNodes)
        
End Sub

        
Public   Shared   Sub  ForEach( ByVal  node  As  Node( Of  T),  ByVal  action  As  Action( Of  T),  ByVal  includeSubNodes  As   Boolean )
            
For   Each  n  As  Node( Of  T)  In  node.gChildren
                action.Invoke(n.Item)


                
If  includeSubNodes  Then  ForEach(n, action,  True )
            
Next
        
End Sub

        
Public   Function  Clone()  As  Node( Of  T)
            
Return  uSystem.uRuntime.uSerialization.SerializeHelper.Clone( Of  Node( Of  T))( Me )
        
End Function

    
End Class

End Namespace

 

NodeCollection.vb

Namespace  LzmTW.uSystem.uCollection

    
< Serializable() >  _
    
Public   Class  NodeCollection( Of  T)
        
Inherits  System.Collections.ObjectModel.Collection( Of  Node( Of  T))

        
Private  gOwner  As  Node( Of  T)

        
Friend   Sub   New ( ByVal  node  As  Node( Of  T))
            gOwner 
=  node
        
End Sub

        
Public   Shadows   Function  Add( ByVal  Value  As  T)  As  Node( Of  T)
            
Dim  mNode  As   New  Node( Of  T)(Value)

            Add(mNode)
            gOwner.gChildren.Add(mNode)

            
Return  mNode
        
End Function

        
Private   Shadows   Sub  Add( ByVal  item  As  Node( Of  T))
            
With  item
                .gParent 
=  gOwner
                .gIsRoot 
=   False
            
End   With

            
MyBase .Add(item)
        
End Sub

        
Public   Shadows   Sub  RemoveAt( ByVal  index  As   Integer )
            
If   Not  IsValidIndex(index)  Then
                
Throw   New  Exception( " 索引无效 " )
            
End   If

            
Dim  mNode  As  Node( Of  T)  =   Me .Item(index)
            Remove(mNode)

            gOwner.gChildren.Remove(mNode)
        
End Sub

        
Public   Shadows   Sub  Remove( ByVal  index  As   Integer )
            
Me .RemoveAt(index)
        
End Sub

        
Private   Shadows   Function  Remove( ByVal  item  As  Node( Of  T))  As   Boolean
            
Return   MyBase .Remove(item)
        
End Function

        
Public   Shadows   Sub  Insert( ByVal  index  As   Integer ByVal  Value  As  T)
            
If   Not  IsValidIndex(index)  Then
                
Throw   New  Exception( " 索引无效 " )
            
End   If

            
Dim  mNode  As   New  Node( Of  T)(Value)

            Insert(index, mNode)
            gOwner.gChildren.Insert(index, mNode)
        
End Sub

        
Private   Shadows   Sub  Insert( ByVal  index  As   Integer ByVal  item  As  Node( Of  T))
            
With  item
                .gParent 
=  gOwner
                .gIsRoot 
=   False
            
End   With

            
MyBase .Insert(index, item)
        
End Sub

        
Public   Overloads   Sub  Clear()
            
MyBase .Clear()
            
If  gOwner.gChildren.Count  >   0   Then  gOwner.gChildren.Clear()
        
End Sub

        
Private   Function  IsValidIndex( ByVal  index  As   Integer As   Boolean
            
If  index  >=   0   Then
                
Return  index  <   Me .Count
            
End   If

            
Return   False
        
End Function

    
End Class
End Namespace

两个辅助的类,专用于序列化、反射取值赋值用的。
SerializeHelper.vb

Namespace  LzmTW.uSystem.uRuntime.uSerialization

    
Public   Class  SerializeHelper

        
Private   Sub   New ()
        
End Sub

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Function  ItemToXml( Of  T)( ByVal  obj  As  T)  As   String
            
Dim  mResult  As   String   =   ""
            
Dim  mSerializer  As   New  System.Xml.Serialization.XmlSerializer( GetType (T))
            
Dim  mStringWriter  As   New  System.IO.StringWriter
            
Using  mStringWriter
                mSerializer.Serialize(mStringWriter, obj)
                mResult 
=  mStringWriter.ToString
                mStringWriter.Close()
            
End   Using
            
Return  mResult
        
End Function

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Function  XmlToItem( Of  T)( ByVal  xml  As   String As  T
            
Dim  mSerializer  As   New  System.Xml.Serialization.XmlSerializer( GetType (T))
            
Dim  mStringReader  As   New  System.IO.StringReader(xml)
            
Return   CType (mSerializer.Deserialize(mStringReader), T)
        
End Function

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Sub  ItemToXmlFile( Of  T)( ByVal  filename  As   String ByVal  obj  As  T)
            
Dim  XmlWriter  As   New  System.IO.StreamWriter(filename,  False , System.Text.Encoding.Default)
            
Using  XmlWriter
                XmlWriter.Write(ItemToXml(obj))
                XmlWriter.Close()
            
End   Using
        
End Sub

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Function  XmlFileToItem( Of  T)( ByVal  filename  As   String As  T
            
Dim  XmlReader  As   New  System.IO.StreamReader(filename, System.Text.Encoding.Default)
            
Dim  mObj  As  T
            
Using  XmlReader
                mObj 
=  XmlToItem( Of  T)(XmlReader.ReadToEnd)
                XmlReader.Close()
            
End   Using
            
Return  mObj
        
End Function

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Sub  ItemToFormatterFile( Of  T)( ByVal  filename  As   String ByVal  formatter  As  System.Runtime.Serialization.IFormatter,  ByVal  obj  As  T)
            
Dim  mFileStream  As  System.IO.Stream  =  System.IO.File.Open(filename, System.IO.FileMode.Create)
            
Using  mFileStream
                formatter.Serialize(mFileStream, obj)
                mFileStream.Close()
            
End   Using
        
End Sub

        
< System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced) >  _
        
Public   Shared   Function  FormatterFileToItem( Of  T)( ByVal  FileName  As   String ByVal  formatter  As  System.Runtime.Serialization.IFormatter)  As  T
            
Dim  mFileStream  As  System.IO.Stream  =  System.IO.File.Open(FileName, System.IO.FileMode.Open)
            
Dim  mObj  As  T
            
Using  mFileStream
                mObj 
=   CType (formatter.Deserialize(mFileStream), T)
                mFileStream.Close()
            
End   Using
            
Return  mObj
        
End Function

        
Public   Shared   Function  Clone( Of  T)( ByVal  obj  As  T)  As  T
            
Dim  tmpT  As  T
            
Dim  mFormatter  As   New  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            
Dim  mMemoryStream  As   New  System.IO.MemoryStream
            
Using  mMemoryStream
                mFormatter.Serialize(mMemoryStream, obj)
                mMemoryStream.Position 
=   0
                tmpT 
=   CType (mFormatter.Deserialize(mMemoryStream), T)
                mMemoryStream.Close()
            
End   Using
            
Return  tmpT
        
End Function

        
Public   Shared   Sub  Save( Of  T)( ByVal  filename  As   String ByVal  formattype  As  FormatType,  ByVal  obj  As  T)
            
Select   Case  formattype
                
Case  formattype.Binary
                    ItemToFormatterFile(filename, 
New  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, obj)
                
Case  formattype.Soap
                    ItemToFormatterFile(filename, 
New  System.Runtime.Serialization.Formatters.Soap.SoapFormatter, obj)
                
Case  formattype.Xml
                    ItemToXmlFile(filename, obj)
            
End   Select
        
End Sub

        
Public   Shared   Function  Load( Of  T)( ByVal  filename  As   String ByVal  formattype  As  FormatType)  As  T
            
Select   Case  formattype
                
Case  formattype.Binary
                    
Return  FormatterFileToItem( Of  T)(filename,  New  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter)
                
Case  formattype.Soap
                    
Return  FormatterFileToItem( Of  T)(filename,  New  System.Runtime.Serialization.Formatters.Soap.SoapFormatter)
                
Case  formattype.Xml
                    
Return  XmlFileToItem( Of  T)(filename)
            
End   Select
            
Return   Nothing
        
End Function

    
End Class

    
Public   Enum  FormatType
        Xml
        Binary
        Soap
    
End Enum

End Namespace

ReflectionCommonFunction.vb

Namespace  LzmTW.uSystem.uReflection
    
Public   Class  CommonFunction
        
Private   Sub   New ()
        
End Sub

        
Public   Shared   Function  TypeHasFields( ByVal  t  As  Type)  As   Boolean
            
Return  t.GetFields.Length  >   0
        
End Function

        
Public   Shared   Function  CreateTableFromType( ByVal  t  As  Type)  As  DataTable
            
Dim  tmpTable  As   New  DataTable

            
If  TypeHasFields(t)  Then
                
For   Each  f  As  Reflection.FieldInfo  In  t.GetFields
                    tmpTable.Columns.Add(f.Name, f.FieldType)
                
Next
            
Else
                
For   Each  p  As  Reflection.PropertyInfo  In  t.GetProperties
                    
If  p.CanRead  Then  tmpTable.Columns.Add(p.Name, p.PropertyType)
                
Next
            
End   If

            
Return  tmpTable
        
End Function

        
Public   Shared   Function  ItemToDataRow( Of  T)( ByVal  item  As  T,  ByVal  table  As  DataTable)  As  DataRow
            
Dim  tmpRow  As  DataRow  =  table.NewRow

            
Dim  mName  As   String
            
Dim  mType  As  Type  =   GetType (T)

            
For   Each  c  As  DataColumn  In  table.Columns
                mName 
=  c.ColumnName

                
If  TypeHasFields(mType)  Then
                    tmpRow(mName) 
=  mType.GetField(mName).GetValue(item)
                
Else
                    tmpRow(mName) 
=  mType.GetProperty(mName).GetValue(item,  Nothing )
                
End   If
            
Next

            
Return  tmpRow
        
End Function

        
Public   Shared   Sub  ItemAppendToTable( Of  T)( ByVal  item  As  T,  ByVal  table  As  DataTable)
            table.Rows.Add(ItemToDataRow(
Of  T)(item, table))
        
End Sub

        
Public   Shared   Sub  ItemAppendToTable( Of  T)( ByVal  items()  As  T,  ByVal  table  As  DataTable)
            
For   Each  item  As  T  In  items
                ItemAppendToTable(
Of  T)(item, table)
            
Next
        
End Sub

        
Public   Shared   Function  ItemsToTable( Of  T)( ByVal  items()  As  T)  As  DataTable
            
Dim  mTable  As  DataTable  =  CreateTableFromType( GetType (T))

            
If  items  Is   Nothing   Then   Return  mTable

            ItemAppendToTable(
Of  T)(items, mTable)

            
Return  mTable
        
End Function

    
End Class
End Namespace

现在可以测试一下。
测试代码:

Public   Class  Form1

    
Private  gNode  As   New  LzmTW.uSystem.uCollection.Node( Of  item)( New  item( " Root " ))

    
Private   Sub  Button1_Click( ByVal  sender  As  System.Object,  ByVal  e  As  System.EventArgs)  Handles  Button1.Click

        gNode.Nodes.add(
New  item( " First " ))
        gNode.Nodes.Add(
New  item( " Second " )).Nodes.Add( New  item( " Four " )).Nodes.Add( New  item( " Five " )).Nodes.Add( New  item( " Seven " ))
        gNode.Nodes.Add(
New  item( " Third " ))
        gNode.Nodes.Insert(
1 New  item( " Six " ))

    
End Sub

    
Private   Sub  Button2_Click( ByVal  sender  As  System.Object,  ByVal  e  As  System.EventArgs)  Handles  Button2.Click
        
Me .TreeView1.Nodes.Add(gNode.ConvertToTreeNode( True " Name " True ))
        
Me .DataGridView1.DataSource  =  gNode.ConvertToDataTable( True True )
    
End Sub

End Class

< Serializable() >  _
Public   Class  item
    
Private  gName  As   String
    
Private  gDeclare  As   String

    
Public   Property  Name()  As   String
        
Get
            
Return  gName
        
End   Get
        
Set ( ByVal  value  As   String )
            gName 
=  value
        
End   Set
    
End Property

    
Public   Property  [ Declare ]()  As   String
        
Get
            
Return  gDeclare
        
End   Get
        
Set ( ByVal  value  As   String )
            gDeclare 
=  value
        
End   Set
    
End Property

    
Sub   New ()
    
End Sub

    
Sub   New ( ByVal  name  As   String )
        gName 
=  name
        gDeclare 
=  name
    
End Sub

    
Sub   New ( ByVal  name  As   String ByVal  [ declare As   String )
        gName 
=  name
        gDeclare 
=  [ declare ]
    
End Sub

End Class

效果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值