|
|
|
|
|
<template>
|
|
|
|
|
|
<aside v-if="customSteps.length" class="dynamic-step-navigator">
|
|
|
|
|
|
<span class="navigator-label">补充步骤</span>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-for="step in customSteps"
|
|
|
|
|
|
:key="step.id"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
plain
|
|
|
|
|
|
@click="openStep(step)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ step.name }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
taskKey: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const customSteps = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.taskKey,
|
|
|
|
|
|
async (taskKey) => {
|
|
|
|
|
|
customSteps.value = [];
|
|
|
|
|
|
if (!taskKey) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getTrainingTaskByKey(taskKey);
|
|
|
|
|
|
customSteps.value = parseSteps(res?.data?.steps).filter((step) => step.kind === "custom");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// The original task page remains available when task configuration is unavailable.
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function parseSteps(value) {
|
|
|
|
|
|
let source = value;
|
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
|
try {
|
|
|
|
|
|
source = JSON.parse(value);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
source = value.split(/[,,\n]/);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(source)) return [];
|
|
|
|
|
|
return source
|
|
|
|
|
|
.map((item, index) => {
|
|
|
|
|
|
const raw = item && typeof item === "object" ? item : {};
|
|
|
|
|
|
const name = typeof item === "object" ? String(raw.name || "").trim() : String(item || "").trim();
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: raw.id || `${index < 4 ? "builtin" : "custom"}-${index + 1}`,
|
|
|
|
|
|
name,
|
|
|
|
|
|
kind: raw.kind === "custom" || (!raw.kind && index >= 4) ? "custom" : "builtin",
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter((step) => step.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openStep(step) {
|
|
|
|
|
|
router.push({ name: "CustomTrainingStep", params: { taskKey: props.taskKey, stepId: step.id } });
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.dynamic-step-navigator {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
right: 28px;
|
|
|
|
|
|
bottom: 24px;
|
|
|
|
|
|
z-index: 20;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
max-width: min(560px, calc(100vw - 48px));
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
border: 1px solid rgba(85, 202, 255, 0.42);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: rgba(3, 26, 42, 0.94);
|
|
|
|
|
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.navigator-label {
|
|
|
|
|
|
align-self: center;
|
|
|
|
|
|
color: #8cdfff;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.dynamic-step-navigator { right: 12px; bottom: 12px; }
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|