游戏开发论坛

 找回密码
 立即注册
搜索
查看: 3839|回复: 6

请问怎么在mfc窗口上面显示DX画的3D场景?

[复制链接]

14

主题

88

帖子

92

积分

注册会员

Rank: 2

积分
92
发表于 2005-11-25 00:23:00 | 显示全部楼层 |阅读模式
请问怎么在mfc窗口上面显示DX画的3D场景?  请高手指点~~

60

主题

1319

帖子

1319

积分

金牌会员

Rank: 6Rank: 6

积分
1319
发表于 2005-11-25 03:30:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

得到窗口句柄,然后用这个句柄创建Device。
DX9SDK的工具Effect Edit就是MFC程序,可以看看它的源码.它显示3D场景用的是一个图片控件

5

主题

41

帖子

41

积分

注册会员

Rank: 2

积分
41
发表于 2005-11-25 12:22:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

有一种非常直接方法,调用IDirect3DDevice9:resent函数,把其中hDestWindowOverride参数设置成你相要画的的窗口HWND就可以了!

14

主题

88

帖子

92

积分

注册会员

Rank: 2

积分
92
 楼主| 发表于 2005-11-25 14:01:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

非常感谢楼上的啊...
但是这样笼统的说还不太能解决问题,大家是否有相关的代码?
事实上我这里有类似功能的代码但是我不知道怎么用...

// ======================================================================================

// 文件:main.cpp

// ======================================================================================



// ======================================================================================

// 定义预处理器

// ======================================================================================



#ifndef VC_EXTRALEAN

#define VC_EXTRALEAN

#endif



#ifndef _AFX_ALL_WARNINGS

#define _AFX_ALL_WARNINGS

#endif



// ======================================================================================

// 标准包含文件

// ======================================================================================



#ifdef _DEBUG

#pragma comment(lib, "d3dx9d.lib")

#else

#pragma comment(lib, "d3dx9.lib")

#endif

#pragma comment(lib, "d3d9.lib")

#pragma comment(lib, "winmm.lib")



#include <afxwin.h>

#include <d3dx9.h>

#pragma warning(disable: 4201)

#include <Mmsystem.h>

#pragma warning(default: 4201)



// ======================================================================================

// 宏定义

// ======================================================================================



#ifdef _DEBUG

#define new DEBUG_NEW

#endif



#ifdef _DEBUG

#define DECLARE_DUMP()
     public:
         virtual void AssertValid() const;
         virtual void Dump(CDumpContext &dc) const;

#else

#define DECLARE_DUMP()

#endif



#ifdef _DEBUG

#define IMPLEMENT_DUMP(class_name, base_class_name)
     void class_name::AssertValid() const {base_class_name::AssertValid();}
     void class_name:ump(CDumpContext &dc) const {base_class_name::Dump(dc);}

#else

#define IMPLEMENT_DUMP(class_name, base_class_name)

#endif



// ======================================================================================

// Direct3D 封装类

// ======================================================================================



#define SafeRelease(pObject) if(pObject != NULL) {pObject->Release(); pObject = NULL;}

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE)

#define D3DUSAGE_NONE 0

#define D3DLOCK_NONE 0



struct CUSTOMVERTEX

{

     FLOAT x, y, z;

     DWORD diffuse;

};



// //////////////////////////////////////////////////////////////////////////////////////

// CDirect3D 类



class CDirect3D : public CObject

{

     DECLARE_DYNAMIC(CDirect3D)

     DECLARE_DUMP()



public:

     CDirect3D();

     virtual ~CDirect3D();



private:

     static LPDIRECT3D9 m_pD3D;

     static LPDIRECT3DDEVICE9 m_pD3DDevice;



protected:

     BOOL CreateD3D();

     VOID ReleaseD3D();

     HRESULT CreateD3DDevice(HWND);

     VOID ReleaseD3DDevice();

     HRESULT CreateObject();

     VOID ReleaseObject();

     LPDIRECT3D9 GetD3D() const;

     LPDIRECT3DDEVICE9 GetD3DDevice() const;

     HRESULT SetRenderState(D3DRENDERSTATETYPE state, DWORD value);

     HRESULT SetTransform(D3DTRANSFORMSTATETYPE state, const D3DMATRIX * pMatrix);

     HRESULT Clear(D3DCOLOR color, DWORD count = 0, const D3DRECT * pRects = NULL,

         DWORD flags = D3DCLEAR_TARGET, FLOAT z = 1.0f, DWORD stencil = 0);

     HRESULT BeginScene();

     HRESULT EndScene();

     HRESULT Present(const RECT * pSourceRect = NULL, const RECT * pDestRect = NULL,

         HWND hDestWindowOverride = NULL, const RGNDATA * pDirtyRegion = NULL);

     HRESULT SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 * pStreamData,

         UINT OffsetInBytes, UINT Stride);

     HRESULT SetFVF(DWORD FVF);

     HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex,

         UINT PrimitiveCount);

};



IMPLEMENT_DYNAMIC(CDirect3D, CObject)

IMPLEMENT_DUMP(CDirect3D, CObject)



LPDIRECT3D9 CDirect3D::m_pD3D = NULL;

LPDIRECT3DDEVICE9 CDirect3D::m_pD3DDevice = NULL;



CDirect3D::CDirect3D()

{

     ASSERT(m_pD3D == NULL);

     ASSERT(m_pD3DDevice == NULL);

}



CDirect3D::~CDirect3D()

{

     if(m_pD3DDevice != NULL)

     {

         TRACE0("警告:使用 CDirect3D 析构函数释放 D3DDevice 接口\n");

         ReleaseD3DDevice();

     }

     if(m_pD3D != NULL)

     {

         TRACE0("警告:使用 CDirect3D 析构函数释放 D3D 接口\n");

         ReleaseD3D();

     }

}



BOOL CDirect3D::CreateD3D()

{

     if(NULL == (m_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))

         return FALSE;

     else

         return TRUE;

}



VOID CDirect3D::ReleaseD3D()

{

     SafeRelease(m_pD3D);

}



HRESULT CDirect3D::CreateD3DDevice(HWND hWnd)

{

     ASSERT(IsWindow(hWnd));

     ASSERT(m_pD3D != NULL);

     HRESULT hr;

     D3DDISPLAYMODE d3ddm;

     hr = m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

     if(FAILED(hr))

         return hr;

     D3DPRESENT_PARAMETERS d3dpp;

     ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));

     {

         d3dpp.Windowed = TRUE;

         d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

         d3dpp.BackBufferFormat = d3ddm.Format;

         d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

     }

     hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,

         D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &m_pD3DDevice);

     return hr;

}



VOID CDirect3D::ReleaseD3DDevice()

{

     SafeRelease(m_pD3DDevice);

}



inline LPDIRECT3D9 CDirect3D::GetD3D() const

     {return m_pD3D;}



inline LPDIRECT3DDEVICE9 CDirect3D::GetD3DDevice() const

     {return m_pD3DDevice;}



inline HRESULT CDirect3D::SetRenderState(D3DRENDERSTATETYPE state, DWORD value)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->SetRenderState(state, value);}



inline HRESULT CDirect3D::SetTransform(D3DTRANSFORMSTATETYPE state, const D3DMATRIX * pMatrix)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->SetTransform(state, pMatrix);}



inline HRESULT CDirect3D::Clear(D3DCOLOR color, DWORD count,

     const D3DRECT * pRects, DWORD flags, FLOAT z, DWORD stencil)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->Clear(

         count, pRects, flags, color, z, stencil);}



inline HRESULT CDirect3D::BeginScene()

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->BeginScene();}



inline HRESULT CDirect3D::EndScene()

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->EndScene();}



inline HRESULT CDirect3D:resent(const RECT * pSourceRect, const RECT * pDestRect ,

     HWND hDestWindowOverride, const RGNDATA * pDirtyRegion)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice-&gtresent(

         pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);}



inline HRESULT CDirect3D::SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 * pStreamData,

     UINT OffsetInBytes, UINT Stride)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->SetStreamSource(StreamNumber, pStreamData,

         OffsetInBytes, Stride);}



inline HRESULT CDirect3D::SetFVF(DWORD FVF)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->SetFVF(FVF);}



inline HRESULT CDirect3D::DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex,

     UINT PrimitiveCount)

     {ASSERT(m_pD3DDevice != NULL); return m_pD3DDevice->DrawPrimitive(

         PrimitiveType, StartVertex, PrimitiveCount);}



// //////////////////////////////////////////////////////////////////////////////////////

// CGame 类



class CGame : public CDirect3D

{

     DECLARE_DYNAMIC(CGame)

     DECLARE_DUMP()



public:

     CGame();

     virtual ~CGame();



private:

     LPDIRECT3DVERTEXBUFFER9 m_pD3DVertexBuffer;

     HWND m_hWnd;



protected:

     HRESULT CreateD3DVertexBuffer();

     VOID ReleaseD3DVertexBuffer();

     VOID SetupMatrices();



public:

     HRESULT CreateGame(HWND);

     VOID DistroyGame();

     VOID Render();

};



IMPLEMENT_DYNAMIC(CGame, CDirect3D)

IMPLEMENT_DUMP(CGame, CDirect3D)



CGame::CGame()

{

     m_pD3DVertexBuffer = NULL;

     m_hWnd = NULL;

}



CGame::~CGame()

{

     if(m_pD3DVertexBuffer != NULL)

     {

         TRACE0("警告:使用 CGame 析构函数释放 D3DVertexBuffer 接口\n");

         ReleaseD3DVertexBuffer();

     }

}



HRESULT CGame::CreateD3DVertexBuffer()

{

     ASSERT(GetD3DDevice() != NULL);

     HRESULT hr;

     hr = GetD3DDevice()->CreateVertexBuffer(sizeof(CUSTOMVERTEX) * 14, D3DUSAGE_NONE,

         D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pD3DVertexBuffer, NULL);

     if(FAILED(hr))

         return hr;

     ASSERT(m_pD3DVertexBuffer != NULL);

     CUSTOMVERTEX vertices[] =

     {

         {-5.0f,  5.0f,  5.0f, D3DCOLOR_XRGB(255,   0,   0),},   // 0

         {-5.0f, -5.0f,  5.0f, D3DCOLOR_XRGB(  0, 255,   0),},   // 1

         { 5.0f,  5.0f,  5.0f, D3DCOLOR_XRGB(  0,   0, 255),},   // 2

         { 5.0f, -5.0f,  5.0f, D3DCOLOR_XRGB(255, 255,   0),},   // 3

         { 5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(255,   0,   0),},   // 4

         {-5.0f, -5.0f,  5.0f, D3DCOLOR_XRGB(  0, 255,   0),},   // 5

         {-5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(  0,   0, 255),},   // 6

         {-5.0f,  5.0f,  5.0f, D3DCOLOR_XRGB(255,   0,   0),},   // 7

         {-5.0f,  5.0f, -5.0f, D3DCOLOR_XRGB(  0, 255,   0),},   // 8

         { 5.0f,  5.0f,  5.0f, D3DCOLOR_XRGB(  0,   0, 255),},   // 9

         { 5.0f,  5.0f, -5.0f, D3DCOLOR_XRGB(255, 255,   0),},   // 10

         { 5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(255,   0,   0),},   // 11

         {-5.0f,  5.0f, -5.0f, D3DCOLOR_XRGB(  0, 255,   0),},   // 12

         {-5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(  0,   0, 255),},   // 13

     };

     VOID * pVertices = NULL;

     hr = m_pD3DVertexBuffer->Lock(0, sizeof(CUSTOMVERTEX) * 14, &pVertices, D3DLOCK_NONE);

     if(FAILED(hr))

         return hr;

     {

         memcpy(pVertices, vertices, sizeof(CUSTOMVERTEX) * 14);

     }

     m_pD3DVertexBuffer->Unlock();

     return S_OK;

}



VOID CGame::ReleaseD3DVertexBuffer()

{

     SafeRelease(m_pD3DVertexBuffer);

}



VOID CGame::SetupMatrices()

{

     {

         FLOAT fAngle = timeGetTime() / 400.0f;

         D3DXMATRIX matX, matY, matZ, matWorld;

         D3DXMatrixRotationX(&matX, fAngle);

         D3DXMatrixRotationY(&matY, fAngle);

         D3DXMatrixRotationZ(&matZ, fAngle);

         D3DXMatrixMultiply(&matWorld, &matX, &matY);

         D3DXMatrixMultiply(&matWorld, &matWorld, &matZ);

         SetTransform(D3DTS_WORLD, &matWorld);

     };

     {

         D3DXVECTOR3 vEye(0.0f, 0.0f, -30.0f);

         D3DXVECTOR3 vLookAt(0.0f, 0.0f, 0.0f);

         D3DXVECTOR3 vLookUp(0.0f, 1.0f, 0.0f);

         D3DXMATRIX matView;

         D3DXMatrixLookAtLH(&matView, &vEye, &vLookAt, &vLookUp);

         SetTransform(D3DTS_VIEW, &matView);

     };

     {

         FLOAT fovAngle = D3DX_PI / 4,

              rate = 1.0f,

              frontClipping = 1.0f,

              backClipping = 500.0f;

         D3DXMATRIX matProj;

         D3DXMatrixPerspectiveFovLH(

              &matProj

              ,fovAngle

              ,rate

              ,frontClipping

              ,backClipping

              );

         SetTransform(D3DTS_PROJECTION, &matProj);

     };

}



HRESULT CGame::CreateGame(HWND hWnd)

{

     ASSERT(IsWindow(hWnd));

     m_hWnd = hWnd;

     if(!CreateD3D())

         return E_FAIL;

     HRESULT hr = CreateD3DDevice(m_hWnd);

     if(FAILED(hr))

         return hr;

     {

         SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

         SetRenderState(D3DRS_LIGHTING, FALSE);

     }

     if(FAILED(CreateD3DVertexBuffer()))

         return hr;

     return S_OK;

}



VOID CGame::DistroyGame()

{

     ReleaseD3DVertexBuffer();

     ReleaseD3DDevice();

     ReleaseD3D();

}



VOID CGame::Render()

{

     if(GetD3DDevice() == NULL)

         return;

     Clear(D3DCOLOR_XRGB(100, 150, 100));

     if(SUCCEEDED(BeginScene()))

     {

         {

              SetupMatrices();

              SetStreamSource(0, m_pD3DVertexBuffer, 0, sizeof(CUSTOMVERTEX));

              SetFVF(D3DFVF_CUSTOMVERTEX);

              DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 12);

         }

         EndScene();

     }

     Present();

}



// ======================================================================================

// 应用程序框架类

// ======================================================================================



// //////////////////////////////////////////////////////////////////////////////////////

// CChildView 类



class CChildView : public CWnd

{

     DECLARE_DYNAMIC(CChildView)

     DECLARE_MESSAGE_MAP()

     DECLARE_DUMP()



public:

     CChildView();

     virtual ~CChildView();



protected:

     //{{AFX_MSG(CChildView)

     //}}AFX_MSG

};



IMPLEMENT_DYNAMIC(CChildView, CWnd)

BEGIN_MESSAGE_MAP(CChildView, CWnd)

     //{{AFX_MSG_MAP(CChildView)

     //}}AFX_MSG_MAP

END_MESSAGE_MAP()

IMPLEMENT_DUMP(CChildView, CWnd)



CChildView::CChildView() {}



CChildView::~CChildView() {}



// //////////////////////////////////////////////////////////////////////////////////////

// CMainFrame 类



class CMainFrame : public CFrameWnd

{

     DECLARE_DYNAMIC(CMainFrame)

     DECLARE_MESSAGE_MAP()

     DECLARE_DUMP()



public:

     CMainFrame();

     virtual ~CMainFrame();



protected:

     CChildView m_wndView;

     CGame m_game;



public:

     HRESULT CreateGame();

     VOID Render();



protected:

     //{{AFX_MSG(CMainFrame)

     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

     afx_msg void OnDestroy();

     afx_msg BOOL OnEraseBkgnd(CDC* pDC);

     afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

     //}}AFX_MSG

};



IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

     //{{AFX_MSG_MAP(CMainFrame)

     ON_WM_CREATE()

     ON_WM_DESTROY()

     ON_WM_ERASEBKGND()

     ON_WM_KEYDOWN()

     //}}AFX_MSG_MAP

END_MESSAGE_MAP()

IMPLEMENT_DUMP(CMainFrame, CFrameWnd)



CMainFrame::CMainFrame() {}



CMainFrame::~CMainFrame() {}



inline HRESULT CMainFrame::CreateGame()

     {return m_game.CreateGame(m_wndView.m_hWnd);}



inline VOID CMainFrame::Render()

     {m_game.Render();}



int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

     if(CFrameWnd::OnCreate(lpCreateStruct) == -1)

         return -1;

     if(!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this,

         AFX_IDW_PANE_FIRST, NULL))

     {

         TRACE0("错误:未能创建 ChildView\n");

         return -1;

     }

     return 0;

}



void CMainFrame::OnDestroy()

{

     m_game.DistroyGame();

     CFrameWnd::OnDestroy();

}



BOOL CMainFrame::OnEraseBkgnd(CDC *)

{

     return TRUE;

}



void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

     CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);

     if(nChar == VK_ESCAPE)

         DestroyWindow();

}



// //////////////////////////////////////////////////////////////////////////////////////

// CDXDemoApp 类



class CDXDemoApp : public CWinApp

{

     DECLARE_DYNAMIC(CDXDemoApp)

     DECLARE_MESSAGE_MAP()

     DECLARE_DUMP()



public:

     CDXDemoApp(LPCTSTR lpszAppName = NULL);

     virtual ~CDXDemoApp();



protected:

     virtual BOOL InitInstance();

     virtual BOOL ExitInstance();

     virtual int Run();



protected:

     //{{AFX_MSG(CDXDemoApp)

     //}}AFX_MSG

};



IMPLEMENT_DYNAMIC(CDXDemoApp, CWinApp)

BEGIN_MESSAGE_MAP(CDXDemoApp, CWinApp)

     //{{AFX_MSG_MAP(CDXDemoApp)

     //}}AFX_MSG_MAP

END_MESSAGE_MAP()

IMPLEMENT_DUMP(CDXDemoApp, CWinApp)



CDXDemoApp::CDXDemoApp(LPCTSTR lpszAppName)

     : CWinApp(lpszAppName) {}



CDXDemoApp::~CDXDemoApp() {}



BOOL CDXDemoApp::InitInstance()

{

     CWinApp::InitInstance();

     CMainFrame * pMainWnd = new CMainFrame;

     CRect rect(100, 100, 100 + 400, 100 + 400);

     ASSERT(pMainWnd != NULL);

     if(!pMainWnd->Create(NULL, "DXDemo", WS_OVERLAPPEDWINDOW, rect))

         return FALSE;

     m_pMainWnd = pMainWnd;

     pMainWnd->ShowWindow(SW_SHOWDEFAULT);

     pMainWnd->UpdateWindow();

     if(FAILED(pMainWnd->CreateGame()))

         return FALSE;

     return TRUE;

}



BOOL CDXDemoApp::ExitInstance()

{

     return CWinApp::ExitInstance();

}



int CDXDemoApp::Run()

{

     ASSERT(m_pMainWnd != NULL);

     ASSERT_VALID(this);

     _AFX_THREAD_STATE* pState = AfxGetThreadState();

     BOOL bIdle = TRUE;

     LONG lIdleCount = 0;

     for(;;)

     {

         if(::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))

         {

              if(!PumpMessage())

                   return ExitInstance();

              if(bIdle == FALSE)

              {

                   bIdle = TRUE;

                   lIdleCount = 0;

              }

         }

         else

         {

              if(bIdle && !OnIdle(lIdleCount++))

                   bIdle = FALSE;

              ((CMainFrame *)m_pMainWnd)->Render();

         }

     }

}



// ======================================================================================

// 应用程序主对象

// ======================================================================================



CDXDemoApp theApp;

182

主题

445

帖子

459

积分

中级会员

Rank: 3Rank: 3

积分
459
QQ
发表于 2005-11-25 23:36:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

如果你是用单文档。
可以参考网上的PFD书《dx9初级入门教程》第一单就是在MFC下建窗口渲D3D。
如果你不创建CWND,可以封底个D3D渲染类在单文档多文档中的CView里渲。然后用SetTimer,定时Invalidate(),另外CView的初始化OnInitUpdate什么函数里初始化D3D,用GetSafeWnd()取窗口句柄。
如果是基于对话框的。
更简单,GetSafeWnd()取窗口句柄。然后OnPaint里渲,用定时器Invalidate.
呵呵~
可看到我做的效果
http://bbs.gameres.com/showthread.asp?threadid=41455

14

主题

88

帖子

92

积分

注册会员

Rank: 2

积分
92
 楼主| 发表于 2005-11-26 00:37:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

谢谢楼上的啊,你做的正是我所需要的~~
不知道你能否提供联系方式?

182

主题

445

帖子

459

积分

中级会员

Rank: 3Rank: 3

积分
459
QQ
发表于 2005-11-27 14:54:00 | 显示全部楼层

Re:请问怎么在mfc窗口上面显示DX画的3D场景?

我已经说的很明白了,你不懂可以提问.难道说我把我工具源程序都提供给你?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-22 18:37

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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