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:
@@ -12,6 +12,7 @@ import {
|
||||
type PriorityWeights,
|
||||
type TaskApplication,
|
||||
} from "@/api/tasks";
|
||||
import { fetchTitles } from "@/utils/fetchTitle";
|
||||
|
||||
interface TaskItem {
|
||||
title: string;
|
||||
@@ -48,6 +49,18 @@ const selectedTask = computed(() =>
|
||||
tasks.value.find((item) => item.taskId === selectedTaskId.value) || null
|
||||
);
|
||||
|
||||
// 多 URL 支持
|
||||
const urlList = computed(() => {
|
||||
const raw = selectedTask.value?.materialURL || "";
|
||||
return raw.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.startsWith("http"));
|
||||
});
|
||||
const urlTitles = ref<string[]>([]);
|
||||
async function loadUrlTitles() {
|
||||
const urls = urlList.value;
|
||||
if (urls.length === 0) { urlTitles.value = []; return; }
|
||||
urlTitles.value = await fetchTitles(urls);
|
||||
}
|
||||
|
||||
const formatEffectiveTime = (seconds: number): string => {
|
||||
if (!seconds || seconds <= 0) return "0分钟";
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
@@ -154,8 +167,10 @@ watch(selectedTaskId, (newId) => {
|
||||
if (task) {
|
||||
loadTaskStats(task.taskNum);
|
||||
loadTaskApplications(task.taskNum);
|
||||
loadUrlTitles();
|
||||
} else {
|
||||
taskApplications.value = [];
|
||||
urlTitles.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,14 +313,13 @@ onMounted(() => {
|
||||
|
||||
<div class="detail-block">
|
||||
<p class="label">学习材料</p>
|
||||
<el-link
|
||||
v-if="selectedTask.materialURL"
|
||||
:href="selectedTask.materialURL"
|
||||
target="_blank"
|
||||
type="primary"
|
||||
>
|
||||
{{ selectedTask.materialURL }}
|
||||
</el-link>
|
||||
<div v-if="urlList.length > 0" class="material-list">
|
||||
<div v-for="(url, i) in urlList" :key="i" class="material-item">
|
||||
<el-link :href="url" target="_blank" type="success">
|
||||
{{ urlTitles[i] || url }}
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty-text">暂未填写材料地址</p>
|
||||
</div>
|
||||
|
||||
@@ -559,6 +573,21 @@ onMounted(() => {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.material-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.material-item {
|
||||
padding: 4px 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.material-item .el-link {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
Reference in New Issue
Block a user