ITPub博客

首页 > IT基础架构 > 服务器/存储 > 技术解读:HarmonyOS应用实战:首页快捷问题别污染历史,区分占位问题、用户问题和系统推荐

技术解读:HarmonyOS应用实战:首页快捷问题别污染历史,区分占位问题、用户问题和系统推荐

服务器/存储 作者:Flow_Official 时间:2026-10-12 17:29:36 0 删除 编辑

“输入你的问题…(可跳过)”只是输入框的提示,不能成为用户的历史记录;系统推荐的“今天适合开始吗?”即使被点进输入框,也不该在用户没有确认前占据历史列表。否则历史抽屉很快从“我问过什么”变成“应用替我准备过什么”,用户再次打开时无法判断哪些内容真的由自己提交。

《答案之书》当前首页已经做了最关键的一步: Index.onSubmit 对 this.question.trim() 判空,只有非空文本才调用 QuestionHistoryService.add ,然后跳转抽取页并清空输入框。本文把这条现有链路拆开,说明怎样在后续加入快捷问题和系统推荐时不破坏它。

当前代码里,真正会写历史的只有一次提交

首页 TextInput 的 placeholder 没有写入逻辑;用户点击“获取答案”后, onSubmit 组装 DrawingParams 。若题库尚未就绪,代码先弹出“题库尚未就绪,请稍候”并返回;若问题为空,则把 question 传成 undefined ,允许用户跳过问题直接抽取答案,但不会写入历史。

| 输入状态 | 路由参数 | QuestionHistoryService.add | 语义 |
| --- | --- | --- | --- |
| placeholder | 不存在 | 不会调用 | 视觉提示,不是数据 |
| 空输入后点击 | question: undefined | 不会调用 | 无题抽取,不是提问 |
| 用户输入“要不要换工作” | 非空字符串 | 调用 | 用户确认的提问 |
| 以后点选快捷问题 | 先填入输入框 | 未确认前不调用 | 待用户编辑/确认的建议 |

这个区分看似简单,却决定了历史列表的可信度:历史不是“曾出现在界面上的文本集合”,而是用户已经发起过的业务动作。

在这里插入图片描述

先定义来源,再讨论该不该持久化

当前源码没有“快捷问题”模型,因此下面的类型是为扩展准备的设计示例,而不是项目已有接口。它的价值是禁止页面用“字符串是否为空”这一条规则猜测来源。

type
QuestionSource
=
'user_input'
|
'quick_pick'
|
'system_recommendation'
;
interface
PendingQuestion
{
text
:
string
;
source
:
QuestionSource
;
selectedAt
:
number
;
}
function
canWriteHistory
(
question
:
PendingQuestion
)
:
boolean
{
return
question
.
source
===
'user_input'
&&
question
.
text
.
trim
(
)
.
length
>
0
;
}

如果产品希望“用户点选快捷问题再提交”也进入历史,条件不能写成 source === 'user_input' ,而应引入 confirmedAt 或 submittedByUser 。重点不在于选哪种产品规则,而在于这条规则只能有一个归属位置,不能由首页、历史抽屉、推荐卡片各自猜一遍。

不要在点击推荐卡片时提前写入

下面这种实现很诱人:点击即调用 add ,历史会马上显示新内容。但用户可能只是想看看、又改了文案、切换题库,甚至根本没点击“获取答案”。提前写入会把未提交的意图伪装成已发生的操作。

private
onPickQuickQuestion
(
text
:
string
)
:
void
{
this
.
question
=
text
;
QuestionHistoryService
.
add
(
text
)
;
}

更稳的首页实现是只回填输入框并保留来源;真正的写入仍在一次成功的 onSubmit 中发生。当前项目可以先不引入 PendingQuestion ,因为它只有手输文本;等推荐入口真的出现时再增加,避免无用状态。

private
pendingSource
:
QuestionSource
=
'user_input'
;
private
onPickQuickQuestion
(
text
:
string
)
:
void
{
this
.
question
=
text
;
this
.
pendingSource
=
'quick_pick'
;
}
private
onQuestionChanged
(
value
:
string
)
:
void
{
this
.
question
=
value
;
this
.
pendingSource
=
'user_input'
;
}

这里把“用户在 TextInput 继续编辑”视作一次重新确认:来源改回 user_input 。如果保留推荐来源,就可以在提交前选择“只用于本次抽取,不写历史”;如果产品确定快捷问题也应入历史,则需在按钮文案中明确说明。

历史服务已经负责去重、截断和刷新

QuestionHistoryService.add 的职责不是判断推荐来源,而是处理已经确认可保存的文本:它先 trim ,从旧列表中过滤同文本,再 unshift 到首位;超过 MAX_QUESTION_HISTORY (当前为 30)时截断;最后 QuestionHistoryRepository.saveAll(next) 并更新 LastQuestionHistoryUpdateAt 。

async
function
commitQuestionHistory
(
question
:
PendingQuestion
)
:
Promise
<
void
>
{
if
(
!
canWriteHistory
(
question
)
)
{
return
;
}
await
QuestionHistoryService
.
add
(
question
.
text
)
;
}

不要把“最近使用的推荐”硬塞进 QuestionHistoryRepository 来复用列表 UI。它们的保留时间、删除方式和隐私预期不同:提问历史包含用户表达,推荐曝光只是产品状态。需要记录推荐时,应单独建立轻量的推荐使用记录,并默认不显示在“提问历史”里。

提交顺序要保证:跳转失败时不能留下假历史

当前 Index.onSubmit 在 pathStack.pushPath 后异步调用 QuestionHistoryService.add ,并通过 .catch 记录失败。它适合当前本地路由和轻量历史;但若将来“获取答案”要等待网络、权限或题库检查,写历史时机要重新评估。

可采用下面的提交结果模型,把“用户尝试”与“业务已接受”分开。它是后续异步抽取场景的设计示例。

interface
SubmitResult
{
accepted
:
boolean
;
historyText
?
:
string
;
reason
?
:
'deck_not_ready'
|
'empty_question'
|
'route_failed'
;
}
async
function
submitAndRecord
(
result
:
SubmitResult
)
:
Promise
<
void
>
{
if
(
result
.
accepted
&&
result
.
historyText
)
{
await
QuestionHistoryService
.
add
(
result
.
historyText
)
;
}
}

这样“题库尚未就绪”和“路由未成功”都不会污染历史。反过来,若产品想保留用户未成功提交的草稿,应把它命名为草稿并单独存储,绝不能让历史列表同时承担两种含义。

历史抽屉只订阅刷新信号,不保留业务副本

QuestionHistoryService 在保存、删除、清空后都会更新 AppStorageKey.LastQuestionHistoryUpdateAt 。这个时间戳只说明“请重新读取”,不携带完整文本。 QuestionHistorySheet 应在收到变化后通过 Service 重新加载,而不是持有一份永远不更新的数组副本。

用户确认提交
  → QuestionHistoryService.add
  → QuestionHistoryRepository.saveAll
  → LastQuestionHistoryUpdateAt
  → QuestionHistorySheet 重新 list()

这种分工也让冷启动更简单:持久化真相在 Repository,AppStorage 是运行期通知。系统回收页面后,抽屉再次出现仍能读取最新历史,不依赖首页的组件内存。

验证不要只点一次“获取答案”

  • 不输入任何文字直接获取答案:可以进入抽取页,但历史仍为空。

  • 输入“ 要不要开始 ”提交:历史只出现去掉首尾空格后的文本。

  • 连续两次提交同一句:它应回到队首,数量不增加。

  • 连续提交 31 条不同问题:只保留最近 30 条,最早一条被裁掉。

  • 点选系统推荐后关闭页面或清空输入框:历史不应多出该推荐。

  • 点选快捷问题再编辑后提交:按明确产品规则验证,不能由实现偶然决定。

  • 删除、清空历史后重新打开 Sheet:确认内容来自 Repository 的最新读取。

日志也应保持克制。当前首页记录的是 deckId 和 qLen ,历史服务记录的是总数,没有输出提问正文;扩展推荐入口时应延续这种做法,不要为了排障把用户问题写进 hilog 。

常见问题与处理方式

| 现象 | 先检查 | 修复方向 |
| --- | --- | --- |
| placeholder 出现在历史中 | 是否把 placeholder 当作 question 初值 | placeholder 只用于 UI,不进入状态与服务 |
| 点一下推荐就出现历史 | 是否在卡片点击回调调用 add | 只回填输入框,留到明确提交后记录 |
| 历史重复且顺序混乱 | 是否绕开 QuestionHistoryService 直写仓储 | 统一走 add 的去重与队首规则 |
| 清空后抽屉仍显示旧内容 | Sheet 是否只读了一次 @State | 监听刷新信号后重新 list() |
| 题库未就绪也留下记录 | 写入是否在前置检查前执行 | 先确认 currentDeckId 再决定提交 |

排查顺序建议从 Index.onSubmit 的判空与题库检查开始,再看 QuestionHistoryService.add 是否被调用,最后检查 Repository 和抽屉重读。这样能区分“根本不该写”“写了但没保存”“保存了但没刷新”三类问题。

小结

在《答案之书》中,提问历史应代表用户已经确认发起的提问,而不是 placeholder、推荐文案或一次未完成的点击。现有 Index 的非空保护与 QuestionHistoryService 的去重、截断、刷新边界已经满足这一原则。

后续增加快捷问题时,先把来源和确认时机写清楚:卡片只提供候选,用户提交才形成历史;草稿、推荐曝光和提问历史分别存放。本文中的来源模型为扩展设计,尚未在项目中实现;真机交互与发布效果仍需在实际工程中验证。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:CSDN HarmonyOS

原创地址:https://blog.csdn.net/AD0227/article/details/163311773

原文作者:AD0227;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-08-02

  • 博文量
    36
  • 访问量
    34494