Files
lpt-fe/src/components/Study.vue
T

300 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import request from "@/utils/request";
import router from "@/router";
import { ElMessage } from "element-plus";
interface TaskItem {
title: string;
description: string;
materialURL: string;
lastLearningStatus: string;
priority: number | string;
taskNum: string;
taskId: number;
}
const tasks = ref<TaskItem[]>([]);
const selectedTaskId = ref<number | null>(null);
const loading = ref(false);
const selectedTask = computed(() =>
tasks.value.find((item) => item.taskId === selectedTaskId.value) || null
);
const fetchTasks = async () => {
loading.value = true;
try {
const res = await request.get("/tasks", { pageSize: 100, pageNum: 1 });
tasks.value = (res?.data?.records || []).map((record: any) => ({
title: record.taskName,
description: record.taskDescription || "",
materialURL: record.materialURL || record.materialUrl || "",
lastLearningStatus: record.lastLearningStatus || "未开始",
priority: Number.isFinite(record.taskPriority) ? Math.round(record.taskPriority) : "-",
taskNum: record.taskNum,
taskId: record.id,
}));
selectedTaskId.value = tasks.value[0]?.taskId ?? null;
} catch (error: any) {
tasks.value = [];
selectedTaskId.value = null;
ElMessage.error(error?.message || "任务列表加载失败,请重试");
} finally {
loading.value = false;
}
};
const startTask = () => {
if (!selectedTask.value) {
ElMessage.warning("请先选择一个任务");
return;
}
window.location.assign(`/start-task/${encodeURIComponent(selectedTask.value.taskNum)}`);
};
const addTask = () => {
router.push({ name: "add-task" });
};
const changeTask = () => {
if (!selectedTask.value) {
ElMessage.warning("请先选择一个任务");
return;
}
router.push({ name: "update-task", params: { taskId: selectedTask.value.taskId } });
};
const removeTask = async () => {
if (!selectedTask.value) {
ElMessage.warning("请先选择一个任务");
return;
}
const target = selectedTask.value;
const res = await request.del(`/tasks/${target.taskId}`, {});
if (res.code === 200) {
ElMessage.success(`任务 ${target.title} 删除成功`);
tasks.value = tasks.value.filter((item) => item.taskId !== target.taskId);
selectedTaskId.value = tasks.value[0]?.taskId ?? null;
}
};
onMounted(() => {
fetchTasks();
});
</script>
<template>
<section class="study-page">
<header class="surface-card page-head">
<div>
<h1 class="page-title">学习任务</h1>
<p class="page-subtitle">先选任务再确认材料与优先级然后开始一次学习会话</p>
</div>
<el-button type="success" plain @click="fetchTasks" :loading="loading">刷新列表</el-button>
</header>
<div class="layout-grid">
<aside class="surface-card task-list">
<div class="block-title">任务清单</div>
<el-scrollbar height="420px">
<div
v-for="item in tasks"
:key="item.taskId"
class="task-list-item"
:class="{ active: selectedTaskId === item.taskId }"
@click="selectedTaskId = item.taskId"
>
<p class="task-name">{{ item.title }}</p>
<p class="task-meta">状态{{ item.lastLearningStatus }}</p>
</div>
</el-scrollbar>
</aside>
<main class="content-zone">
<article class="surface-card detail-card" v-loading="loading">
<template v-if="selectedTask">
<div class="detail-head">
<h3>{{ selectedTask.title }}</h3>
<el-tag type="success" effect="plain">优先级 {{ selectedTask.priority }}</el-tag>
</div>
<div class="detail-block">
<p class="label">任务描述</p>
<el-input type="textarea" :model-value="selectedTask.description" :rows="4" readonly />
</div>
<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>
<p v-else class="empty-text">暂未填写材料地址</p>
</div>
</template>
<el-empty v-else description="暂无任务,请先创建任务" />
</article>
<aside class="surface-card action-card">
<div class="block-title">常用操作</div>
<el-button type="success" size="large" @click="startTask">开始任务</el-button>
<el-button size="large" @click="addTask">添加任务</el-button>
<el-button size="large" @click="changeTask">更新任务</el-button>
<el-button type="danger" size="large" @click="removeTask">删除任务</el-button>
</aside>
</main>
</div>
</section>
</template>
<style scoped>
.study-page {
display: flex;
flex-direction: column;
gap: 14px;
}
.page-head {
padding: 18px 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.layout-grid {
display: grid;
grid-template-columns: 280px 1fr;
gap: 14px;
}
.task-list {
padding: 16px;
}
.block-title {
margin: 0 0 12px;
font-size: 15px;
font-weight: 700;
color: var(--green-900);
}
.task-list-item {
border: 1px solid var(--border-soft);
border-radius: 12px;
padding: 12px;
cursor: pointer;
background: #fbfefc;
transition: all 0.2s ease;
}
.task-list-item + .task-list-item {
margin-top: 10px;
}
.task-list-item:hover {
border-color: #b9d9c8;
}
.task-list-item.active {
border-color: var(--green-600);
background: var(--green-100);
box-shadow: inset 0 0 0 1px rgba(47, 143, 104, 0.2);
}
.task-name {
margin: 0;
font-size: 15px;
font-weight: 600;
}
.task-meta {
margin: 4px 0 0;
font-size: 12px;
color: var(--text-secondary);
}
.content-zone {
display: grid;
grid-template-columns: 1fr 220px;
gap: 14px;
}
.detail-card {
padding: 18px 20px;
}
.detail-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.detail-head h3 {
margin: 0;
font-size: 22px;
}
.detail-block {
margin-top: 16px;
}
.label {
margin: 0 0 8px;
color: var(--text-secondary);
font-size: 13px;
}
.empty-text {
margin: 0;
color: var(--text-secondary);
}
.action-card {
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.action-card .el-button {
margin: 0;
}
@media (max-width: 1100px) {
.layout-grid {
grid-template-columns: 1fr;
}
.content-zone {
grid-template-columns: 1fr;
}
.action-card {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
}
.action-card .block-title {
grid-column: 1 / -1;
}
}
@media (max-width: 768px) {
.page-head {
flex-direction: column;
}
.action-card {
grid-template-columns: 1fr;
}
}
</style>