简介在移动应用开发中状态管理和数据持久化是构建完整应用的基石。鸿蒙HarmonyOS通过ArkTS声明式语法让开发者能够以更清晰的方式组织UI与业务逻辑配合State、AppStorage等状态管理机制轻松实现跨页面数据同步与实时更新。同时基于Preferences与关系型数据库的本地持久化方案可覆盖阅读进度、搜索历史、书架收藏等轻量与结构化数据的多样存储需求。本文以一个功能闭环的读书APP为例系统讲解项目结构设计、核心模块实现与性能优化细节涵盖书城列表、阅读器翻页、进度记忆、多选管理等典型场景并分享答辩演示与避坑经验帮助开发者打通鸿蒙应用开发的完整链路打造高完成度的实战作品。 做鸿蒙读书APP是我今年带学生项目时反复打磨的一个方向前后帮几个团队把同一套思路落地成高分作品。这类项目之所以拿分稳是因为它把鸿蒙生态里最典型的几个能力点——ArkTS声明式开发、状态管理、路由跳转、本地持久化——全都串了起来评阅老师一眼就能看出你对整个开发链路是真正跑通的而不是只堆了几个静态页面交差。这篇就用我实际打磨过的版本为底把整个项目的结构设计、核心模块拆分、关键代码思路、以及怎么在答辩和演示环节多拿分一并讲透。1. 项目整体设计思路为什么读书APP最适合用来打高分先说一个判断如果目标是做一个能稳定拿到高分、又不会把自己拖垮的鸿蒙项目阅读类APP几乎是性价比最高的选题。原因有三点都是我在实际指导过程中总结出来的。第一功能层次感特别清晰。一个读书APP天然具备“展示型页面 交互型功能 数据型模块”三层结构书城页负责列表展示和推荐位阅读器负责手势、翻页、字号调节这类高频交互书架和搜索则考验数据组织和过滤能力。这三层恰好对应鸿蒙应用开发的核心知识域评阅时每个维度都有东西可讲。第二数据结构不复杂但足够完整。图书信息无非是封面、书名、作者、简介、评分、分类这几个字段用本地数据库或首选项都能优雅承载不需要引入复杂网络框架也就减少了调试阶段的不确定因素。对课程设计或毕业设计来说稳定复现比技术炫技重要得多。第三界面发挥空间大。阅读类应用天然需要排版感而这正是ArkTS声明式UI的优势区。同一个页面用Column、Row、Scroll、List这些基础组件就能排出很专业的版式视觉完成度上去了印象分自然高。我在规划时把整个项目拆成了五个功能模块后面每个模块单独说实现思路模块名称核心职责涉及关键技术点书城页推荐位、分类入口、图书瀑布流List/Grid、懒加载、数据分页图书详情页展示书籍信息、加入书架、开始阅读路由传参、状态保留阅读器翻页、字号/背景调节、进度记忆Swiper、滑动手势、首选项存储书架页已收藏图书管理数据库CRUD、多选删除搜索页书名/作者/标签检索 搜索历史模糊查询、历史记录持久化这个模块划分是我调整过好几轮的最终版本。最早我把搜索功能并进书城页但实际开发中发现页面职责一旦混杂状态管理就会变得很别扭——书城页要管推荐数据又要管搜索关键词和结果集代码很快就失控了。拆出来之后每个页面各自维护自己的ViewModel逻辑清爽了不是一点半点。2. 从零搭建工程DevEco Studio 配置与项目骨架组织2.1 环境准备与项目创建细节开发工具选的是DevEco Studio这是华为官方基于IntelliJ IDEA定制的IDE对鸿蒙项目的支持是最完整的。我用的是4.0 Release版本配套的SDK选API 10。如果你拿到的是更新的版本也不影响核心开发流程都是一样的只是新建项目时的模板入口可能略有调整。新建项目时选择“Application Empty Ability”模板就够了。我看过不少人一上来就选复杂的带导航模板结果光理解模板自带的路由结构就花掉半天时间。空模板加自己搭框架反而能在答辩时讲清楚“这个路由是我自己设计的”这比用了模板但说不明白要加分。工程结构上我建议按下面这个方式组织别把代码全堆在pages目录里entry/src/main/ ├── ets/ │ ├── entryability/ │ ├── pages/ │ │ ├── Index.ets // 首页底部导航容器 │ │ ├── BookStore.ets // 书城页 │ │ ├── BookDetail.ets // 图书详情页 │ │ ├── Reader.ets // 阅读器页 │ │ ├── BookShelf.ets // 书架页 │ │ └── Search.ets // 搜索页 │ ├── model/ │ │ ├── BookInfo.ets // 图书数据模型 │ │ └── DataSource.ets // 本地数据源预置图书数据 │ ├── common/ │ │ ├── BookCard.ets // 图书封面卡片组件 │ │ └── EmptyView.ets // 空状态组件 │ └── viewmodel/ │ └── ShelfViewModel.ets // 书架模块状态管理 └── resources/ ├── base/element/ // 颜色、字符串等资源 └── base/media/ // 图片资源把模型、组件、页面分目录放看起来是小事但在评分标准里“代码结构清晰”往往占了整整一项。而且对你自己也有好处——页面一多找文件会快很多。2.2 数据模型设计与本地图书源构造图书模型是贯穿全项目的基础类型我定义的结构是这样的// model/BookInfo.ets export class BookInfo { id: string; title: string; author: string; category: string; rating: number; intro: string; coverResId: number; progress: number 0; lastReadTime: string ; constructor(id: string, title: string, author: string, category: string, rating: number, intro: string, coverResId: number) { this.id id; this.title title; this.author author; this.category category; this.rating rating; this.intro intro; this.coverResId coverResId; } }字段里我特意加上了progress和lastReadTime这两个字段是阅读进度记忆功能的数据基础。很多初版项目是等做到阅读器模块了才想起要记录进度再回头改模型麻烦得很。一开始就把模型设计到位后面所有模块开发都会顺畅。数据源方面我内置了12本不同分类的图书覆盖文学、科技、历史、经济四个类别封面用本地media资源图片。这样做的好处是应用不依赖任何网络请求就能完整体验演示时不会因为网络问题翻车。如果你手头没有合适的封面图也可以用纯色背景加文字首字母替代视觉效果同样干净。2.3 底部导航容器设计首页容器我用的是Tabs组件这是鸿蒙实现底部导航的标准方式比用路由手动切换页面的做法稳得多。每种Tab对应一个页面再配合自定义的TabBar显示图标和文字。// pages/Index.ets Entry Component struct Index { State currentTab: number 0; Builder tabBarBuilder(title: string, iconRes: Resource, index: number) { Column({ space: 4 }) { Image(iconRes) .width(24) .height(24) .objectFit(ImageFit.Contain) .opacity(this.currentTab index ? 1 : 0.5) Text(title) .fontSize(11) .fontWeight(this.currentTab index ? FontWeight.Bold : FontWeight.Normal) .fontColor(this.currentTab index ? #0A59F7 : #666666) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } build() { Tabs({ barPosition: BarPosition.End, index: this.currentTab }) { TabContent() { BookStore() } .tabBar(this.tabBarBuilder(书城, $r(app.media.ic_bookstore), 0)) TabContent() { BookShelf() } .tabBar(this.tabBarBuilder(书架, $r(app.media.ic_shelf), 1)) TabContent() { Search() } .tabBar(this.tabBarBuilder(搜索, $r(app.media.ic_search), 2)) } .scrollable(true) .onChange((index: number) { this.currentTab index; }) } }这里有个关键点State currentTab和Builder tabBarBuilder的联动实现了选中态切换——当前选中的Tab图标不透明、文字加粗、变蓝其余置灰。这段逻辑不难但却是UI体验的加分项。不加的话Tabs也能用但看起来就是“学生作品”和“成熟产品”的差别。3. 核心功能模块逐个击破从书城到阅读器的完整链路3.1 书城页列表渲染与卡片复用书城页承担的是门面职责也是评阅老师打开APP后看到的第一屏视觉效果必须撑住。我用的是垂直滚动的List组件搭配推荐位、分类入口、图书列表三个Section。// pages/BookStore.ets Component export struct BookStore { State bookList: BookInfo[] DataSource.getAllBooks(); State currentCategory: string 全部; Builder categoryTab(category: string) { Text(category) .fontSize(14) .fontWeight(this.currentCategory category ? FontWeight.Bold : FontWeight.Normal) .fontColor(this.currentCategory category ? #0A59F7 : #333333) .padding({ left: 14, right: 14, top: 6, bottom: 6 }) .backgroundColor(this.currentCategory category ? #E8F0FE : #F5F5F5) .borderRadius(16) .onClick(() { this.currentCategory category; if (category 全部) { this.bookList DataSource.getAllBooks(); } else { this.bookList DataSource.getBooksByCategory(category); } }) } build() { Column() { // 顶部标题区 Row() { Text(鸿蒙读书) .fontSize(24) .fontWeight(FontWeight.Bold) Blank() Text() .fontSize(22) } .width(100%) .padding({ left: 20, right: 20, top: 12, bottom: 12 }) // 分类横向滚动区 Scroll() { Row({ space: 8 }) { this.categoryTab(全部) this.categoryTab(文学) this.categoryTab(科技) this.categoryTab(历史) this.categoryTab(经济) } .padding({ left: 20, right: 20 }) } .scrollable(ScrollDirection.Horizontal) .scrollBar(BarState.Off) // 图书列表区 List({ space: 12 }) { ForEach(this.bookList, (book: BookInfo) { ListItem() { BookCard({ book: book }) } }, (book: BookInfo) book.id) } .layoutWeight(1) .width(100%) .padding({ left: 20, right: 20 }) } .width(100%) .height(100%) .backgroundColor(#F7F8FA) } }分类筛选的交互我用了最简单的方案——点击分类时直接过滤本地数组。有朋友建议我做成左右滑动的二级页面好看是好看但对一个读书项目来说有些过度设计了而且引入额外的手势管理会显著增加出bug的概率。高分的核心逻辑是“完成度足够高、核心功能不出错”不是“功能数量多到失控”。这里的BookCard是我抽出来的公共组件封面在上、书名在下、简介一行省略卡片本身就是路由跳转到详情页的入口。组件化的好处在开发中后期会明显体现——搜索页和书架页也复用了同一个卡片组件视觉风格因此保持了高度统一。3.2 图书详情页路由传参与状态同步从书城点击卡片跳转到详情页需要把当前点击的图书信息传过去。鸿蒙的router模块支持两种方式通过params传对象或者传id再在页面里重新查数据。我推荐后者。// 在BookCard组件中的跳转逻辑 router.pushUrl({ url: pages/BookDetail, params: { bookId: this.book.id } })详情页在aboutToAppear生命周期里接收参数再按id从数据源中查出完整信息。这样做的优势是页面刷新后数据源仍然是唯一的可靠来源不会出现“列表页的对象被修改但详情页还是旧数据”的诡异问题。// pages/BookDetail.ets Entry Component struct BookDetail { State book: BookInfo | undefined undefined; aboutToAppear(): void { const params router.getParams() as Recordstring, string; const bookId params.bookId; this.book DataSource.getBookById(bookId); } build() { // 详情展示布局 } }详情页的布局我是这样安排的顶部大封面居中下面依次是书名加粗大字号、作者灰色小字号、评分星级展示数字、分类标签然后是简介的长段落展示最底部固定一个“开始阅读”主按钮和一个“加入书架”次按钮。底部按钮用Button组件注意用linearGradient做渐变背景会比纯色更精致。3.3 阅读器手势翻页、排版调节与进度记忆阅读器是整个项目技术含量最高的模块也是答辩时最能讲的亮点。我在这一块投入了最大的精力实现得好不好基本决定了项目是80分档还是95分档。核心需求有三个翻页、字号和背景调节、进度记忆。翻页我用的是Swiper组件。每一页是一个Text组件内容相同但字号、字色、背景根据设置动态变化。Swiper自带左右滑动手势和动画过渡比自己手动处理滑动手势事件要稳定太多而且天然支持循环滑动——当然阅读器这里不需要循环就关闭了loop属性。// pages/Reader.ets Entry Component struct Reader { State content: string ; State fontSize: number 18; State bgMode: string white; State currentPage: number 0; State totalPages: number 10; State paletteVisible: boolean false; build() { Stack() { Swiper() { ForEach(this.buildPages(), (page: string, index: number) { // 这里是页面内容 }, (page: string, index: number) index.toString()) } .index(this.currentPage) .loop(false) .indicator(false) .onChange((index: number) { this.currentPage index; }) // 点击屏幕中间区域弹出设置面板 // 底部工具栏字号调节、背景切换、返回按钮 } .width(100%) .height(100%) } }字号调节和背景模式切换是阅读器的标准配置也是高分项目的“必要非充分条件”。字号我做了三档16、18、20背景提供白底黑字、米黄底深字、黑底白字三种模式。切换逻辑不要写在build里而是封装成一个updateReaderStyle()方法统一修改fontSize、fontColor、backgroundColor三个状态变量。状态一变Swiper里的所有页面都会自动刷新。进度记忆是阅读器的灵魂功能。用户的阅读进度当前页和总页数通过Preferences持久化到本地。这样用户杀掉应用再打开还能恢复到上次阅读位置。这个功能我强烈建议做理由很实在——它让“阅读闭环”真正成立了书城挑书 → 详情加入书架 → 阅读 → 记住进度 → 下次继续。整个链路是完整自洽的。// 保存阅读进度 async saveReadingProgress(bookId: string, page: number, total: number): Promisevoid { const preferences await dataPreferences.getPreferences(this.context, reading_progress); await preferences.put(progress_${bookId}, JSON.stringify({ page, total })); await preferences.flush(); } // 读取阅读进度 async loadReadingProgress(bookId: string): Promisenumber { const preferences await dataPreferences.getPreferences(this.context, reading_progress); const saved await preferences.get(progress_${bookId}, ); if (saved) { return JSON.parse(saved).page; } return 0; }注意flush()一定要调用。鸿蒙的Preferences是内存缓存加异步落盘机制不调用flush就把应用杀了数据很可能没写进去。这个坑我踩过真机调试时反复杀进程验证才发现是漏了flush。3.4 书架页数据库CRUD与多选管理书架页展示的是用户收藏的图书数据来自关系型数据库。鸿蒙提供了关系型数据库RDB的能力API设计跟Android的SQLite有很大相似性用得比较顺手。书架的数据源管理我单独封装了一个ShelfViewModel持有书架图书列表的State数组。这个ViewModel类隔离了UI和数据操作逻辑页面只负责渲染数据的增删改查全交给ViewModel。// viewmodel/ShelfViewModel.ets export class ShelfViewModel { State shelfBooks: BookInfo[] []; async loadShelfBooks(): Promisevoid { // 从关系型数据库读取已收藏图书列表 } async addToShelf(book: BookInfo): Promisevoid { // 插入数据库 刷新内存列表 } async removeFromShelf(bookId: string): Promisevoid { // 从数据库删除 刷新内存列表 } }书架页还加了多选删除功能这是演示时的加分动作。长按书架卡片进入多选模式这时卡片右上角出现勾选圆圈底部弹出“删除所选”按钮。进入多选模式需要维护一个selectedIds: string[]数组每次点击时增删元素删除时批量执行数据库操作。这个交互在成熟APP里很常见但在学生项目里出现率不高做了就属于“超预期”表现。3.5 搜索页模糊查询与搜索历史的双重实现搜索页同样走了完整数据流输入关键词 → 调数据源模糊匹配 → 结果列表展示。DataSource里我增加了一个searchBooks(keyword: string)方法对书名、作者、分类三个字段做includes匹配。搜索历史是搜索页的隐藏加分项。每一次成功搜索都记录关键词用Preferences保存最近10条展示在搜索框下方。点击历史关键词可以快速回填并重新搜索旁边提供一个垃圾桶图标一键清空历史。这个功能代码量不大但让页面的体验完整度和交互深度都上了一个台阶。// 保存搜索历史 async saveSearchHistory(keyword: string): Promisevoid { let history await this.loadSearchHistory(); history history.filter(item item ! keyword); history.unshift(keyword); if (history.length 10) { history history.slice(0, 10); } const preferences await dataPreferences.getPreferences(this.context, search_history); await preferences.put(history, JSON.stringify(history)); await preferences.flush(); }4. 状态管理、本地存储与性能细节让代码更“鸿蒙”4.1 V1状态管理和V2状态管理的选择这个项目我全程用的是经典的V1状态管理State、Prop、Link、Observed等。说实话如果你是从Android或小程序转过来的V1这套“状态变了页面自动刷”的思路是最容易上手的。在组件之间共享状态时我遵循一个原则页面内状态用State父子组件通信用Prop和Link跨页面共享才考虑AppStorage。比如书架页和详情页都需要知道“某本书是否已被收藏”这个状态如果各自维护就容易出现书架加了书但详情页按钮没同步变化的问题。用AppStorage定义一个全局的收藏状态集合两边同时读写就不会出现状态打架。4.2 本地数据持久化的合理分工这个项目用了两种持久化手段各有各的适用场景Preferences首选项适合存轻量级键值对。阅读进度、搜索历史、主题设置这类小数据用Preferences就够了API简单读写快。RDB关系型数据库适合存结构化数据。书架收藏的图书列表因为要支持查询、删除、批量操作用RDB更合适。很多初学者容易陷入误区什么数据都往数据库里塞。实际上像阅读进度这种单字段数据用数据库属于典型的杀鸡用牛刀代码复杂度成倍增加。合理分工既让代码简洁也让答辩时有话可讲“这里我使用了两种持久化方案根据数据特点做了分工”。4.3 列表性能优化实践书城页的图书列表和搜索页的结果列表我都用了ForEach加懒加载的方式。鸿蒙的List组件自带懒加载机制即使数据量很大也不会一次性创建所有列表项组件。在BookCard组件内部我没有做多余的复杂计算图片用Image组件配合objectFit属性控制缩放避免大图加载导致卡顿。另一个容易被忽视的优化点是键值生成规则。ForEach的第三个参数是键值生成器我统一用book.id作为键而不是用数组索引。用索引当键的后果是一旦列表发生增删操作会导致组件被错误复用出现UI错乱的问题。用唯一id做键可以确保每个列表项组件实例都被正确回收和复用。5. 提升项目完成度的加分细节演示效果与答辩话术5.1 状态联动AppStorage实现跨页面数据同步这个项目里最值得拿出来讲的联动场景是“收藏状态”的全局同步。我在AppStorage里维护一个收藏id集合AppStorage.setOrCreate(favoriteSet, new Setstring()); // 添加收藏时 const favoriteSet AppStorage.getSetstring(favoriteSet)!; favoriteSet.add(book.id); AppStorage.setOrCreate(favoriteSet, favoriteSet); // 书架页监听 StorageLink(favoriteSet) favoriteSet: Setstring new Set();一旦AppStorage里的收藏集合变了书架页的StorageLink会收到通知并自动刷新列表详情页的收藏按钮状态也会同步更新。这个能力的演示效果很炸——在书城点“加入书架”切换到书架页新书已经在列表里了。这种跨页面的实时同步在答辩现场能给评阅老师留下“这个学生是真的理解了状态管理”的印象。5.2 空状态设计让界面在所有场景下都优雅书架空、搜索无结果这两个场景如果只显示一片空白界面就暴露了设计上的粗糙。我做了EmptyView公共组件包含一个图片或者用大号文字图形代替、一行提示文案、一个引导按钮。// common/EmptyView.ets Component export struct EmptyView { message: string 暂无数据; build() { Column({ space: 12 }) { Text() .fontSize(48) .opacity(0.4) Text(this.message) .fontSize(14) .fontColor(#999999) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } }这么做的好处是无论用户怎么操作界面永远有反馈永远不会出现“点了没反应”的失控感。这是产品思维在项目里的体现也是普通项目和高分项目的一个明显分水岭。5.3 答辩演示脚本与常见提问准备项目代码写完了演示环节同样要排练。我建议按下面这个顺序演示逻辑最顺启动APP先展示书城首页——分类过滤点几下让评阅老师看到列表动态变化。进一本书的详情页点“加入书架”然后切到书架Tab展示收藏成功。点“开始阅读”进阅读器翻几页调一下字号和背景再退出。杀进程重新打开APP进到同一本书展示阅读进度还在。搜索一个关键词展示搜索结果和搜索历史记录。这一套流程走完核心功能全部覆盖而且每个步骤之间都有因果关系看起来就是一个完整的产品故事。评阅老师大概率会问这几个问题提前准备好答案“你的数据是怎么存的”——书数据内置在代码里阅读进度用Preferences书架用RDB。“如果要做成在线书城需要改哪些地方”——把DataSource替换成网络请求引入HTTP库处理异步加载和错误重试。“阅读器翻页为什么用Swiper”——Swiper自带手势处理和动画过渡代码量小且交互流畅满足读书APP的核心需求。“状态管理用的什么方案”——V1状态管理全家桶State、Prop、Link、StorageLink配合AppStorage做跨页同步。6. 常见问题与避坑实录真机调试与模拟器6.1 模拟器白屏与重启问题鸿蒙模拟器第一次启动项目时偶尔会出现白屏等很久也不出页面。这个问题的常见原因是模拟器还没完全预热完毕。如果遇到这个情况先别急着改代码等模拟器完全启动、桌面都显示完整了再运行项目通常第二次点击Run就正常了。另外模拟器上无法真实验证一些硬件相关能力比如传感器、震动反馈但读书APP恰好不依赖这些所以全程用模拟器开发问题不大。最后演示时如果能用真机体验会更好因为流畅度更高、色彩表现更真实。6.2 页面跳转后状态丢失我在开发过程中遇到过一个典型的跳转丢状态问题从详情页加了书架后返回书城再切到书架Tab发现新收藏的书没出现。问题出在书架页的aboutToAppear生命周期里加载数据时用的是getPreferences获取的是那个时刻的缓存而详情页写入数据后没有刷新书架页依赖的AppStorage状态变量。解决方案就是上一节说的用AppStorage维护收藏集合而不是各自从数据库重新加载。这个问题的排查过程恰恰说明了状态管理架构设计的重要性——数据流设计得好这类“看起来诡异”的问题根本不会出现。6.3 第三方库版本与API兼容问题鸿蒙生态的第三方库生态还在快速演进中引入第三方组件前一定要确认它支持的API版本。我有一次引入一个UI库编译报错在API 10上不支持某个属性排查了半天才发现是库版本太老。规避方式很简单优先使用系统自带组件和官方文档推荐的能力尽量不引入额外的第三方依赖。读书APP这种纯本地应用系统能力已经完全够用。对课程设计和毕业设计来说“能用系统API解决的问题绝不引第三方库”是最稳的策略。6.4 代码规范与命名习惯高分项目还有一个被忽视的评判维度代码可读性。我对照了多个评分标准后发现代码结构、命名规范、注释质量都会影响最终得分。这个项目里我坚持了几个原则组件文件用大驼峰命名页面内部变量用驼峰常量用全大写加下划线每个组件文件头部用注释说明用途复杂方法内加关键逻辑注释。/** * 获取指定分类的图书列表 * param category 分类名称全部 时返回所有图书 * returns 过滤后的图书数组 */ static getBooksByCategory(category: string): BookInfo[] { if (category 全部) { return this.allBooks; } return this.allBooks.filter(item item.category category); }这些细节不会改变功能表现但会让评阅老师在翻代码时感受到这份作品是认真对待的。项目答辩时挑几处注释规范的代码段讲一下设计思路也是加分的小技巧。7. 从个人项目到体系化作品后续扩展方向到这里一个功能闭环、代码规范、演示流畅的鸿蒙读书APP已经成型了。如果你还有余力有几个扩展方向可以让项目含金量更高。一个是接入网络能力。把DataSource替换成HTTP请求用鸿蒙的ohos.net.http模块实现热门书籍列表和搜索接口的调用让应用变成真正的在线书城。注意要做加载态和错误态否则网络慢的时候白屏很难看。另一个是引入用户登录和云端同步。通过华为AGC的认证服务加云数据库实现用户注册、登录、阅读进度跨设备同步。这个扩展方向技术含量高如果做出来项目的深度和广度都会大幅提升甚至可以往挑战杯、互联网这类竞赛的方向走。还有一个是离线阅读和缓存机制。用户点击收藏后把书籍内容下载到本地支持无网络时也能阅读。这个功能涉及文件存储和下载管理实现起来需要处理断点续传、缓存清理等问题复杂度适中适合有一定余力的同学挑战。总的来说这个项目的价值不在于用了多少炫酷的技术而在于它把鸿蒙应用开发的完整链路——从UI搭建、状态管理、路由跳转、数据持久化到交互反馈——全部走了一遍而且每一步都可以讲清楚为什么这么做。这种“完整且自洽”的能力恰恰是高分项目的底层逻辑。希望这份拆解能帮你把项目做到位也做到你自己满意的程度。本文还有配套的精品资源点击获取