Refresh visible news risk ratings after analysis
Poll only visible news IDs and update ratings for matching revisions without replacing the feed, scrolling, or clearing pending-news notifications. Bind refresh results to the page and session, and cover stale responses and bounded ID queries.
This commit is contained in:
1 parent
4f43ca8b57
commit
91bc65df75
7 files changed
+101
-3
No files matched your search
@@ -3,11 +3,12 @@ import { request } from "@/http/axios"
|
||||
import { consumeStream } from "@/http/stream"
|
||||
|
||||
/** 分页查询新闻 */
|
||||
export function listNewsApi(params: News.NewsQuery) {
|
||||
export function listNewsApi(params: News.NewsQuery, notify = true) {
|
||||
return request<News.ListNewsResponseData>({
|
||||
url: "/news",
|
||||
method: "get",
|
||||
params,
|
||||
notify,
|
||||
paramsSerializer: { indexes: null }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface NewsBrief {
|
||||
}
|
||||
|
||||
export interface NewsQuery {
|
||||
ids?: number[]
|
||||
source?: string[]
|
||||
q?: string
|
||||
start?: string
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { NewsBrief, NewsEvent } from "@@/apis/news/type"
|
||||
import { listNewsApi, subscribeNewsApi } from "@@/apis/news"
|
||||
import { useSessionScope } from "@@/composables/useSessionScope"
|
||||
import { useSourceNames } from "@@/composables/useSourceNames"
|
||||
import { usePersisted } from "@@/utils/storage"
|
||||
import { useEventSource } from "@/http/useEventSource"
|
||||
|
||||
export function useNewsFeed() {
|
||||
const scope = useSessionScope()
|
||||
const sourceNames = useSourceNames()
|
||||
const filters = reactive({
|
||||
source: [] as string[],
|
||||
@@ -17,6 +19,9 @@ export function useNewsFeed() {
|
||||
const pageSize = usePersisted("news-page-size", 30)
|
||||
const loading = ref(false)
|
||||
const failed = ref(false)
|
||||
const riskFailed = ref(false)
|
||||
let riskLoading = false
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
const pendingCount = ref(0)
|
||||
const openedId = ref<number | null>(null)
|
||||
const listBox = ref<HTMLElement>()
|
||||
@@ -39,6 +44,7 @@ export function useNewsFeed() {
|
||||
if (seq !== requestSeq)
|
||||
return
|
||||
failed.value = false
|
||||
riskFailed.value = false
|
||||
items.value = result.items
|
||||
total.value = result.total
|
||||
if (page.value === 1)
|
||||
@@ -58,6 +64,26 @@ export function useNewsFeed() {
|
||||
else return load()
|
||||
}
|
||||
|
||||
async function refreshRisk() {
|
||||
if (!scope.isCurrent() || loading.value || riskLoading || !items.value.length) return
|
||||
const seq = requestSeq
|
||||
riskLoading = true
|
||||
try {
|
||||
const result = await listNewsApi({ ids: items.value.map(item => item.id), limit: 100 }, false)
|
||||
if (seq !== requestSeq || !scope.isCurrent()) return
|
||||
const latest = new Map(result.items.map(item => [item.id, item]))
|
||||
for (const item of items.value) {
|
||||
const current = latest.get(item.id)
|
||||
if (current?.revision_no === item.revision_no) item.risk = current.risk
|
||||
}
|
||||
riskFailed.value = false
|
||||
} catch {
|
||||
if (seq === requestSeq && scope.isCurrent()) riskFailed.value = true
|
||||
} finally {
|
||||
riskLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
watch([page, pageSize], ([, size], [, oldSize]) => {
|
||||
if (size !== oldSize && page.value !== 1) {
|
||||
page.value = 1
|
||||
@@ -73,6 +99,15 @@ export function useNewsFeed() {
|
||||
})
|
||||
|
||||
onMounted(load)
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
if (!document.hidden && items.value.some(item => item.risk?.state !== "done")) void refreshRisk()
|
||||
}, 15000)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
requestSeq++
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
return { sourceNames, filters, items, total, page, pageSize, loading, failed, pendingCount, openedId, listBox, connected, load, search }
|
||||
return { sourceNames, filters, items, total, page, pageSize, loading, failed, riskFailed, pendingCount, openedId, listBox, connected, load, search, refreshRisk }
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Refresh, Search } from "@element-plus/icons-vue"
|
||||
import NewsDetail from "./components/NewsDetail.vue"
|
||||
import { useNewsFeed } from "./composables/useNewsFeed"
|
||||
|
||||
const { sourceNames, filters, items, total, page, pageSize, loading, failed, pendingCount, openedId, listBox, connected, load, search } = useNewsFeed()
|
||||
const { sourceNames, filters, items, total, page, pageSize, loading, failed, riskFailed, pendingCount, openedId, listBox, connected, load, search } = useNewsFeed()
|
||||
const mobile = ref(false)
|
||||
let media: MediaQueryList
|
||||
const updateMobile = () => mobile.value = media.matches
|
||||
@@ -23,6 +23,7 @@ onBeforeUnmount(() => media?.removeEventListener("change", updateMobile))
|
||||
● {{ connected ? "实时推送已连接" : "实时推送断开" }}
|
||||
</el-tag>
|
||||
<div class="flex-1" />
|
||||
<span v-if="riskFailed" class="text-xs text-amber-600">AI 状态刷新失败,将自动重试</span>
|
||||
<div
|
||||
v-if="pendingCount > 0"
|
||||
class="flex cursor-pointer items-center gap-1 border border-sky-200 rounded-full border-solid bg-sky-50 px-4 py-1 text-sm text-[var(--el-color-primary)] hover:bg-sky-100"
|
||||
|
||||
Reference in new issue
Block a user