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/competitive-environment-ana...

105 lines
30 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="competitive-analysis-page student-training-shell">
<main class="analysis-core">
<section class="task-header"><div class="task-badge"><el-icon><TrendCharts /></el-icon> 核心实训 · 市场洞察与需求分析</div><h1>{{ taskTitle }}</h1></section>
<TrainingTaskBrief :background="taskBackground" :goals="taskGoals" :requirements="taskRequirement" :show-material="false" />
<section class="analysis-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="`${index}-${step}`"><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>{{ step }}</strong><small>{{ currentStep === index + 1 ? '当前步骤' : '点击切换' }} →</small></span></button><i v-if="index < trainingSteps.length - 1" class="step-connector" /></template></nav>
<section class="step-card">
<template v-if="currentStep === 1">
<div class="step-title-row"><div><p>Step 01</p><h2>{{ trainingSteps[0] }}</h2></div><span>SWOT 分类练习</span></div>
<p class="step-desc">请根据案例文档中信息进行 SWOT 分析,进行各项内容归类,把文档中相应内容填写到以下四类中。</p>
<div class="swot-table-wrap"><table class="swot-table"><thead><tr><th>序号</th><th>SWOT维度</th><th>关键信息要点</th></tr></thead><tbody><tr v-for="(row, index) in swotRows" :key="row.key"><td>{{ index + 1 }}</td><td class="dimension-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 btn-primary" :disabled="saving" @click="saveAndGoToStepTwo">保存并进入第 2 步</button><button type="button" class="btn" :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>Step 02</p><h2>{{ trainingSteps[1] }}</h2></div><span>资料补充</span></div>
<div class="research-guide"><p>请自行收集资料,补充完善 SWOT 信息,每个维度至少补充 1 条。信息收集可参考以下方法:</p><ol><li>自行通过互联网平台收集;可在电商平台(京东/天猫)搜索“智能健身镜”,浏览竞品用户评价,从中发现竞品的优劣势和用户未满足的需求。</li><li>使用 AI 大模型智能搜索功能进行信息收集,参考提示词见实训任务案例文档。</li></ol><p>请把补充的 SWOT 信息资讯填入下表:</p></div>
<p v-if="!stepOnePassed" class="step-warning">请先完成并保存第 1 步的 SWOT 信息归类,再填写本步骤。</p>
<div class="swot-table-wrap"><table class="swot-table"><thead><tr><th>序号</th><th>SWOT维度</th><th>补充信息要点</th></tr></thead><tbody><tr v-for="(row, index) in swotRows" :key="row.key"><td>{{ index + 1 }}</td><td class="dimension-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 btn-primary" :disabled="saving || !stepOnePassed" @click="saveAndGoToStepThree">保存并进入第 3 步</button><button type="button" class="btn" :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>Step 03</p><h2>{{ trainingSteps[2] }}</h2></div><span>SWOT 策略矩阵</span></div>
<p class="step-desc">结合 SWOT 因素矩阵,将内部因素与外部因素交叉组合,分别提出四种策略。每类策略至少提出 1 条。</p>
<p v-if="!stepTwoPassed" class="step-warning">请先完成并保存第 2 步的 SWOT 补充信息,再填写本步骤。</p>
<div class="strategy-table-wrap"><table class="strategy-table"><thead><tr><th>策略类型</th><th>组合逻辑(自动生成)</th><th>具体策略建议</th><th>预期效果</th></tr></thead><tbody><tr v-for="row in strategyDefinitions" :key="row.key"><td class="strategy-type">{{ row.label }}</td><td class="logic-cell"><p v-for="item in getCombinedLogic(row)" :key="item.label"><strong>{{ item.label }}</strong>{{ item.value || '暂无已保存信息' }}</p></td><td><textarea v-model.trim="strategyForm[row.key].recommendation" :disabled="!stepTwoPassed" placeholder="填写具体策略建议" /></td><td><textarea v-model.trim="strategyForm[row.key].expectedEffect" :disabled="!stepTwoPassed" placeholder="填写预期效果" /></td></tr></tbody></table></div>
<div class="task-actions"><button type="button" class="btn btn-primary" :disabled="saving || !stepTwoPassed" @click="saveAndGoToStepFour">保存并进入第 4 步</button><button type="button" class="btn" :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>Step 04</p><h2>{{ trainingSteps[3] }}</h2></div><span>最终决策</span></div>
<p class="step-desc">结合前述 SWOT 分析,完成市场进入决策、策略优先级排序、风险识别与竞争环境总结。</p>
<p v-if="!stepThreePassed" class="step-warning">请先完成并保存第 3 步的四类 SWOT 策略,再完成最终决策。</p>
<section class="decision-section"><h3>(1)总体竞争态势评估</h3><p>请选择该产品进入目标市场的总体竞争判断:</p><div class="radio-options"><label v-for="option in evaluationOptions" :key="option"><input v-model="finalForm.overallEvaluation" :disabled="!stepThreePassed" type="radio" name="competitive-evaluation" :value="option" /><span>{{ option }}</span></label></div></section>
<section class="decision-section"><h3>(2)优先策略排序</h3><p class="section-tip">策略内容由第 3 步填写的“具体策略建议”自动带入;请完成优先级排序并说明理由。</p><div class="priority-table-wrap"><table class="priority-table"><thead><tr><th>优先级排序</th><th>策略</th><th>理由</th></tr></thead><tbody><tr v-for="row in strategyDefinitions" :key="row.key"><td><select v-model.number="finalForm.priorities[row.key].priority" :disabled="!stepThreePassed" :aria-label="`${row.label} 的优先级排序`"><option :value="null">请选择</option><option v-for="rank in 4" :key="rank" :value="rank">{{ rank }}</option></select></td><td class="priority-strategy"><strong>{{ row.label }}</strong><p>{{ strategyForm[row.key].recommendation || '请先在第 3 步填写该策略建议' }}</p></td><td><textarea v-model.trim="finalForm.priorities[row.key].reason" :disabled="!stepThreePassed" placeholder="说明该策略的排序理由" /></td></tr></tbody></table></div></section>
<section class="decision-section"><h3>(3)需关注的风险点</h3><p>请至少指出 2 个风险点。</p><div class="risk-grid"><label><span>核心风险点 1</span><textarea v-model.trim="finalForm.coreRiskOne" :disabled="!stepThreePassed" placeholder="填写核心风险点" /></label><label><span>核心风险点 2</span><textarea v-model.trim="finalForm.coreRiskTwo" :disabled="!stepThreePassed" placeholder="填写核心风险点" /></label><label><span>其他风险点</span><textarea v-model.trim="finalForm.otherRisk" :disabled="!stepThreePassed" placeholder="可补充其他风险点" /></label></div></section>
<section class="decision-section"><h3>(4)总结 SWOT 竞争环境评估结论</h3><textarea v-model.trim="finalForm.conclusion" class="conclusion-textarea" :disabled="!stepThreePassed" placeholder="综合说明总体判断、优先策略与风险应对方向" /></section>
<div class="task-actions"><button type="button" class="btn btn-primary" :disabled="saving || !stepThreePassed" @click="saveAndCompleteTraining">保存并完成实训</button><button type="button" class="btn" :disabled="saving || !stepThreePassed" @click="resetStepFour"><el-icon><RefreshLeft /></el-icon> 清空填写</button></div>
</template>
<div v-else class="step-placeholder"><p>第 {{ currentStep }} 步内容待配置</p><span>第 1 步 SWOT 分类已保存,可从上方步骤栏返回查看。</span></div>
</section>
</section>
</main>
<TrainingAiSidebar study-tip="先辨别内部条件与外部环境,再归类为优势、劣势、机会和威胁。" reference-title="SWOT 分类提示" reference-text="· 优势:企业或产品已有的资源、能力、渠道与差异化<br />· 劣势:内部能力、成本、体验、资源或覆盖不足<br />· 机会:外部市场增长、政策变化、消费趋势与技术窗口<br />· 威胁:竞争加剧、替代品、成本风险、政策限制与需求变化" :progress-items="progressItems" />
</div>
</template>
<script setup>
import { Download, RefreshLeft, TrendCharts } from "@element-plus/icons-vue";
import { checkCompetitiveEnvironmentAnalysisStepOne, checkCompetitiveEnvironmentAnalysisStepTwo, checkCompetitiveEnvironmentAnalysisStepThree, checkCompetitiveEnvironmentAnalysisStepFour, 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 = "competitive-environment-analysis";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const DEFAULT_STEPS = ["SWOT竞争环境分析", "竞品调研与比较", "竞争策略设计", "竞争环境结论"];
const swotRows = [
{ key: "strengths", label: "优势(S)", placeholder: "填写案例中的资源、能力、渠道、产品或服务优势" },
{ key: "weaknesses", label: "劣势(W)", placeholder: "填写案例中的内部短板、成本、体验或资源不足" },
{ key: "opportunities", label: "机会(O)", placeholder: "填写案例中的市场、政策、消费趋势或技术机会" },
{ key: "threats", label: "威胁(T)", placeholder: "填写案例中的竞争、替代、成本、政策或需求风险" },
];
const strategyDefinitions = [
{ key: "so", label: "SO策略(优势×机会)", first: "strengths", firstLabel: "优势", second: "opportunities", secondLabel: "机会" },
{ key: "wo", label: "WO策略(劣势×机会)", first: "weaknesses", firstLabel: "劣势", second: "opportunities", secondLabel: "机会" },
{ key: "st", label: "ST策略(优势×威胁)", first: "strengths", firstLabel: "优势", second: "threats", secondLabel: "威胁" },
{ key: "wt", label: "WT策略(劣势×威胁)", first: "weaknesses", firstLabel: "劣势", second: "threats", secondLabel: "威胁" },
];
const defaultTask = { title: "竞争环境分析", background: "通过 SWOT 模型归类案例资料中的竞争环境信息,为后续竞品比较与竞争策略判断提供依据。", goals: ["掌握 SWOT 四维度的分类方法", "提炼案例中的竞争环境关键信息", "形成竞争分析的基础材料"], requirement: "请阅读案例资料,将相关信息归类为优势、劣势、机会和威胁。" };
const evaluationOptions = ["优势明显,建议快速进入", "机会大于挑战,建议积极布局", "挑战与机会并存,需谨慎推进", "劣势突出,建议暂缓进入"];
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 strategyForm = ref(createStrategyForm()); const finalForm = ref(createFinalForm());
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(() => swotRows.map((row) => ({ text: `${strategyForm.value.so.recommendation ? "已制定策略" : supplementForm.value[row.key] ? "已补充" : form.value[row.key] ? "已归类" : "待填写"}:${row.label}`, status: strategyForm.value.so.recommendation || 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 { strengths: "", weaknesses: "", opportunities: "", threats: "" }; }
function createStrategyForm() { return { so: createStrategyRow(), wo: createStrategyRow(), st: createStrategyRow(), wt: createStrategyRow() }; }
function createStrategyRow() { return { recommendation: "", expectedEffect: "" }; }
function createFinalForm() { return { overallEvaluation: "", priorities: Object.fromEntries(strategyDefinitions.map((row) => [row.key, { priority: null, reason: "" }])), coreRiskOne: "", coreRiskTwo: "", otherRisk: "", conclusion: "" }; }
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) { restoreStrategyOutcome(answer.step3Answer); stepThreePassed.value = true; } if (answer?.step4Answer) restoreFinalOutcome(answer.step4Answer); if (Number(answer?.currentStep) > 0) currentStep.value = Number(answer.currentStep); } catch (error) { /* Keep local draft when unavailable. */ } }
function restoreOutcome(value, target, field = "swot") { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; target.value = { ...createForm(), ...(parsed?.[field] || parsed || {}) }; } catch (error) { target.value = createForm(); } }
function restoreStrategyOutcome(value) { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; const source = parsed?.strategies || parsed || {}; strategyForm.value = Object.fromEntries(strategyDefinitions.map((row) => [row.key, { ...createStrategyRow(), ...(source[row.key] || {}) }])); } catch (error) { strategyForm.value = createStrategyForm(); } }
function restoreFinalOutcome(value) { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; const source = parsed?.decision || parsed || {}; const priorities = source.priorities || {}; finalForm.value = { ...createFinalForm(), ...source, priorities: Object.fromEntries(strategyDefinitions.map((row) => [row.key, { priority: null, reason: "", ...(priorities[row.key] || {}) }])) }; } catch (error) { finalForm.value = createFinalForm(); } }
function getCombinedLogic(row) { return [{ label: `${row.firstLabel}(第1步)· `, value: form.value[row.first] }, { label: `${row.firstLabel}(第2步补充)· `, value: supplementForm.value[row.first] }, { label: `${row.secondLabel}(第1步)· `, value: form.value[row.second] }, { label: `${row.secondLabel}(第2步补充)· `, value: supplementForm.value[row.second] }]; }
function buildStepOneOutcome() { return { title: taskTitle.value, swot: { ...form.value } }; } function buildStepTwoOutcome() { return { title: taskTitle.value, supplements: { ...supplementForm.value } }; } function buildStepThreeOutcome() { return { title: taskTitle.value, strategies: JSON.parse(JSON.stringify(strategyForm.value)) }; } function buildStepFourOutcome() { return { title: taskTitle.value, decision: JSON.parse(JSON.stringify(finalForm.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) { /* Optional browser draft. */ } } 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"); restoreStrategyOutcome(draft?.step3); restoreFinalOutcome(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 checkCompetitiveEnvironmentAnalysisStepOne(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 || "请完整填写 SWOT 四类信息"); } finally { saving.value = false; } }
async function saveAndGoToStepThree() { if (saving.value || !stepOnePassed.value) return; saving.value = true; try { await checkCompetitiveEnvironmentAnalysisStepTwo(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 || "请为每个 SWOT 维度至少补充 1 条信息"); } finally { saving.value = false; } }
async function saveAndGoToStepFour() { if (saving.value || !stepTwoPassed.value) return; saving.value = true; try { await checkCompetitiveEnvironmentAnalysisStepThree({ strategies: strategyDefinitions.map((row) => ({ strategyType: row.key, ...strategyForm.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 || "请完成四类 SWOT 策略建议和预期效果"); } finally { saving.value = false; } }
async function saveAndCompleteTraining() { if (saving.value || !stepThreePassed.value) return; saving.value = true; try { await checkCompetitiveEnvironmentAnalysisStepFour({ overallEvaluation: finalForm.value.overallEvaluation, priorities: strategyDefinitions.map((row) => ({ strategyType: row.key, ...finalForm.value.priorities[row.key] })), coreRiskOne: finalForm.value.coreRiskOne, coreRiskTwo: finalForm.value.coreRiskTwo, otherRisk: finalForm.value.otherRisk, conclusion: finalForm.value.conclusion }); 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 || "请完成总体判断、策略排序、风险点和总结结论"); } finally { saving.value = false; } }
async function resetTraining() { form.value = createForm(); supplementForm.value = createForm(); strategyForm.value = createStrategyForm(); finalForm.value = createFinalForm(); 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() { strategyForm.value = createStrategyForm(); proxy?.$modal?.msgSuccess("已清空第 3 步填写内容"); }
function resetStepFour() { finalForm.value = createFinalForm(); 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>
.competitive-analysis-page { display: grid; grid-template-columns: minmax(720px,1fr) 340px; gap: 24px; min-height: calc(100vh - 106px); padding: 24px; color: #e8f7ff; background: #06111d; }.analysis-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,.step-title-row p { color: #65dcff; font-weight: 700; }.task-header h1 { margin: 10px 0 0; color: #fff; font-size: 28px; }.analysis-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 { 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; gap: 4px; min-width: 0; }.step-copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.step-copy small { color: #5ec9ed; }.step-connector { flex: 0 0 25px; height: 2px; background: rgba(83,204,255,.4); }.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 p { margin: 0; }.step-title-row h2 { margin: 5px 0 0; color: #fff; font-size: 24px; }.step-title-row > span { border: 1px solid #38caff; border-radius: 999px; padding: 7px 12px; color: #9aeaff; }.step-desc { margin: 24px 0; color: #c5e4ef; font-size: 16px; line-height: 1.8; }.research-guide { margin: 22px 0 18px; padding: 16px 18px; border-left: 3px solid #35cfff; color: #c5e4ef; background: rgba(13,95,129,.16); line-height: 1.8; }.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; }.swot-table-wrap,.strategy-table-wrap,.priority-table-wrap { overflow-x: auto; }.swot-table { width: 100%; min-width: 720px; border-collapse: collapse; }.swot-table th,.swot-table td,.strategy-table th,.strategy-table td,.priority-table th,.priority-table td { border: 1px solid rgba(139,213,241,.45); }.swot-table th,.strategy-table th,.priority-table th { padding: 14px; color: #fff; background: #079ed9; font-size: 16px; }.swot-table th:first-child,.swot-table td:first-child { width: 8%; text-align: center; }.swot-table th:nth-child(2),.dimension-cell { width: 18%; text-align: center; font-weight: 700; }.swot-table textarea,.strategy-table textarea,.priority-table textarea,.risk-grid textarea,.conclusion-textarea { box-sizing: border-box; display: block; width: 100%; min-height: 136px; resize: vertical; border: 0; padding: 16px; color: #e9faff; background: transparent; font: inherit; line-height: 1.7; }.swot-table textarea:focus,.strategy-table textarea:focus,.priority-table textarea:focus,.risk-grid textarea:focus,.conclusion-textarea:focus { outline: 2px solid rgba(57,207,255,.4); outline-offset: -2px; }.swot-table textarea:disabled,.strategy-table textarea:disabled,.priority-table textarea:disabled,.risk-grid textarea:disabled,.conclusion-textarea:disabled { cursor: not-allowed; color: #8aa8b5; background: rgba(255,255,255,.03); }.strategy-table { width: 100%; min-width: 980px; border-collapse: collapse; }.strategy-table th:first-child,.strategy-table .strategy-type { width: 18%; text-align: center; font-weight: 700; }.strategy-table th:nth-child(2) { width: 28%; }.strategy-table .logic-cell { padding: 12px 14px; color: #cdebf5; line-height: 1.7; }.strategy-table .logic-cell p { margin: 0 0 10px; }.strategy-table .logic-cell p:last-child { margin-bottom: 0; }.strategy-table .logic-cell strong { color: #68dcff; font-size: 13px; }.decision-section { margin-top: 30px; padding: 20px; border: 1px solid rgba(66,203,252,.24); border-radius: 14px; background: linear-gradient(130deg,rgba(10,55,79,.32),rgba(2,20,34,.24)); }.decision-section h3 { margin: 0; color: #fff; font-size: 18px; }.decision-section > p { margin: 12px 0 16px; color: #c5e4ef; line-height: 1.7; }.section-tip { color: #82dcfa !important; }.radio-options { display: flex; flex-wrap: wrap; gap: 14px 30px; }.radio-options label { display: inline-flex; align-items: center; gap: 8px; color: #dff6ff; cursor: pointer; }.radio-options input { width: 17px; height: 17px; accent-color: #16bcf0; }.priority-table { width: 100%; min-width: 820px; border-collapse: collapse; }.priority-table th:first-child,.priority-table td:first-child { width: 16%; text-align: center; }.priority-table th:nth-child(2) { width: 42%; }.priority-table select { min-width: 90px; border: 1px solid rgba(102,218,255,.55); border-radius: 6px; padding: 8px 10px; color: #e9faff; background: #09293c; font: inherit; }.priority-strategy { padding: 14px 16px; color: #d9f2fb; line-height: 1.65; }.priority-strategy strong { color: #69dcff; }.priority-strategy p { margin: 7px 0 0; white-space: pre-line; }.priority-table textarea { min-height: 112px; }.risk-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 18px; }.risk-grid label { display: grid; gap: 10px; color: #e7f8ff; font-weight: 700; }.risk-grid textarea { min-height: 154px; border: 1px solid rgba(139,213,241,.45); background: rgba(0,0,0,.08); }.conclusion-textarea { min-height: 190px; border: 1px solid rgba(139,213,241,.45); background: rgba(0,0,0,.08); }.task-actions { display: flex; justify-content: center; gap: 12px; margin-top: 26px; }.btn-primary { border-color: #36d9ff; background: linear-gradient(110deg,#0eaae4,#0572ac); font-weight: 700; }.btn: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) { .competitive-analysis-page { grid-template-columns: 1fr; }.step-indicator { overflow-x: auto; }.step-tab { min-width: 190px; } } @media (max-width:720px) { .competitive-analysis-page { padding: 12px; }.analysis-workbench { margin: 12px; padding: 14px; }.task-header { padding: 18px; }.workbench-head { justify-content: flex-start; flex-wrap: wrap; }.step-title-row > span { display: none; }.risk-grid { grid-template-columns: 1fr; } }
</style>