- 删除 settings 的 sections 与 sidebar 六个旧排序/显隐字段,前后端类型与校验同步 - 删除旧分类模型 types、旧 /api/catalog 客户端、旧路由解析与 settings CRUD - 删除未使用的 AppProject::initial - 删除 27 条已无组件的 CSS 规则(159 行) - 保持现有首页/接口页渲染,cargo test 52 passed
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
export type Project = {
|
|
name: string
|
|
route_path: string
|
|
}
|
|
|
|
type ProjectsPayload = {
|
|
projects?: Project[]
|
|
}
|
|
|
|
const PROJECTS_ENDPOINT = '/api/projects'
|
|
const EVENTS_ENDPOINT = '/api/projects/events'
|
|
/** 读取服务端扫描出的项目列表。 */
|
|
export async function fetchProjects(signal?: AbortSignal): Promise<Project[]> {
|
|
const response = await fetch(PROJECTS_ENDPOINT, {
|
|
signal,
|
|
headers: { Accept: 'application/json' },
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`请求 ${PROJECTS_ENDPOINT} 失败:HTTP ${response.status}`)
|
|
}
|
|
|
|
const payload = (await response.json()) as ProjectsPayload
|
|
return Array.isArray(payload.projects) ? payload.projects : []
|
|
}
|
|
|
|
type ProjectEventsHandlers = {
|
|
onProjects: (projects: Project[]) => void
|
|
/** SSE 连接建立 / 断开时回调,浏览器的 EventSource 会自行重连。 */
|
|
onConnectionChange?: (connected: boolean) => void
|
|
}
|
|
|
|
/**
|
|
* 订阅服务端的项目快照推送。
|
|
* 浏览器不支持 SSE 时静默降级为空清理函数,页面仍可用手动刷新。
|
|
*/
|
|
export function subscribeProjects(handlers: ProjectEventsHandlers): () => void {
|
|
if (typeof EventSource === 'undefined') {
|
|
return () => {}
|
|
}
|
|
|
|
const source = new EventSource(EVENTS_ENDPOINT)
|
|
|
|
const handleProjects = (event: Event) => {
|
|
try {
|
|
const payload = JSON.parse((event as MessageEvent<string>).data) as ProjectsPayload
|
|
if (Array.isArray(payload.projects)) {
|
|
handlers.onProjects(payload.projects)
|
|
}
|
|
} catch {
|
|
// 单条事件格式异常时保留旧数据,等待下一次推送
|
|
}
|
|
}
|
|
|
|
const handleOpen = () => handlers.onConnectionChange?.(true)
|
|
const handleError = () => handlers.onConnectionChange?.(false)
|
|
|
|
source.addEventListener('projects', handleProjects)
|
|
source.addEventListener('open', handleOpen)
|
|
source.addEventListener('error', handleError)
|
|
|
|
return () => {
|
|
source.removeEventListener('projects', handleProjects)
|
|
source.removeEventListener('open', handleOpen)
|
|
source.removeEventListener('error', handleError)
|
|
source.close()
|
|
}
|
|
}
|