ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android图书管理系统开发实战:从SQLite到RecyclerView的完整实现

Android图书管理系统开发实战:从SQLite到RecyclerView的完整实现 简介这是一份面向Android初学者与移动开发实践者的完整图书管理系统项目源码基于Android Studio平台构建聚焦SQLite本地数据库的增删改查核心操作助力开发者掌握移动端数据持久化开发全流程。资源包大小为23.26MB包含完整的Android Studio工程文件如Java/Kotlin源码、XML布局、SQLiteOpenHelper实现类、Activity逻辑及资源目录等结构清晰可直接导入编译运行。已有4057人学习下载反映出其在教学实践与课程设计中的广泛认可度。读者可获得一个功能完备的实战案例支持图书信息录入、列表展示、条件查询、编辑删除等基础管理能力并预留二维码扫码入库扩展接口便于后续集成Zxing等库实现高效数据采集同时代码规范、注释充分涵盖数据库版本升级、UI线程安全操作及常见异常处理是理解Android原生数据库开发的理想参考范本。一、项目概述安卓图书管理系统到底在做什么1.1 项目背景与核心定位如果你正在学安卓开发大概率绕不开图书管理系统这个经典练手项目。它几乎是实战版的教学项目比单纯的Hello World有营养得多又比电商类App简单得多非常适合用来打通界面搭建-数据存储-业务逻辑这条完整链路。这个系统跑在Android设备上用Android Studio开发和编译核心用途是模拟一个小型图书馆或者个人书库的管理流程管理员能录入新书、修改图书信息、删除下架书籍读者或管理员能完成借书、还书操作系统还要支持按书名、作者、分类等维度查询图书。整个项目不依赖后端服务器数据直接存在手机本地用Android自带的关系型数据库SQLite来实现持久化。说完这些先说结论这个项目非常适合两类人——第一类是刚学完Android基础Activity、布局、事件监听想找一个完整项目练手的学生第二类是负责期末课程设计、需要快速交付一个能演示、能答辩的安卓应用的开发者。因为你只需要一台电脑、一部装有Android Studio的机器或者一台安卓真机就能把整个系统从0到1跑起来不需要部署服务器、不需要申请联网接口、不需要花钱买云资源。1.2 为什么是Android Studio版本严格来说安卓项目的开发工具不止Android Studio一种Eclipse加ADT插件、IDEA加Android插件也能写。但Android Studio已经成为官方指定IDE它内置了布局预览器、模拟器管理、性能分析工具Profiler、Gradle构建系统同时对新版本安卓系统的适配也是最及时的。Android Studio版本这个后缀之所以值得单独强调是因为在不同开发环境下项目的配置方式、依赖管理方式、文件结构是有差异的。比如Eclipse时代的项目用Ant构建依赖包扔在libs目录里手动引入而Android Studio项目则基于Gradle所有依赖都写在build.gradle脚本里由Gradle统一从远程仓库拉取。如果你拿一个老Eclipse项目直接往Android Studio里塞大概率会报Project Structure错误甚至根本打不开。所以标注Android Studio版本就意味着我们这个项目的构建配置是贴合现代安卓开发工具链的导入即可运行版本兼容性经过验证。1.3 适合谁来参考能解决什么问题从功能边界来看这个项目解决的核心问题是纸质图书借阅登记效率低、容易出错的场景。现实中很多中小学图书馆、小型企业资料室还在用Excel表格甚至纸质台账记录借阅查询图书位置、统计哪些书逾期未还都非常痛苦。图书管理系统把这套流程搬上手机管理员随时可以打开App扫码式的登记、搜索减少人工抄写错误提高管理效率。对学习者来说这个项目最大的价值不在于图书管理这个业务本身而在于它覆盖了安卓应用开发的常见技术点多界面导航从登录页到主界面到详情页的跳转列表展示RecyclerView Adapter ViewHolder数据存储SQLite数据库的建表、增删改查用户交互输入框校验、按钮事件、列表点击、长按删除资源管理字符串国际化、主题样式定制。掌握这些后续再去啃复杂项目比如联网版的阅读App、笔记应用就有了底座。二、开发环境准备Android Studio安装配置与踩坑实录2.1 Android Studio版本选择与安装细节开发这个项目我建议使用当前稳定版Android Studio比如Koala或Ladybug版本以官网最新稳定版为准尽量别用Canary预览版。预览版经常有插件不上不下的情况写学习项目用不上新特性反而徒增烦恼。安装包可以从Android开发者官网下载。注意Windows用户下载.exe安装包macOS用户下载.dmg。安装过程本身没什么难度主要注意两点第一安装路径不要带中文和空格否则后面Gradle构建时会有不可名状的路径问题第二如果C盘空间紧张建议在安装时把SDK路径也改到D盘或者E盘Android Studio安装目录和SDK目录是分开的很多人后来发现C盘爆红就是因为SDK默认被塞在C:\Users\xxx\AppData\Local\Android\Sdk。装好之后第一次启动会有一个下载SDK的引导界面网络正常的情况下会自动拉取相关组件。如果网络不稳定或者想跳过引导可以后面手动在File Settings Appearance Behavior System Settings Android SDK里勾选需要的API Level。对于图书管理系统这种项目选一个比较大众的版本就够了建议API 30Android 11或者API 34Android 14前者兼容性好后者更贴近新机。2.2 汉化、SDK与镜像配置不少初学者问Android Studio怎么设置中文官方版本本质上没有中文语言包选项。网上所谓汉化其实是装了中文语言包插件Chinese Language Pack——在Settings Plugins中搜索Chinese安装插件后重启IDE即可。这里我建议新手装一下汉化插件降低找功能菜单的门槛但如果你准备走专业开发路线还是尽早切换到英文界面因为很多技术文档、报错信息、社区讨论都是英文的长期依赖汉化会阻碍你排查问题。SDK配置里最考验新手的其实是Gradle仓库的下载问题。Android Studio创建项目后Gradle会默认从谷歌的Maven仓库google()和mavenCentral()拉依赖部分地区的网络访问这些仓库非常慢甚至直接超时。通用做法是给项目根目录下的build.gradle或者settings.gradle加上阿里云镜像pluginManagement { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/public } google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } google() mavenCentral() } }注意仓库顺序把镜像仓库放在前面Gradle会优先从镜像拉取依赖。改完后在File Sync Project with Gradle Files重新同步即可。2.3 模拟器配置与真机调试备选方案Android Studio自带的AVDAndroid Virtual Device模拟器是调试的首选不用真机也能跑项目。但模拟器卡顿是很多人的噩梦尤其是电脑没有开启虚拟化技术的时候。创建模拟器时有两个关键配置系统镜像选择建议选x86_64架构的镜像如果你的电脑是Intel或AMD CPU不要选ARM架构因为ARM镜像在PC上跑效率极低。虚拟化开关Windows系统需要在BIOS里开启Intel VT-x或AMD-V然后确保Windows的虚拟机监控程序Hyper-V没有和HAXM冲突。如果你用的是比较新的Android Studio版本模拟器底层的加速方案已经换成了AEHDAndroid Emulator Hypervisor Driver兼容性比老的HAXM好。老项目如果报HAXM相关错误常见做法是在SDK Manager里下载Android Emulator Hypervisor Driver并安装或者检查Windows功能里是否同时启用了Hyper-V和Windows Hypervisor Platform二者冲突会导致模拟器起不来。如果你已经有安卓真机用真机调试更省事手机开启开发者模式在开发者选项里打开USB调试插线后在Android Studio的设备下拉列表里就能看到真机。需要注意新机型可能还要打开USB安装权限不然部署App时会提示安装失败。三、功能模块解析图书管理系统需要哪些核心能力3.1 数据层设计SQLite表结构图书管理系统最核心的部分是数据库。这个项目用的是Android内置的SQLite不需要额外引入数据库框架用SDK自带的SQLiteOpenHelper就能搞定。虽然现在业界有Room这类ORM框架但练手项目手写SQL更能帮助理解数据库原理。先看一下数据库设计。总共有三张核心表用户表user、图书表book和借阅记录表borrow。用户表CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, is_admin INTEGER DEFAULT 0 );图书表CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, author TEXT, publisher TEXT, price REAL, category TEXT, total INTEGER DEFAULT 1, remaining INTEGER DEFAULT 1, create_time TEXT );借阅记录表CREATE TABLE borrow ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, book_id INTEGER, borrow_time TEXT, return_time TEXT, status INTEGER DEFAULT 0 );设计细节说明一下图书表里我同时存了total总册数和remaining剩余可借册数这样同一本书有三册的时候借出一册就remaining-1还回来就remaining1不用每次借还书都遍历借阅记录统计剩余数量属于典型的冗余字段优化。借阅记录表里status字段用0/1表示借出中/已归还比删除记录、记时间戳的方式更直观。3.2 SQLiteOpenHelper实现与升级策略数据库的创建和版本升级都要通过SQLiteOpenHelper的子类完成重写onCreate和onUpgrade两个方法。示例代码public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME library.db; private static final int DB_VERSION 1; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE user (...)); db.execSQL(CREATE TABLE book (...)); db.execSQL(CREATE TABLE borrow (...)); // 插入默认管理员账号和初始样例数据 db.execSQL(INSERT INTO user(username, password, is_admin) VALUES(admin, 123456, 1)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS user); db.execSQL(DROP TABLE IF EXISTS book); db.execSQL(DROP TABLE IF EXISTS borrow); onCreate(db); } }这里有个很实用的建议开发阶段onUpgrade里直接DROP表重建没问题但如果是真实上线的App绝对不能这么干否则用户一升级数据全没了。正确的做法是写增量迁移脚本比如ALTER TABLE加字段。对于学习项目保留DROP重建逻辑倒是省事但建议你养成升级需谨慎的意识在代码注释里写明这个坑。3.3 核心功能实现思路用户登录登录页面输入用户名密码校验非空和数据库匹配。如果是管理员账号is_admin1登录后进入管理界面普通用户则进入借阅查询界面。图书录入管理页提供表单页字段对应book表字段。这里要注意价格字段的处理用户输入的是字符串Java里要Float.parseFloat转换搞不好就崩。实际开发里一定要try-catch。图书查询主界面顶部一个SearchView或者EditText搜索按钮按书名模糊查询SQL语句用WHERE name LIKE %关键字%即可。如果有余力还可以做分类筛选对category字段精确匹配。借书还书借书时先查book表里remaining是否大于0是则remaining-1同时插入一条borrow记录还书时更新对应borrow记录status为1return_time写当前时间再把book表的remaining1。这个逻辑用事务Transaction包起来防止中途出错导致数据不一致。四、Android Studio项目实现关键点拆解4.1 项目目录与Gradle配置新建项目时选Empty Views Activity如果选Compose模板代码风格会完全不同本项目的传统XML布局技能就显得更重要。项目结构里需要特别注意以下几个文件settings.gradle管理仓库、模块依赖。app/build.gradle项目的编译版本、应用的包名、依赖列表。核心配置android { namespace com.example.library compileSdk 34 defaultConfig { applicationId com.example.library minSdk 24 targetSdk 34 versionCode 1 versionName 1.0 } }minSdk 24意味着覆盖Android 7.0及以上设备市面上绝大多数手机都能跑。targetSdk 34意味着在新系统下Android会按新版规范处理一些行为变更比如权限模型、后台限制等。如果你的手机是Android 14出现一些奇怪的行为限制多半是targetSdk太高导致的行为变更可以先降targetSdk排查。4.2 UI布局实现的几种方式图书管理系统的界面虽然多但布局套路相对固定。我总结下来有四种页面类型登录/注册页使用LinearLayout纵向排列里面放两个EditText和一个Button。在真机上第一次运行会发现软键盘弹出来布局被顶上去可以在AndroidManifest.xml给对应Activity设置windowSoftInputModeadjustPan让整个页面自适应。图书列表页RecyclerViewLinearLayoutManager每个item一个card样式CardView包含书名、作者、剩余数量等文本和借阅按钮。图书编辑页ScrollView内部套LinearLayout多个EditText从数据库读取旧值填充保存时校验并更新数据库。统计/个人中心页简单展示当前用户、借阅数量、系统版本信息。用RecyclerView实现列表时大多数初学者会栽在Adapter的写法上。核心是ViewHolder缓存避免重复findViewById。示例Item布局是book_item.xmlAdapter关键代码public class BookAdapter extends RecyclerView.AdapterBookAdapter.BookHolder { private ListBook bookList; private OnItemClickListener listener; NonNull Override public BookHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_book, parent, false); return new BookHolder(view); } Override public void onBindViewHolder(NonNull BookHolder holder, int position) { Book book bookList.get(position); holder.tvName.setText(book.getName()); holder.tvAuthor.setText(book.getAuthor()); holder.tvRemaining.setText(剩余 book.getRemaining() 册); holder.itemView.setOnClickListener(v - { if (listener ! null) listener.onItemClick(book); }); } Override public int getItemCount() { return bookList null ? 0 : bookList.size(); } static class BookHolder extends RecyclerView.ViewHolder { TextView tvName, tvAuthor, tvRemaining; BookHolder(View itemView) { super(itemView); tvName itemView.findViewById(R.id.tv_name); tvAuthor itemView.findViewById(R.id.tv_author); tvRemaining itemView.findViewById(R.id.tv_remaining); } } }写完Adapter后列表Activity里做三件事创建Adapter、设置LayoutManager、把查询到的数据源set进去。这个模式适用于所有列表场景建议彻底吃透。4.3 数据操作封装DAO层为了让代码不至于写成所有SQL都堆在Activity里建议加一个简单的DAO层。每个实体类写一个DAO类负责这个表所有的insert/delete/update/query操作。以BookDAO为例public class BookDAO { private DBHelper dbHelper; public BookDAO(Context context) { dbHelper new DBHelper(context); } public long insertBook(Book book) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(name, book.getName()); values.put(author, book.getAuthor()); values.put(publisher, book.getPublisher()); values.put(price, book.getPrice()); values.put(category, book.getCategory()); values.put(total, book.getTotal()); values.put(remaining, book.getRemaining()); values.put(create_time, getNowTime()); long id db.insert(book, null, values); db.close(); return id; } public ListBook queryBooks(String keyword) { ListBook list new ArrayList(); SQLiteDatabase db dbHelper.getReadableDatabase(); String sql SELECT * FROM book WHERE name LIKE ?; Cursor cursor db.rawQuery(sql, new String[]{% keyword %}); while (cursor.moveToNext()) { Book book new Book(); book.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); book.setName(cursor.getString(cursor.getColumnIndexOrThrow(name))); // 其他字段赋值... list.add(book); } cursor.close(); db.close(); return list; } }多提一句每次操作完记得cursor.close()和db.close()不然连接池会满。虽然项目中数据量不大看不出问题但这个习惯是专业开发者应该有的。五、踩坑实录运行图书管理系统时的高频问题5.1 Gradle构建失败与换源这类问题的复现概率几乎100%基本每个初学者都会碰到。现象是同步项目时一直转圈、报Could not resolve com.android.tools.build:gradle:xxx或者Connection refused。除了前面提到的阿里云镜像之外还有一个选项是直接使用本地Gradle压缩包。在Android Studio的Settings Build Tools Gradle里可以指定本地distribution如果你在Gradle官网预先下载了完整压缩包选择Local Gradle distribution并指到那个zip包的路径即可。这个方法在没有外网的环境下非常有用。还有一种情况项目里用的Android Gradle Plugin版本和当前Android Studio版本不匹配。比如Android Studio版本较老却在build.gradle里写了AGP 8.0就会报Minimum supported Gradle version is 8.0这类错误。解决办法是进入Project Structure Project把Android Gradle Plugin版本改成当前IDE推荐的版本。5.2 模拟器界面卡顿、启动黑屏启动模拟器后黑屏或者操作明显延迟通常有三个原因第一模拟器分辨率和电脑屏幕不匹配过高的分辨率对CPU和GPU压力大。建议创建AVD时选择中等分辨率比如1080x1920密度选320dpi或者400dpi不要一上来就选Phone大屏高刷新。第二模拟器的图形加速模式不对。在模拟器设置Extended Controls Settings里把Graphics选成Hardware- GLES 2.0如果还卡再换Software渲染至少能跑起来。第三宿主机没有开启硬件虚拟化。如果你用Windows可以在命令提示符里输入systeminfo查看底部的Hyper-V要求是否显示已启用。如果显示检测到虚拟机监控程序将不显示Hyper-V所需的功能说明Hyper-V没开。这时候可以到启用或关闭Windows功能里勾选Windows虚拟机监控程序平台和虚拟机平台重启电脑后一般就能正常跑模拟器了。5.3 RecyclerView不显示数据这是经典的新手问题明明在Activity里查到了list数据源非空但界面上什么都不显示。原因大概率是忘了调用adapter.notifyDataSetChanged()。正确姿势是你更新了RecyclerView的数据源之后一定要通过adapter更新界面。写法是adapter.setBookList(newList); adapter.notifyDataSetChanged(); 如果做了搜索功能每次搜索完都要重新设置整个列表否则列表内容还是旧的。另一个隐蔽的坑是item布局里根节点高度设置成了wrap_content导致item高度为0看起来像是没数据。我在做图书列表时第一次就踩了这坑最后发现是布局的高度问题。5.4 老项目迁移64位与兼容性问题如果你手头有旧的Cocos2d-x、老安卓项目需要迁移到64位环境或者你只是想把这个图书管理系统放在新手机上跑需要关注一下.so文件的兼容性。Android应用从2019年开始要求支持64位架构如果项目里有jniLibs目录下的so库需要同时包含arm64-v8a和armeabi-v7a目录纯Java写法的图书管理系统没有so库所以天然兼容64位不用担心。真机调试时如果老机型安装失败另一个常见原因是INSTALL_FAILED_UPDATE_INCOMPATIBLE说明手机上已经有另一个签名的同名应用。卸载旧应用再装即可如果是测试机也可以直接把debug签名换成release签名来避免频繁卸载。六、从能跑到好看体验优化与功能扩展建议6.1 交互细节上的优化点一个图书管理系统如果只是能用距离好用还有很长一段路。我建议在基础功能之外至少加入以下几个层面的优化第一空数据处理。图书列表为空时RecyclerView底部就是一张白纸用户根本不知道怎么回事。用LinearLayout或者ProgressBar做一个空视图查询不到结果时显示未找到相关图书同时隐藏RecyclerView。实现思路很简单在Activity里拿到查询结果后先根据list.isEmpty()切换两个View的可见性。第二操作反馈。借书、还书这种操作必须有Toast提示不然用户会连续点击产生重复数据。更稳妥的做法是在借阅按钮的点击事件里先禁用按钮处理完再恢复。第三颜色与状态。用不同的背景色区分可借图书和已全部借出的图书这个可以通过Adapter的bind逻辑里condition判断来实现。读者的使用体验会好很多。第四搜索防抖动。如果在EditText中输入文字就实时搜索注意InputType和TextWatcher会频繁触发数据库查询。数据量小问题不大但为了演示还是建议加一个short delay比如300ms或者索性用搜索按钮手动触发。6.2 功能扩展方向图书管理系统做完基础版之后你可以按下面的优先级逐步迭代这会直接提升你的安卓水平分级权限管理员能管理图书普通用户只能借阅查询。需要把登录角色用SharedPreferences保存再在不同界面做权限判断。借阅历史在我的页面展示当前用户的所有借阅记录包括已还和借出中的用另一个RecyclerView展示。逾期提醒借出时间加上最大借阅天数超过就标注红色已逾期。这需要本地日期计算能练习SimpleDateFormat和Calendar的用法。导入/导出把图书数据导出成CSV文件放到公共存储目录再用系统分享发送。这个功能涉及文件读写是很有价值的练习。拍照封面用CameraX或Intent调起相机给图书拍照封面存本地文件路径存数据库。这个扩展会牵涉到权限申请Android 6.0的动态权限、Android 13的读写媒体权限是进阶必备。每个扩展方向都对应一个Android技能点做完这些你的Android开发能力会比单纯抄课程设计提升一大截。七、完整运行流程示例从创建项目到借书还书7.1 第一次运行登录页输入默认管理员账号admin/123456数据库初始化时预置点击登录如果登录成功跳到MainActivity。如果提示登录失败多半是数据库没创建成功或者在登录SQL里传了空值检查一下SQLiteDatabase对象的初始化。一个常见问题是在DBHelper.onCreate里写了INSERT语句但没生效表现是无论输入什么都登录不上。排查方法是把/data/data/com.example.library/databases/library.db文件从模拟器导出用SQLite工具打开看一眼表里的数据。Android Studio自带Database Inspector工具在View Tool Windows Database Inspector连接设备后能实时查看应用的SQLite数据库强烈推荐你熟悉这个工具调试数据问题效率翻倍。7.2 图书录入流程登录后进入主界面点击添加按钮跳转到BookEditActivity。输入书名、作者、出版社、价格小数、分类、总数量。保存时校验必填字段书名不能为空价格如果输入的不是数字就try-catch然后调用BookDAO.insertBook()。录入成功后返回列表页这段逻辑需要在onActivityResult里接收新增结果并刷新列表或者用startActivityForResult从Activity Result API的角度推荐用registerForActivityResult旧的startActivityForResult在代码复杂度上比较尴尬。如果你用的是新版AndroidX强烈建议用registerForActivityResultprivate ActivityResultLauncherIntent editLauncher registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result - { if (result.getResultCode() RESULT_OK) { refreshBookList(); } } );7.3 借书还书流程在列表页点击某一个图书item进入详情页或直接弹窗。借阅时先去查当前图书的remaining若为0提示不可借阅。借出成功后将remaining减一然后在borrow表插入记录borrow_time取当前时间status0。还书流程在我的借阅列表页找到借出中的记录点击还书更新那条记录的status1并设置return_time同时对应图书的remaining加一。如果这是管理员代还图书还要先在界面选择借阅人但本项目的简化版本可以直接绑定当前登录用户。这里有一个代码层面的建议借还书的数据库操作要放在同一个数据库事务里执行确保多个表的数据一致性。虽然学习项目偶尔失败也不会出大问题但你养成这个习惯之后接触后端涉及分布式事务的时候会轻松很多。八、老生常谈写在最后的几点个人建议做这个图书管理系统我最大的体会是这类管理系统项目重点从来不在UI多炫酷而在数据闭环。登录态、增删改查、状态流转每一步都要想清楚数据从哪里来、到哪里去。项目里每写一个功能都先问自己一句如果数据库里的数据是脏的、字段是空的、用户传了异常值页面会不会崩多问几遍代码质量自然就上去了。另外很多同学会习惯性去GitHub搜别人的图书管理系统代码然后直接抄。我的建议是可以参考但务必自己手写一遍。抄代码时觉得理所当然自己写RecyclerView的Adapter可能要卡三四个小时。这不是浪费时间恰恰是必经之路。从一个空Activity开始一步步搭出登录、建表、做列表、测试借还书逻辑这个过程走完一遍比你刷十套视频教程都更靠谱。最后再分享一个小技巧如果你想把书架的封面照片存到数据库里不要直接存Bitmap对象的文件流而是存图片文件的路径图片文件本身压缩后放到应用私有目录。这样数据库不会膨胀加载列表时用Glide加载图片性能会比裸存Base64字符串好得多。这个思路书本上也写过但只有真正跑过项目的人才会特别注意。本文还有配套的精品资源点击获取
返回列表