|
|
|
|
<template>
|
|
|
|
|
<section class="training-intro">
|
|
|
|
|
<div class="training-intro__head">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="training-intro__eyebrow">Training Brief</p>
|
|
|
|
|
<h2>{{ displayTitle }}</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="training-intro__hint">实训说明</span>
|
|
|
|
|
</div>
|
|
|
|
|
<TrainingTaskBrief
|
|
|
|
|
:background="briefBackground"
|
|
|
|
|
:goals="remoteGoals"
|
|
|
|
|
:requirements="briefRequirements"
|
|
|
|
|
:material-name="remoteTask?.materialName"
|
|
|
|
|
:material-url="remoteTask?.materialUrl"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<nav v-if="remoteSteps.length" class="step-indicator training-intro__steps" aria-label="实训步骤">
|
|
|
|
|
<button
|
|
|
|
|
v-for="(step, index) in remoteSteps"
|
|
|
|
|
:key="`${step}-${index}`"
|
|
|
|
|
type="button"
|
|
|
|
|
:class="['step-tab', { active: activeStep === index }]"
|
|
|
|
|
@click="activeStep = index"
|
|
|
|
|
>
|
|
|
|
|
<span>{{ step }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
|
import { parseTrainingArray, resolveTrainingTaskKey } from "@/views/training/taskKeyMap";
|
|
|
|
|
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
title: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: "实训任务",
|
|
|
|
|
},
|
|
|
|
|
taskKey: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: "",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const remoteTask = ref(null);
|
|
|
|
|
const activeStep = ref(0);
|
|
|
|
|
const currentTaskKey = computed(() => resolveTrainingTaskKey(route, props.taskKey));
|
|
|
|
|
const displayTitle = computed(() => remoteTask.value?.taskName || props.title);
|
|
|
|
|
const remoteGoals = computed(() => parseTrainingArray(remoteTask.value?.objectives));
|
|
|
|
|
const remoteSteps = computed(() => parseTrainingArray(remoteTask.value?.steps).slice(0, 4));
|
|
|
|
|
const briefBackground = computed(() => remoteTask.value?.background || "");
|
|
|
|
|
const briefRequirements = computed(() => remoteTask.value?.requirements || "");
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => currentTaskKey.value,
|
|
|
|
|
loadRemoteTask,
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
async function loadRemoteTask(taskKey) {
|
|
|
|
|
remoteTask.value = null;
|
|
|
|
|
if (!taskKey) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await getTrainingTaskByKey(taskKey);
|
|
|
|
|
remoteTask.value = res?.data || null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
remoteTask.value = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.training-intro {
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 1px solid rgba(0, 180, 255, 0.25);
|
|
|
|
|
border-radius: 32px;
|
|
|
|
|
background: rgba(8, 18, 28, 0.62);
|
|
|
|
|
box-shadow: 0 22px 48px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(169, 229, 255, 0.08);
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
|
|
|
|
|
&__head {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 24px 28px 14px;
|
|
|
|
|
border-bottom: 1px solid rgba(0, 180, 255, 0.18);
|
|
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
color: transparent;
|
|
|
|
|
background: linear-gradient(135deg, #ffffff, #8bcbff);
|
|
|
|
|
-webkit-background-clip: text;
|
|
|
|
|
background-clip: text;
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
line-height: 1.3;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
letter-spacing: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__eyebrow {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #73d7ff;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
letter-spacing: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__hint {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
padding: 7px 14px;
|
|
|
|
|
border: 1px solid rgba(0, 170, 255, 0.5);
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
color: #bcefff;
|
|
|
|
|
background: rgba(0, 170, 255, 0.14);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__body {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
margin: 0 24px 24px;
|
|
|
|
|
padding: 18px 20px;
|
|
|
|
|
border: 1px solid rgba(45, 95, 126, 0.9);
|
|
|
|
|
border-radius: 24px;
|
|
|
|
|
background: rgba(0, 35, 55, 0.52);
|
|
|
|
|
color: #d7ecff;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
line-height: 1.85;
|
|
|
|
|
|
|
|
|
|
p,
|
|
|
|
|
ol {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ol {
|
|
|
|
|
padding-left: 18px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__section + &__section {
|
|
|
|
|
padding-top: 12px;
|
|
|
|
|
border-top: 1px solid rgba(96, 184, 235, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__subtitle {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
color: #b9e2ff;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&__steps {
|
|
|
|
|
margin: 0 24px 24px !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|