|
|
|
@ -18,42 +18,37 @@
|
|
|
|
class="comprehensive-training-toolbar"
|
|
|
|
class="comprehensive-training-toolbar"
|
|
|
|
:material-name="page.material?.name"
|
|
|
|
:material-name="page.material?.name"
|
|
|
|
:material-url="page.material?.url"
|
|
|
|
:material-url="page.material?.url"
|
|
|
|
|
|
|
|
:task-key="pageKey"
|
|
|
|
:on-submit="submitTask"
|
|
|
|
:on-submit="submitTask"
|
|
|
|
:on-reset="resetTraining"
|
|
|
|
:on-reset="resetTraining"
|
|
|
|
:on-export="exportOutcome"
|
|
|
|
:on-export="exportOutcome"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<nav v-if="isComprehensiveTurnNavigation" class="comprehensive-turn-nav" aria-label="实训步骤">
|
|
|
|
<nav
|
|
|
|
<div class="comprehensive-turn-row comprehensive-turn-row--forward">
|
|
|
|
v-if="isComprehensiveCaseTraining && indexedConfiguredSteps.length"
|
|
|
|
<template v-for="item in comprehensiveTurnForwardSteps" :key="`${item.step}-${item.index}`">
|
|
|
|
ref="comprehensiveStepNavRef"
|
|
|
|
<button
|
|
|
|
:class="['comprehensive-step-nav', { 'is-dragging': comprehensiveNavDragging }]"
|
|
|
|
type="button"
|
|
|
|
aria-label="综合实训步骤,可左右拖动浏览"
|
|
|
|
:class="['step-tab', { active: currentStep === item.index + 1, complete: currentStep > item.index + 1 }]"
|
|
|
|
@dragstart.prevent
|
|
|
|
:aria-label="`切换至第 ${item.index + 1} 步:${item.step}`"
|
|
|
|
@pointerdown="startComprehensiveNavDrag"
|
|
|
|
:aria-current="currentStep === item.index + 1 ? 'step' : undefined"
|
|
|
|
@pointermove="moveComprehensiveNavDrag"
|
|
|
|
@click="currentStep = item.index + 1"
|
|
|
|
@pointerup="endComprehensiveNavDrag"
|
|
|
|
|
|
|
|
@pointercancel="endComprehensiveNavDrag"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<span class="step-marker" aria-hidden="true">{{ String(item.index + 1).padStart(2, '0') }}</span>
|
|
|
|
<div class="comprehensive-step-strip">
|
|
|
|
<span class="step-copy"><strong class="step-name" :title="item.step">{{ item.step }}</strong><span class="step-action">{{ currentStep === item.index + 1 ? '当前步骤' : '点击切换' }} <span aria-hidden="true">→</span></span></span>
|
|
|
|
<template v-for="item in indexedConfiguredSteps" :key="`${item.step}-${item.index}`">
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<span v-if="item.index < comprehensiveTurnForwardSteps[comprehensiveTurnForwardSteps.length - 1]?.index" class="step-connector" aria-hidden="true"></span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<span class="comprehensive-turn-rail" aria-hidden="true"></span>
|
|
|
|
|
|
|
|
<div class="comprehensive-turn-row comprehensive-turn-row--return">
|
|
|
|
|
|
|
|
<template v-for="item in comprehensiveTurnReturnSteps" :key="`${item.step}-${item.index}`">
|
|
|
|
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
type="button"
|
|
|
|
:class="['step-tab', { active: currentStep === item.index + 1, complete: currentStep > item.index + 1 }]"
|
|
|
|
:class="['step-tab', { active: currentStep === item.index + 1, complete: currentStep > item.index + 1 }]"
|
|
|
|
:aria-label="`切换至第 ${item.index + 1} 步:${item.step}`"
|
|
|
|
:aria-label="`切换至第 ${item.index + 1} 步:${item.step}`"
|
|
|
|
:aria-current="currentStep === item.index + 1 ? 'step' : undefined"
|
|
|
|
:aria-current="currentStep === item.index + 1 ? 'step' : undefined"
|
|
|
|
@click="currentStep = item.index + 1"
|
|
|
|
@click="selectComprehensiveStep(item.index + 1)"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<span class="step-marker" aria-hidden="true">{{ String(item.index + 1).padStart(2, '0') }}</span>
|
|
|
|
<span class="step-marker" aria-hidden="true">{{ String(item.index + 1).padStart(2, '0') }}</span>
|
|
|
|
<span class="step-copy"><strong class="step-name" :title="item.step">{{ item.step }}</strong><span class="step-action">{{ currentStep === item.index + 1 ? '当前步骤' : '点击切换' }} <span aria-hidden="true">←</span></span></span>
|
|
|
|
<span class="step-copy"><strong class="step-name" :title="item.step">{{ item.step }}</strong><span class="step-action">{{ currentStep === item.index + 1 ? '当前步骤' : '点击切换' }} <span aria-hidden="true">→</span></span></span>
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
<span v-if="item.index > comprehensiveTurnReturnSteps[comprehensiveTurnReturnSteps.length - 1]?.index" class="step-connector step-connector--reverse" aria-hidden="true"></span>
|
|
|
|
<span v-if="item.index < indexedConfiguredSteps.length - 1" class="step-connector" aria-hidden="true"></span>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
</nav>
|
|
|
|
@ -747,10 +742,14 @@ const uploading = ref(false);
|
|
|
|
const currentStep = ref(1);
|
|
|
|
const currentStep = ref(1);
|
|
|
|
const configuredSteps = computed(() => parseArray(remoteTask.value?.steps));
|
|
|
|
const configuredSteps = computed(() => parseArray(remoteTask.value?.steps));
|
|
|
|
const indexedConfiguredSteps = computed(() => configuredSteps.value.map((step, index) => ({ step, index })));
|
|
|
|
const indexedConfiguredSteps = computed(() => configuredSteps.value.map((step, index) => ({ step, index })));
|
|
|
|
const isComprehensiveTurnNavigation = computed(() => isComprehensiveCaseTraining.value && indexedConfiguredSteps.value.length > 7);
|
|
|
|
const comprehensiveStepNavRef = ref(null);
|
|
|
|
// 综合实训专用折返步骤条:上行保留更宽的文字空间,下行承载剩余四步。
|
|
|
|
const comprehensiveNavDragging = ref(false);
|
|
|
|
const comprehensiveTurnForwardSteps = computed(() => indexedConfiguredSteps.value.slice(0, 6));
|
|
|
|
const comprehensiveNavDrag = {
|
|
|
|
const comprehensiveTurnReturnSteps = computed(() => indexedConfiguredSteps.value.slice(6).reverse());
|
|
|
|
pointerId: null,
|
|
|
|
|
|
|
|
startX: 0,
|
|
|
|
|
|
|
|
startScrollLeft: 0,
|
|
|
|
|
|
|
|
moved: false,
|
|
|
|
|
|
|
|
};
|
|
|
|
const activeStepName = computed(() => configuredSteps.value[currentStep.value - 1] || configuredSteps.value[0] || "");
|
|
|
|
const activeStepName = computed(() => configuredSteps.value[currentStep.value - 1] || configuredSteps.value[0] || "");
|
|
|
|
const visibleProcessRows = computed(() => {
|
|
|
|
const visibleProcessRows = computed(() => {
|
|
|
|
if (!configuredSteps.value.length || !processRows.value.length) {
|
|
|
|
if (!configuredSteps.value.length || !processRows.value.length) {
|
|
|
|
@ -793,6 +792,48 @@ watch(configuredSteps, (steps) => {
|
|
|
|
currentStep.value = Math.min(Math.max(currentStep.value, 1), steps.length);
|
|
|
|
currentStep.value = Math.min(Math.max(currentStep.value, 1), steps.length);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function startComprehensiveNavDrag(event) {
|
|
|
|
|
|
|
|
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
|
|
|
|
|
|
const nav = comprehensiveStepNavRef.value;
|
|
|
|
|
|
|
|
if (!nav) return;
|
|
|
|
|
|
|
|
comprehensiveNavDrag.pointerId = event.pointerId;
|
|
|
|
|
|
|
|
comprehensiveNavDrag.startX = event.clientX;
|
|
|
|
|
|
|
|
comprehensiveNavDrag.startScrollLeft = nav.scrollLeft;
|
|
|
|
|
|
|
|
comprehensiveNavDrag.moved = false;
|
|
|
|
|
|
|
|
comprehensiveNavDragging.value = true;
|
|
|
|
|
|
|
|
nav.setPointerCapture?.(event.pointerId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function moveComprehensiveNavDrag(event) {
|
|
|
|
|
|
|
|
if (!comprehensiveNavDragging.value || event.pointerId !== comprehensiveNavDrag.pointerId) return;
|
|
|
|
|
|
|
|
const nav = comprehensiveStepNavRef.value;
|
|
|
|
|
|
|
|
if (!nav) return;
|
|
|
|
|
|
|
|
const distance = event.clientX - comprehensiveNavDrag.startX;
|
|
|
|
|
|
|
|
if (Math.abs(distance) > 4) {
|
|
|
|
|
|
|
|
comprehensiveNavDrag.moved = true;
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
nav.scrollLeft = comprehensiveNavDrag.startScrollLeft - distance;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function endComprehensiveNavDrag(event) {
|
|
|
|
|
|
|
|
if (event.pointerId !== comprehensiveNavDrag.pointerId) return;
|
|
|
|
|
|
|
|
const nav = comprehensiveStepNavRef.value;
|
|
|
|
|
|
|
|
if (nav?.hasPointerCapture?.(event.pointerId)) {
|
|
|
|
|
|
|
|
nav.releasePointerCapture(event.pointerId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
comprehensiveNavDragging.value = false;
|
|
|
|
|
|
|
|
comprehensiveNavDrag.pointerId = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectComprehensiveStep(step) {
|
|
|
|
|
|
|
|
if (comprehensiveNavDrag.moved) {
|
|
|
|
|
|
|
|
comprehensiveNavDrag.moved = false;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
currentStep.value = step;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadRemoteTask() {
|
|
|
|
async function loadRemoteTask() {
|
|
|
|
remoteTask.value = null;
|
|
|
|
remoteTask.value = null;
|
|
|
|
if (!pageKey.value) return;
|
|
|
|
if (!pageKey.value) return;
|
|
|
|
@ -2004,86 +2045,68 @@ function exportOutcome() {
|
|
|
|
border-color: #63e6f1;
|
|
|
|
border-color: #63e6f1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-nav {
|
|
|
|
.comprehensive-step-nav {
|
|
|
|
position: relative;
|
|
|
|
position: relative;
|
|
|
|
width: calc(100% + 24px);
|
|
|
|
width: calc(100% + 24px);
|
|
|
|
margin: 0 -12px 32px;
|
|
|
|
margin: 0 -12px 32px;
|
|
|
|
padding: 18px 22px;
|
|
|
|
padding: 18px 22px;
|
|
|
|
overflow-x: auto;
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
|
|
overflow-y: hidden;
|
|
|
|
border: 1px solid rgba(53, 178, 222, 0.42);
|
|
|
|
border: 1px solid rgba(53, 178, 222, 0.42);
|
|
|
|
border-radius: 22px;
|
|
|
|
border-radius: 22px;
|
|
|
|
background: linear-gradient(90deg, rgba(4, 29, 47, 0.94), rgba(5, 44, 67, 0.75));
|
|
|
|
background: linear-gradient(90deg, rgba(4, 29, 47, 0.94), rgba(5, 44, 67, 0.75));
|
|
|
|
|
|
|
|
cursor: grab;
|
|
|
|
|
|
|
|
overscroll-behavior-x: contain;
|
|
|
|
|
|
|
|
scroll-behavior: smooth;
|
|
|
|
scrollbar-width: thin;
|
|
|
|
scrollbar-width: thin;
|
|
|
|
scrollbar-color: rgba(86, 189, 227, 0.75) rgba(11, 43, 63, 0.75);
|
|
|
|
scrollbar-color: rgba(86, 189, 227, 0.75) rgba(11, 43, 63, 0.75);
|
|
|
|
|
|
|
|
touch-action: pan-y;
|
|
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-step-nav.is-dragging {
|
|
|
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
|
|
|
scroll-behavior: auto;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row {
|
|
|
|
.comprehensive-step-strip {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
min-width: 1040px;
|
|
|
|
width: max-content;
|
|
|
|
|
|
|
|
min-width: 100%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-tab {
|
|
|
|
.comprehensive-step-strip .step-tab {
|
|
|
|
flex: 1 1 0;
|
|
|
|
flex: 0 0 190px;
|
|
|
|
min-width: 112px;
|
|
|
|
width: 190px;
|
|
|
|
|
|
|
|
min-width: 190px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-name {
|
|
|
|
.comprehensive-step-strip .step-name {
|
|
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
font-size: 13px;
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
-webkit-line-clamp: unset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-marker {
|
|
|
|
.comprehensive-step-strip .step-marker {
|
|
|
|
flex-basis: 32px;
|
|
|
|
flex-basis: 32px;
|
|
|
|
width: 32px;
|
|
|
|
width: 32px;
|
|
|
|
height: 32px;
|
|
|
|
height: 32px;
|
|
|
|
font-size: 11px;
|
|
|
|
font-size: 11px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-copy {
|
|
|
|
.comprehensive-step-strip .step-copy {
|
|
|
|
margin-left: 8px;
|
|
|
|
margin-left: 8px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-connector {
|
|
|
|
.comprehensive-step-strip .step-connector {
|
|
|
|
flex: 0 1 30px;
|
|
|
|
flex: 0 0 30px;
|
|
|
|
min-width: 14px;
|
|
|
|
width: 30px;
|
|
|
|
|
|
|
|
min-width: 30px;
|
|
|
|
margin: 0 5px;
|
|
|
|
margin: 0 5px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row--return {
|
|
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row--return .step-tab {
|
|
|
|
|
|
|
|
flex: 0 1 190px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-rail {
|
|
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
width: 2px;
|
|
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
|
|
background: rgba(98, 169, 201, 0.48);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-rail::after {
|
|
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
bottom: -1px;
|
|
|
|
|
|
|
|
left: -1px;
|
|
|
|
|
|
|
|
width: 7px;
|
|
|
|
|
|
|
|
height: 7px;
|
|
|
|
|
|
|
|
border-bottom: 1px solid #73b1ce;
|
|
|
|
|
|
|
|
border-left: 1px solid #73b1ce;
|
|
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
|
|
transform: translateX(-50%) rotate(45deg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-connector--reverse::after {
|
|
|
|
|
|
|
|
right: auto;
|
|
|
|
|
|
|
|
left: -1px;
|
|
|
|
|
|
|
|
transform: translateY(-50%) rotate(225deg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-title-row {
|
|
|
|
.step-title-row {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: flex-start;
|
|
|
|
align-items: flex-start;
|
|
|
|
@ -2628,45 +2651,34 @@ function exportOutcome() {
|
|
|
|
transform: translateX(-50%) rotate(135deg);
|
|
|
|
transform: translateX(-50%) rotate(135deg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-nav {
|
|
|
|
.comprehensive-step-nav {
|
|
|
|
width: 100%;
|
|
|
|
width: 100%;
|
|
|
|
margin: 0 0 32px;
|
|
|
|
margin: 0 0 32px;
|
|
|
|
padding: 16px;
|
|
|
|
padding: 16px;
|
|
|
|
overflow: visible;
|
|
|
|
overflow-x: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row {
|
|
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row--return {
|
|
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-tab {
|
|
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-row .step-connector {
|
|
|
|
.comprehensive-step-strip .step-tab {
|
|
|
|
flex: 0 0 20px;
|
|
|
|
flex: 0 0 172px;
|
|
|
|
width: 2px;
|
|
|
|
width: 172px;
|
|
|
|
height: 20px;
|
|
|
|
min-width: 172px;
|
|
|
|
margin: 4px 0 4px 18px;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-rail {
|
|
|
|
.comprehensive-step-strip .step-connector {
|
|
|
|
display: none;
|
|
|
|
flex: 0 0 24px;
|
|
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
|
|
min-width: 24px;
|
|
|
|
|
|
|
|
height: 2px;
|
|
|
|
|
|
|
|
align-self: center;
|
|
|
|
|
|
|
|
margin: 0 5px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.comprehensive-turn-nav .step-connector--reverse::after {
|
|
|
|
.comprehensive-step-strip .step-connector::after {
|
|
|
|
top: auto;
|
|
|
|
top: 50%;
|
|
|
|
right: auto;
|
|
|
|
right: -1px;
|
|
|
|
bottom: -1px;
|
|
|
|
bottom: auto;
|
|
|
|
left: 50%;
|
|
|
|
left: auto;
|
|
|
|
transform: translateX(-50%) rotate(135deg);
|
|
|
|
transform: translateY(-50%) rotate(45deg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.product-development-process-page {
|
|
|
|
.product-development-process-page {
|
|
|
|
|