You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/teacherEnd/trainingTask/index.vue

1107 lines
27 KiB
Vue

<template>
<div class="training-task-page app-container">
<section class="task-filter">
<div class="project-filter">
<span class="filter-label">所属项目</span>
<button
v-for="project in projects"
:key="project"
type="button"
:class="['project-tab', { active: activeProject === project }]"
@click="toggleProject(project)"
>
{{ project }}
</button>
</div>
<div class="search-panel">
<el-input v-model.trim="keyword" clearable placeholder="请输入实训任务名称" @keyup.enter="queryTasks" />
<el-button type="primary" @click="queryTasks"></el-button>
<div class="toolbar-actions">
<button type="button" class="tool-link" @click="downloadTemplate"></button>
<el-upload action="#" :auto-upload="false" :show-file-list="false" :on-change="handleImportChange">
<button type="button" class="tool-link">导入配置</button>
</el-upload>
</div>
</div>
</section>
<section v-loading="loading" class="task-grid">
<article
v-for="(task, index) in filteredTasks"
:key="task.id || task.taskKey"
class="task-card"
role="button"
tabindex="0"
@click="viewTask(task)"
@keydown.enter.prevent="viewTask(task)"
>
<div class="task-card-index">{{ String(index + 1).padStart(2, "0") }}</div>
<div class="task-card-core">
<div class="task-project">{{ task.project }}</div>
<div class="task-name">{{ task.title }}</div>
<div class="task-card-hint">点击卡片查看详情</div>
</div>
<div class="task-actions">
<el-button type="primary" size="small" @click.stop="editTask(task)">编辑任务</el-button>
</div>
<div class="task-card-scanline"></div>
</article>
<el-empty v-if="!filteredTasks.length && !loading" class="task-empty" description="暂无匹配的实训任务" />
</section>
<el-drawer
v-model="dialogVisible"
:size="860"
class="training-task-drawer"
append-to-body
destroy-on-close
:with-header="false"
>
<div class="drawer-shell">
<header class="drawer-header">
<div class="drawer-heading">
<span class="drawer-kicker">{{ dialogMode === "view" ? "Training Task Preview" : "Training Task Editor" }}</span>
<h3>{{ dialogMode === "view" ? "查看实训任务" : "编辑实训任务" }}</h3>
<p>任务数量与步骤数量由系统固定老师可维护任务显示名称实训说明目标要求和步骤名称</p>
</div>
<div class="drawer-header-actions">
<button type="button" class="drawer-close-button" aria-label="" @click="dialogVisible = false">x</button>
</div>
</header>
<div class="drawer-identity">
<div>
<span>所属项目</span>
<strong>{{ taskForm.project }}</strong>
</div>
<div>
<span>实训任务</span>
<strong>{{ taskForm.title }}</strong>
</div>
<div>
<span>任务状态</span>
<strong>{{ taskForm.enabled ? "启用" : "停用" }}</strong>
</div>
</div>
<el-form class="task-form" label-position="top" :disabled="isReadonly">
<section class="drawer-section">
<div class="section-title">
<span>01</span>
<h4>基础说明</h4>
</div>
<el-form-item label="实训任务名称:">
<el-input v-model="taskForm.title" placeholder="保存后会同步为学生端左侧导航名称" />
</el-form-item>
<el-form-item label="任务分析:">
<el-input v-model="taskForm.knowledge" type="textarea" :rows="4" placeholder="请输入任务分析" />
</el-form-item>
<el-form-item label="实训背景:">
<el-input v-model="taskForm.background" type="textarea" :rows="5" placeholder="请输入实训背景" />
</el-form-item>
</section>
<section class="drawer-section">
<div class="section-title">
<span>02</span>
<h4>目标与要求</h4>
</div>
<el-form-item label="实训目标:">
<div class="objective-list">
<div v-for="(objective, index) in taskForm.objectives" :key="index" class="objective-row">
<el-input v-model="taskForm.objectives[index]" placeholder="请输入实训目标" />
<button
v-if="!isReadonly"
type="button"
class="circle-action"
:disabled="taskForm.objectives.length <= 1"
@click="removeObjective(index)"
>
-
</button>
</div>
<button v-if="!isReadonly" type="button" class="add-objective-button" @click="addObjective">+ </button>
</div>
</el-form-item>
<el-form-item label="实训要求:">
<el-input v-model="taskForm.requirements" type="textarea" :rows="5" placeholder="请输入实训要求" />
</el-form-item>
</section>
<section class="drawer-section">
<div class="section-title">
<span>03</span>
<h4>资料与步骤</h4>
</div>
<div class="material-row">
<a v-if="hasTaskMaterial" class="material-link" :href="taskForm.materialUrl || 'javascript:void(0)'" target="_blank">{{ taskForm.materialName }}</a>
<span v-else class="material-placeholder">暂未上传案例资料</span>
<el-upload
v-if="!isReadonly"
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="handleMaterialChange"
>
<el-button type="primary" :loading="materialUploading">上传案例资料文档</el-button>
</el-upload>
</div>
<el-form-item label="步骤名称:">
<div class="step-row">
<template v-for="(step, index) in taskForm.steps" :key="index">
<el-input v-model="taskForm.steps[index]" placeholder="步骤名称" />
<span v-if="index < taskForm.steps.length - 1" class="step-arrow">&gt;&gt;&gt;</span>
</template>
</div>
</el-form-item>
</section>
</el-form>
<footer class="drawer-footer">
<el-button @click="dialogVisible = false">{{ isReadonly ? "关闭" : "取消" }}</el-button>
<el-button v-if="!isReadonly" type="primary" :loading="saving" @click="submitTask"></el-button>
</footer>
</div>
</el-drawer>
</div>
</template>
<script setup>
import {
createTrainingTask,
downloadTrainingTaskTemplate,
importTrainingTasks,
listTrainingTasks,
updateTrainingTask,
uploadTrainingTaskMaterial,
} from "@/api/trainingTask";
const { proxy } = getCurrentInstance();
const activeProject = ref("");
const keyword = ref("");
const loading = ref(false);
const saving = ref(false);
const materialUploading = ref(false);
const tasks = ref([]);
const dialogVisible = ref(false);
const dialogMode = ref("view");
const currentTaskId = ref("");
const taskForm = reactive(createEmptyTask());
const isReadonly = computed(() => dialogMode.value === "view");
const hasTaskMaterial = computed(() => isRealMaterial(taskForm.materialName, taskForm.materialUrl));
const projects = computed(() => [...new Set(tasks.value.map((task) => task.project).filter(Boolean))]);
const filteredTasks = computed(() => {
const text = keyword.value.trim();
return tasks.value.filter((task) => {
const matchProject = !activeProject.value || task.project === activeProject.value;
const matchKeyword = !text || task.title.includes(text);
return matchProject && matchKeyword;
});
});
onMounted(() => {
queryTasks();
});
function toggleProject(project) {
activeProject.value = activeProject.value === project ? "" : project;
}
async function queryTasks() {
loading.value = true;
try {
const res = await listTrainingTasks({ enabledOnly: false });
tasks.value = (res.data || []).map(normalizeTask);
if (activeProject.value && !projects.value.includes(activeProject.value)) {
activeProject.value = "";
}
} catch (error) {
tasks.value = [];
} finally {
loading.value = false;
}
}
function viewTask(task) {
openTaskDialog(task, "view");
}
function editTask(task) {
openTaskDialog(task, "edit");
}
function createEmptyTask() {
return {
id: "",
taskKey: "",
project: "",
title: "",
knowledge: "",
background: "",
objectives: [""],
requirements: "",
materialName: "",
materialUrl: "",
steps: ["", "", "", ""],
sort: 0,
enabled: true,
};
}
function normalizeTask(task) {
const material = normalizeMaterial(task.materialName, task.materialUrl);
return {
id: task.id,
taskKey: task.taskKey,
project: task.projectName,
title: task.taskName,
knowledge: task.knowledge || "",
background: task.background || "",
objectives: parseArray(task.objectives, [""]),
requirements: task.requirements || "",
materialName: material.name,
materialUrl: material.url,
steps: normalizeSteps(parseArray(task.steps, ["", "", "", ""])),
sort: task.sort || 0,
enabled: task.enabled !== false,
};
}
function normalizeMaterial(materialName, materialUrl) {
const name = String(materialName || "").trim();
const url = String(materialUrl || "").trim();
return isRealMaterial(name, url) ? { name, url } : { name: "", url: "" };
}
function isRealMaterial(materialName, materialUrl) {
const name = String(materialName || "").trim();
const url = String(materialUrl || "").trim();
if (!name) {
return false;
}
return !(name === "案例资料.rar" && (!url || url === "/file/example.rar"));
}
function parseArray(value, fallback) {
if (Array.isArray(value)) {
return value.length ? value : fallback;
}
if (!value) {
return fallback;
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) && parsed.length ? parsed : fallback;
} catch (error) {
return String(value)
.split(/[;|]/)
.map((item) => item.trim())
.filter(Boolean);
}
}
function normalizeSteps(steps) {
const result = steps.filter((item) => String(item || "").trim()).slice(0, 4);
while (result.length < 4) {
result.push("");
}
return result;
}
function openTaskDialog(task, mode) {
dialogMode.value = mode;
currentTaskId.value = task.id;
Object.assign(taskForm, cloneTask(task));
dialogVisible.value = true;
}
function cloneTask(task) {
return {
...task,
objectives: [...(task.objectives?.length ? task.objectives : [""])],
steps: normalizeSteps(task.steps?.length ? task.steps : ["", "", "", ""]),
};
}
function addObjective() {
taskForm.objectives.push("");
}
function removeObjective(index) {
if (taskForm.objectives.length <= 1) return;
taskForm.objectives.splice(index, 1);
}
async function handleImportChange(file) {
if (!file?.raw) return;
await importTrainingTasks(file.raw);
proxy?.$modal?.msgSuccess("导入成功");
queryTasks();
}
async function handleMaterialChange(file) {
const rawFile = file?.raw;
if (!rawFile) return;
const data = new FormData();
data.append("file", rawFile);
materialUploading.value = true;
try {
const res = await uploadTrainingTaskMaterial(data);
taskForm.materialName = res?.originalFilename || rawFile.name || file?.name || taskForm.materialName;
taskForm.materialUrl = res?.url || res?.fileName || "";
proxy?.$modal?.msgSuccess("案例资料上传成功");
} catch (error) {
proxy?.$modal?.msgError?.("案例资料上传失败");
} finally {
materialUploading.value = false;
}
}
async function downloadTemplate() {
const blob = await downloadTrainingTaskTemplate();
const url = URL.createObjectURL(new Blob([blob]));
const link = document.createElement("a");
link.href = url;
link.download = "实训任务导入模板.xlsx";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
async function submitTask() {
saving.value = true;
try {
if (currentTaskId.value) {
await updateTrainingTask(currentTaskId.value, buildPayload());
} else {
await createTrainingTask(buildPayload());
}
dialogVisible.value = false;
proxy?.$modal?.msgSuccess("保存成功");
await queryTasks();
} finally {
saving.value = false;
}
}
function buildPayload() {
return {
projectName: taskForm.project,
taskKey: taskForm.taskKey,
taskName: taskForm.title,
knowledge: taskForm.knowledge,
background: taskForm.background,
objectives: JSON.stringify(taskForm.objectives.map((item) => item.trim()).filter(Boolean)),
requirements: taskForm.requirements,
steps: JSON.stringify(taskForm.steps.map((item) => item.trim()).filter(Boolean)),
materialName: taskForm.materialName,
materialUrl: taskForm.materialUrl,
sort: taskForm.sort,
enabled: taskForm.enabled,
};
}
</script>
<style lang="scss" scoped>
.training-task-page {
min-height: calc(100vh - 130px);
color: #eff8ff;
}
.task-filter {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 20px 22px;
margin: 0 0 32px;
border: 1px solid rgba(42, 190, 255, 0.5);
border-radius: 8px;
background:
linear-gradient(90deg, rgba(23, 187, 255, 0.12), transparent 28%, rgba(18, 116, 178, 0.08)),
linear-gradient(135deg, rgba(3, 34, 53, 0.94), rgba(4, 22, 36, 0.9));
box-shadow:
inset 0 1px 0 rgba(174, 238, 255, 0.12),
inset 0 0 34px rgba(0, 174, 255, 0.08),
0 16px 40px rgba(0, 0, 0, 0.22);
&::before,
&::after {
position: absolute;
width: 92px;
height: 1px;
content: "";
background: linear-gradient(90deg, rgba(58, 219, 255, 0), rgba(58, 219, 255, 0.85));
}
&::before {
top: 0;
left: 24px;
}
&::after {
right: 24px;
bottom: 0;
transform: rotate(180deg);
}
}
.project-filter {
display: flex;
align-items: center;
flex: 1;
flex-wrap: wrap;
gap: 8px 12px;
min-width: 0;
}
.filter-label {
flex: 0 0 auto;
color: #e8f7ff;
font-weight: 700;
}
.project-tab {
flex: 0 0 auto;
height: 34px;
padding: 0 14px;
color: #b9d5e5;
font-weight: 600;
line-height: 32px;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
background: rgba(5, 32, 50, 0.48);
transition: all 0.18s ease;
&:hover,
&.active {
color: #ffffff;
border-color: rgba(48, 197, 255, 0.72);
background: linear-gradient(180deg, rgba(34, 169, 239, 0.72), rgba(7, 91, 140, 0.62));
box-shadow: 0 0 16px rgba(33, 178, 255, 0.26), inset 0 0 16px rgba(255, 255, 255, 0.08);
}
}
.search-panel {
display: flex;
align-items: center;
flex: 0 0 600px;
gap: 12px;
:deep(.el-input) {
flex: 1;
min-width: 220px;
}
:deep(.el-input__wrapper) {
border: 1px solid rgba(76, 190, 245, 0.48);
border-radius: 4px;
background: rgba(0, 20, 32, 0.92);
box-shadow: none;
}
:deep(.el-input__inner) {
color: #e9f8ff;
}
:deep(.el-button) {
min-width: 72px;
border: 0;
margin-left: 0;
background: linear-gradient(180deg, #22a9ef, #087bc2);
}
}
.toolbar-actions {
display: inline-flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
height: 32px;
padding-left: 10px;
border-left: 1px solid rgba(92, 190, 236, 0.2);
:deep(.el-upload) {
display: inline-flex;
}
}
.tool-link {
height: 28px;
padding: 0 8px;
color: #9bdcf6;
font-size: 13px;
font-weight: 700;
cursor: pointer;
border: 0;
border-radius: 4px;
background: transparent;
transition: color 0.18s ease, background 0.18s ease;
&:hover {
color: #ffffff;
background: rgba(56, 190, 255, 0.12);
}
}
.task-grid {
display: grid;
grid-template-columns: repeat(3, minmax(240px, 1fr));
gap: 42px 70px;
min-height: 280px;
padding: 4px 54px 42px;
}
.task-card {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 166px;
padding: 28px 28px 24px;
text-align: center;
isolation: isolate;
cursor: pointer;
border: 1px solid rgba(76, 199, 255, 0.58);
border-radius: 8px;
background:
radial-gradient(circle at 20% 0%, rgba(65, 221, 255, 0.18), transparent 34%),
radial-gradient(circle at 86% 112%, rgba(0, 128, 255, 0.22), transparent 34%),
linear-gradient(145deg, rgba(13, 58, 79, 0.9), rgba(3, 24, 39, 0.96) 58%, rgba(4, 38, 58, 0.92));
box-shadow:
inset 0 1px 0 rgba(195, 246, 255, 0.12),
inset 0 0 28px rgba(23, 164, 226, 0.08),
0 18px 42px rgba(0, 0, 0, 0.18);
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
&::before,
&::after {
position: absolute;
width: 36px;
height: 36px;
content: "";
z-index: -1;
}
&::before {
top: 13px;
left: 13px;
border-top: 2px solid rgba(71, 225, 255, 0.78);
border-left: 2px solid rgba(71, 225, 255, 0.78);
}
&::after {
right: 13px;
bottom: 13px;
border-right: 2px solid rgba(71, 225, 255, 0.52);
border-bottom: 2px solid rgba(71, 225, 255, 0.52);
}
&:hover {
border-color: rgba(90, 233, 255, 0.98);
box-shadow:
0 0 0 1px rgba(62, 216, 255, 0.16),
0 18px 44px rgba(0, 0, 0, 0.28),
0 0 34px rgba(34, 179, 242, 0.18),
inset 0 0 32px rgba(34, 179, 242, 0.14);
transform: translateY(-3px);
.task-card-scanline {
opacity: 1;
transform: translateX(55%);
}
}
}
.task-card-index {
position: absolute;
top: 18px;
right: 20px;
color: rgba(87, 220, 255, 0.2);
font-size: 42px;
font-weight: 800;
line-height: 1;
pointer-events: none;
}
.task-card-core {
position: relative;
z-index: 1;
width: 100%;
}
.task-project {
display: inline-flex;
align-items: center;
max-width: 100%;
min-height: 26px;
padding: 0 12px;
margin-bottom: 16px;
color: #8addff;
font-size: 12px;
line-height: 24px;
border: 1px solid rgba(72, 199, 255, 0.28);
border-radius: 999px;
background: rgba(6, 31, 49, 0.72);
box-shadow: inset 0 0 12px rgba(32, 180, 245, 0.08);
}
.task-name {
color: #f4fbff;
font-size: 18px;
font-weight: 700;
text-shadow: 0 0 16px rgba(96, 219, 255, 0.2);
}
.task-card-hint {
margin-top: 10px;
color: rgba(176, 225, 243, 0.62);
font-size: 12px;
}
.task-actions {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
margin-top: 18px;
:deep(.el-button) {
min-width: 92px;
border: 0;
margin-left: 0;
border-radius: 999px;
background: linear-gradient(180deg, #2ec4ff, #0784ce);
box-shadow: 0 8px 18px rgba(0, 132, 204, 0.2);
&:hover {
background: linear-gradient(180deg, #5ad8ff, #0b93df);
}
}
}
.task-card-scanline {
position: absolute;
top: 0;
bottom: 0;
left: -60%;
width: 38%;
opacity: 0;
pointer-events: none;
background: linear-gradient(90deg, transparent, rgba(136, 238, 255, 0.12), transparent);
transform: translateX(0);
transition: opacity 0.2s ease, transform 0.62s ease;
}
.task-empty {
grid-column: 1 / -1;
}
@media (max-width: 1280px) {
.task-filter {
align-items: flex-start;
flex-direction: column;
}
.search-panel {
width: 100%;
flex-basis: auto;
}
.task-grid {
grid-template-columns: repeat(2, minmax(220px, 1fr));
padding-inline: 24px;
}
}
@media (max-width: 760px) {
.task-grid {
grid-template-columns: 1fr;
gap: 22px;
padding-inline: 4px;
}
.search-panel {
flex-direction: column;
align-items: stretch;
}
}
</style>
<style lang="scss">
.training-task-drawer {
color: #e9f8ff;
border-left: 1px solid rgba(72, 201, 255, 0.58);
background:
radial-gradient(circle at 14% 0%, rgba(40, 187, 255, 0.2), transparent 34%),
linear-gradient(160deg, #081827 0%, #06111d 58%, #071b2a 100%);
box-shadow: -28px 0 80px rgba(0, 0, 0, 0.42), inset 1px 0 0 rgba(151, 226, 255, 0.12);
.el-drawer__body {
height: 100%;
padding: 0;
overflow: hidden;
}
.drawer-shell {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.drawer-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding: 28px 32px 22px;
border-bottom: 1px solid rgba(59, 186, 244, 0.32);
background:
linear-gradient(90deg, rgba(13, 65, 96, 0.96), rgba(8, 36, 58, 0.9)),
linear-gradient(135deg, rgba(48, 197, 255, 0.12), transparent);
}
.drawer-heading {
min-width: 0;
h3 {
margin: 6px 0 8px;
color: #f4fbff;
font-size: 24px;
font-weight: 900;
line-height: 1.25;
letter-spacing: 0;
}
p {
max-width: 560px;
margin: 0;
color: #b8d8ea;
font-size: 13px;
line-height: 1.7;
}
}
.drawer-kicker {
display: inline-flex;
padding: 4px 12px;
border: 1px solid rgba(94, 217, 255, 0.5);
border-radius: 999px;
color: #96e9ff;
background: rgba(4, 38, 58, 0.8);
font-size: 12px;
font-weight: 800;
}
.drawer-header-actions {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 12px;
}
.drawer-close-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
color: #d9f4ff;
font-size: 18px;
line-height: 1;
cursor: pointer;
border: 1px solid rgba(109, 211, 255, 0.48);
border-radius: 50%;
background: rgba(5, 31, 48, 0.72);
transition: all 0.18s ease;
&:hover {
color: #ffffff;
border-color: rgba(109, 211, 255, 0.88);
background: rgba(18, 91, 130, 0.86);
box-shadow: 0 0 14px rgba(33, 178, 255, 0.24);
}
}
.drawer-identity {
display: grid;
grid-template-columns: 1fr 1.35fr 120px;
gap: 12px;
padding: 18px 32px;
border-bottom: 1px solid rgba(59, 186, 244, 0.18);
background: rgba(2, 20, 32, 0.58);
div {
min-width: 0;
padding: 13px 14px;
border: 1px solid rgba(68, 183, 234, 0.28);
border-radius: 8px;
background: rgba(1, 23, 36, 0.74);
}
span {
display: block;
margin-bottom: 6px;
color: #7fc8e8;
font-size: 12px;
font-weight: 700;
}
strong {
display: block;
overflow: hidden;
color: #f4fbff;
font-size: 14px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.task-form {
flex: 1 1 auto;
min-height: 0;
padding: 22px 32px 118px;
overflow-y: auto;
.el-form-item {
margin-bottom: 18px;
}
.el-form-item__label {
color: #e9f8ff;
font-weight: 800;
line-height: 22px;
}
.el-input__wrapper,
.el-textarea__inner {
color: #effaff;
border: 1px solid rgba(80, 191, 241, 0.58);
border-radius: 6px;
background: rgba(1, 23, 36, 0.92);
box-shadow: inset 0 0 16px rgba(38, 176, 244, 0.08);
}
.el-input__inner,
.el-textarea__inner {
color: #effaff;
}
.el-input.is-disabled .el-input__wrapper,
.el-textarea.is-disabled .el-textarea__inner {
border-color: rgba(73, 147, 184, 0.34);
background: rgba(9, 31, 46, 0.72);
box-shadow: none;
}
.el-input.is-disabled .el-input__inner,
.el-textarea.is-disabled .el-textarea__inner {
color: #bed8e8;
-webkit-text-fill-color: #bed8e8;
}
}
.drawer-section {
padding: 20px 22px 22px;
border: 1px solid rgba(54, 177, 232, 0.34);
border-radius: 10px;
background:
linear-gradient(145deg, rgba(6, 36, 55, 0.88), rgba(3, 20, 32, 0.88)),
radial-gradient(circle at 100% 0%, rgba(48, 197, 255, 0.1), transparent 38%);
box-shadow: inset 0 0 22px rgba(42, 176, 244, 0.06);
& + .drawer-section {
margin-top: 18px;
}
}
.section-title {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 18px;
span {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 26px;
border: 1px solid rgba(96, 219, 255, 0.56);
border-radius: 999px;
color: #8eeaff;
background: rgba(10, 77, 110, 0.58);
font-size: 12px;
font-weight: 900;
}
h4 {
margin: 0;
color: #f4fbff;
font-size: 16px;
font-weight: 900;
}
}
.objective-list {
width: 100%;
}
.objective-row {
display: grid;
grid-template-columns: 1fr 34px;
gap: 12px;
align-items: center;
& + .objective-row {
margin-top: 10px;
}
}
.circle-action {
width: 32px;
height: 32px;
color: #ecfbff;
font-size: 18px;
font-weight: 800;
line-height: 28px;
cursor: pointer;
border: 1px solid rgba(120, 217, 255, 0.72);
border-radius: 50%;
background: rgba(8, 51, 75, 0.86);
&:disabled {
cursor: not-allowed;
opacity: 0.42;
}
}
.add-objective-button {
display: inline-flex;
align-items: center;
justify-content: center;
height: 34px;
padding: 0 16px;
margin-top: 12px;
color: #9feaff;
font-weight: 800;
cursor: pointer;
border: 1px dashed rgba(103, 219, 255, 0.55);
border-radius: 999px;
background: rgba(6, 43, 65, 0.56);
transition: all 0.18s ease;
&:hover {
color: #ffffff;
border-style: solid;
border-color: rgba(130, 235, 255, 0.9);
background: rgba(9, 72, 104, 0.74);
box-shadow: 0 0 18px rgba(35, 185, 245, 0.12);
}
}
.material-row {
display: flex;
align-items: center;
gap: 20px;
min-height: 54px;
padding: 12px 0 18px;
margin-bottom: 14px;
border-bottom: 1px solid rgba(65, 171, 220, 0.22);
}
.material-link {
color: #6fd8ff;
text-decoration: none;
}
.material-placeholder {
color: rgba(184, 218, 235, 0.68);
}
.step-row {
display: grid;
grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
gap: 12px;
align-items: center;
width: 100%;
}
.step-arrow {
color: #7ddaff;
font-weight: 800;
}
.drawer-footer {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: flex-end;
gap: 14px;
padding: 18px 32px;
border-top: 1px solid rgba(59, 186, 244, 0.26);
background: rgba(4, 18, 30, 0.94);
box-shadow: 0 -14px 30px rgba(0, 0, 0, 0.24);
.el-button {
min-width: 112px;
border-radius: 5px;
font-weight: 800;
}
.el-button--primary {
border: 0;
background: linear-gradient(180deg, #22a9ef, #087bc2);
}
}
}
@media (max-width: 960px) {
.training-task-drawer {
width: calc(100vw - 20px) !important;
.drawer-header,
.drawer-identity,
.task-form,
.drawer-footer {
padding-right: 20px;
padding-left: 20px;
}
.drawer-header {
flex-direction: column;
}
.drawer-identity {
grid-template-columns: 1fr;
}
.step-row {
grid-template-columns: 1fr;
}
.step-arrow {
display: none;
}
}
}
</style>