feat(chat): 页内日期选择器(跨会话持久)+ 关键词搜索(已加载页过滤)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-08 15:29:19 +08:00
parent 8f166a9c18
commit 239f07e7a1
+70 -20
View File
@@ -57,6 +57,16 @@ const hasActive = computed(() => !!activeId.value)
const hasMoreOlder = computed(() => currentPage.value > 1)
/**
* 关键词过滤:仅作用于"当前已加载"的消息(/api/messages 无 q 参数,非全量历史)。
* keyword 为空 → 直接返回 messages.value。
*/
const visibleMessages = computed(() => {
const k = keyword.value.trim()
if (!k) return messages.value
return messages.value.filter((m) => (m.text ?? '').includes(k))
})
const conversationTitle = computed(() => {
if (isGroup.value) {
return `群聊记录 · ${props.roomId}`
@@ -68,23 +78,41 @@ const conversationTitle = computed(() => {
return '聊天记录'
})
/** 从路由 query 读日期窗(从 StaffList stat modal 带窗跳来时有值);缺失=全量(向后兼容)。 */
const windowStart = computed(() => {
const s = Array.isArray(route.query.start) ? route.query.start[0] : route.query.start
return s != null && /^\d+$/.test(String(s)) ? Number(s) : undefined
})
const windowEnd = computed(() => {
const e = Array.isArray(route.query.end) ? route.query.end[0] : route.query.end
return e != null && /^\d+$/.test(String(e)) ? Number(e) : undefined
})
const TWO_WEEKS_MS = 14 * 86_400_000
function defaultRange(): [Date, Date] {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date(end.getTime() - TWO_WEEKS_MS)
start.setHours(0, 0, 0, 0)
return [start, end]
}
function rangeFromQuery(): [Date, Date] | null {
const s = route.query.start,
e = route.query.end
const sv = Array.isArray(s) ? s[0] : s,
ev = Array.isArray(e) ? e[0] : e
if (sv && ev && /^\d+$/.test(String(sv)) && /^\d+$/.test(String(ev))) {
return [new Date(Number(sv)), new Date(Number(ev))]
}
return null
}
// 本地日期窗 ref:mount 时取 route.query(stat modal 带窗入口)否则默认近 2 周;
// 跨会话切换持久(不随路由 param 变化重置),与云客真机一致。
const dateRange = ref<[Date, Date]>(rangeFromQuery() ?? defaultRange())
const winStart = computed(() => dateRange.value[0].getTime())
const winEnd = computed(() => dateRange.value[1].getTime())
const keyword = ref('')
function onDateChange(): void {
loadInitial()
}
const subtitle = computed(() => {
let s =
`渠道:${channelLabel(channelTyped.value)} · 归属账号:${props.ownerId}` +
(total.value > 0 ? ` · 共 ${total.value} 条` : '')
if (windowStart.value && windowEnd.value) {
s += ` · 时段:${formatTime(windowStart.value)} ~ ${formatTime(windowEnd.value)}`
}
s += ` · 时段:${formatTime(winStart.value)} ~ ${formatTime(winEnd.value)}`
return s
})
@@ -99,15 +127,13 @@ function scrollToBottom(): void {
/** Shared query params for both DM and group timelines. */
function queryFor(pageNum: number) {
// 日期窗为 both-or-neither:仅当 start/end 同时存在才透传,避免单边过滤
// (getMessages→clean() 会逐键剔除 undefined,单边 start 会变成下界过滤)。
const hasWindow = windowStart.value != null && windowEnd.value != null
return {
channel: channelTyped.value,
owner_account_id: props.ownerId,
talker_id: isGroup.value ? undefined : props.talkerId,
room_id: isGroup.value ? props.roomId : undefined,
...(hasWindow ? { start: windowStart.value, end: windowEnd.value } : {}),
start: winStart.value,
end: winEnd.value,
page: pageNum,
page_size: PAGE_SIZE,
}
@@ -223,9 +249,9 @@ const drawerTitle = computed(() => {
})
// Reload when the route params change (navigating between conversations).
// dateRange 变化走 onDateChange,不在此 watcher 内。
watch(
() =>
[props.channel, props.ownerId, props.talkerId, props.roomId, windowStart.value, windowEnd.value] as const,
() => [props.channel, props.ownerId, props.talkerId, props.roomId] as const,
() => {
loadInitial()
},
@@ -256,7 +282,22 @@ onMounted(loadInitial)
</div>
<div class="yk-chat__sub">{{ subtitle }}</div>
</div>
<!-- Task 4 在此插日期选择器 + 关键词搜索;本 task 留空 -->
<div class="yk-chat__filters">
<el-date-picker
v-model="dateRange"
type="daterange"
size="small"
:clearable="false"
@change="onDateChange"
/>
<el-input
v-model="keyword"
size="small"
placeholder="搜索当前已加载消息"
clearable
style="width: 200px"
/>
</div>
</div>
<el-alert
@@ -288,9 +329,12 @@ onMounted(loadInitial)
<template v-if="messages.length === 0 && !loading">
<div class="yk-empty">暂无聊天记录</div>
</template>
<template v-else-if="visibleMessages.length === 0 && keyword && !loading">
<div class="yk-empty">当前已加载消息中无匹配</div>
</template>
<MessageBubble
v-for="m in messages"
v-for="m in visibleMessages"
:key="m.msg_id"
:message="m"
@open-detail="openDetail"
@@ -386,6 +430,12 @@ onMounted(loadInitial)
justify-content: center;
}
.yk-chat__filters {
display: flex;
gap: 8px;
align-items: center;
}
/* Make the stream its own scroll region so scrollToBottom + load-older scroll
* anchoring target the right element. Height reserves space for the app
* header + chat header + main padding. */