ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: welcpa

[VBA程序开发] VBAPI函数参考使用手册!

[复制链接]

TA的精华主题

TA的得分主题

 楼主| 发表于 2005-6-1 00:39 | 显示全部楼层

例子

pEYPGPFe.rar (161.97 KB, 下载次数: 110)

TA的精华主题

TA的得分主题

 楼主| 发表于 2005-6-1 00:48 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助

在vb中叫出控制台Item的内容

协助工具 内容rundll32.exe最後一个叁数1代表协助工具中TabStrip中的第1个Tab(键盘) 若为2表第二个Tab(声音)以此类推 Call Shell("rundll32.exe shell32.dll,Control_RunDLL access.cpl,,1",vbNormalFocus) 新增移除程式 rundll32.exe最後一个叁数1代表新增移除程式中TabStrip中的第1个Tab安装/移除) Call Shell("rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl,,1",vbNormalFocus) 显示器 rundll32.exe最後一个叁数0代表TabStrip中的第1个Tab 背景,若为1则为萤幕保护装置,以此类推) Call Shell("rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,1",vbNormalFocus) 使用Flying Windows.scr 当作Screen Saver Call Shell("rundll32.exe desk.cpl,InstallScreenSaver c:\windows\system\Flying Windows.scr", vbNormalFocus) 国别设定rundll32.exe最後一个叁数0代表TabStrip中的第1个Tab 国别,若为1则为 数字,以此类推) Call Shell("rundll32.exe shell32.dll,Control_RunDLL intl.cpl,,0",vbNormalFocus) JoyStick 设定 Call Shell("rundll32.exe shell32.dll,Control_RunDLL joy.cpl", vbNormalFocus) 滑鼠 Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @0",vbNormalFocus) 键盘 Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @1",vbNormalFocus) 印表机 Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @2",vbNormalFocus) Fonts Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @3",vbNormalFocus) 输入法 Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @4",vbNormalFocus) 电源 Call Shell("rundll32.exe shell32.dll,Control_RunDLL main.cpl @5",vbNormalFocus) MS Exchange设定 Call Shell("rundll32.exe shell32.dll,Control_RunDLL mlcfg32.cpl",vbNormalFocus) 多媒体 rundll32.exe最後一个叁数0代表TabStrip中的第1个Tab 音效,若为1则为影像,以此类推) Call Shell("rundll32.exe shell32.dll,Control_RunDLL mmsys.cpl,,0",vbNormalFocus) 声音 Call Shell("rundll32.exe shell32.dll,Control_RunDLL mmsys.cpl @1",vbNormalFocus) 数据机 Call Shell("rundll32.exe shell32.dll,Control_RunDLL modem.cpl",vbNormalFocus) 网路 Call Shell("rundll32.exe shell32.dll,Control_RunDLL netcpl.cpl",vbNormalFocus) 拨号网路 Call Shell("rundll32.exe rnaui.dll,RnaWizard", vbNormalFocus) 密码 Call Shell("rundll32.exe shell32.dll,Control_RunDLL password.cpl",vbNormalFocus) 系统 rundll32.exe最後一个叁数0代表TabStrip中的第1个Tab 一般,若为1则为装置管理员,以此类推) Call Shell("rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl,,0",vbNormalFocus) 新增硬体 精灵 Call Shell("rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl @1",vbNormalFocus) 新增印表机 精灵 Call Shell("rundll32.exe shell32.dll,SHHelpShortcuts_RunDLL AddPrinter",vbNormalFocus) 日期/时间 之日期时间 Call Shell("rundll32.exe shell32.dll,Control_RunDLL timedate.cpl",vbNormalFocus) 日期/时间 之时区 Call Shell("rundll32.exe shell32.dll,Control_RunDLL timedate.cpl,,/f",vbNormalFocus) Microsoft Mail 邮局 Call Shell("rundll32.exe shell32.dll,Control_RunDLL wgpocpl.cpl",vbNormalFocus) 开启档案 Call Shell("rundll32.exe shell32.dll,OpenAs_RunDLL d:\path\filename.ext",vbNormalFocus) 复制磁碟 Call Shell("rundll32 diskcopy.dll,DiskCopyRunDll", vbNormalFocus) Internet(ie4) Call Shell("rundll32.exe shell32.dll,Control_RunDLL inetcpl.cpl,,0",vbNormalFocus) 32位元ODBC Call Shell("rundll32.exe shell32.dll,Control_RunDLL odbccp32.cpl",vbNormalFocus) 拨号网路 Call Shell("undll32.exe shell32.dll,Control_RunDLL telephon.cpl users", vbNormalFocus)

TA的精华主题

TA的得分主题

 楼主| 发表于 2005-6-1 00:49 | 显示全部楼层

VB6.0动态加载ActiveX控件

---- 熟悉VB的朋友对使用ActiveX控件一定不会陌生,众多控件极大地方便了编程,但唯一的缺陷是不能动态加载控件,必须在设计时通过引用,将控件放置在窗体上。VB6.0已能够解决该问题,只是帮助中没有明确说明,并且没有描述到一些关键功能,由于以前的版本中可以动态创建进程外服务:如果对象是外部可创建的,可在 Set 语句中用 New 关键字、CreateObject 或 GetObject 从部件外面将对象引用赋予变量。如果对象是从属对象,则需使用高层对象的方法,在 Set 语句中指定一个对象引用: Dim xlApp1 As Excel.Application Set xlApp1 = New Excel.Application 或 Dim xlApp As Object '定义存放引用对象的变量。 Set xlApp = CreateObject("excel.application") xlApp.Visible = True ---- 这些语法很容易造成误导,以为动态加载ActiveX控件也是此方法,可能有朋友也象我一样利用CreateObject尝试了无数次,却无功而返,不知微软公司是出于何种考虑,动态加载ActiveX控件是扩展控件集合的方式实现,通过实际摸索,终于就如何实现动态ActiveX控件找出了一条切实可行的方法,下面以一个具体的实例来详细说明。 ---- 一、ActiveX控件 ---- ActiveX 控件是 Visual Basic 工具箱的扩充部分。使用 ActiveX 控件的方法与使用其它标准内装的控件,如 CheckBox 控件,完全一样。在程序中加入 ActiveX 控件后,它将成为开发和运行环境的一部分,并为应用程序提供新的功能。 ---- ActiveX 部件通过客户端/服务器关系与应用程序— 及与部件相互之间— 交互作用。客户端是使用部件功能的应用程序代码或部件。服务器是部件及其关联的对象。例如,假设应用程序使用 ActiveX 控件来提供一个标准的雇员窗体,供公司的多种应用程序使用。提供雇员窗体的 ActiveX 控件就是服务器,使用这个控件的应用程序就是服务器的客户端。 ---- 二、加载方法 ---- VB6.0中对Controls 集合进行了扩展,以前版本中Controls 集合在窗体上列举出已加载的控件,这在迭代过程中是很有用的。Controls 集合标识一个叫做 Controls 的内在窗体级变量。如果忽略可选的 object 所在处的整数,则关键字 Controls 必须包括在内。我们通常在窗口中使用如下代码: Text1.Text="Hello, world" 其实也可以使用如下代码达到同一目的: Controls(1).Text="Hello, world" ---- 在VB6.0中除了原来的Clear、Remove 方法外(很奇怪,为什么微软在VB5.0中只提供这两种方法,而没有提供Add方法,因为没有Add,这两种方法也就没什么用处),增加了Add方法,该方法就是用于动态加载控件的: Controls.Add(progid as String, Name as String); progid: ActiveX部件的ProgID,如:"VB.CheckBox"; Name:ActiveX部件加载后的名称,如: "MyCheckBox"; 若要在窗体上添加一个名为MyButton的按钮,可以使用: dim oControl as Object '窗体级变量 注意:这里声明为Object对象类型 Private Sub LoadControl() Set oControl = Controls.Add ("VB.CommandButton", "MyButton") oControl.Left = 10 oControl.Top = 10 oControl.Visible = True '使控件可见 End Sub ---- 这是VB6.0的标准语法,它在例程中也是如此演示的,不过该方法虽然现实了控件的动态加载,按钮显示在窗体上,可以象普通按钮一样按下去,但加载的控件不能预先设计响应事件代码,如:事件Sub MyButton_Click()将是非法的,当然,可以将要响应的事件封装在控件内部。就编程的观点来看该方法没什么大的用处,开发ActiveX控件的目的是为了资源共享,为了被其他开发人员利用,所以要提供必要的事件接口,显然利用该方法不行,通过分析VBControls等相关对象,找出VBControlExtender对象与EventInfo相结合能提供事件陷井捕捉,VBControlExtender对象对动态添加控件特别有用,它提供了一套通用的属性、方法、事件给开发人员,它的一个突出特点是能编程设计控件的事件,熟习类编程的朋友对带事件的对象声明一定不会陌生: ---- Dim WithEvents objElemt as CElemtVBControlExtender也不例外,声明的语法一样,只不过它有个特殊的事件ObjectEvent(Info As EventInfo),它能捕捉到对象使用RaiseEvent产生的所有事件,EventInfo数据结构映射了事件的名称、参数个数和参数的值。VBControlExtender和 EventInfo相结合,采用Select Case 就可以预先将不同类对象的事件放置一起,各自独立运作。将上面的代码改写一下就能提供Click事件了: ---- Dim WithEvents oControl As VBControlExtender '带事件声明声明之后您就可以在代码窗口的左上角的对象下拉框中发现该对象出现了,也就是说,该对象有了事件或方法了,它的事件有DragDrop,DragOver ,LostFocus ,GotFocus ,ObjectEvent和Validate,其中ObjectEvent是通用的事件捕捉。 Private Sub LoadControl() Set oControl = Controls.Add ("VB. CommandButton", "MyButton") oControl.Visible = True End Sub Private Sub oControl_ObjectEvent(Info As EventInfo) Select Case Info.Name Case "Click" 'Click事件 '您可以添加处理Click事件代码 MsgBox "您按了MyButton!" Case Else ' 其他事件 ' Handle unknown events here. End Select End Sub ---- 当然对微软提供的标准控件能采用该方法添加,大家都不会怀疑,但自己开发的控件也能吗?答案是肯定的,我们可以用一个实际的例子进行说明。 ---- 三、实例描述 ---- 假设一个本地网络的监控系统,需要在原理图与实物示意图间切换,原理图包括组网结构、传输资源、监控主机等,而实物示意图包括路由器、设备、采集器等,当然两种图的事件要一致,如双击某个设备图形将显示给设备的实时数据等,为了简化维护,将原理图与实物示意图封装成ActiveX控件,由于每种图需要加载许多图形控件,消耗资源较大,不能同时加载,需要将其分解为两个控件,在切换时首先卸载一个控件,然后加载另一个控件,所以要实现动态加载ActiveX控件。 ---- 原理图控件为--Theory.ocx ,对应工程为CTheory; ---- 实物图控件为---Fact.ocx ,对应工程为CFact; ---- 注意:为了简化,在设计控件时不设置许可证关键字。 ---- 实物图控件上的图形对象可以被拖动,拖动后的位置信息通过事件ChangePosition来通知拥有该控件的窗体,以便下次加载能显示在最后位置,实物图和原理图控件都有双击事件完成的工作相同,其他事件此处忽略。 ---- 四、具体示例 ---- 1、准备工作 ---- 对控件Theory.ocx 、Fact.ocx 进行注册(利用Regsvr32.exe注册); ---- 建立窗体frmTest.frm ,在窗体上放置按钮cmdLoadOcx—“原理图” ---- 2、声明窗体级变量与加载函数LoadControl Dim WithEvents oControl As VBControlExtender '地图仿真控件对象 Dim mblnTheory As Boolean '是否显示原理图 Private Function LoadControl(intType As Integer) If Not oControl Is Nothing Then '首先判断对象是否存在,若存在则卸载 Controls.Remove("MapView") '卸载控件,此操作非常重要 End If If intType = 0 Then Set oControl = Controls.Add ("CTheory. Theory", "MapView") Else Set oControl = Controls.Add ("CFact.Fact", "MapView") End If oControl.Height = 3500 oControl.Width = 6500 oControl.Top = 100 oControl.Visible = True End Function Private Sub Form_Load() mblnTheory = True End Sub ---- 3、为按钮cmdLoadOcx编写代码 Private Sub cmdLoadOCX_Click() If mblnTheory Then Call LoadControl(0) mblnTheory = False cmdLoadOCX.Caption = "实物图" Else Call LoadControl(1) mblnTheory = True cmdLoadOCX.Caption = "原理图" End If End Sub ---- 4、为事件ChangePosition编写代码 Private Sub oControl_ObjectEvent (Info As EventInfo) Select Case Info.Name Case "ChangePosition" MsgBox CStr(Info.EventParamters.Item( 1).Value) + ":" + _ CStr(Info.EventParamters.Item(2).Value) Case "DbClick" '双击处理代码 Case Else ' End Select End Sub ---- 注意: EventInfo的参数EventParamters集合中是以1开始的,一般来说,微软新的集合一般是以1开始的,而旧的是以0开始的,如RdoErrors.Item(0)。 ---- 5、关闭窗口前卸载控件 Private Sub Form_Unload(Cancel As Integer) Controls.Remove("MapView") Set oControl = Nothing End Sub ---- 6、特别注意 ---- 通过Controls.Add方法添加的ActiveX控件一定不能在该工程中有该控件的任何引用,否则系统将出错。 ---- 四、小结 ---- 通过使用动态加载ActiveX控件使用庞大的应用程序变得很小,将不同的ActiveX控件进行各种组合,使应用程序更加灵活多变,如您的应用系统要处理三十种门禁,而某个具体的用户可能只有一种或两种门禁,根本没必要首先将所有门禁包含到应用中,可将各个门禁独立封装,只安装注册需要的组件,就象Windows的自定义安装一样。微软的未来技术基础是分布式的组件技术(DCOM),将会把代码的重用发挥得淋漓尽致。您不妨试一试动态加载,也许会产生令您惊喜的效果! ---- 本代码在Win98、VB6.0(英文版)上编译、运行。

TA的精华主题

TA的得分主题

 楼主| 发表于 2005-6-1 00:53 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助

ActiveX .Exe .Dll Server的多执行绪

ActiveX .exe .dll Server都可以设定成多执行绪,但手册上面得大多有看没有 懂,尤其是Project本身可设定多执行绪,而Class又可设定成MultiUSe,搞得不是 很清楚,在经过一些testing後,整理出一些东西,以补足手册上的不清楚,看完它 再去看手册的说明就会比较清楚啦(我想)。如果我的观念有误,也请更正。 MyProject 假设有一个ActiveX Server,其内有一个Class和.bas Module组成 | +- Class | | | +--- MyClass | Public cp as long | Private cv as Long | Public Sub cFunc1() | . | . | End Function | Public Sub cFunc2() | . | . | End Sub | +- Module | +--- MyBas.Bas Public basVar as long Public Sub Sub1() . . End Sub 一、Myproject为ActiveX .exe Server 1.MyProject设定成单一执行绪 a.Myclass 为MultiUse 表示该Class的每一个Instance都是固定由某一个Thread来执行, 所谓MultiUse便是单一个执行绪可对多个Instance提供服务 client A 的程式 client B 的程式 dim aa as New MyClass dim bb as New Myclass Call aa.cFunc1 Call bb.cFunc2 如上例,ClientA ClientB同时对某一部电脑上的MyProject提出需求, 此时,第一个呼叫它的程式(假设是Call aa.cFunc1)将该Server叫起来了, 如果此时ClientB也对之提出要求,那麽的ClinetA aa这个instance和 Client B的bb Instance共用同一个Thread,也就是说这两个Instance必 需进入排程,共用一个Thread的情况又有什麽特性呢?此时aa, bb共用 MyBas.Bas内的资料,如上例中的basVar会共用,不论是改了它都会影响 另一个人,但是bb, aa这两个Class Instance有自己的变数,即aa 有自己 cp, cv的变数,bb也有自己的,这就是所谓的公寓模式,在.clx内的东西 各人有各人的,但共用.bas的资料。以这个例子来说,如果aa.cFunc1已在 执行,而且它是一个Long Job,而 bb.cFunc2也要来执行时,它便得进入 Queue中等待,所以会有交通阻塞的情况。 b.SingleUse 表示某个Class的instance 只在一个Thread上执行,同样以上面的例子来看 aa 这个Instance产生了,那它在Thread1 上执行,而同时bb也产生了,那 它会在另一个Thread2上执行,而Thread1, Thread2本身有自己的区域资料 ,所以aa, bb就不会用MyBas.Bas内的任何资料,正因aa, bb在不同的thread 上执行,所以它们两个便以排程的方式来执行,不会有谁先做,另一个人一定 得等,aa的thread先做,做了一段时间後会Swap变bb的Thread做,所以有可能 bb.cFunc2先完成。但缺点是浪费Resource。不要被MyProject的单一执行绪给 骗了,它是设成单一执行绪,但Class设成SingleUse时,仍有可能是 MultiThread的Server 另一个例子: Myproject中有二个Class,Class1为MultiUse, Class2为SingleUse 且有一个MyBas.Bas Module Dim aa as New Class1 Dim bb as New Class1 Dim aa2 as New Class2 Dim bb2 as New Class2 aa.method1 aa2.method2 bb.func1 bb2,func2 则aa, aa2, bb 共用mybas.bas之资料,bb2没有共用,aa, bb同一个thread 没有问题,aa2呢?那是因为SingleUse对象是Class,方才的thread虽有aa, bb 於其上,但那是Class1, Class2尚未有Instance於thread上,故选择方才的 thread来执行,而bb2自然得在另一个Thread上啦。这个前提是只有一个Client 来执行,如果有两个以上Client时,我们不知执行的前後顺序,所以不易预测 谁和谁共用资料。而我们可透过App.ThreadId来取得Thread的id,以上例来说 如果於Class1, Class2中各有引用App.ThreadId时,会发现aa, aa2, bb所取得 的是相同的,而bb2所取得者不相同。 2.MyProject设成多执行绪 假设我们设定MyProject有4个Thread (T1 to T4) T1 | | T4 ---+---- T2 | | T3 如果有Client1 to 3 呼叫Server的顺序如下 ClassA (multiUse) client1 要求 ClassA -->A1 则於 T1执行 ClassB (MultiUse) client2 要求 ClassA -->A2 .. T2 .. ClassC (multiUse) client1 .. ClassB -->B1 .. T3 .. Mybas.bas client3 .. ClassC -->C3 .. T4 .. client2 .. ClassC -->C2 .. T1 .. client1 .. ClassC -->C1 .. T2 .. 也就是说,Client 端每产生一个Instance,该Instance便会一在下一个Thread上 执行,如此的循环,所以这个例子来看,变成A1, C2共用mybas.bas的资料,A2, C1 是另一组共用者。如此造成谁和共用资料十分不明确,不过定的是每个.clx都有自己 的资料,符合公寓式模型。 另外,如果Instance A1(Client1所产生的ClassA Instance)於T1中执行,而A1中 去Create Instance B时,If使用New指令则Instance B也在T1中执行,故和A1共用 Mybas.Bas但,使用CreateObject则Instabce B会在T2中执行。 至於三个Class中有一个是SingleUse时会如何,本人已没有再测下去,可以知道的是 SingleUse的Class会找一个没有执行同一Class的Thread来执行,但如果4个Thread 都有时,会不会产生第五个Thread来做,可能吧,没有力testing了。 3.MyProject设成独立执行绪 同多执行绪,只是没有限制thread个数。 二、MyProject 是ActiveX. Dll Server 因是.Dll 的方式存在,所以Class不可能是SingleUse,因为一个Process 一不能有相同的两份.dll 1.MyProject设单一执行绪 客户端多执行绪.exe 有Thread 1 -3 thread1, thread2, thread3同时要求该class时,则後到的要等待 前面的处理完才能做,因该Server只有单一执行绪。各个Instance 共用.Bas内的资料。 2.MyProject设成公寓模型(多执行绪) 同上的状况来说,Thread1- 3 对该Class的运作,就没有谁等谁的情况。 因为 多执行绪.exe的各个Thread,都定义了Dll Server中的一问公寓 ,所有在执行绪中所建之的物件都住在该执行绪定义的公寓中,除非Thread3 使用Thread2的物件方法,那这呼叫会被排在Thread2本身目前正在处理的 物件之後(循序化)。

TA的精华主题

TA的得分主题

 楼主| 发表于 2005-6-1 00:57 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册

利用API创建文件目录

2001-11-13· · ··yesky Private Declare Function CreateDirectory Lib "kernel32" Alias "CreateDirectoryA" (ByVal lpPathName As String, lpSecurityAttributes As SECURITY_ATTRIBUTES) As Long Private Type SECURITY_ATTRIBUTES nLength As Long lpSecurityDescriptor As Long bInheritHandle As Long End Type Sub Main() 在C盘创建了"VB API"目录 Call CreateNewDirectory("C:\VB API") MsgBox "在C盘创建了 VB API 目录" End Sub Public Sub CreateNewDirectory(NewDirectory As String) Dim sDirTest As String Dim SecAttrib As SECURITY_ATTRIBUTES Dim bSuccess As Boolean Dim sPath As String Dim iCounter As Integer Dim sTempDir As String Dim iFlag As Integer iFlag = 0 sPath = NewDirectory If Right(sPath, Len(sPath)) <> "\" Then sPath = sPath & "\" End If iCounter = 1 Do Until InStr(iCounter, sPath, "\") = 0 iCounter = InStr(iCounter, sPath, "\") sTempDir = Left(sPath, iCounter) sDirTest = Dir(sTempDir) iCounter = iCounter + 1 创建目录 SecAttrib.lpSecurityDescriptor = &O0 SecAttrib.bInheritHandle = False SecAttrib.nLength = Len(SecAttrib) bSuccess = CreateDirectory(sTempDir, SecAttrib) Loop End Sub

TA的精华主题

TA的得分主题

发表于 2005-6-7 14:32 | 显示全部楼层
下了东东,还是要谢谢楼主

TA的精华主题

TA的得分主题

发表于 2005-6-24 15:27 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2005-8-20 15:17 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2005-8-22 17:43 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
正在学习中,非常需要。谢谢楼主分享。

TA的精华主题

TA的得分主题

发表于 2006-5-14 22:23 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-24 02:17 , Processed in 0.037003 second(s), 9 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表