“输入你的问题…(可跳过)”只是输入框的提示,不能成为用户的历史记录;系统推荐的“今天适合开始吗?”即使被点进输入框,也不该在用户没有确认前占据历史列表。否则历史抽屉很快从“我问过什么”变成“应用替我准备过什么”,用户再次打开时无法判断哪些内容真的由自己提交。
《答案之书》当前首页已经做了最关键的一步: 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
原文作者:AD0227;如有侵权,请联系管理员删除。