|
|
|
|
@ -69,11 +69,6 @@
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-actions"><button class="btn primary" type="button" :disabled="saving"
|
|
|
|
|
@click="checkAndContinue">判题并进入第 2 步</button><button class="btn" type="button"
|
|
|
|
|
:disabled="saving" @click="resetStepOne"><el-icon>
|
|
|
|
|
<RefreshLeft />
|
|
|
|
|
</el-icon> 清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="currentStep === 2">
|
|
|
|
|
<div class="step-heading">
|
|
|
|
|
@ -125,12 +120,6 @@
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-actions"><button class="btn primary" type="button"
|
|
|
|
|
:disabled="saving || !stepOnePassed" @click="saveAndGoToStepThree">保存并进入第 3
|
|
|
|
|
步</button><button class="btn" type="button" :disabled="saving || !stepOnePassed"
|
|
|
|
|
@click="resetStepTwo"><el-icon>
|
|
|
|
|
<RefreshLeft />
|
|
|
|
|
</el-icon> 清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="currentStep === 3">
|
|
|
|
|
<div class="step-heading">
|
|
|
|
|
@ -175,12 +164,6 @@
|
|
|
|
|
@click="downloadInstructions">下载实训操作说明</button><button class="btn primary"
|
|
|
|
|
type="button" @click="downloadTemplate">下载自动化模板</button></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-actions"><button class="btn primary" type="button"
|
|
|
|
|
:disabled="saving || !stepTwoPassed" @click="saveAndGoToStepFour">保存并进入第 4
|
|
|
|
|
步</button><button class="btn" type="button" :disabled="saving || !stepTwoPassed"
|
|
|
|
|
@click="resetStepThree"><el-icon>
|
|
|
|
|
<RefreshLeft />
|
|
|
|
|
</el-icon> 清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="currentStep === 4">
|
|
|
|
|
<div class="step-heading">
|
|
|
|
|
@ -222,9 +205,6 @@
|
|
|
|
|
class="btn primary" type="button"
|
|
|
|
|
@click="downloadVisualizationTemplate">下载自动化模板</button></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-actions"><button class="btn primary" type="button"
|
|
|
|
|
:disabled="saving || uploading || !stepThreePassed" @click="saveAndGoToStepFive">保存并进入第
|
|
|
|
|
5 步</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="currentStep === 5">
|
|
|
|
|
<div class="step-heading">
|
|
|
|
|
@ -272,12 +252,6 @@
|
|
|
|
|
<p>提示词:请计算 Excel 表中产品评论数据的好评率,提炼用户痛点和未被满足的需求。</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-actions"><button class="btn primary" type="button"
|
|
|
|
|
:disabled="saving || !stepFourPassed" @click="saveAndGoToStepSix">保存并进入第 6
|
|
|
|
|
步</button><button class="btn" type="button" :disabled="saving || !stepFourPassed"
|
|
|
|
|
@click="resetStepFive"><el-icon>
|
|
|
|
|
<RefreshLeft />
|
|
|
|
|
</el-icon> 清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="currentStep === 6">
|
|
|
|
|
<div class="step-heading">
|
|
|
|
|
@ -293,15 +267,41 @@
|
|
|
|
|
id="market-opportunity-conclusion" v-model.trim="developmentConclusion" type="textarea"
|
|
|
|
|
:autosize="{ minRows: 4 }" :disabled="!stepFivePassed"
|
|
|
|
|
placeholder="请结合综合得分、评论痛点与未被满足的需求,说明建议开发的产品及具体原因。" /></div>
|
|
|
|
|
<div class="step-actions"><button class="btn" type="button"
|
|
|
|
|
:disabled="saving || !stepFivePassed" @click="resetStepSix"><el-icon>
|
|
|
|
|
<RefreshLeft />
|
|
|
|
|
</el-icon> 清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
<div v-else class="placeholder">
|
|
|
|
|
<p>第 {{ currentStep }} 步内容待配置</p><span>已保存的步骤可从上方步骤栏返回查看。</span>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<div class="step-navigation">
|
|
|
|
|
<div class="step-nav-left">
|
|
|
|
|
<button v-if="currentStep > 1" type="button" class="btn-nav btn-ghost" :disabled="saving"
|
|
|
|
|
@click="prevStep">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<ArrowLeft />
|
|
|
|
|
</el-icon>
|
|
|
|
|
上一步
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" class="btn-nav btn-save" :disabled="saving"
|
|
|
|
|
@click="saveCurrentProgress">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<CircleCheck />
|
|
|
|
|
</el-icon>
|
|
|
|
|
保存当前进度
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step-nav-right">
|
|
|
|
|
<span class="step-progress">Step {{ String(currentStep).padStart(2, "0") }} / {{
|
|
|
|
|
String(trainingSteps.length).padStart(2, "0") }}</span>
|
|
|
|
|
<button v-if="currentStep < trainingSteps.length" type="button" class="btn-nav btn-primary"
|
|
|
|
|
:disabled="saving" @click="nextStep">
|
|
|
|
|
下一步
|
|
|
|
|
<el-icon>
|
|
|
|
|
<ArrowRight />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
<TrainingAiSidebar study-tip="先辨别指标描述的是规模、增长潜力、供给竞争,还是流量获取难度,再进行归类。" reference-title="指标分类提示"
|
|
|
|
|
@ -312,7 +312,7 @@
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import * as XLSX from "xlsx"
|
|
|
|
|
import { Download, RefreshLeft, UploadFilled } from "@element-plus/icons-vue"
|
|
|
|
|
import { ArrowLeft, ArrowRight, CircleCheck, Download, RefreshLeft, UploadFilled } from "@element-plus/icons-vue"
|
|
|
|
|
import { checkMarketOpportunitySelectionStepOne, checkMarketOpportunitySelectionStepTwo, checkMarketOpportunitySelectionStepThree, checkMarketOpportunitySelectionStepFour, checkMarketOpportunitySelectionStepFive, checkMarketOpportunitySelectionStepSix, deleteStudentTrainingAnswer, getStudentTrainingAnswer, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer"
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask"
|
|
|
|
|
import { parseTrainingArray } from "@/views/training/taskKeyMap"
|
|
|
|
|
@ -358,6 +358,39 @@ function buildStepFourOutcome() { return { title: taskTitle.value, visualization
|
|
|
|
|
function buildStepFiveOutcome() { return { title: taskTitle.value, reviewInsights: Object.fromEntries(products.map((product) => [product.id, { ...reviewInsights.value[product.id] }])) } }
|
|
|
|
|
function buildStepSixOutcome() { return { title: taskTitle.value, developmentConclusion: developmentConclusion.value.trim() } }
|
|
|
|
|
function buildDynamicStepAnswers(overrides) { return { ...dynamicStepAnswers.value, ...overrides } }
|
|
|
|
|
|
|
|
|
|
/* ===== 底部步骤导航 ===== */
|
|
|
|
|
function prevStep() { if (currentStep.value > 1) switchStep(currentStep.value - 1) }
|
|
|
|
|
async function saveCurrentProgress() {
|
|
|
|
|
if (saving.value) return
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
const step = currentStep.value
|
|
|
|
|
const payload = { currentStep: step, submitted: false, saveAction: "SAVE" }
|
|
|
|
|
if (step === 1) payload.step1Answer = JSON.stringify(buildStepOneOutcome())
|
|
|
|
|
if (step === 2) payload.step2Answer = JSON.stringify(buildStepTwoOutcome())
|
|
|
|
|
if (step === 3) payload.step3Answer = JSON.stringify(buildStepThreeOutcome())
|
|
|
|
|
if (step === 4) payload.step4Answer = JSON.stringify(buildStepFourOutcome())
|
|
|
|
|
if (step === 5) { dynamicStepAnswers.value = buildDynamicStepAnswers({ step5: buildStepFiveOutcome() }); payload.dynamicStepAnswers = JSON.stringify(dynamicStepAnswers.value) }
|
|
|
|
|
if (step === 6) { dynamicStepAnswers.value = buildDynamicStepAnswers({ step5: buildStepFiveOutcome(), step6: buildStepSixOutcome() }); payload.dynamicStepAnswers = JSON.stringify(dynamicStepAnswers.value) }
|
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, payload)
|
|
|
|
|
proxy?.$modal?.msgSuccess("进度已保存")
|
|
|
|
|
} catch (error) {
|
|
|
|
|
proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试")
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function nextStep() {
|
|
|
|
|
const step = currentStep.value
|
|
|
|
|
if (step === 1) checkAndContinue()
|
|
|
|
|
else if (step === 2) saveAndGoToStepThree()
|
|
|
|
|
else if (step === 3) saveAndGoToStepFour()
|
|
|
|
|
else if (step === 4) saveAndGoToStepFive()
|
|
|
|
|
else if (step === 5) saveAndGoToStepSix()
|
|
|
|
|
else if (step === 6) saveAndCompleteTraining()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkAndContinue() { if (saving.value) return; saving.value = true; try { const result = await checkMarketOpportunitySelectionStepOne(indicators.map((item) => ({ id: item.id, category: answers.value[item.id] }))); stepOneCheckResults.value = Object.fromEntries((result?.data?.items || []).map((item) => [item.id, item])); if (!result?.data?.allCorrect) throw new Error(`当前答对 ${result?.data?.correctCount || 0}/${indicators.length} 项,请根据表内提示修改`); await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(buildStepOneOutcome()), currentStep: 2, submitted: false, saveAction: "SAVE" }); stepOnePassed.value = true; currentStep.value = 2; proxy?.$modal?.msgSuccess("分类正确,已进入第 2 步") } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成指标分类并根据提示修改") } finally { saving.value = false } }
|
|
|
|
|
function isIntegerWeight(value) { return value !== null && value !== "" && Number.isInteger(Number(value)) && Number(value) >= 1 && Number(value) <= 100 }
|
|
|
|
|
function dimensionWeightTotal(group) { return calculateDimensionWeightTotal(group, weights.value) }
|
|
|
|
|
@ -986,14 +1019,6 @@ function exportOutcome() { const blob = new Blob([JSON.stringify({ taskName: tas
|
|
|
|
|
outline-offset: -2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ===== 底部按钮 ===== */
|
|
|
|
|
.step-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
@ -1043,6 +1068,168 @@ function exportOutcome() { const blob = new Blob([JSON.stringify({ taskName: tas
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
* 底部导航:工具栏式
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
.step-navigation {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-nav-left,
|
|
|
|
|
.step-nav-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-nav-right {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-nav {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
height: 36px;
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
border: 1px solid rgba(37, 99, 235, 0.35);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
color: var(--student-accent);
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
font-size: var(--student-fs-button);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.16s ease;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
border-color: var(--student-accent);
|
|
|
|
|
background: var(--student-accent-soft);
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
height: 36px;
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
border: 1px solid var(--student-accent);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
background: var(--student-accent);
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
font-size: var(--student-fs-button);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
box-shadow: 0 1px 2px rgba(37, 99, 235, 0.2), 0 2px 8px rgba(37, 99, 235, 0.15);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.16s ease;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
border-color: var(--student-accent-deep);
|
|
|
|
|
background: var(--student-accent-deep);
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
box-shadow: 0 2px 6px rgba(29, 78, 216, 0.3), 0 4px 12px rgba(29, 78, 216, 0.22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:active:not(:disabled) {
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-ghost {
|
|
|
|
|
border-color: rgba(37, 99, 235, 0.35);
|
|
|
|
|
color: var(--student-accent);
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
border-color: var(--student-accent);
|
|
|
|
|
background: var(--student-accent-soft);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-save {
|
|
|
|
|
border-color: #e5e7eb;
|
|
|
|
|
color: #4b5563;
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
border-color: #d1d5db;
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
transform: none;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary:disabled,
|
|
|
|
|
.btn-ghost:disabled,
|
|
|
|
|
.btn-save:disabled,
|
|
|
|
|
.btn-nav:disabled {
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
transform: none;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-progress {
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
font-family: var(--student-font-data);
|
|
|
|
|
font-size: var(--student-fs-caption);
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1100px) {
|
|
|
|
|
.step-nav-right {
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.step-navigation {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-nav-left,
|
|
|
|
|
.step-nav-right {
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-nav,
|
|
|
|
|
.btn-primary {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.placeholder {
|
|
|
|
|
padding: 60px 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
|