feat(学习材料):多 URL 支持 + 网页标题自动抓取

TaskForm.vue:
- 学习材料输入从单行改为 textarea,每行一个链接

Study.vue:
- 改为迭代多 URL 展示,每行显示网页标题(通过 lpt-ai
  /fetch-title 获取)或回退到 URL 本身
- 选中不同任务时自动刷新标题

新增 src/utils/fetchTitle.ts:
- fetchTitle(url) / fetchTitles(urls) 工具函数
- 内存缓存避免重复请求
- 失败时回退到 URL,不阻塞页面

后端:
- V20260704_1 迁移:material_url varchar(255) → TEXT
This commit is contained in:
2026-07-04 22:37:01 +08:00
parent 5f7a5a55a4
commit a63942d105
3 changed files with 74 additions and 9 deletions
+36
View File
@@ -0,0 +1,36 @@
/**
* 网页标题抓取工具。
* 调用 lpt-ai 的 /fetch-title 接口获取 URL 对应的 <title> 标签内容。
*
* 设计要点:
* - 内存缓存避免同页面重复请求
* - 失败时回退到 URL 本身
* - 仅在 lpt-ai 运行时可用
*/
const LPT_AI_URL = "http://localhost:5199";
const titleCache: Record<string, string> = {};
export async function fetchTitle(url: string): Promise<string> {
if (titleCache[url]) return titleCache[url];
try {
const res = await fetch(`${LPT_AI_URL}/fetch-title`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
signal: AbortSignal.timeout(12_000),
});
if (!res.ok) return url;
const data = await res.json();
titleCache[url] = data.title || url;
return titleCache[url];
} catch {
return url;
}
}
/** 批量获取标题 */
export async function fetchTitles(urls: string[]): Promise<string[]> {
return Promise.all(urls.map((url) => fetchTitle(url)));
}