前言

在前几周完成 CarLearn 项目的顺序练习、专项练习、模拟考试、错题复习以及智能复盘等核心学习功能之后,本周我继续围绕“学习闭环”和“用户个性化体验”进行功能完善。本周的开发重点主要集中在两个方面:一是新增并完善“收藏题练习”功能,让用户在做题过程中可以主动标记重点题目,并在后续集中练习;二是继续修复个人中心相关问题,尤其是编辑个人资料时保存失败、提示“未获取到用户信息,请重新登录”的问题。

相比前几周偏向大模块搭建的工作,本周的任务看似更细,但实际涉及到多个层面的协同:做题页面 UI、Room 本地数据库字段、Repository 数据更新、ViewModel 状态同步、导航入口配置、个人中心数据缓存、用户 id 获取逻辑等。尤其是收藏题功能,它并不是简单在页面上加一个星星图标,而是要保证用户在顺序练习、专项练习等不同做题场景中都能收藏题目,同时这些收藏状态要能够稳定保存,并最终进入“收藏题练习”入口中供用户二次复习。

本周 AI 在项目中的参与仍然非常深入。它不仅帮助我定位了很多 Android Studio 中的 ViewBinding 报错、导航 action 缺失、ViewModel 方法未实现等问题,也在功能设计上帮助我判断收藏题是否需要立即新增后端数据库表。经过多轮讨论后,我最终决定先利用 Room 本地字段完成收藏功能闭环,避免过早引入后端同步复杂度,等后续需要跨设备同步时再扩展 MySQL 收藏表。这种“先完成当前稳定闭环,再保留后续扩展空间”的开发思路,也是本周最大的收获之一。


本周工作关键词

Android Studio Kotlin Room MVVM ViewBinding Navigation 收藏题练习 个人中心 SharedPreferences UserPreferences AI辅助开发


一、本周工作概览

本周我主要完成了以下几个方面的工作:

  • 设计并实现“收藏题目”功能,在做题页加入经典星星收藏按钮;
  • 将收藏按钮从顶部栏调整到题型标签同一行,使界面更加自然;
  • 实现空心灰色星星与黄色实心星星之间的状态切换;
  • 在 Room 本地题库中复用 isFavorite 字段保存收藏状态;
  • QuestionDao 中补充收藏题查询接口;
  • QuestionRepository 中新增 toggleFavorite()、收藏题列表查询等方法;
  • ExamViewModel 中新增收藏题模式,使“收藏题练习”可以独立加载收藏题;
  • 在专项练习页面中同步接入收藏逻辑,避免不同做题模式体验割裂;
  • 在学习首页和“我的”页面中增加收藏题练习入口;
  • 修复 ProfileFragmentrowFavoriteQuestions 未定义导致的 ViewBinding 报错;
  • 修复编辑资料页保存失败问题,解决用户 id 获取不到的问题;
  • 继续通过 AI 辅助定位多文件联动问题,提高调试效率。

这一周的核心目标,是让 CarLearn 不只是“能刷题”,而是让用户可以把自己认为重要的题沉淀下来,形成一个主动复习入口。错题复习关注的是“系统发现用户薄弱点”,而收藏题练习则更偏向“用户主动标记重点”。这两个模块结合起来,才能让学习闭环更加完整。


二、收藏题功能设计:从“做过题”到“主动沉淀重点题”

在之前的版本中,用户可以进行顺序练习、随机练习、专项练习和错题复习,但用户主动标记题目的能力还不够明显。对于驾考类学习应用来说,有些题目可能并不是用户做错了,但它仍然值得反复看,例如容易混淆的法规题、图片识别题、标志标线题,或者用户觉得自己虽然做对了但还不够熟练的题。

因此,本周我开始实现“收藏题目”功能。这个功能的目标并不是简单增加一个页面,而是希望在用户做题的过程中形成一种自然的学习习惯:看到重点题时点击右上角的小星星,题目就会被加入收藏;之后用户可以从“收藏题练习”入口重新集中练习这些题目。

最初我考虑过是否需要直接在后端 MySQL 中新增收藏表,例如设计 user_favorite_questions,用于保存用户与题目之间的收藏关系。但经过分析后,我发现当前阶段还没有迫切需要跨设备同步收藏题,且现有 Room 的 QuestionEntity 中已经存在 isFavorite 字段。因此,本周选择先在本地完成稳定闭环,也就是利用 Room 保存收藏状态,后续如果需要账号级同步,再新增后端收藏表。

这个决策看似简单,但对项目节奏很重要。如果一开始就把收藏题做成完整的后端同步体系,就会涉及用户登录状态、接口设计、MySQL 迁移、前后端同步冲突等一系列问题,反而可能拖慢当前功能落地。现在先用本地 Room 实现,既能快速完成用户体验,也能为后续扩展保留空间。


三、做题页星星收藏按钮:从顶部栏调整到题型标签同一行

本周最直观的界面变化,是在做题页面加入了收藏星星。

一开始,我把星星按钮放在了顶部栏右侧,也就是页面标题旁边。但是实际运行后发现,这个位置并不理想。由于手机状态栏、标题栏以及设置按钮都集中在顶部,星星放在那里会显得比较突兀,而且在模拟器中还容易和状态栏视觉上靠得太近。用户反馈后,我重新调整了布局,把收藏星星移动到了题型标签所在的一行。

最终的布局效果是:

判断题                                      ☆

收藏后变为:

判断题                                      ★

也就是说,左侧仍然显示“判断题”“单选题”“多选题”等题型标签,右侧显示收藏星星。这样设计有几个好处:第一,星星和题目本身的关系更强,用户一眼就能理解这是对当前题目的操作;第二,星星高度与题型标签保持一致,视觉更加平衡;第三,顶部栏仍然只保留返回、标题和设置入口,界面不会过于拥挤。

fragment_exam.xml 中,我将原来单独的题型 TextView 改成了一整行横向布局:

<LinearLayout
    android:id="@+id/layoutQuestionTypeRow"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tvQuestionType"
        android:layout_width="wrap_content"
        android:layout_height="28dp"
        android:gravity="center"
        android:minWidth="60dp"
        android:paddingStart="12dp"
        android:paddingEnd="12dp"
        android:textColor="#2563EB"
        android:textSize="12sp"
        android:textStyle="bold"
        tools:text="判断题" />

    <View
        android:layout_width="0dp"
        android:layout_height="1dp"
        android:layout_weight="1" />

    <TextView
        android:id="@+id/btnFavoriteStar"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:clickable="true"
        android:contentDescription="收藏题目"
        android:focusable="true"
        android:gravity="center"
        android:text="☆"
        android:textColor="#9CA3AF"
        android:textSize="20sp"
        android:textStyle="bold" />
</LinearLayout>

这部分虽然只是 UI 调整,但它让我意识到,一个功能能不能真正自然地融入产品,不只取决于逻辑是否正确,也取决于它出现在什么位置、是否符合用户直觉、是否会破坏原有页面的平衡感。


四、收藏状态切换:空心灰星与黄色实心星

收藏题功能的核心交互,是点击星星后立即切换状态。未收藏时显示灰色空心星星,收藏后显示黄色实心星星。这是很多学习类、阅读类、题库类应用中非常经典的交互方式,用户几乎不需要学习成本。

ExamFragment.kt 中,我为星星按钮添加点击事件:

binding.btnFavoriteStar.setOnClickListener {
    currentQuestion()?.let { question ->
        updateFavoriteStar(!question.isFavorite)
        viewModel.toggleFavorite(question)
    }
}

这里有一个细节:我让页面先立即刷新星星状态,再调用 ViewModel 保存。这样用户点击后能马上看到反馈,不会产生“我点了但是没反应”的感觉。之前我就遇到过类似问题:逻辑可能已经执行了,但界面没有及时刷新,用户就会认为按钮失效。因此,这次我更加注意 UI 反馈和数据保存之间的顺序。

星星状态刷新方法如下:

private fun updateFavoriteStar(isFavorite: Boolean) {
    binding.btnFavoriteStar.text = if (isFavorite) "★" else "☆"
    binding.btnFavoriteStar.setTextColor(
        Color.parseColor(if (isFavorite) "#FACC15" else "#9CA3AF")
    )
}

这个方法会在两个地方调用:一是在用户点击星星时调用,二是在切换到下一题或上一题时根据当前题目的 isFavorite 状态重新刷新。这样就可以保证每一道题显示的收藏状态都是正确的,不会出现上一题的星星状态残留到下一题的问题。


五、Room 与 Repository 层完善:让收藏状态真正保存下来

只做 UI 显示是不够的。收藏题功能真正成立的前提,是收藏状态必须被保存下来,否则用户退出页面后收藏就丢失了。

当前项目中,题目实体 QuestionEntity 已经包含 isFavorite 字段,因此本周没有立即新增数据库表,而是继续利用这个字段保存收藏状态。在 QuestionDao 中,我补充了收藏题查询方法:

@Query("SELECT * FROM questions WHERE isFavorite = 1 ORDER BY id ASC")
suspend fun getFavoriteQuestionsOnce(): List<QuestionEntity>

@Query("SELECT COUNT(*) FROM questions WHERE isFavorite = 1")
suspend fun getFavoriteQuestionCount(): Int

QuestionRepository 中,我新增了统一的收藏切换方法:

suspend fun toggleFavorite(question: QuestionEntity): QuestionEntity {
    val updatedQuestion = question.copy(isFavorite = !question.isFavorite)
    questionDao.insertQuestions(listOf(updatedQuestion))
    return updatedQuestion
}

这里我没有直接使用单独的 updateQuestion(),而是复用了已有的 insertQuestions() 插入/替换逻辑。这样做的好处是兼容性更强,不管当前题目来自顺序练习、专项远程加载还是个性化题库,只要最终能转成 QuestionEntity,都可以通过同一套逻辑保存收藏状态。

同时,我也注意到一个潜在问题:如果题库同步时重新创建 QuestionEntity,可能会覆盖原来的 isFavorite 状态。因此在同步题库时,需要尽量把旧题目的收藏状态保留下来,避免用户已经收藏的题在下次同步后被重置。这一点也是 AI 在分析过程中提醒我的地方。它让我意识到,收藏题虽然看起来只是一个布尔字段,但它属于用户行为数据,不应该被普通题库同步轻易覆盖。


六、ViewModel 层接入:新增收藏题练习模式

收藏题不只是要能点亮星星,还要能从入口进入后集中练习。因此,我在 ExamViewModel 中新增了收藏题模式。

首先定义收藏题分类:

const val CATEGORY_FAVORITE = "收藏题目"

然后在加载题目时判断当前模式:

if (category == CATEGORY_FAVORITE) {
    currentCategory = CATEGORY_FAVORITE
    loadFavoriteQuestions(startQuestionId)
    return
}

收藏题加载逻辑如下:

fun loadFavoriteQuestions(startQuestionId: Int? = null) {
    _isLoading.value = true
    _questions.value = emptyList()
    _currentQuestionIndex.value = 0
    _selectedOption.value = null
    _isCorrect.value = null
    hasMore = false

    viewModelScope.launch {
        try {
            val list = withContext(Dispatchers.IO) {
                repository.getFavoriteQuestionsOnce()
            }

            _questions.value = list

            if (startQuestionId != null) {
                val startIndex = list.indexOfFirst { it.id == startQuestionId }
                if (startIndex >= 0) {
                    _currentQuestionIndex.value = startIndex
                }
            }
        } catch (e: Exception) {
            _errorMessage.value = e.message ?: "加载收藏题失败"
        } finally {
            _isLoading.value = false
        }
    }
}

和顺序练习不同,收藏题练习不需要继续分页加载,因为它的数据来源是本地已收藏题目。因此在分页相关逻辑中,我也增加了对收藏题模式的判断,避免收藏题练习误触发普通题库的分页加载。

这一步完成后,“收藏题练习”就不再只是一个入口按钮,而是真正拥有了独立的数据加载逻辑。


七、多个入口打通:学习页与我的页面均可进入收藏题

为了让用户更方便地进入收藏题练习,本周我在两个地方增加了入口。

第一个入口是学习首页。用户在学习页可以看到“顺序练习”“随机练习”“专项练习”“模拟考试”“错题复习”等功能卡片,现在也增加了“收藏题练习”。这个入口更偏向学习场景,用户刷题时可以直接进入收藏题复习。

第二个入口是“我的”页面。用户在个人中心中可以查看自己的学习信息,因此这里也适合放一个“我的收藏题目”入口。这样从用户资产角度看,错题、收藏题、学习记录都可以在个人中心中形成统一入口。

ProfileFragment.kt 中,我为收藏题入口添加了跳转逻辑:

binding.rowFavoriteQuestions.setOnClickListener {
    val bundle = Bundle().apply {
        putString("mode", "FAVORITE")
        putString("subject", "收藏题目")
    }
    findNavController().navigate(R.id.action_profileFragment_to_examFragment, bundle)
}

在这个过程中,我也遇到了一个典型的 ViewBinding 报错:

Unresolved reference 'rowFavoriteQuestions'

这个错误的原因并不复杂,但很典型:我在 Kotlin 代码里使用了 binding.rowFavoriteQuestions,但是在 fragment_profile.xml 中还没有定义对应的 @+id/rowFavoriteQuestions。因此 ViewBinding 在生成绑定类时自然找不到这个控件。

最终,我在布局文件中补充了这一行入口:

<TextView
    android:id="@+id/rowFavoriteQuestions"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:clickable="true"
    android:drawableEnd="@drawable/ic_arrow_forward"
    android:focusable="true"
    android:foreground="?attr/selectableItemBackground"
    android:gravity="center_vertical"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:text="我的收藏题目"
    android:textColor="@color/theme_green_primary"
    android:textSize="15sp" />

这个问题也让我再次意识到,Android 开发中很多报错并不是复杂逻辑导致的,而是 XML、Binding、Fragment、Navigation 之间没有完全对齐。尤其是使用 ViewBinding 后,控件 id 是否存在、是否拼写一致、布局文件是否重新构建,都会直接影响编译结果。


八、专项练习接入收藏:保证不同做题场景体验一致

除了顺序练习,我也希望专项练习中的题目同样可以收藏。因为用户在专项练习中遇到的题,往往更具有针对性,例如交通标志、法规处罚、道路安全等类型。如果专项练习不能收藏,就会造成体验割裂。

在接入专项练习时,我遇到了另一个典型错误:

Unresolved reference 'toggleFavorite'

这次问题出现在 SpecialExamFragment.kt 中。原因是我在 Fragment 中调用了:

viewModel.toggleFavorite(question)

但是对应的 SpecialExamViewModel 中还没有实现这个方法。最终我在 SpecialExamViewModel 中补充了与普通做题页类似的逻辑:

fun toggleFavorite(question: QuestionEntity) {
    viewModelScope.launch {
        try {
            val updatedQuestion = withContext(Dispatchers.IO) {
                repository.toggleFavorite(question)
            }

            val oldList = _questions.value.orEmpty()
            val newList = oldList.map {
                if (it.id == updatedQuestion.id) updatedQuestion else it
            }

            _questions.value = newList

            _pluginToastMessage.value = if (updatedQuestion.isFavorite) {
                "已加入收藏"
            } else {
                "已取消收藏"
            }
        } catch (e: Exception) {
            _errorMessage.value = e.message ?: "收藏操作失败"
        }
    }
}

这样一来,专项练习也能共用 QuestionRepository 中的收藏逻辑。这个设计让我进一步体会到 Repository 层的重要性:如果每个页面都自己写一套收藏逻辑,后续一定会越来越乱;而现在统一放在 Repository 中,不同 ViewModel 只负责调用,整体结构更清晰。


九、个人资料保存问题修复:真正原因是 userId 获取失败

除了收藏题功能,本周还修复了一个个人中心相关的问题:在编辑资料页面点击“保存资料”后,看起来没有反应,但底部会弹出提示:

未获取到用户信息,请重新登录

一开始我以为可能是按钮点击事件没有绑定,或者后端接口没有响应。但经过排查后发现,按钮其实已经响应了,真正的问题是编辑资料页面没有正确获取到当前登录用户的 userId

原来的逻辑主要从 user_info 这个 SharedPreferences 中读取 user_id。如果这个地方没有写入成功,页面就会认为用户未登录,于是直接 return,不会继续请求后端保存资料。

最终我对 EditProfileFragment.kt 做了增强:先从 SharedPreferences 读取用户 id,如果读不到,就继续从 UserPreferences 中读取;如果读取成功,再把它写回 user_info,保证后续页面也能正常拿到。

核心逻辑如下:

private fun ensureUserId(): Int {
    if (userId > 0) return userId

    userId = getSavedUserId(sp)

    if (userId <= 0) {
        userId = userPreferences.getUserId()
    }

    if (userId > 0) {
        sp.edit()
            .putInt("user_id", userId)
            .apply()
    }

    return userId
}

然后在保存资料前进行检查:

val currentUserId = ensureUserId()

if (currentUserId <= 0) {
    Toast.makeText(requireContext(), "未获取到用户信息,请重新登录", Toast.LENGTH_SHORT).show()
    return
}

这次问题给我的启发很明显:对于用户体系相关功能,不能只假设某一个缓存位置一定有数据。登录状态可能存在 SharedPreferences、DataStore、UserPreferences 等不同层级中,页面使用时应该有更稳的兜底逻辑。尤其是在项目不断重构后,旧页面和新封装之间很容易出现数据来源不一致的问题。


十、AI 辅助开发过程中的具体帮助

本周 AI 对我的帮助非常具体,而且基本贯穿了整个开发流程。

首先,在文件定位方面,AI 帮助我确认了学习首页并不叫 StudyFragment,而是实际使用 MainFragment.ktfragment_main.xml。这看起来是一个小问题,但在一个文件越来越多的 Android 项目中,能快速判断真正对应的页面,可以节省很多时间。

其次,在做题页定位方面,AI 帮助我确认当前普通练习页面对应的是 ExamFragment.ktfragment_exam.xml,专项练习则对应 SpecialExamFragment.kt。如果没有先理清这些页面关系,很容易把代码改到错误文件中,导致界面没有变化。

第三,在报错排查方面,AI 对几个典型错误的定位非常直接。例如:

Unresolved reference 'rowFavoriteQuestions'

AI 判断这是 XML 中缺少对应 id,而不是 Kotlin 逻辑问题。

Unresolved reference 'toggleFavorite'

AI 判断这是 ViewModel 中缺少方法,而不是 Fragment 的点击事件问题。

未获取到用户信息,请重新登录

AI 判断按钮已经响应,真正原因是 userId 获取失败,而不是保存按钮失效。

这些判断对我来说非常有帮助,因为它不是单纯告诉我“哪一行报错”,而是帮助我理解为什么这一行会报错,以及这个错误背后的链路缺口在哪里。

最后,在功能设计方面,AI 也帮助我判断收藏题功能当前是否需要新增后端数据库。最终我们选择先用 Room 本地 isFavorite 字段实现闭环,等后续需要跨设备同步时再扩展后端表。这让我避免了为了一个当前阶段还不必要的功能引入过多复杂度。


十一、本周遇到的问题与思考

本周遇到的问题虽然不像数据库迁移那样复杂,但都非常贴近真实项目开发。

第一个问题是 UI 位置设计。星星按钮一开始虽然能显示,但放在顶部栏并不合适。这个问题让我意识到,功能完成不代表体验完成。尤其是学习类应用,做题页面本身就需要保持清晰,任何新增按钮都应该尽量贴近它所操作的对象。最终把星星放到题型标签同一行,就是一次从“能用”到“更合理”的调整。

第二个问题是多页面状态一致性。收藏题不是只存在于一个页面中,顺序练习、专项练习、收藏题练习入口都要和它产生关系。如果只在一个 Fragment 中写逻辑,后续一定会出现功能割裂。因此,本周我更加重视 Repository 和 ViewModel 的分层,把收藏切换逻辑放在 Repository 中,让不同页面都能复用。

第三个问题是用户状态缓存。编辑资料保存失败让我意识到,用户 id 是很多业务接口的前提。如果用户 id 获取链路不稳定,后面所有依赖用户身份的功能都会受到影响。这个问题也提醒我,后续在做收藏题后端同步、学习进度同步、个性化推荐时,必须先保证用户身份获取逻辑稳定可靠。

第四个问题是 AI 协作方式。以前我有时候会直接问“这个报错怎么改”,但本周我更加习惯把相关文件发给 AI,让它基于真实项目结构判断。这样得到的答案更接近最终可用版本,而不是孤立代码片段。这种方式让我感觉 AI 更像一个长期参与项目的协作开发者,而不是临时搜索工具。


十二、本周收获

本周最大的收获,是我进一步理解了“学习闭环”不是靠一个单独功能完成的,而是由很多细小但互相连接的模块共同构成的。

错题复习解决的是用户被动暴露出的薄弱点,而收藏题练习解决的是用户主动沉淀出的重点题。两者结合之后,CarLearn 的学习路径会更加完整:用户做题、答错、复盘、收藏、再练习,这些行为开始逐渐形成循环。

同时,本周也让我对 Android 项目的分层结构有了更深的理解。一个收藏按钮背后,其实要经过 XML 布局、Fragment 点击事件、ViewModel 状态管理、Repository 数据更新、Room 数据保存、Navigation 页面跳转等多个环节。任何一个环节没接上,最终都会表现为“点了没反应”或者“页面没有数据”。这让我更加体会到,前端开发并不是简单写界面,而是要把状态和数据流完整串起来。

另外,个人资料保存问题也让我重新重视用户体系的稳定性。只要涉及用户数据,就不能只关心页面是否好看,还要关心用户 id 是否正确传递、缓存是否一致、接口是否拿到真实用户身份。后续如果要做更多个性化学习分析,这些基础能力会非常关键。

最后,本周再次证明 AI 对项目开发的帮助已经不仅仅是“写代码”。它更重要的价值在于帮助我拆解问题、定位文件、判断架构边界、比较不同实现方案,并在我遇到多个小 Bug 时快速把它们串成一条完整链路。这种协作方式,让我在面对复杂项目时更有方向感。


十三、下周计划

在本周完成收藏题练习和个人资料保存修复之后,下一步我准备继续围绕学习闭环和个性化能力进行完善,重点包括:

  • 继续测试收藏题练习在顺序练习、专项练习中的稳定性;
  • 优化收藏题列表为空时的提示文案和引导;
  • 考虑是否为收藏题增加后端 MySQL 表,实现账号级同步;
  • 继续完善“我的”页面中的学习数据展示;
  • 修复个人资料页面中更多边界情况,例如头像上传、资料回显、重新登录后的状态恢复;
  • 进一步联动错题、收藏题和智能复盘,为后续个性化推荐做准备;
  • 持续利用 AI 辅助进行代码重构、Bug 定位和技术总结。

我希望下一阶段的 CarLearn 不只是拥有多个功能入口,而是能够真正围绕用户学习过程形成一套完整的数据闭环。用户做过什么题、错过什么题、收藏过什么题、在哪些类型上表现较弱,这些信息最终都应该成为智能学习建议的基础。


总结

本周的工作主要围绕“收藏题练习”和“个人资料保存修复”展开。虽然这两个功能看起来不像模拟考试或智能复盘那样复杂,但它们都非常贴近真实用户体验。收藏题让用户可以主动沉淀重点题,个人资料保存则保证用户体系更加稳定。

通过这周的开发,我更加清楚地认识到,一个学习类 App 的完善过程并不是不断堆叠页面,而是让每个学习行为都能够被记录、被复用、被分析,并最终服务于用户下一步的学习。收藏题功能正是这个方向上的重要一步。

同时,本周 AI 在项目中的作用仍然非常明显。从定位页面文件,到修复 ViewBinding 报错,再到判断 userId 获取失败和收藏数据是否需要后端持久化,AI 都提供了非常具体且有效的帮助。它让我在面对多文件、多模块、多状态联动的问题时,可以更快建立整体思路,也让我越来越习惯用工程化的方式思考一个长期项目。

总体来说,本周 CarLearn 在“用户主动学习沉淀”和“个人中心稳定性”方面都向前推进了一步。下一阶段,我会继续围绕错题、收藏题、学习数据和 AI 智能分析,把 CarLearn 从一个刷题工具逐步完善为一个更具有个性化学习能力的智能驾考辅助系统。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐