|
|
<template>
|
|
|
<div class="industry-pest-page student-training-shell">
|
|
|
<main class="industry-core">
|
|
|
<section class="task-header">
|
|
|
<div class="task-badge"><el-icon><DataAnalysis /></el-icon> 核心实训 · 市场洞察与需求分析</div>
|
|
|
<h1 class="task-title">{{ taskTitle }}</h1>
|
|
|
</section>
|
|
|
<TrainingTaskBrief :background="taskBackground" :goals="taskGoals" :requirements="taskRequirement" :show-material="false" />
|
|
|
|
|
|
<section class="industry-workbench">
|
|
|
<div class="workbench-head">
|
|
|
<TrainingMaterialButton :material-name="taskConfig?.materialName" :material-url="taskConfig?.materialUrl" />
|
|
|
<button type="button" class="outline-action" @click="exportOutcome"><el-icon><Download /></el-icon> 导出实训成果</button>
|
|
|
</div>
|
|
|
<nav class="step-indicator" aria-label="行业需求分析步骤">
|
|
|
<template v-for="(step, index) in trainingSteps" :key="`${step}-${index}`">
|
|
|
<button type="button" :class="['step-tab', { active: currentStep === index + 1, complete: currentStep > index + 1 }]" @click="switchStep(index + 1)">
|
|
|
<span class="step-marker">{{ String(index + 1).padStart(2, '0') }}</span>
|
|
|
<span class="step-copy"><strong class="step-name" :title="step">{{ step }}</strong><span class="step-action">{{ currentStep === index + 1 ? '当前步骤' : '点击切换' }} →</span></span>
|
|
|
</button>
|
|
|
<span v-if="index < trainingSteps.length - 1" class="step-connector" />
|
|
|
</template>
|
|
|
</nav>
|
|
|
|
|
|
<section class="industry-step-card">
|
|
|
<template v-if="currentStep === 1">
|
|
|
<div class="step-title-row"><div><p class="section-eyebrow">Step 01</p><h3>{{ trainingSteps[0] }}</h3></div><span class="step-chip">PEST 分类练习</span></div>
|
|
|
<p class="step-desc">请根据案例文档中的案例信息进行 PEST 分析,进行上述各项内容归类,把文档中相应内容填写到以下四类中。</p>
|
|
|
<div class="pest-table-wrap">
|
|
|
<table class="pest-table"><thead><tr><th>序号</th><th>PEST类型</th><th>信息资讯</th></tr></thead>
|
|
|
<tbody><tr v-for="(row, index) in pestRows" :key="row.key"><td>{{ index + 1 }}</td><td class="type-cell">{{ row.label }}</td><td><textarea v-model.trim="form[row.key]" :placeholder="row.placeholder" /></td></tr></tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving" @click="saveAndGoToStepTwo">保存并进入第 2 步</button><button type="button" class="btn-nav" :disabled="saving" @click="resetTraining"><el-icon><RefreshLeft /></el-icon> 清空填写</button></div>
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="currentStep === 2">
|
|
|
<div class="step-title-row"><div><p class="section-eyebrow">Step 02</p><h3>{{ trainingSteps[1] }}</h3></div><span class="step-chip">资料补充</span></div>
|
|
|
<div class="research-guide">
|
|
|
<p>请自行收集资料,补充 PEST 完善,每个维度至少补充 1 条。信息收集可参考以下方法:</p>
|
|
|
<ol>
|
|
|
<li>自行通过互联网平台搜索、电商平台(京东/天猫搜索“智能健身镜”)、行业报告网站(艾瑞咨询、前瞻产业研究院等)、新闻资讯(百度/头条搜索健身行业关键词)。</li>
|
|
|
<li>使用 AI 大模型智能搜索功能进行信息收集,参考提示词见实训任务案例文档。</li>
|
|
|
</ol>
|
|
|
<p>请把补充的 PEST 信息资讯填入下表:</p>
|
|
|
</div>
|
|
|
<p v-if="!stepOnePassed" class="step-warning">请先完成并保存第 1 步的 PEST 信息归类,再填写本步骤。</p>
|
|
|
<div class="pest-table-wrap">
|
|
|
<table class="pest-table"><thead><tr><th>序号</th><th>PEST类型</th><th>补充信息资讯</th></tr></thead>
|
|
|
<tbody><tr v-for="(row, index) in pestRows" :key="row.key"><td>{{ index + 1 }}</td><td class="type-cell">{{ row.label }}</td><td><textarea v-model.trim="supplementForm[row.key]" :disabled="!stepOnePassed" :placeholder="row.placeholder" /></td></tr></tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving || !stepOnePassed" @click="saveAndGoToStepThree">保存并进入第 3 步</button><button type="button" class="btn-nav" :disabled="saving || !stepOnePassed" @click="resetStepTwo"><el-icon><RefreshLeft /></el-icon> 清空填写</button></div>
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="currentStep === 3">
|
|
|
<div class="step-title-row"><div><p class="section-eyebrow">Step 03</p><h3>{{ trainingSteps[2] }}</h3></div><span class="step-chip">机会与挑战</span></div>
|
|
|
<p class="step-desc">基于上述 PEST 信息归类与补充的信息,从每个 PEST 维度分别提炼该产品面临的核心机遇与潜在挑战,并给出应对思路。每个单元格至少填写 1 条,可按换行分条。</p>
|
|
|
<p v-if="!stepTwoPassed" class="step-warning">请先完成并保存第 2 步的 PEST 补充资讯,再填写本步骤。</p>
|
|
|
<div class="analysis-table-wrap">
|
|
|
<table class="analysis-table"><thead><tr><th>PEST维度</th><th>核心机遇(至少1条)</th><th>潜在挑战(至少1条)</th><th>应对思路建议</th></tr></thead>
|
|
|
<tbody><tr v-for="row in pestAnalysisRows" :key="row.key"><td class="type-cell">{{ row.label }}</td><td><textarea v-model.trim="analysisForm[row.key].opportunities" :disabled="!stepTwoPassed" placeholder="例如:政策、需求或技术带来的增长机会" /></td><td><textarea v-model.trim="analysisForm[row.key].challenges" :disabled="!stepTwoPassed" placeholder="例如:合规、成本、竞争或使用门槛" /></td><td><textarea v-model.trim="analysisForm[row.key].strategies" :disabled="!stepTwoPassed" placeholder="例如:针对挑战给出的产品、运营或合规方案" /></td></tr></tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving || !stepTwoPassed" @click="saveAndGoToStepFour">保存并进入第 4 步</button><button type="button" class="btn-nav" :disabled="saving || !stepTwoPassed" @click="resetStepThree"><el-icon><RefreshLeft /></el-icon> 清空填写</button></div>
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="currentStep === 4">
|
|
|
<div class="step-title-row"><div><p class="section-eyebrow">Step 04</p><h3>{{ trainingSteps[3] }}</h3></div><span class="step-chip">综合结论</span></div>
|
|
|
<p class="step-desc">基于前述 PEST 分析,完成行业环境评估、市场进入判断与风险总结。</p>
|
|
|
<p v-if="!stepThreePassed" class="step-warning">请先完成并保存第 3 步的机会与挑战分析,再填写本步骤。</p>
|
|
|
<div class="conclusion-form" :class="{ 'is-disabled': !stepThreePassed }">
|
|
|
<section class="conclusion-section"><h4>(1)总体 PEST 环境评估</h4><p>概括当前宏观环境对该产品的总体友好程度,从以下五个等级中选择一个:</p><div class="option-row"><label v-for="item in assessmentOptions" :key="item" class="option-card"><input v-model="conclusionForm.overallAssessment" type="radio" name="overallAssessment" :value="item" :disabled="!stepThreePassed" /><span>{{ item }}</span></label></div></section>
|
|
|
<section class="conclusion-section"><h4>(2)市场进入结论</h4><p>从以下三个结论中选择一个,并说明理由:</p><div class="option-row decision-options"><label v-for="item in decisionOptions" :key="item" class="option-card"><input v-model="conclusionForm.marketDecision" type="radio" name="marketDecision" :value="item" :disabled="!stepThreePassed" /><span>{{ item }}</span></label></div><textarea v-model.trim="conclusionForm.decisionReason" :disabled="!stepThreePassed" placeholder="请说明市场进入结论的理由" /></section>
|
|
|
<section class="conclusion-section"><h4>(3)市场进入需要关注的风险点</h4><p>请指出 2 个以上的风险点。</p><div class="risk-grid"><label>核心风险点 1:<textarea v-model.trim="conclusionForm.riskOne" :disabled="!stepThreePassed" placeholder="填写核心风险点" /></label><label>核心风险点 2:<textarea v-model.trim="conclusionForm.riskTwo" :disabled="!stepThreePassed" placeholder="填写核心风险点" /></label><label>其他风险点:<textarea v-model.trim="conclusionForm.otherRisks" :disabled="!stepThreePassed" placeholder="可补充其他风险点" /></label></div></section>
|
|
|
<section class="conclusion-section"><h4>(4)总结 PEST 环境评估结论</h4><textarea v-model.trim="conclusionForm.summary" :disabled="!stepThreePassed" placeholder="总结该产品的 PEST 环境评估结论" /></section>
|
|
|
</div>
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving || !stepThreePassed" @click="submitTraining">提交任务</button><button type="button" class="btn-nav" :disabled="saving || !stepThreePassed" @click="resetStepFour"><el-icon><RefreshLeft /></el-icon> 清空填写</button></div>
|
|
|
</template>
|
|
|
|
|
|
<div v-else class="step-placeholder"><p>第 {{ currentStep }} 步内容待配置</p><span>前序步骤已保存,可从上方步骤栏返回查看。</span></div>
|
|
|
</section>
|
|
|
</section>
|
|
|
</main>
|
|
|
<TrainingAiSidebar study-tip="先从案例中提取宏观环境信息,再按政治、经济、社会、技术四个维度归类和补充。" reference-title="PEST 分类提示" reference-text="· 政治:政策、法规、认证、税收、数据合规<br />· 经济:市场规模、成本、商业模式、供应链<br />· 社会:人群需求、消费趋势、生活方式与场景<br />· 技术:产品技术、平台能力、设备互联与创新" :progress-items="progressItems" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { DataAnalysis, Download, RefreshLeft } from "@element-plus/icons-vue";
|
|
|
import { checkIndustryDemandAnalysisStepOne, checkIndustryDemandAnalysisStepTwo, checkIndustryDemandAnalysisStepThree, checkIndustryDemandAnalysisStepFour, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
|
|
|
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
|
|
|
|
|
|
const TASK_KEY = "industry-demand-analysis";
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
const userStore = useUserStore();
|
|
|
const DEFAULT_STEPS = ["PEST宏观环境分析", "行业机会识别", "需求判断", "分析结论"];
|
|
|
const pestRows = [
|
|
|
{ key: "political", label: "政治环境", placeholder: "填写政策、法规、认证、税收或数据合规等信息" },
|
|
|
{ key: "economic", label: "经济环境", placeholder: "填写市场规模、成本、商业模式或供应链等信息" },
|
|
|
{ key: "social", label: "社会环境", placeholder: "填写用户需求、消费趋势、生活方式或场景变化等信息" },
|
|
|
{ key: "technology", label: "技术环境", placeholder: "填写产品技术、平台能力、设备互联或创新等信息" },
|
|
|
];
|
|
|
const pestAnalysisRows = [
|
|
|
{ key: "political", label: "政治环境(P)" },
|
|
|
{ key: "economic", label: "经济环境(E)" },
|
|
|
{ key: "social", label: "社会环境(S)" },
|
|
|
{ key: "technology", label: "技术环境(T)" },
|
|
|
];
|
|
|
const assessmentOptions = ["高度友好", "较为友好", "中性", "存在风险", "风险较高"];
|
|
|
const decisionOptions = ["建议进入", "谨慎进入", "建议暂缓"];
|
|
|
const defaultTask = {
|
|
|
title: "行业需求分析",
|
|
|
background: "通过 PEST 模型识别影响行业需求的宏观环境因素,为后续产品机会判断提供依据。",
|
|
|
goals: ["掌握 PEST 四维度的分类方法", "提炼案例中的宏观环境信息", "形成行业需求分析的基础材料"],
|
|
|
requirement: "请阅读案例资料,将相关信息归类到政治、经济、社会和技术环境中,并补充自行收集的行业资讯。",
|
|
|
};
|
|
|
const taskConfig = ref(null);
|
|
|
const currentStep = ref(1);
|
|
|
const saving = ref(false);
|
|
|
const draftReady = ref(false);
|
|
|
const stepOnePassed = ref(false);
|
|
|
const stepTwoPassed = ref(false);
|
|
|
const stepThreePassed = ref(false);
|
|
|
const form = ref(createForm());
|
|
|
const supplementForm = ref(createForm());
|
|
|
const analysisForm = ref(createAnalysisForm());
|
|
|
const conclusionForm = ref(createConclusionForm());
|
|
|
const taskTitle = computed(() => taskConfig.value?.taskName || defaultTask.title);
|
|
|
const taskBackground = computed(() => taskConfig.value?.background || defaultTask.background);
|
|
|
const taskGoals = computed(() => parseArray(taskConfig.value?.objectives, defaultTask.goals));
|
|
|
const taskRequirement = computed(() => taskConfig.value?.requirements || defaultTask.requirement);
|
|
|
const trainingSteps = computed(() => parseArray(taskConfig.value?.steps, DEFAULT_STEPS));
|
|
|
const progressItems = computed(() => pestRows.map((row) => ({ text: `${conclusionForm.value.summary ? "已总结" : analysisForm.value[row.key].opportunities ? "已分析" : supplementForm.value[row.key] ? "已补充" : form.value[row.key] ? "已归类" : "待填写"}:${row.label}`, status: conclusionForm.value.summary || analysisForm.value[row.key].opportunities || supplementForm.value[row.key] || form.value[row.key] ? "done" : "" })));
|
|
|
|
|
|
onMounted(async () => { await loadTaskConfig(); loadDraft(); await loadSavedAnswer(); draftReady.value = true; });
|
|
|
watch(() => JSON.stringify(buildDraft()), persistDraft);
|
|
|
|
|
|
function createForm() { return { political: "", economic: "", social: "", technology: "" }; }
|
|
|
function createAnalysisForm() { return { political: createAnalysisRow(), economic: createAnalysisRow(), social: createAnalysisRow(), technology: createAnalysisRow() }; }
|
|
|
function createAnalysisRow() { return { opportunities: "", challenges: "", strategies: "" }; }
|
|
|
function createConclusionForm() { return { overallAssessment: "", marketDecision: "", decisionReason: "", riskOne: "", riskTwo: "", otherRisks: "", summary: "" }; }
|
|
|
function parseArray(value, fallback) { if (Array.isArray(value)) return value.length ? value : fallback; if (!value) return fallback; try { const parsed = JSON.parse(value); return Array.isArray(parsed) && parsed.length ? parsed.map((item) => String(item?.name ?? item).trim()).filter(Boolean) : fallback; } catch (error) { const list = String(value).split(/[,,|]/).map((item) => item.trim()).filter(Boolean); return list.length ? list : fallback; } }
|
|
|
async function loadTaskConfig() { try { taskConfig.value = (await getTrainingTaskByKey(TASK_KEY))?.data || null; } catch (error) { taskConfig.value = null; } }
|
|
|
async function loadSavedAnswer() {
|
|
|
try {
|
|
|
const answer = (await getStudentTrainingAnswer(TASK_KEY))?.data;
|
|
|
if (answer?.step1Answer) { restoreOutcome(answer.step1Answer, form); stepOnePassed.value = true; clearDraft(); }
|
|
|
if (answer?.step2Answer) { restoreOutcome(answer.step2Answer, supplementForm, "supplements"); stepTwoPassed.value = true; }
|
|
|
if (answer?.step3Answer) { restoreAnalysisOutcome(answer.step3Answer); stepThreePassed.value = true; }
|
|
|
if (answer?.step4Answer) restoreConclusionOutcome(answer.step4Answer);
|
|
|
if (Number(answer?.currentStep) > 0) currentStep.value = Number(answer.currentStep);
|
|
|
} catch (error) { /* Keep the local draft when the answer endpoint is unavailable. */ }
|
|
|
}
|
|
|
function restoreOutcome(value, target, field = "pest") { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; target.value = { ...createForm(), ...(parsed?.[field] || parsed || {}) }; } catch (error) { target.value = createForm(); } }
|
|
|
function restoreAnalysisOutcome(value) { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; const source = parsed?.analysis || parsed || {}; analysisForm.value = Object.fromEntries(pestAnalysisRows.map((row) => [row.key, { ...createAnalysisRow(), ...(source[row.key] || {}) }])); } catch (error) { analysisForm.value = createAnalysisForm(); } }
|
|
|
function restoreConclusionOutcome(value) { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; conclusionForm.value = { ...createConclusionForm(), ...(parsed?.conclusion || parsed || {}) }; } catch (error) { conclusionForm.value = createConclusionForm(); } }
|
|
|
function buildStepOneOutcome() { return { title: taskTitle.value, pest: { ...form.value } }; }
|
|
|
function buildStepTwoOutcome() { return { title: taskTitle.value, supplements: { ...supplementForm.value } }; }
|
|
|
function buildStepThreeOutcome() { return { title: taskTitle.value, analysis: JSON.parse(JSON.stringify(analysisForm.value)) }; }
|
|
|
function buildStepFourOutcome() { return { title: taskTitle.value, conclusion: { ...conclusionForm.value } }; }
|
|
|
function buildDraft() { return { step1: buildStepOneOutcome(), step2: buildStepTwoOutcome(), step3: buildStepThreeOutcome(), step4: buildStepFourOutcome(), currentStep: currentStep.value }; }
|
|
|
function getLocalDraftKey() { const info = userStore.userInfo || {}; return `${TASK_KEY}:draft:${info.userId || info.user_id || info.username || info.userName || "anonymous"}`; }
|
|
|
function persistDraft() { if (!draftReady.value) return; try { localStorage.setItem(getLocalDraftKey(), JSON.stringify(buildDraft())); } catch (error) { /* Browser draft is optional. */ } }
|
|
|
function loadDraft() { try { const raw = localStorage.getItem(getLocalDraftKey()); if (raw) { const draft = JSON.parse(raw); restoreOutcome(draft?.step1 || draft, form); restoreOutcome(draft?.step2, supplementForm, "supplements"); restoreAnalysisOutcome(draft?.step3); restoreConclusionOutcome(draft?.step4); if (Number(draft?.currentStep) > 0) currentStep.value = Number(draft.currentStep); } } catch (error) { clearDraft(); } }
|
|
|
function clearDraft() { try { localStorage.removeItem(getLocalDraftKey()); } catch (error) { /* Ignore storage failures. */ } }
|
|
|
function switchStep(step) { if (step === 2 && !stepOnePassed.value) { proxy?.$modal?.msgWarning("请先完成并保存第 1 步"); return; } if (step === 3 && !stepTwoPassed.value) { proxy?.$modal?.msgWarning("请先完成并保存第 2 步"); return; } if (step === 4 && !stepThreePassed.value) { proxy?.$modal?.msgWarning("请先完成并保存第 3 步"); return; } currentStep.value = step; }
|
|
|
async function saveAndGoToStepTwo() { if (saving.value) return; saving.value = true; try { await checkIndustryDemandAnalysisStepOne(form.value); await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(buildStepOneOutcome()), currentStep: 2, submitted: false, saveAction: "SAVE" }); stepOnePassed.value = true; clearDraft(); currentStep.value = 2; proxy?.$modal?.msgSuccess("已保存,已进入第 2 步"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完整填写 PEST 四类信息"); } finally { saving.value = false; } }
|
|
|
async function saveAndGoToStepThree() { if (saving.value || !stepOnePassed.value) return; saving.value = true; try { await checkIndustryDemandAnalysisStepTwo(supplementForm.value); await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(buildStepTwoOutcome()), currentStep: 3, submitted: false, saveAction: "SAVE" }); stepTwoPassed.value = true; clearDraft(); currentStep.value = 3; proxy?.$modal?.msgSuccess("已保存,已进入第 3 步"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请为每个 PEST 维度至少补充 1 条信息"); } finally { saving.value = false; } }
|
|
|
async function saveAndGoToStepFour() { if (saving.value || !stepTwoPassed.value) return; saving.value = true; try { await checkIndustryDemandAnalysisStepThree({ rows: pestAnalysisRows.map((row) => ({ dimension: row.key, ...analysisForm.value[row.key] })) }); await saveStudentTrainingAnswer(TASK_KEY, { step3Answer: JSON.stringify(buildStepThreeOutcome()), currentStep: 4, submitted: false, saveAction: "SAVE" }); stepThreePassed.value = true; clearDraft(); currentStep.value = 4; proxy?.$modal?.msgSuccess("已保存,已进入第 4 步"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成每个 PEST 维度的机遇、挑战和应对思路"); } finally { saving.value = false; } }
|
|
|
async function submitTraining() { if (saving.value || !stepThreePassed.value) return; saving.value = true; try { await checkIndustryDemandAnalysisStepFour(conclusionForm.value); await saveStudentTrainingAnswer(TASK_KEY, { step4Answer: JSON.stringify(buildStepFourOutcome()), currentStep: 4, submitted: true, saveAction: "SUBMIT" }); clearDraft(); proxy?.$modal?.msgSuccess("任务提交成功"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成 PEST 环境评估结论"); } finally { saving.value = false; } }
|
|
|
async function resetTraining() { form.value = createForm(); supplementForm.value = createForm(); analysisForm.value = createAnalysisForm(); conclusionForm.value = createConclusionForm(); stepOnePassed.value = false; stepTwoPassed.value = false; stepThreePassed.value = false; currentStep.value = 1; clearDraft(); try { saving.value = true; await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(buildStepOneOutcome()), currentStep: 1, submitted: false, saveAction: "RESET" }); } catch (error) { /* Local clearing still succeeds. */ } finally { saving.value = false; } proxy?.$modal?.msgSuccess("已清空填写内容"); }
|
|
|
function resetStepTwo() { supplementForm.value = createForm(); proxy?.$modal?.msgSuccess("已清空第 2 步填写内容"); }
|
|
|
function resetStepThree() { analysisForm.value = createAnalysisForm(); proxy?.$modal?.msgSuccess("已清空第 3 步填写内容"); }
|
|
|
function resetStepFour() { conclusionForm.value = createConclusionForm(); proxy?.$modal?.msgSuccess("已清空第 4 步填写内容"); }
|
|
|
function exportOutcome() { const blob = new Blob([JSON.stringify(buildDraft(), null, 2)], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "行业需求分析-实训成果.json"; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); proxy?.$modal?.msgSuccess("导出成功"); }
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.industry-pest-page { display: grid; grid-template-columns: minmax(720px, 1fr) 340px; gap: 24px; min-height: calc(100vh - 106px); padding: 24px; color: #e8f7ff; background: #06111d; }
|
|
|
.industry-core { min-width: 0; border: 1px solid rgba(0,180,255,.25); background: rgba(8,18,28,.6); box-shadow: 0 22px 48px rgba(0,0,0,.22); }.task-header { padding: 22px 28px 4px; }.task-badge,.section-eyebrow { color: #65dcff; font-weight: 700; }.task-title { margin: 10px 0 0; color: #fff; font-size: 28px; }.industry-workbench { margin: 24px; padding: 22px; border: 1px solid rgba(33,194,255,.35); border-radius: 20px; background: rgba(1,24,39,.72); }.workbench-head { display: flex; justify-content: flex-end; gap: 12px; }.outline-action,.btn-nav { display: inline-flex; align-items: center; justify-content: center; gap: 6px; border: 1px solid #2cc9ff; border-radius: 999px; padding: 10px 18px; color: #dff8ff; background: rgba(8,87,121,.55); cursor: pointer; }.step-indicator { display: flex; align-items: center; gap: 12px; margin: 28px 0; padding: 12px; border: 1px solid rgba(30,190,255,.28); border-radius: 22px; }.step-tab { display: flex; align-items: center; flex: 1; gap: 10px; min-width: 0; padding: 10px; border: 0; border-radius: 16px; color: #96c6d8; background: transparent; text-align: left; cursor: pointer; }.step-tab.active { color: #fff; background: linear-gradient(110deg,#148fc6,#0879aa); }.step-marker { display: grid; place-items: center; flex: 0 0 42px; width: 42px; height: 42px; border: 1px solid #4ed7ff; border-radius: 50%; font-weight: 800; }.step-copy { display: grid; min-width: 0; gap: 4px; }.step-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.step-action { color: #5ec9ed; font-size: 12px; }.step-connector { flex: 0 0 25px; height: 2px; background: rgba(83,204,255,.4); }.industry-step-card { padding: 24px; border: 1px solid rgba(31,185,249,.32); border-radius: 18px; background: rgba(1,21,34,.65); }.step-title-row { display: flex; justify-content: space-between; align-items: flex-start; }.step-title-row h3 { margin: 5px 0 0; color: #fff; font-size: 24px; }.step-chip { border: 1px solid #38caff; border-radius: 999px; padding: 7px 12px; color: #9aeaff; }.step-desc,.research-guide { color: #c5e4ef; font-size: 16px; line-height: 1.8; }.step-desc { margin: 24px 0; }.research-guide { margin: 22px 0 18px; padding: 16px 18px; border-left: 3px solid #35cfff; background: rgba(13,95,129,.16); }.research-guide p { margin: 0 0 8px; }.research-guide ol { margin: 0 0 8px; padding-left: 24px; }.research-guide li + li { margin-top: 6px; }.step-warning { margin: 12px 0; color: #ffd58e; }.pest-table-wrap,.analysis-table-wrap { overflow-x: auto; }.pest-table,.analysis-table { width: 100%; min-width: 720px; border-collapse: collapse; }.pest-table th,.pest-table td,.analysis-table th,.analysis-table td { border: 1px solid rgba(139,213,241,.45); }.pest-table th,.analysis-table th { padding: 14px; color: #fff; background: #079ed9; font-size: 16px; }.pest-table th:first-child,.pest-table td:first-child { width: 8%; text-align: center; }.pest-table th:nth-child(2),.pest-table .type-cell { width: 18%; text-align: center; font-weight: 700; }.analysis-table th:first-child,.analysis-table .type-cell { width: 14%; text-align: center; font-weight: 700; }.pest-table textarea,.analysis-table textarea,.conclusion-section textarea { box-sizing: border-box; display: block; width: 100%; min-height: 136px; resize: vertical; border: 1px solid rgba(139,213,241,.45); border-radius: 10px; padding: 16px; color: #e9faff; background: rgba(3,30,46,.76); font: inherit; line-height: 1.7; }.pest-table textarea,.analysis-table textarea { border: 0; border-radius: 0; background: transparent; }.analysis-table textarea { min-height: 156px; }.pest-table textarea:focus,.analysis-table textarea:focus,.conclusion-section textarea:focus { outline: 2px solid rgba(57,207,255,.4); outline-offset: -2px; }.pest-table textarea:disabled,.analysis-table textarea:disabled,.conclusion-section textarea:disabled { cursor: not-allowed; color: #8aa8b5; background: rgba(255,255,255,.03); }.conclusion-form { display: grid; gap: 18px; }.conclusion-section { padding: 18px; border: 1px solid rgba(70,194,237,.22); border-radius: 14px; background: linear-gradient(120deg,rgba(6,58,84,.28),rgba(4,23,39,.18)); }.conclusion-section h4 { margin: 0; color: #f0fbff; font-size: 17px; }.conclusion-section p { margin: 8px 0 14px; color: #b5d5e1; }.option-row { display: flex; flex-wrap: wrap; gap: 12px 18px; margin-bottom: 16px; }.option-card { display: inline-flex; align-items: center; gap: 8px; min-width: 112px; padding: 10px 12px; border: 1px solid rgba(96,211,255,.35); border-radius: 10px; color: #ddf8ff; cursor: pointer; transition: .2s ease; }.option-card:hover { border-color: #4cd5ff; background: rgba(31,174,225,.12); }.option-card input { accent-color: #27caff; }.risk-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; }.risk-grid label { display: grid; gap: 10px; color: #e5f8ff; font-weight: 700; }.risk-grid textarea { min-height: 150px; font-weight: 400; }.task-actions { display: flex; justify-content: center; gap: 12px; margin-top: 26px; }.btn-submit { border-color: #36d9ff; background: linear-gradient(110deg,#0eaae4,#0572ac); font-weight: 700; }.btn-nav:disabled { cursor: not-allowed; opacity: .6; }.step-placeholder { padding: 90px 20px; text-align: center; color: #a8cfdd; }.step-placeholder p { color: #fff; font-size: 20px; } @media (max-width:1280px) { .industry-pest-page { grid-template-columns: 1fr; }.step-indicator { overflow-x: auto; }.step-tab { min-width: 190px; } } @media (max-width:840px) { .risk-grid { grid-template-columns: 1fr; } } @media (max-width:720px) { .industry-pest-page { padding: 12px; }.industry-workbench { margin: 12px; padding: 14px; }.task-header { padding: 18px; }.workbench-head { justify-content: flex-start; flex-wrap: wrap; }.step-chip { display: none; } }
|
|
|
</style>
|