You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/demand/industry-demand-analysis.vue

187 lines
28 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>