|
|
|
@ -52,15 +52,46 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-show="isSectionVisible(1)" class="scenario-step-section">
|
|
|
|
<section v-show="isSectionVisible(1)" class="scenario-step-section">
|
|
|
|
<div class="journey-head">
|
|
|
|
<div class="journey-sort-head">
|
|
|
|
<h4>用户购买旅程</h4>
|
|
|
|
<div>
|
|
|
|
<div class="journey-flow" aria-label="用户购买旅程阶段">
|
|
|
|
<p class="journey-sort-kicker">Customer Journey</p>
|
|
|
|
<template v-for="(phase, index) in phases" :key="phase.key">
|
|
|
|
<h4>将购买旅程卡片按正确顺序排列</h4>
|
|
|
|
<div class="phase-node">{{ phase.label }}</div>
|
|
|
|
<p>从需求触发到购买后的使用反馈,共五个阶段。拖动卡片,或使用卡片右侧的上下移动按钮调整顺序。</p>
|
|
|
|
<div v-if="index < phases.length - 1" class="phase-arrow" aria-hidden="true"></div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<button type="button" class="journey-check-action" :disabled="checkingJourney" @click="checkJourneyOrder">
|
|
|
|
|
|
|
|
{{ checkingJourney ? "正在校验…" : "检查排序" }}
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="journey-sort-list" role="list" aria-label="消费者购买旅程排序">
|
|
|
|
|
|
|
|
<template v-for="(stage, index) in orderedJourneyStages" :key="stage.id">
|
|
|
|
|
|
|
|
<article
|
|
|
|
|
|
|
|
class="journey-sort-card"
|
|
|
|
|
|
|
|
:class="{ dragging: draggedStageId === stage.id }"
|
|
|
|
|
|
|
|
draggable="true"
|
|
|
|
|
|
|
|
role="listitem"
|
|
|
|
|
|
|
|
@dragstart="startJourneyDrag(stage.id)"
|
|
|
|
|
|
|
|
@dragover.prevent
|
|
|
|
|
|
|
|
@drop="dropJourneyStage(stage.id)"
|
|
|
|
|
|
|
|
@dragend="endJourneyDrag"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<span class="journey-card-order">{{ String(index + 1).padStart(2, "0") }}</span>
|
|
|
|
|
|
|
|
<div class="journey-card-copy">
|
|
|
|
|
|
|
|
<strong>{{ stage.title }}</strong>
|
|
|
|
|
|
|
|
<span>{{ stage.description }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="journey-card-actions" aria-label="调整卡片顺序">
|
|
|
|
|
|
|
|
<button type="button" :disabled="index === 0" :aria-label="`将${stage.title}上移`" @click="moveJourneyStage(index, -1)">↑</button>
|
|
|
|
|
|
|
|
<button type="button" :disabled="index === orderedJourneyStages.length - 1" :aria-label="`将${stage.title}下移`" @click="moveJourneyStage(index, 1)">↓</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
<span v-if="index < orderedJourneyStages.length - 1" class="journey-sort-arrow" aria-hidden="true">→</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="journeyCheckMessage" :class="['journey-check-message', { success: journeyCheckedCorrectly }]" role="status">
|
|
|
|
|
|
|
|
{{ journeyCheckMessage }}
|
|
|
|
|
|
|
|
</p>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-show="isSectionVisible(2)" class="scenario-step-section">
|
|
|
|
<section v-show="isSectionVisible(2)" class="scenario-step-section">
|
|
|
|
@ -204,7 +235,7 @@ import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
|
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
|
|
|
|
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
|
|
|
|
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
|
|
|
|
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
import { checkConsumerScenarioStepOne, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
|
|
|
|
|
|
|
|
const TASK_KEY = "consumer-scenario";
|
|
|
|
const TASK_KEY = "consumer-scenario";
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
@ -219,6 +250,34 @@ const defaultGoals = [
|
|
|
|
"将场景洞察转化为产品机会。",
|
|
|
|
"将场景洞察转化为产品机会。",
|
|
|
|
];
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const journeyStages = [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: "problem-awareness",
|
|
|
|
|
|
|
|
title: "问题感知(需求触发)",
|
|
|
|
|
|
|
|
description: "用户意识到自身存在某个问题或需求",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: "information-search",
|
|
|
|
|
|
|
|
title: "信息搜索(产品探索)",
|
|
|
|
|
|
|
|
description: "用户主动搜索相关信息,了解可选方案",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: "evaluation-decision",
|
|
|
|
|
|
|
|
title: "评估决策(对比选择)",
|
|
|
|
|
|
|
|
description: "用户对比不同产品的功能、价格、口碑",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: "purchase-conversion",
|
|
|
|
|
|
|
|
title: "购买转化(下单支付)",
|
|
|
|
|
|
|
|
description: "用户完成购买决策并下单",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: "post-purchase",
|
|
|
|
|
|
|
|
title: "购后行为(使用反馈)",
|
|
|
|
|
|
|
|
description: "用户使用产品后形成体验评价,决定是否推荐或复购",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const phases = [
|
|
|
|
const phases = [
|
|
|
|
{ key: "problem", label: "问题感知(需求触发)" },
|
|
|
|
{ key: "problem", label: "问题感知(需求触发)" },
|
|
|
|
{ key: "search", label: "信息搜索(产品探索)" },
|
|
|
|
{ key: "search", label: "信息搜索(产品探索)" },
|
|
|
|
@ -316,6 +375,10 @@ const emotionPoints = [
|
|
|
|
|
|
|
|
|
|
|
|
const taskConfig = ref(null);
|
|
|
|
const taskConfig = ref(null);
|
|
|
|
const saving = ref(false);
|
|
|
|
const saving = ref(false);
|
|
|
|
|
|
|
|
const checkingJourney = ref(false);
|
|
|
|
|
|
|
|
const draggedStageId = ref("");
|
|
|
|
|
|
|
|
const journeyCheckMessage = ref("");
|
|
|
|
|
|
|
|
const journeyCheckedCorrectly = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
function createMatrix() {
|
|
|
|
function createMatrix() {
|
|
|
|
return journeyRows.reduce((matrix, row) => {
|
|
|
|
return journeyRows.reduce((matrix, row) => {
|
|
|
|
@ -337,6 +400,7 @@ function createClassifications() {
|
|
|
|
function createForm() {
|
|
|
|
function createForm() {
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
activeStep: 1,
|
|
|
|
activeStep: 1,
|
|
|
|
|
|
|
|
journeyOrder: createShuffledJourneyOrder(),
|
|
|
|
classifications: createClassifications(),
|
|
|
|
classifications: createClassifications(),
|
|
|
|
matrix: createMatrix(),
|
|
|
|
matrix: createMatrix(),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -351,13 +415,30 @@ const taskRequirement = computed(() => taskConfig.value?.requirements || default
|
|
|
|
const trainingSteps = computed(() => parseArray(taskConfig.value?.steps, []));
|
|
|
|
const trainingSteps = computed(() => parseArray(taskConfig.value?.steps, []));
|
|
|
|
const hasConfiguredSteps = computed(() => trainingSteps.value.length > 0);
|
|
|
|
const hasConfiguredSteps = computed(() => trainingSteps.value.length > 0);
|
|
|
|
const activeStepName = computed(() => trainingSteps.value[form.value.activeStep - 1] || "");
|
|
|
|
const activeStepName = computed(() => trainingSteps.value[form.value.activeStep - 1] || "");
|
|
|
|
|
|
|
|
const orderedJourneyStages = computed(() =>
|
|
|
|
|
|
|
|
form.value.journeyOrder
|
|
|
|
|
|
|
|
.map((stageId) => journeyStages.find((stage) => stage.id === stageId))
|
|
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const progressItems = computed(() => [
|
|
|
|
const progressItems = computed(() => [
|
|
|
|
{ text: isClassifiedCorrectly.value ? "已完成:旅程阶段判断" : "进行中:旅程阶段判断", status: isClassifiedCorrectly.value ? "done" : "doing" },
|
|
|
|
{ text: journeyCheckedCorrectly.value ? "已完成:购买旅程排序" : "进行中:购买旅程排序", status: journeyCheckedCorrectly.value ? "done" : "doing" },
|
|
|
|
{ text: hasMatrixContent.value ? "进行中:用户旅程地图填写" : "待完成:用户旅程地图填写", status: hasMatrixContent.value ? "doing" : "" },
|
|
|
|
{ text: hasMatrixContent.value ? "进行中:用户旅程地图填写" : "待完成:用户旅程地图填写", status: hasMatrixContent.value ? "doing" : "" },
|
|
|
|
{ text: "待完成:提交实训成果", status: "" },
|
|
|
|
{ text: "待完成:提交实训成果", status: "" },
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createShuffledJourneyOrder() {
|
|
|
|
|
|
|
|
const order = journeyStages.map((stage) => stage.id);
|
|
|
|
|
|
|
|
for (let index = order.length - 1; index > 0; index -= 1) {
|
|
|
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * (index + 1));
|
|
|
|
|
|
|
|
[order[index], order[randomIndex]] = [order[randomIndex], order[index]];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (order.every((stageId, index) => stageId === journeyStages[index].id)) {
|
|
|
|
|
|
|
|
[order[0], order[1]] = [order[1], order[0]];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return order;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isClassifiedCorrectly = computed(() =>
|
|
|
|
const isClassifiedCorrectly = computed(() =>
|
|
|
|
scenarioStatements.every((item) => form.value.classifications[item.key] === item.defaultPhase)
|
|
|
|
scenarioStatements.every((item) => form.value.classifications[item.key] === item.defaultPhase)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@ -415,17 +496,30 @@ function applySavedAnswer(value) {
|
|
|
|
if (!parsed || typeof parsed !== "object") return;
|
|
|
|
if (!parsed || typeof parsed !== "object") return;
|
|
|
|
form.value = {
|
|
|
|
form.value = {
|
|
|
|
activeStep: normalizeActiveStep(parsed.currentStep),
|
|
|
|
activeStep: normalizeActiveStep(parsed.currentStep),
|
|
|
|
|
|
|
|
journeyOrder: normalizeJourneyOrder(parsed.journeyOrder),
|
|
|
|
classifications: {
|
|
|
|
classifications: {
|
|
|
|
...createClassifications(),
|
|
|
|
...createClassifications(),
|
|
|
|
...(parsed.classifications || {}),
|
|
|
|
...(parsed.classifications || {}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
matrix: normalizeMatrix(parsed.matrix),
|
|
|
|
matrix: normalizeMatrix(parsed.matrix),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
journeyCheckedCorrectly.value = parsed.journeyCheckedCorrectly === true;
|
|
|
|
|
|
|
|
journeyCheckMessage.value = journeyCheckedCorrectly.value ? "排序正确,已完成本步骤。" : "";
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
// Ignore malformed historical answers.
|
|
|
|
// Ignore malformed historical answers.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeJourneyOrder(value) {
|
|
|
|
|
|
|
|
if (!Array.isArray(value) || value.length !== journeyStages.length) return createShuffledJourneyOrder();
|
|
|
|
|
|
|
|
const uniqueOrder = [...new Set(value)];
|
|
|
|
|
|
|
|
const validIds = journeyStages.map((stage) => stage.id);
|
|
|
|
|
|
|
|
if (uniqueOrder.length !== validIds.length || uniqueOrder.some((stageId) => !validIds.includes(stageId))) {
|
|
|
|
|
|
|
|
return createShuffledJourneyOrder();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return uniqueOrder;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeMatrix(value = {}) {
|
|
|
|
function normalizeMatrix(value = {}) {
|
|
|
|
const matrix = createMatrix();
|
|
|
|
const matrix = createMatrix();
|
|
|
|
journeyRows.forEach((row) => {
|
|
|
|
journeyRows.forEach((row) => {
|
|
|
|
@ -443,9 +537,11 @@ function normalizeActiveStep(value) {
|
|
|
|
return total ? Math.min(step, total) : 1;
|
|
|
|
return total ? Math.min(step, total) : 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setCurrentStep(step) {
|
|
|
|
async function setCurrentStep(step) {
|
|
|
|
if (!hasConfiguredSteps.value) return;
|
|
|
|
if (!hasConfiguredSteps.value) return;
|
|
|
|
form.value.activeStep = normalizeActiveStep(step);
|
|
|
|
const targetStep = normalizeActiveStep(step);
|
|
|
|
|
|
|
|
if (targetStep > form.value.activeStep && form.value.activeStep === 1 && !(await persist("IN_PROGRESS"))) return;
|
|
|
|
|
|
|
|
form.value.activeStep = targetStep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isSectionVisible(section) {
|
|
|
|
function isSectionVisible(section) {
|
|
|
|
@ -457,8 +553,66 @@ function goPreviousStep() {
|
|
|
|
setCurrentStep(form.value.activeStep - 1);
|
|
|
|
setCurrentStep(form.value.activeStep - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function goNextStep() {
|
|
|
|
async function goNextStep() {
|
|
|
|
setCurrentStep(form.value.activeStep + 1);
|
|
|
|
await setCurrentStep(form.value.activeStep + 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function startJourneyDrag(stageId) {
|
|
|
|
|
|
|
|
draggedStageId.value = stageId;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function endJourneyDrag() {
|
|
|
|
|
|
|
|
draggedStageId.value = "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function dropJourneyStage(targetStageId) {
|
|
|
|
|
|
|
|
const sourceStageId = draggedStageId.value;
|
|
|
|
|
|
|
|
endJourneyDrag();
|
|
|
|
|
|
|
|
if (!sourceStageId || sourceStageId === targetStageId) return;
|
|
|
|
|
|
|
|
const sourceIndex = form.value.journeyOrder.indexOf(sourceStageId);
|
|
|
|
|
|
|
|
const targetIndex = form.value.journeyOrder.indexOf(targetStageId);
|
|
|
|
|
|
|
|
if (sourceIndex < 0 || targetIndex < 0) return;
|
|
|
|
|
|
|
|
const nextOrder = [...form.value.journeyOrder];
|
|
|
|
|
|
|
|
nextOrder.splice(sourceIndex, 1);
|
|
|
|
|
|
|
|
nextOrder.splice(targetIndex, 0, sourceStageId);
|
|
|
|
|
|
|
|
updateJourneyOrder(nextOrder);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function moveJourneyStage(index, offset) {
|
|
|
|
|
|
|
|
const targetIndex = index + offset;
|
|
|
|
|
|
|
|
if (targetIndex < 0 || targetIndex >= form.value.journeyOrder.length) return;
|
|
|
|
|
|
|
|
const nextOrder = [...form.value.journeyOrder];
|
|
|
|
|
|
|
|
[nextOrder[index], nextOrder[targetIndex]] = [nextOrder[targetIndex], nextOrder[index]];
|
|
|
|
|
|
|
|
updateJourneyOrder(nextOrder);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateJourneyOrder(nextOrder) {
|
|
|
|
|
|
|
|
form.value.journeyOrder = nextOrder;
|
|
|
|
|
|
|
|
journeyCheckedCorrectly.value = false;
|
|
|
|
|
|
|
|
journeyCheckMessage.value = "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function checkJourneyOrder() {
|
|
|
|
|
|
|
|
await ensureJourneyOrderValid(true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function ensureJourneyOrderValid(showSuccess = false) {
|
|
|
|
|
|
|
|
if (checkingJourney.value) return false;
|
|
|
|
|
|
|
|
checkingJourney.value = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await checkConsumerScenarioStepOne({ stageOrder: form.value.journeyOrder });
|
|
|
|
|
|
|
|
const result = response?.data || {};
|
|
|
|
|
|
|
|
journeyCheckedCorrectly.value = result.valid === true;
|
|
|
|
|
|
|
|
journeyCheckMessage.value = result.message || (result.valid ? "排序正确,已完成本步骤。" : "排序还不正确,请继续调整。");
|
|
|
|
|
|
|
|
if (result.valid && showSuccess) proxy?.$modal?.msgSuccess("排序正确,可以进入下一步");
|
|
|
|
|
|
|
|
return result.valid === true;
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
journeyCheckedCorrectly.value = false;
|
|
|
|
|
|
|
|
journeyCheckMessage.value = error?.message || "排序校验失败,请稍后重试。";
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
checkingJourney.value = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildOutcome() {
|
|
|
|
function buildOutcome() {
|
|
|
|
@ -467,6 +621,8 @@ function buildOutcome() {
|
|
|
|
taskId: taskConfig.value?.id || "",
|
|
|
|
taskId: taskConfig.value?.id || "",
|
|
|
|
taskName: taskTitle.value,
|
|
|
|
taskName: taskTitle.value,
|
|
|
|
currentStep: form.value.activeStep,
|
|
|
|
currentStep: form.value.activeStep,
|
|
|
|
|
|
|
|
journeyOrder: form.value.journeyOrder,
|
|
|
|
|
|
|
|
journeyCheckedCorrectly: journeyCheckedCorrectly.value,
|
|
|
|
phases,
|
|
|
|
phases,
|
|
|
|
classifications: form.value.classifications,
|
|
|
|
classifications: form.value.classifications,
|
|
|
|
matrix: form.value.matrix,
|
|
|
|
matrix: form.value.matrix,
|
|
|
|
@ -485,10 +641,12 @@ function buildAnswerPayload(status) {
|
|
|
|
|
|
|
|
|
|
|
|
async function persist(status = "IN_PROGRESS") {
|
|
|
|
async function persist(status = "IN_PROGRESS") {
|
|
|
|
if (saving.value) return;
|
|
|
|
if (saving.value) return;
|
|
|
|
|
|
|
|
if (status !== "RESET" && form.value.activeStep === 1 && !(await ensureJourneyOrderValid())) return false;
|
|
|
|
saving.value = true;
|
|
|
|
saving.value = true;
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
|
|
|
|
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
|
|
|
|
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
saving.value = false;
|
|
|
|
saving.value = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -872,6 +1030,184 @@ function exportOutcome() {
|
|
|
|
font-weight: 900;
|
|
|
|
font-weight: 900;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-head {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
|
|
margin-bottom: 22px;
|
|
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
|
|
border: 1px solid rgba(91, 218, 255, 0.28);
|
|
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
|
|
background: linear-gradient(120deg, rgba(7, 94, 132, 0.26), rgba(2, 26, 43, 0.72));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
|
|
margin: 5px 0 8px;
|
|
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
|
|
font-size: 21px;
|
|
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
|
|
max-width: 700px;
|
|
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
color: #b9d8e9;
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-kicker {
|
|
|
|
|
|
|
|
color: #69e3ff !important;
|
|
|
|
|
|
|
|
font-size: 12px !important;
|
|
|
|
|
|
|
|
font-weight: 900;
|
|
|
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-check-action {
|
|
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
|
|
min-height: 42px;
|
|
|
|
|
|
|
|
padding: 0 18px;
|
|
|
|
|
|
|
|
border: 1px solid rgba(112, 235, 255, 0.74);
|
|
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
|
|
color: #ecfdff;
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, #078ac2, #07558a);
|
|
|
|
|
|
|
|
box-shadow: 0 8px 20px rgba(0, 145, 203, 0.2);
|
|
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, #0ba9df, #0870aa);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
|
|
opacity: 0.64;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-list {
|
|
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
|
|
grid-template-columns: repeat(9, minmax(0, 1fr));
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-card {
|
|
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
|
|
grid-column: span 1;
|
|
|
|
|
|
|
|
min-height: 162px;
|
|
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
grid-template-rows: auto 1fr auto;
|
|
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
|
|
border: 1px solid rgba(63, 174, 222, 0.48);
|
|
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
|
|
color: #ecfbff;
|
|
|
|
|
|
|
|
background: linear-gradient(150deg, rgba(8, 74, 109, 0.82), rgba(3, 24, 42, 0.9));
|
|
|
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(201, 247, 255, 0.1), 0 12px 24px rgba(0, 0, 0, 0.16);
|
|
|
|
|
|
|
|
cursor: grab;
|
|
|
|
|
|
|
|
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
|
|
transform: translateY(-3px);
|
|
|
|
|
|
|
|
border-color: rgba(106, 235, 255, 0.94);
|
|
|
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(201, 247, 255, 0.16), 0 16px 30px rgba(0, 0, 0, 0.28), 0 0 20px rgba(15, 199, 242, 0.14);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.dragging {
|
|
|
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-card-order {
|
|
|
|
|
|
|
|
color: #78e9ff;
|
|
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
font-weight: 900;
|
|
|
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-card-copy {
|
|
|
|
|
|
|
|
align-self: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strong,
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strong {
|
|
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
|
|
line-height: 1.45;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
|
|
|
color: #b6d9e9;
|
|
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
line-height: 1.65;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-card-actions {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
|
|
|
display: inline-grid;
|
|
|
|
|
|
|
|
width: 26px;
|
|
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
|
|
border: 1px solid rgba(111, 220, 246, 0.45);
|
|
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
|
|
color: #dffbff;
|
|
|
|
|
|
|
|
background: rgba(4, 41, 65, 0.76);
|
|
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
|
|
font-weight: 900;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
|
|
|
border-color: #72e8ff;
|
|
|
|
|
|
|
|
background: rgba(9, 119, 160, 0.9);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
|
|
opacity: 0.28;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-arrow {
|
|
|
|
|
|
|
|
justify-self: center;
|
|
|
|
|
|
|
|
color: #42cfff;
|
|
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
|
|
font-weight: 900;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-check-message {
|
|
|
|
|
|
|
|
margin: 20px 0 0;
|
|
|
|
|
|
|
|
padding: 11px 14px;
|
|
|
|
|
|
|
|
border: 1px solid rgba(255, 136, 106, 0.7);
|
|
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
|
|
color: #ffd2c7;
|
|
|
|
|
|
|
|
background: rgba(127, 45, 30, 0.25);
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.success {
|
|
|
|
|
|
|
|
border-color: rgba(90, 238, 198, 0.65);
|
|
|
|
|
|
|
|
color: #d6fff3;
|
|
|
|
|
|
|
|
background: rgba(16, 122, 98, 0.25);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.journey-flow {
|
|
|
|
.journey-flow {
|
|
|
|
display: grid;
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(5, minmax(120px, 1fr));
|
|
|
|
grid-template-columns: repeat(5, minmax(120px, 1fr));
|
|
|
|
@ -1295,6 +1631,26 @@ function exportOutcome() {
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-head {
|
|
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-check-action {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-list {
|
|
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-card {
|
|
|
|
|
|
|
|
min-height: 126px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.journey-sort-arrow {
|
|
|
|
|
|
|
|
transform: rotate(90deg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-action,
|
|
|
|
.nav-action,
|
|
|
|
.training-action {
|
|
|
|
.training-action {
|
|
|
|
width: 100%;
|
|
|
|
width: 100%;
|
|
|
|
|