|
|
@@ -0,0 +1,2152 @@
|
|
|
+<template>
|
|
|
+ <div class="shPage">
|
|
|
+ <!-- ============ 成员选择阶段 ============ -->
|
|
|
+ <div class="shMemberStage" v-if="work.stage === 'member'">
|
|
|
+ <div class="shMemberCard">
|
|
|
+ <div class="shMemberIconWrap">
|
|
|
+ <div class="shMemberIcon">
|
|
|
+ <svg viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="#F5A623" stroke-width="2">
|
|
|
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
|
+ <circle cx="9" cy="7" r="4"></circle>
|
|
|
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
|
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <h2 class="shMemberTitle">{{ lang.ssSHMemberTitle }}</h2>
|
|
|
+ <p class="shMemberSubtitle">{{ lang.ssSHMemberSubtitle }}</p>
|
|
|
+
|
|
|
+ <div class="shMemberBox">
|
|
|
+ <el-select v-model="work.members" multiple filterable :filter-method="onMemberFilter" collapse-tags
|
|
|
+ placeholder="" :popper-class="'shMemberSelectPopper'" :no-data-text="' '" @change="onMemberChange">
|
|
|
+ <el-option v-for="stu in filteredStudents" :key="stu.id" :label="stu.name" :value="stu.id">
|
|
|
+ <span>{{ stu.name }}</span>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button class="shMemberConfirmBtn"
|
|
|
+ :class="{ shMemberConfirmDisabled: !work.members || work.members.length === 0 }"
|
|
|
+ :disabled="!work.members || work.members.length === 0" @click="confirmMembers">
|
|
|
+ {{ lang.ssSHMemberConfirmN.replace('{n}', work.members ? work.members.length : 0) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ============ 设置内容阶段 ============ -->
|
|
|
+ <div class="shSetupStage" v-else-if="work.stage === 'setup'">
|
|
|
+ <!-- 顶部 tab -->
|
|
|
+ <div class="shTopTabs">
|
|
|
+ <div class="shTab shTabActive">
|
|
|
+ <div class="shTabNum">①</div>
|
|
|
+ <div class="shTabLabel">{{ lang.ssSHSetupContent }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="shTabDivider"></div>
|
|
|
+ <div class="shTab shTabDisabled">
|
|
|
+ <div class="shTabNum">②</div>
|
|
|
+ <div class="shTabLabel">{{ lang.ssSHStartActivity }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="shSetupHeader">
|
|
|
+ <h2 class="shSetupTitle">{{ lang.ssSHBeforeStart }}</h2>
|
|
|
+ <p class="shSetupDesc">{{ lang.ssSHSetupDesc }}</p>
|
|
|
+ <div class="shStepIndicator">
|
|
|
+ <div class="shStepIndicator" v-for="(label, idx) in stageLabels" :key="label">
|
|
|
+ <span class="shStepDot"
|
|
|
+ :class="{ shStepDotActive: idx <= setupStep, shStepDotCurrent: idx === setupStep }"></span>
|
|
|
+ <span class="shStepLabel" :class="{ shStepLabelActive: idx === setupStep }">{{ label }}</span>
|
|
|
+ <span v-if="idx < stageLabels.length - 1" class="shStepLine"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 步骤 1:情境 -->
|
|
|
+ <div class="shStepCard" v-if="setupStep === 0">
|
|
|
+ <div class="shStepRow">
|
|
|
+ <div class="shStepLeft">
|
|
|
+ <div class="shStepTitleRow">
|
|
|
+ <div class="shStepIcon shScenarioIcon">
|
|
|
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#F5A623" stroke-width="2">
|
|
|
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h3 class="shStepTitle">{{ lang.ssSHScenarioTitle }}</h3>
|
|
|
+ <p class="shStepSub">{{ lang.ssSHScenarioSubtitle }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shExampleLabel">{{ lang.ssSHScenarioExamples }}</div>
|
|
|
+ <ul class="shExampleList">
|
|
|
+ <li class="shExampleItem"><span class="shExampleDot">•</span>{{ lang.ssSHScenarioEx1 }}</li>
|
|
|
+ <li class="shExampleItem"><span class="shExampleDot">•</span>{{ lang.ssSHScenarioEx2 }}</li>
|
|
|
+ <li class="shExampleItem"><span class="shExampleDot">•</span>{{ lang.ssSHScenarioEx3 }}</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="shStepRight">
|
|
|
+ <div class="shInputTitle">{{ lang.ssSHScenarioInputTitle }}</div>
|
|
|
+ <div class="shTextareaWrap">
|
|
|
+ <textarea v-model="work.scenario" class="shTextarea" :placeholder="lang.ssSHScenarioInputPH"></textarea>
|
|
|
+ <div class="shMicBtn" :class="{ shMicBtnActive: recordObj.status == '1' }"
|
|
|
+ :title="recordObj.status == '1' ? lang.ssSHStopRec : lang.ssSHVoiceInput"
|
|
|
+ @click="recordObj.status == '1' ? stopRecord() : startRecord('scenario')">
|
|
|
+ <svg v-if="recordObj.status == '1'" width="18" height="18" viewBox="0 0 120 20">
|
|
|
+ <rect x="5" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="29" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="53" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="77" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="101" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
|
|
|
+ </rect>
|
|
|
+ </svg>
|
|
|
+ <svg v-else viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
|
|
|
+ stroke-width="2">
|
|
|
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
|
|
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
|
|
+ <line x1="12" y1="19" x2="12" y2="23"></line>
|
|
|
+ <line x1="8" y1="23" x2="16" y2="23"></line>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shStepFooter shStepFooterSingle">
|
|
|
+ <button class="shPrimaryBtn" :class="{ shPrimaryBtnDisabled: !work.scenario || !work.scenario.trim() }"
|
|
|
+ :disabled="!work.scenario || !work.scenario.trim()" @click="setupStep = 1">{{ lang.ssSHStepNext }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 步骤 2:计时 -->
|
|
|
+ <div class="shStepCard" v-if="setupStep === 1">
|
|
|
+ <div class="shTimerWrap">
|
|
|
+ <div class="shTimerTopRow">
|
|
|
+ <div class="shStepTitleRow">
|
|
|
+ <div class="shStepIcon shTimerIcon">
|
|
|
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#F5A623" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <polyline points="12 6 12 12 16 14"></polyline>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h3 class="shStepTitle">{{ lang.ssSHTimerTitle }}</h3>
|
|
|
+ <p class="shStepSub">{{ lang.ssSHTimerSubtitle }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shSwitch" :class="{ shSwitchOn: work.timerEnabled }"
|
|
|
+ @click="work.timerEnabled = !work.timerEnabled">
|
|
|
+ <div class="shSwitchThumb"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template v-if="work.timerEnabled">
|
|
|
+ <div class="shTimerQuestion">{{ lang.ssSHTimerQuestion }}</div>
|
|
|
+ <div class="shTimerOpts">
|
|
|
+ <button v-for="(opt, idx) in timerPresets" :key="idx" class="shTimerOpt"
|
|
|
+ :class="{ shTimerOptActive: work.timerDuration === opt.value }"
|
|
|
+ @click="work.timerDuration = opt.value">{{ opt.label }}</button>
|
|
|
+ <button class="shTimerOpt" :class="{ shTimerOptActive: timerCustomFocused }" @click="focusTimerCustom">{{ lang.ssSHTimerCustom }}</button>
|
|
|
+ <input v-if="timerCustomFocused" type="number" min="1" class="shTimerCustomInput"
|
|
|
+ v-model.number="work.timerCustomValue" @blur="onTimerCustomBlur" @keyup.enter="onTimerCustomBlur" />
|
|
|
+ </div>
|
|
|
+ <div class="shTimerHint">
|
|
|
+ <div class="shTimerHintMain">
|
|
|
+ {{ lang.ssSHTimerPerHat.replace('{n}', work.timerDuration) }}
|
|
|
+ </div>
|
|
|
+ <div class="shTimerHintSub">{{ currentTimerHint }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <div class="shTimerOffHint">
|
|
|
+ <div class="shTimerOffMain">{{ lang.ssSHTimerOff }}</div>
|
|
|
+ <div class="shTimerOffSub">{{ lang.ssSHTimerOffSub }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div class="shStepFooter">
|
|
|
+ <button class="shSecondaryBtn" @click="setupStep = 0">{{ lang.ssSHStepPrev }}</button>
|
|
|
+ <button class="shPrimaryBtn" @click="setupStep = 2">{{ lang.ssSHStepNext }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 步骤 3:帽子 -->
|
|
|
+ <div class="shStepCard" v-if="setupStep === 2">
|
|
|
+ <h3 class="shHatsTitle">{{ lang.ssSHHatsTitle }}</h3>
|
|
|
+ <p class="shHatsSub">{{ lang.ssSHHatsSubtitle }}</p>
|
|
|
+ <div class="shHatsGrid">
|
|
|
+ <div v-for="hat in hatList" :key="hat.key" class="shHatCard"
|
|
|
+ :class="{ ['shHatSelected shHat' + hat.key + 'Sel']: work.hats.indexOf(hat.key) > -1 }"
|
|
|
+ @click="toggleHat(hat.key)">
|
|
|
+ <div v-if="work.hats.indexOf(hat.key) > -1" class="shHatCheck" :class="'shHatCheck' + hat.key">
|
|
|
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="3">
|
|
|
+ <polyline points="20 6 9 17 4 12"></polyline>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="shHatIcon" :style="{ background: hat.bg }">
|
|
|
+ <svg v-if="hat.key === 'white'" viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="#666"
|
|
|
+ stroke-width="2">
|
|
|
+ <rect x="3" y="12" width="4" height="9"></rect>
|
|
|
+ <rect x="9" y="6" width="4" height="15"></rect>
|
|
|
+ <rect x="15" y="9" width="4" height="12"></rect>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'red'" viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="#fda4af"
|
|
|
+ stroke-width="2">
|
|
|
+ <path
|
|
|
+ d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
|
|
|
+ <svg v-else-if="hat.key === 'yellow'" viewBox="0 0 24 24" width="28" height="28" fill="none"
|
|
|
+ stroke="#ca8a04" stroke-width="2">
|
|
|
+ <polygon
|
|
|
+ points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
|
|
|
+ </polygon>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'green'" viewBox="0 0 24 24" width="28" height="28" fill="none"
|
|
|
+ stroke="#16a34a" stroke-width="2">
|
|
|
+ <path d="M9 18h6"></path>
|
|
|
+ <path d="M10 22h4"></path>
|
|
|
+ <path
|
|
|
+ d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'blue'" viewBox="0 0 24 24" width="28" height="28" fill="none"
|
|
|
+ stroke="#2563eb" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <circle cx="12" cy="12" r="4"></circle>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="shHatName">{{ hat.name }}</div>
|
|
|
+ <div class="shHatDesc">{{ hat.desc }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shStepFooter">
|
|
|
+ <button class="shSecondaryBtn" @click="setupStep = 1">{{ lang.ssSHStepPrev }}</button>
|
|
|
+ <button class="shPrimaryBtn shPrimaryBtnStart"
|
|
|
+ :class="{ shPrimaryBtnDisabled: !work.hats || work.hats.length === 0 }"
|
|
|
+ :disabled="!work.hats || work.hats.length === 0" @click="startActivity">{{ lang.ssSHHatsStart }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ============ 开始活动阶段 ============ -->
|
|
|
+ <div class="shActivityStage" v-else-if="work.stage === 'activity'">
|
|
|
+ <!-- 顶部 tab -->
|
|
|
+ <div class="shTopTabs" v-if="work.isSetting">
|
|
|
+ <div class="shTab shTabClickable" @click="backToSetup">
|
|
|
+ <div class="shTabNum">①</div>
|
|
|
+ <div class="shTabLabel">{{ lang.ssSHSetupContent }} {{ lang.ssSHClickBack }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="shTabDivider"></div>
|
|
|
+ <div class="shTab shTabActive">
|
|
|
+ <div class="shTabNum">②</div>
|
|
|
+ <div class="shTabLabel">{{ lang.ssSHStartActivity }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 标题卡 -->
|
|
|
+ <div class="shHeaderCard" v-if="work.stage === 'activity'">
|
|
|
+ <div class="shHeaderCardLeft">
|
|
|
+ <div class="shHeaderCardTitle">{{ lang.ssSHTitle }}</div>
|
|
|
+ <div class="shHeaderCardSub">{{ lang.ssSHHowToDesc }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="shHeaderCardRight" @click="showHowTo = !showHowTo">
|
|
|
+ <span>{{ lang.ssSHHowTo }}</span>
|
|
|
+ <i :class="['kwlArrow', { 'kwlArrowUp': showHowTo }]">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
|
+ <polyline points="6 9 12 15 18 9"></polyline>
|
|
|
+ </svg>
|
|
|
+ </i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shHowToContent" v-show="showHowTo">
|
|
|
+ <div class="shHowToList">
|
|
|
+ <div class="shHowToItem" v-for="(step, idx) in effectiveHowToSteps" :key="idx">{{ step }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 帽子圆点导航 -->
|
|
|
+ <div class="shHatDots">
|
|
|
+ <div class="shHatDots" v-for="(hatKey, idx) in selectedHats" :key="hatKey">
|
|
|
+ <div class="shHatDot"
|
|
|
+ :class="['shHatDot' + hatKey, { shHatDotDone: !!work.hatContent[hatKey], shHatDotCurrent: idx === currentHatIdx }]"
|
|
|
+ @click="jumpToHat(idx)"></div>
|
|
|
+ <span v-if="idx < selectedHats.length - 1" class="shHatDotLine"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主区域:左帽子卡 + 右输入卡 -->
|
|
|
+ <div class="shActivityRow">
|
|
|
+ <!-- 左侧帽子展示 -->
|
|
|
+ <div class="shActivityHatCard">
|
|
|
+ <div class="shActivityHatIcon" :class="'shActivityHatIcon' + currentHat.key"
|
|
|
+ :style="{ background: currentHat.bg }">
|
|
|
+ <svg v-if="currentHat.key === 'white'" viewBox="0 0 24 24" width="44" height="44" fill="none" stroke="#666"
|
|
|
+ stroke-width="2">
|
|
|
+ <rect x="3" y="12" width="4" height="9"></rect>
|
|
|
+ <rect x="9" y="6" width="4" height="15"></rect>
|
|
|
+ <rect x="15" y="9" width="4" height="12"></rect>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="currentHat.key === 'red'" viewBox="0 0 24 24" width="44" height="44" fill="none"
|
|
|
+ stroke="#fda4af" stroke-width="2">
|
|
|
+ <path
|
|
|
+ d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="currentHat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
|
|
|
+ <svg v-else-if="currentHat.key === 'yellow'" viewBox="0 0 24 24" width="44" height="44" fill="none"
|
|
|
+ stroke="#ca8a04" stroke-width="2">
|
|
|
+ <polygon
|
|
|
+ points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
|
|
|
+ </polygon>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="currentHat.key === 'green'" viewBox="0 0 24 24" width="44" height="44" fill="none"
|
|
|
+ stroke="#16a34a" stroke-width="2">
|
|
|
+ <path d="M9 18h6"></path>
|
|
|
+ <path d="M10 22h4"></path>
|
|
|
+ <path
|
|
|
+ d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="currentHat.key === 'blue'" viewBox="0 0 24 24" width="44" height="44" fill="none"
|
|
|
+ stroke="#2563eb" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <circle cx="12" cy="12" r="4"></circle>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="shActivityHatName">{{ currentHat.name }}</div>
|
|
|
+ <div class="shActivityHatDesc">{{ currentHat.desc }}</div>
|
|
|
+ <div class="shActivityHatProgress">{{ lang.ssSHHatProgress.replace('{n}', currentHatIdx +
|
|
|
+ 1).replace('{total}',
|
|
|
+ selectedHats.length) }}</div>
|
|
|
+ <!-- 引导问题 -->
|
|
|
+ <div class="shActivityHatPrompt">{{ currentHatPrompt }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧输入 -->
|
|
|
+ <div class="shActivityInputCard">
|
|
|
+ <div class="shInputTitle">{{ lang.ssSHYourThoughts }}</div>
|
|
|
+ <div class="shTextareaWrap">
|
|
|
+ <textarea v-model="work.hatContent[currentHat.key]" class="shTextarea"
|
|
|
+ :placeholder="lang.ssSHYourThoughtsPH.replace('{hatName}', currentHat.name)"></textarea>
|
|
|
+ <div class="shMicBtn" :class="{ shMicBtnActive: recordObj.status == '1' }"
|
|
|
+ :title="recordObj.status == '1' ? lang.ssSHStopRec : lang.ssSHVoiceInput"
|
|
|
+ @click="recordObj.status == '1' ? stopRecord() : startRecord(currentHat.key)">
|
|
|
+ <svg v-if="recordObj.status == '1'" width="18" height="18" viewBox="0 0 120 20">
|
|
|
+ <rect x="5" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="29" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="53" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="77" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
|
|
|
+ </rect>
|
|
|
+ <rect x="101" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
+ <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
|
|
|
+ <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
|
|
|
+ </rect>
|
|
|
+ </svg>
|
|
|
+ <svg v-else viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
|
|
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
|
|
+ <line x1="12" y1="19" x2="12" y2="23"></line>
|
|
|
+ <line x1="8" y1="23" x2="16" y2="23"></line>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 负责人(mock 显示) -->
|
|
|
+ <div class="shAssignee" v-if="memberNames.length">
|
|
|
+ <span class="shAssigneeLabel">{{ lang.ssOwner }}:</span>
|
|
|
+ <template v-if="memberNames.length">
|
|
|
+ <span v-for="n in memberNames" :key="n" class="shAssigneeTag">{{ n }}</span>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span class="shAssigneeTagEmpty">—</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部栏 -->
|
|
|
+ <div class="shActivityFooter" v-if="work.timerEnabled">
|
|
|
+ <div class="shAutoSwitchBtn" :class="{ shAutoSwitchOn: autoSwitch }" @click="toggleAutoSwitch">
|
|
|
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <polyline points="12 6 12 12 16 14"></polyline>
|
|
|
+ </svg>
|
|
|
+ <span>{{ lang.ssSHAutoSwitchOn }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="shCountdown" v-if="autoSwitch">
|
|
|
+ {{ lang.ssSHCountdownSec.replace('{n}', countdownRemaining) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="shFooterBtns">
|
|
|
+ <button class="shSecondaryBtn" :class="{ shSecondaryBtnDisabled: currentHatIdx === 0 }"
|
|
|
+ :disabled="currentHatIdx === 0" @click="prevHat">{{ lang.ssSHPrevHat }}</button>
|
|
|
+ <button class="shPrimaryBtn" @click="nextOrFinish">{{ isLastHat ? lang.ssSHFinish : lang.ssSHNextHat
|
|
|
+ }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ============ 总结回顾阶段 ============ -->
|
|
|
+ <div class="shReviewStage" v-else-if="work.stage === 'review'">
|
|
|
+ <div class="shReviewHeader">
|
|
|
+ <h2 class="shReviewTitle">{{ lang.ssSHSummaryTitle }}</h2>
|
|
|
+ <p class="shReviewSub">{{ lang.ssSHSummarySub }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="shReviewList">
|
|
|
+ <div v-for="hat in selectedHatsDetail" :key="hat.key" class="shReviewCard" :class="['shReviewCard' + hat.key]"
|
|
|
+ @click="copyHatContent(hat)">
|
|
|
+ <div class="shReviewIcon" :style="{ background: hat.bg }">
|
|
|
+ <svg v-if="hat.key === 'white'" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#666"
|
|
|
+ stroke-width="2">
|
|
|
+ <rect x="3" y="12" width="4" height="9"></rect>
|
|
|
+ <rect x="9" y="6" width="4" height="15"></rect>
|
|
|
+ <rect x="15" y="9" width="4" height="12"></rect>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'red'" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#ef4444"
|
|
|
+ stroke-width="2">
|
|
|
+ <path
|
|
|
+ d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
|
|
|
+ <svg v-else-if="hat.key === 'yellow'" viewBox="0 0 24 24" width="22" height="22" fill="none"
|
|
|
+ stroke="#ca8a04" stroke-width="2">
|
|
|
+ <polygon
|
|
|
+ points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
|
|
|
+ </polygon>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'green'" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#16a34a"
|
|
|
+ stroke-width="2">
|
|
|
+ <path d="M9 18h6"></path>
|
|
|
+ <path d="M10 22h4"></path>
|
|
|
+ <path
|
|
|
+ d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14">
|
|
|
+ </path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else-if="hat.key === 'blue'" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#2563eb"
|
|
|
+ stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <circle cx="12" cy="12" r="4"></circle>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="shReviewInfo">
|
|
|
+ <div class="shReviewName">{{ hat.name }} · {{ hat.desc }}</div>
|
|
|
+ <div class="shReviewContent">
|
|
|
+ <template v-if="work.hatContent[hat.key] && work.hatContent[hat.key].trim()">
|
|
|
+ {{ work.hatContent[hat.key] }}
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span class="shReviewEmpty">{{ lang.ssSHNoAnswer }}</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="shReviewFooter">
|
|
|
+ <!-- <button class="shSecondaryBtn" @click="backToSetupFromReview">{{ lang.ssSHBackToSetup }}</button> -->
|
|
|
+ <button class="shPrimaryBtn" @click="restart">{{ lang.ssSHRestart }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 录音 iframe -->
|
|
|
+ <iframe allow="camera *; microphone *;display-capture;midi;encrypted-media;" :src="iframeUrl" ref="iframeRef"
|
|
|
+ v-show="false"></iframe>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+// 帽子配置
|
|
|
+const HAT_LIST = [
|
|
|
+ { key: 'white', nameKey: 'ssSHWhite', descKey: 'ssSHWhiteDesc', promptKey: 'ssSHHatPromptWhite', bg: '#fafafa' },
|
|
|
+ { key: 'red', nameKey: 'ssSHRed', descKey: 'ssSHRedDesc', promptKey: 'ssSHHatPromptRed', bg: '#fef2f2' },
|
|
|
+ { key: 'black', nameKey: 'ssSHBlack', descKey: 'ssSHBlackDesc', promptKey: 'ssSHHatPromptBlack', bg: '#1f1f1f' },
|
|
|
+ { key: 'yellow', nameKey: 'ssSHYellow', descKey: 'ssSHYellowDesc', promptKey: 'ssSHHatPromptYellow', bg: '#fffbeb' },
|
|
|
+ { key: 'green', nameKey: 'ssSHGreen', descKey: 'ssSHGreenDesc', promptKey: 'ssSHHatPromptGreen', bg: '#f0fdf4' },
|
|
|
+ { key: 'blue', nameKey: 'ssSHBlue', descKey: 'ssSHBlueDesc', promptKey: 'ssSHHatPromptBlue', bg: '#eff6ff' },
|
|
|
+];
|
|
|
+
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ workData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 持久化数据(含后台配置字段)
|
|
|
+ work: {
|
|
|
+ stage: 'member',
|
|
|
+ isSetting: true,
|
|
|
+ members: [],
|
|
|
+ scenario: '',
|
|
|
+ timerEnabled: true,
|
|
|
+ timerDuration: 60,
|
|
|
+ timerCustomValue: 60,
|
|
|
+ hats: ['white', 'red', 'black', 'yellow', 'green', 'blue'],
|
|
|
+ hatContent: {}, // { white: '', red: '', ... }
|
|
|
+ // —— 后台配置字段 ——
|
|
|
+ enableMemberSelect: true,
|
|
|
+ enableContentConfigurable: true,
|
|
|
+ autoExpandHowTo: false,
|
|
|
+ customScenario: '',
|
|
|
+ customTimerEnabled: true,
|
|
|
+ customTimerDuration: 60,
|
|
|
+ customHats: null,
|
|
|
+ howToSteps: null, // null 或 [] → 用默认 i18n 6 步;可由后台覆盖自定义
|
|
|
+ },
|
|
|
+ // 纯 UI 状态
|
|
|
+ setupStep: 0,
|
|
|
+ timerCustomFocused: false,
|
|
|
+ showHowTo: false,
|
|
|
+ currentHatIdx: 0,
|
|
|
+ autoSwitch: true,
|
|
|
+ countdownRemaining: 0,
|
|
|
+ _countdownTimer: null,
|
|
|
+
|
|
|
+ // mock 学生
|
|
|
+ allStudents: [
|
|
|
+ { id: 's1', name: '同学A' },
|
|
|
+ { id: 's2', name: '同学B' },
|
|
|
+ { id: 's3', name: '同学C' },
|
|
|
+ { id: 's4', name: '同学D' },
|
|
|
+ { id: 's5', name: '同学E' },
|
|
|
+ { id: 's6', name: '同学F' },
|
|
|
+ { id: 's7', name: '同学G' },
|
|
|
+ { id: 's8', name: '同学H' },
|
|
|
+ ],
|
|
|
+ recordObj: { status: '0', loading: false, startTime: 0 },
|
|
|
+ memberSearchText: '',
|
|
|
+ _initialized: false,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ // 完整帽子列表(带 i18n 文案)
|
|
|
+ hatList() {
|
|
|
+ return HAT_LIST.map(h => ({
|
|
|
+ ...h,
|
|
|
+ name: this.lang[h.nameKey] || h.key,
|
|
|
+ desc: this.lang[h.descKey] || '',
|
|
|
+ prompt: this.lang[h.promptKey] || '',
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ stageLabels() {
|
|
|
+ return [
|
|
|
+ this.lang.ssSHStageScenario || '情境',
|
|
|
+ this.lang.ssSHStageTimer || '计时',
|
|
|
+ this.lang.ssSHStageHats || '帽子',
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ timerPresets() {
|
|
|
+ return [
|
|
|
+ { value: 10, label: '10秒' },
|
|
|
+ { value: 30, label: '30秒' },
|
|
|
+ { value: 60, label: '60秒' },
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ currentTimerHint() {
|
|
|
+ const n = this.work.timerDuration;
|
|
|
+ const l = this.lang;
|
|
|
+ if (n <= 45) return l.ssSHTimerHintFast || '';
|
|
|
+ if (n <= 90) return l.ssSHTimerHintNormal || '';
|
|
|
+ return l.ssSHTimerHintDeep || '';
|
|
|
+ },
|
|
|
+ filteredStudents() {
|
|
|
+ const q = (this.memberSearchText || '').trim();
|
|
|
+ if (!q) return this.allStudents;
|
|
|
+ return this.allStudents.filter(s => s.name.indexOf(q) > -1);
|
|
|
+ },
|
|
|
+ iframeUrl() {
|
|
|
+ if (this.lang && this.lang.lang === 'com') return 'https://cloud.cocorobo.com/browser/public/index.html';
|
|
|
+ if (this.lang && this.lang.lang === 'hk') return 'https://cloud.cocorobo.hk/browser/public/index.html';
|
|
|
+ return 'https://beta.cloud.cocorobo.cn/browser/public/index.html';
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 活动阶段辅助 ——
|
|
|
+ selectedHats() {
|
|
|
+ // 按 HAT_LIST 的 canonical 顺序过滤出选中的
|
|
|
+ const selected = this.work.hats || [];
|
|
|
+ return HAT_LIST.filter(h => selected.indexOf(h.key) > -1).map(h => h.key);
|
|
|
+ },
|
|
|
+ selectedHatsDetail() {
|
|
|
+ const selected = this.work.hats || [];
|
|
|
+ return HAT_LIST.filter(h => selected.indexOf(h.key) > -1).map(h => ({
|
|
|
+ ...h,
|
|
|
+ name: this.lang[h.nameKey] || h.key,
|
|
|
+ desc: this.lang[h.descKey] || '',
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ currentHat() {
|
|
|
+ const key = this.selectedHats[this.currentHatIdx] || 'white';
|
|
|
+ return this.selectedHatsDetail.find(h => h.key === key) || HAT_LIST[0];
|
|
|
+ },
|
|
|
+ currentHatPrompt() {
|
|
|
+ const key = this.selectedHats[this.currentHatIdx];
|
|
|
+ if (!key) return '';
|
|
|
+ const map = {
|
|
|
+ white: this.lang.ssSHHatPromptWhite,
|
|
|
+ red: this.lang.ssSHHatPromptRed,
|
|
|
+ black: this.lang.ssSHHatPromptBlack,
|
|
|
+ yellow: this.lang.ssSHHatPromptYellow,
|
|
|
+ green: this.lang.ssSHHatPromptGreen,
|
|
|
+ blue: this.lang.ssSHHatPromptBlue,
|
|
|
+ };
|
|
|
+ return map[key] || '';
|
|
|
+ },
|
|
|
+ isLastHat() {
|
|
|
+ return this.currentHatIdx === this.selectedHats.length - 1;
|
|
|
+ },
|
|
|
+ memberNames() {
|
|
|
+ const ids = this.work.members || [];
|
|
|
+ return this.allStudents.filter(s => ids.indexOf(s.id) > -1).map(s => s.name);
|
|
|
+ },
|
|
|
+ // 最终渲染的"怎么做"步骤 —— 固定显示 6 个帽子思考方法(i18n 定义)
|
|
|
+ effectiveHowToSteps() {
|
|
|
+ const l = this.lang;
|
|
|
+ return [
|
|
|
+ l.ssSHHowToStep1, l.ssSHHowToStep2, l.ssSHHowToStep3,
|
|
|
+ l.ssSHHowToStep4, l.ssSHHowToStep5, l.ssSHHowToStep6,
|
|
|
+ ].filter(Boolean);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ // 初始化 hatContent,确保每个选中的帽子都有 key(只在设置/帽子列表真正变化时才做)
|
|
|
+ selectedHats: {
|
|
|
+ handler() {
|
|
|
+ if (!this._initialized) { this._initialized = true; return; }
|
|
|
+ const target = this.work.hatContent || {};
|
|
|
+ const next = {};
|
|
|
+ this.selectedHats.forEach(k => {
|
|
|
+ next[k] = target[k] !== undefined ? target[k] : '';
|
|
|
+ });
|
|
|
+ this.work.hatContent = next;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ work: {
|
|
|
+ deep: true,
|
|
|
+ handler(newVal) {
|
|
|
+ const str = JSON.stringify(newVal);
|
|
|
+ if (str !== JSON.stringify(this.workData.json || {})) {
|
|
|
+ this.$emit('changeWorkData', str);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置阶段保存触发
|
|
|
+ setupStep() {
|
|
|
+ if (this.work.stage === 'setup') this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 帽子切换 → 重置倒计时
|
|
|
+ currentHatIdx() {
|
|
|
+ this.restartCountdown();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 阶段切换 → 清理倒计时
|
|
|
+ 'work.stage'(val, oldVal) {
|
|
|
+ if (val !== 'activity') this.clearCountdown();
|
|
|
+ // 第一次进入 activity 启动倒计时
|
|
|
+ if (val === 'activity' && oldVal !== 'activity') {
|
|
|
+ this.currentHatIdx = 0;
|
|
|
+ this.restartCountdown();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 计时开关 / 时长 变化
|
|
|
+ 'work.timerEnabled'() { this.restartCountdown(); },
|
|
|
+ 'work.timerDuration'() { this.restartCountdown(); },
|
|
|
+
|
|
|
+ workData: {
|
|
|
+ handler(newVal, oldVal) {
|
|
|
+ const _data = JSON.parse(JSON.stringify((newVal && newVal.json) || {}));
|
|
|
+ // 构造默认 work 值(含所有配置字段)
|
|
|
+ const base = {
|
|
|
+ stage: 'member',
|
|
|
+ isSetting: true,
|
|
|
+ members: [],
|
|
|
+ scenario: '',
|
|
|
+ timerEnabled: true,
|
|
|
+ timerDuration: 60,
|
|
|
+ timerCustomValue: 60,
|
|
|
+ hats: ['white', 'red', 'black', 'yellow', 'green', 'blue'],
|
|
|
+ hatContent: {},
|
|
|
+ enableMemberSelect: true,
|
|
|
+ enableContentConfigurable: true,
|
|
|
+ autoExpandHowTo: false,
|
|
|
+ customScenario: '',
|
|
|
+ customTimerEnabled: true,
|
|
|
+ customTimerDuration: 60,
|
|
|
+ customHats: null,
|
|
|
+ howToSteps: null,
|
|
|
+ };
|
|
|
+ const merged = Object.assign(base, _data || {});
|
|
|
+
|
|
|
+ // 仅当没有任何业务数据时(首次进入)才根据配置决定初始 stage
|
|
|
+ const isFresh = !(merged.members && merged.members.length)
|
|
|
+ && !(merged.scenario && merged.scenario.trim())
|
|
|
+ && !(merged.hatContent && Object.keys(merged.hatContent).some(k => merged.hatContent[k]));
|
|
|
+
|
|
|
+ if (isFresh) {
|
|
|
+ // 应用后台预设值(当 enableContentConfigurable=false 时)
|
|
|
+ if (!merged.enableContentConfigurable) {
|
|
|
+ if (merged.customScenario) merged.scenario = merged.customScenario;
|
|
|
+ merged.timerEnabled = merged.customTimerEnabled;
|
|
|
+ merged.timerDuration = merged.customTimerDuration;
|
|
|
+ if (merged.customHats && merged.customHats.length) merged.hats = merged.customHats;
|
|
|
+ }
|
|
|
+ // 决定初始 stage
|
|
|
+ if (!merged.enableMemberSelect && !merged.enableContentConfigurable) {
|
|
|
+ merged.stage = 'activity';
|
|
|
+ } else if (!merged.enableMemberSelect) {
|
|
|
+ merged.stage = 'setup';
|
|
|
+ } else if (!merged.enableContentConfigurable) {
|
|
|
+ merged.stage = 'member';
|
|
|
+ }
|
|
|
+ // 初始化 UI 状态
|
|
|
+ this.showHowTo = !!merged.autoExpandHowTo;
|
|
|
+ this.setupStep = 0;
|
|
|
+ this.currentHatIdx = 0;
|
|
|
+
|
|
|
+ // 同步父组件保存,避免下次加载重复 isFresh
|
|
|
+ this.$nextTick(() => this.$emit('setTestJson', merged));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.work = merged;
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // —— 成员选择 ——
|
|
|
+ onMemberFilter(query) { this.memberSearchText = query || ''; },
|
|
|
+ onMemberChange(val) { this.work.members = val || []; },
|
|
|
+ confirmMembers() {
|
|
|
+ if (!this.work.members || this.work.members.length === 0) return;
|
|
|
+ if (this.work.enableContentConfigurable) {
|
|
|
+ this.work.stage = 'setup';
|
|
|
+ this.setupStep = 0;
|
|
|
+ } else {
|
|
|
+ this.startActivity();
|
|
|
+ }
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 计时设置 ——
|
|
|
+ focusTimerCustom() {
|
|
|
+ this.timerCustomFocused = true;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const inp = document.querySelector('.shPage .shTimerCustomInput');
|
|
|
+ if (inp) inp.focus();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onTimerCustomBlur() {
|
|
|
+ const v = parseInt(this.work.timerCustomValue, 10);
|
|
|
+ if (!isNaN(v) && v > 0) this.work.timerDuration = v;
|
|
|
+ this.timerCustomFocused = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 帽子 ——
|
|
|
+ toggleHat(key) {
|
|
|
+ const idx = (this.work.hats || []).indexOf(key);
|
|
|
+ if (idx > -1) this.work.hats.splice(idx, 1);
|
|
|
+ else this.work.hats.push(key);
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 活动控制 ——
|
|
|
+ startActivity() {
|
|
|
+ // 确保 hatContent 有初始 key
|
|
|
+ const content = {};
|
|
|
+ this.selectedHats.forEach(k => { content[k] = this.work.hatContent[k] || ''; });
|
|
|
+ this.work.hatContent = content;
|
|
|
+ this.work.stage = 'activity';
|
|
|
+ this.currentHatIdx = 0;
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+ backToSetup() {
|
|
|
+ this.clearCountdown();
|
|
|
+ // 如果后台禁用了设置阶段,跳到 member(如果开了)或保持在 activity
|
|
|
+ if (!this.work.enableContentConfigurable) {
|
|
|
+ if (this.work.enableMemberSelect) this.work.stage = 'member';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.work.stage = 'setup';
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+ prevHat() {
|
|
|
+ if (this.currentHatIdx > 0) this.currentHatIdx--;
|
|
|
+ },
|
|
|
+ nextOrFinish() {
|
|
|
+ if (this.isLastHat) {
|
|
|
+ this.work.stage = 'review';
|
|
|
+ this.clearCountdown();
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ } else {
|
|
|
+ this.currentHatIdx++;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ jumpToHat(idx) {
|
|
|
+ if (idx >= 0 && idx < this.selectedHats.length) this.currentHatIdx = idx;
|
|
|
+ },
|
|
|
+ toggleAutoSwitch() {
|
|
|
+ this.autoSwitch = !this.autoSwitch;
|
|
|
+ if (this.autoSwitch) this.restartCountdown();
|
|
|
+ else this.clearCountdown();
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 倒计时 ——
|
|
|
+ restartCountdown() {
|
|
|
+ this.clearCountdown();
|
|
|
+ if (!this.work.timerEnabled || !this.autoSwitch || this.work.stage !== 'activity') return;
|
|
|
+ if (!this.selectedHats.length) return;
|
|
|
+ this.countdownRemaining = this.work.timerDuration;
|
|
|
+ this._countdownTimer = setInterval(() => {
|
|
|
+ if (this.countdownRemaining > 0) {
|
|
|
+ this.countdownRemaining--;
|
|
|
+ } else {
|
|
|
+ // 时间到 → 自动切换到下一顶
|
|
|
+ clearInterval(this._countdownTimer);
|
|
|
+ this._countdownTimer = null;
|
|
|
+ if (!this.isLastHat) {
|
|
|
+ this.currentHatIdx++;
|
|
|
+ } else {
|
|
|
+ // 最后一顶时间到 → 直接进总结
|
|
|
+ this.work.stage = 'review';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ },
|
|
|
+ clearCountdown() {
|
|
|
+ if (this._countdownTimer) {
|
|
|
+ clearInterval(this._countdownTimer);
|
|
|
+ this._countdownTimer = null;
|
|
|
+ }
|
|
|
+ this.countdownRemaining = 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 总结回顾 ——
|
|
|
+ copyHatContent(hat) {
|
|
|
+ const text = (this.work.hatContent[hat.key] || '').trim();
|
|
|
+ if (!text) return; // 空内容不复制
|
|
|
+ if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
|
+ navigator.clipboard.writeText(text).then(() => {
|
|
|
+ this.$message && this.$message.success(this.lang.ssSHAddedToClipboard);
|
|
|
+ }).catch(() => {
|
|
|
+ this.fallbackCopy(text);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.fallbackCopy(text);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fallbackCopy(text) {
|
|
|
+ const ta = document.createElement('textarea');
|
|
|
+ ta.value = text;
|
|
|
+ document.body.appendChild(ta);
|
|
|
+ ta.select();
|
|
|
+ try { document.execCommand('copy'); this.$message && this.$message.success(this.lang.ssSHAddedToClipboard); } catch (e) { }
|
|
|
+ document.body.removeChild(ta);
|
|
|
+ },
|
|
|
+ backToSetupFromReview() {
|
|
|
+ this.clearCountdown();
|
|
|
+ if (this.work.enableContentConfigurable) {
|
|
|
+ this.work.stage = 'setup';
|
|
|
+ } else if (this.work.enableMemberSelect) {
|
|
|
+ this.work.stage = 'member';
|
|
|
+ } else {
|
|
|
+ // 两个都禁用了 — 回到 activity 第 1 顶
|
|
|
+ this.work.stage = 'activity';
|
|
|
+ this.currentHatIdx = 0;
|
|
|
+ }
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+ restart() {
|
|
|
+ if (this.$confirm) {
|
|
|
+ this.$confirm(this.lang.ssSHRestartConfirm, this.lang.ssSHRestart, {
|
|
|
+ confirmButtonText: this.lang.ssSHRestart,
|
|
|
+ cancelButtonText: this.lang.Cancel,
|
|
|
+ type: 'warning',
|
|
|
+ }).then(() => {
|
|
|
+ this.doRestart();
|
|
|
+ }).catch(() => { });
|
|
|
+ } else {
|
|
|
+ if (confirm(this.lang.ssSHRestartConfirm)) this.doRestart();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ doRestart() {
|
|
|
+ const empty = {};
|
|
|
+ this.selectedHats.forEach(k => { empty[k] = ''; });
|
|
|
+ this.work.hatContent = empty;
|
|
|
+ this.currentHatIdx = 0;
|
|
|
+ this.work.stage = 'activity';
|
|
|
+ this.clearCountdown();
|
|
|
+ this.restartCountdown();
|
|
|
+ this.$emit('setTestJson', this.work);
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 语音输入 ——
|
|
|
+ startRecord(targetKey) {
|
|
|
+ const iframe = this.$refs['iframeRef'];
|
|
|
+ if (!iframe || !iframe.contentWindow) {
|
|
|
+ this.$message.error(this.lang.ssSHRecServiceNotReady);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ if (this.lang.lang === 'com') iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 8;
|
|
|
+ else if (this.lang.lang === 'hk') iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 4;
|
|
|
+ else iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 2;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = (e) => {
|
|
|
+ const privText = e.privText;
|
|
|
+ if (!privText) return;
|
|
|
+ if (targetKey === 'scenario') {
|
|
|
+ this.work.scenario = (this.work.scenario ? this.work.scenario + ' ' : '') + privText;
|
|
|
+ } else {
|
|
|
+ const existing = this.work.hatContent[targetKey] || '';
|
|
|
+ this.$set(this.work.hatContent, targetKey, (existing ? existing + ' ' : '') + privText);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ iframe.contentWindow.ConversationTranscriber();
|
|
|
+ this.recordObj.status = '1';
|
|
|
+ this.recordObj.startTime = new Date().getTime();
|
|
|
+ setTimeout(() => {
|
|
|
+ if (!navigator.permissions) return;
|
|
|
+ navigator.permissions.query({ name: 'microphone' }).then(ps => {
|
|
|
+ if (ps.state !== 'granted') {
|
|
|
+ this.recordObj.status = '0';
|
|
|
+ this.recordObj.loading = false;
|
|
|
+ iframe.contentWindow.onSessionStopped = null;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this.$message.warning(this.lang.ssSHNoPermStop);
|
|
|
+ }
|
|
|
+ }).catch(() => { });
|
|
|
+ }, 2000);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('startRecord error:', err);
|
|
|
+ this.$message.error(this.lang.ssSHRecStartFail);
|
|
|
+ this.recordObj.status = '0';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ stopRecord() {
|
|
|
+ if (new Date().getTime() - this.recordObj.startTime < 2000) {
|
|
|
+ this.$message.info(this.lang.ssSHRecordShort);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const iframe = this.$refs['iframeRef'];
|
|
|
+ if (!iframe || !iframe.contentWindow) {
|
|
|
+ this.recordObj.status = '0';
|
|
|
+ this.recordObj.loading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.recordObj.loading = true;
|
|
|
+ try {
|
|
|
+ iframe.contentWindow.window.document.getElementById('scenarioStopButton').click();
|
|
|
+ } catch (err) {
|
|
|
+ console.error('stop click error:', err);
|
|
|
+ this.recordObj.status = '0';
|
|
|
+ this.recordObj.loading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ iframe.contentWindow.onSessionStopped = () => {
|
|
|
+ this.recordObj.status = '0';
|
|
|
+ this.recordObj.loading = false;
|
|
|
+ this.$message.success(this.lang.ssSHRecStopped);
|
|
|
+ iframe.contentWindow.onSessionStopped = null;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
+ beforeUnmount() {
|
|
|
+ this.clearCountdown();
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* ========= 基础 ========= */
|
|
|
+.shPage {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 1rem;
|
|
|
+ background: #fdf8f0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========= 成员选择 ========= */
|
|
|
+.shMemberStage {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 680px;
|
|
|
+ padding-top: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberCard {
|
|
|
+ width: 100%;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 1rem;
|
|
|
+ padding: 2.5rem 2rem 2rem;
|
|
|
+ text-align: center;
|
|
|
+ box-shadow: 0 4px 20px rgba(245, 166, 35, 0.08);
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberIconWrap {
|
|
|
+ margin-bottom: 1.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberIcon {
|
|
|
+ width: 5rem;
|
|
|
+ height: 5rem;
|
|
|
+ border-radius: 1rem;
|
|
|
+ background: #fff6e5;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberTitle {
|
|
|
+ font-size: 1.5rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin: 0 0 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberSubtitle {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ color: #666;
|
|
|
+ margin: 0 0 2rem;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox {
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 2.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-select {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-input__inner {
|
|
|
+ height: 3rem;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ background: #fafafa;
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ padding-left: 1rem;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-input.is-focus .el-input__inner,
|
|
|
+.shMemberBox>>>.el-input__inner:focus {
|
|
|
+ border-color: #F5A623;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-tag {
|
|
|
+ background: #fff6e5;
|
|
|
+ color: #F5A623;
|
|
|
+ border: none;
|
|
|
+ border-radius: 1rem;
|
|
|
+ height: 1.75rem;
|
|
|
+ line-height: 1.5rem;
|
|
|
+ padding: 0 0.75rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-tag .el-tag__close {
|
|
|
+ color: #F5A623;
|
|
|
+ margin-left: 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberBox>>>.el-tag .el-tag__close:hover {
|
|
|
+ background: #fff0d8;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberConfirmBtn {
|
|
|
+ width: 100%;
|
|
|
+ height: 3.25rem;
|
|
|
+ background: linear-gradient(135deg, #F5A623 0%, #f59e0b 100%);
|
|
|
+ border: none;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ box-shadow: 0 4px 12px rgba(245, 166, 35, 0.25);
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberConfirmBtn:hover:not(.shMemberConfirmDisabled) {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 6px 16px rgba(245, 166, 35, 0.35);
|
|
|
+}
|
|
|
+
|
|
|
+.shMemberConfirmDisabled {
|
|
|
+ background: #f5deb3 !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ cursor: not-allowed !important;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========= 设置内容 ========= */
|
|
|
+.shSetupStage,
|
|
|
+.shActivityStage,
|
|
|
+.shReviewStage {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1000px;
|
|
|
+ padding-top: 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTopTabs {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTab {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ padding: 1rem 1.5rem;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shTabActive {
|
|
|
+ background: linear-gradient(135deg, #F5A623 0%, #f59e0b 100%);
|
|
|
+ color: #fff;
|
|
|
+ box-shadow: 0 4px 12px rgba(245, 166, 35, 0.25);
|
|
|
+}
|
|
|
+
|
|
|
+.shTabDisabled {
|
|
|
+ background: #f0f0f0;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.shTabClickable {
|
|
|
+ background: #fff6e5;
|
|
|
+ color: #F5A623;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.shTabClickable:hover {
|
|
|
+ background: #ffecd1;
|
|
|
+}
|
|
|
+
|
|
|
+.shTabDivider {
|
|
|
+ width: 1.5rem;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shSetupHeader {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shSetupTitle {
|
|
|
+ font-size: 1.5rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin: 0 0 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shSetupDesc {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ color: #666;
|
|
|
+ margin: 0 0 1.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepIndicator {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepDot {
|
|
|
+ width: 0.5rem;
|
|
|
+ height: 0.5rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #ddd;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepDotActive {
|
|
|
+ background: #F5A623;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepDotCurrent {
|
|
|
+ box-shadow: 0 0 0 4px rgba(245, 166, 35, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.shStepLabel {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #999;
|
|
|
+ margin: 0 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepLabelActive {
|
|
|
+ color: #F5A623;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepLine {
|
|
|
+ width: 2rem;
|
|
|
+ height: 1px;
|
|
|
+ background: #ddd;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepCard {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 1rem;
|
|
|
+ padding: 1.75rem 2rem;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.shStepRow {
|
|
|
+ display: flex;
|
|
|
+ gap: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepLeft {
|
|
|
+ flex: 0 0 45%;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepRight {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepTitleRow {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 0.75rem;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepIcon {
|
|
|
+ width: 2.5rem;
|
|
|
+ height: 2.5rem;
|
|
|
+ border-radius: 0.625rem;
|
|
|
+ background: #fff6e5;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepTitle {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin: 0 0 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepSub {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+ margin: 0;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.shExampleLabel {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 0.75rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shExampleList {
|
|
|
+ list-style: none;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0.625rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shExampleItem {
|
|
|
+ background: #fafafa;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ padding: 0.875rem 1rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #555;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.shExampleDot {
|
|
|
+ color: #F5A623;
|
|
|
+ margin-right: 0.5rem;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.shInputTitle {
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.75rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTextareaWrap {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.shTextarea {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 14rem;
|
|
|
+ padding: 1rem 3rem 1rem 1rem;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ background: #fafafa;
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ line-height: 1.6;
|
|
|
+ resize: vertical;
|
|
|
+ outline: none;
|
|
|
+ box-sizing: border-box;
|
|
|
+ font-family: inherit;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shTextarea:focus {
|
|
|
+ border-color: #F5A623;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.shTextarea::placeholder {
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+
|
|
|
+.shMicBtn {
|
|
|
+ position: absolute;
|
|
|
+ right: 0.75rem;
|
|
|
+ bottom: 0.75rem;
|
|
|
+ width: 2.25rem;
|
|
|
+ height: 2.25rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #fff6e5;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #F5A623;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shMicBtn:hover {
|
|
|
+ background: #ffe7c2;
|
|
|
+}
|
|
|
+
|
|
|
+.shMicBtnActive {
|
|
|
+ background: #F5A623;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 计时 */
|
|
|
+.shTimerWrap {
|
|
|
+ padding: 0.5rem 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerTopRow {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shSwitch {
|
|
|
+ width: 3.25rem;
|
|
|
+ height: 1.75rem;
|
|
|
+ border-radius: 1rem;
|
|
|
+ background: #ddd;
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-top: 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shSwitchOn {
|
|
|
+ background: #F5A623;
|
|
|
+}
|
|
|
+
|
|
|
+.shSwitchThumb {
|
|
|
+ width: 1.5rem;
|
|
|
+ height: 1.5rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
|
+ position: absolute;
|
|
|
+ top: 0.125rem;
|
|
|
+ left: 0.125rem;
|
|
|
+ transition: left 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shSwitchOn .shSwitchThumb {
|
|
|
+ left: 1.625rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerQuestion {
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.75rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOpts {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 0.625rem;
|
|
|
+ margin-bottom: 1.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOpt {
|
|
|
+ min-width: 3.5rem;
|
|
|
+ height: 2.25rem;
|
|
|
+ padding: 0 1rem;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ background: #fafafa;
|
|
|
+ color: #555;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOpt:hover {
|
|
|
+ border-color: #F5A623;
|
|
|
+ color: #F5A623;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOptActive {
|
|
|
+ background: #F5A623;
|
|
|
+ border-color: #F5A623;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerCustomInput {
|
|
|
+ width: 5rem;
|
|
|
+ height: 2.25rem;
|
|
|
+ padding: 0 0.75rem;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ border: 1px solid #F5A623;
|
|
|
+ background: #fff;
|
|
|
+ color: #1f1f1f;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ outline: none;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerHint {
|
|
|
+ background: #fff6e5;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1rem;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerHintMain {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerHintSub {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOffHint {
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1.5rem 1rem;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOffMain {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shTimerOffSub {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+/* 帽子选择 */
|
|
|
+.shHatsTitle {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin: 0 0 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatsSub {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+ margin: 0 0 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatsGrid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCard {
|
|
|
+ position: relative;
|
|
|
+ background: #fafafa;
|
|
|
+ border: 2px solid #e8e8e8;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1.5rem 1rem 1rem;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCard:hover {
|
|
|
+ border-color: #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatSelected {
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
+}
|
|
|
+
|
|
|
+.shHatwhiteSel {
|
|
|
+ border-color: #d0d0d0;
|
|
|
+ background: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatredSel {
|
|
|
+ border-color: #fda4af;
|
|
|
+ background: #fef2f2;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatblackSel {
|
|
|
+ border-color: #1f1f1f;
|
|
|
+ background: #cecece;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatyellowSel {
|
|
|
+ border-color: #fcd34d;
|
|
|
+ background: #fffbeb;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatgreenSel {
|
|
|
+ border-color: #86efac;
|
|
|
+ background: #f0fdf4;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatblueSel {
|
|
|
+ border-color: #93c5fd;
|
|
|
+ background: #eff6ff;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatIcon {
|
|
|
+ width: 3.5rem;
|
|
|
+ height: 3.5rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin: 0 auto 0.75rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatblackSel .shHatName {
|
|
|
+ color: #000;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatblackSel .shHatDesc {
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatName {
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDesc {
|
|
|
+ font-size: 0.8125rem;
|
|
|
+ color: #999;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheck {
|
|
|
+ position: absolute;
|
|
|
+ top: 0.5rem;
|
|
|
+ right: 0.5rem;
|
|
|
+ width: 1.5rem;
|
|
|
+ height: 1.5rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckwhite {
|
|
|
+ background: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckred {
|
|
|
+ background: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckblack {
|
|
|
+ background: #fff;
|
|
|
+ color: #1f1f1f !important;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckyellow {
|
|
|
+ background: #eab308;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckgreen {
|
|
|
+ background: #16a34a;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatCheckblue {
|
|
|
+ background: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+/* 底部按钮 */
|
|
|
+.shStepFooter {
|
|
|
+ display: flex;
|
|
|
+ gap: 1rem;
|
|
|
+ margin-top: 1.5rem;
|
|
|
+ padding-top: 1.25rem;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.shStepFooterSingle {
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.shPrimaryBtn {
|
|
|
+ height: 3rem;
|
|
|
+ padding: 0 2.5rem;
|
|
|
+ background: linear-gradient(135deg, #F5A623 0%, #f59e0b 100%);
|
|
|
+ border: none;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ box-shadow: 0 4px 12px rgba(245, 166, 35, 0.25);
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.shPrimaryBtn:hover:not(.shPrimaryBtnDisabled) {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 6px 16px rgba(245, 166, 35, 0.35);
|
|
|
+}
|
|
|
+
|
|
|
+.shPrimaryBtnDisabled {
|
|
|
+ background: #f5deb3 !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ cursor: not-allowed !important;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+.shPrimaryBtnStart {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.shSecondaryBtn {
|
|
|
+ height: 3rem;
|
|
|
+ padding: 0 2rem;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ color: #666;
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.shSecondaryBtn:hover {
|
|
|
+ border-color: #F5A623;
|
|
|
+ color: #F5A623;
|
|
|
+}
|
|
|
+
|
|
|
+.shSecondaryBtnDisabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+}
|
|
|
+
|
|
|
+.shSecondaryBtnDisabled:hover {
|
|
|
+ border-color: #e8e8e8;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========= 活动阶段 ========= */
|
|
|
+.shHeaderCard {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1rem 1.5rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.shHeaderCardTitle {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+}
|
|
|
+
|
|
|
+.shHeaderCardSub {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+ margin-top: 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHeaderCardRight {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.375rem;
|
|
|
+ color: #F5A623;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.kwlArrow {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ transition: transform 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.kwlArrowUp {
|
|
|
+ transform: rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+.shHowToContent {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ padding: 1rem 1.5rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.shHowToList {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHowToItem {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ color: #555;
|
|
|
+ line-height: 1.6;
|
|
|
+ padding: 0.375rem 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shScenarioBadge {
|
|
|
+ display: inline-block;
|
|
|
+ background: #fff6e5;
|
|
|
+ color: #F5A623;
|
|
|
+ padding: 0.375rem 0.875rem;
|
|
|
+ border-radius: 1rem;
|
|
|
+ font-size: 0.8125rem;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.shHowToDesc {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDots {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDot {
|
|
|
+ width: 1rem;
|
|
|
+ height: 1rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px solid #e0e0e0;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotLine {
|
|
|
+ width: 0.75rem;
|
|
|
+ height: 2px;
|
|
|
+ background: #e0e0e0;
|
|
|
+ margin: 0 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotDone {
|
|
|
+ border-color: currentColor;
|
|
|
+ background: currentColor;
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotCurrent {
|
|
|
+ width: 1.25rem;
|
|
|
+ height: 1.25rem;
|
|
|
+ box-shadow: 0 0 0 3px rgba(245, 166, 35, 0.25);
|
|
|
+ transform: scale(1.05);
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotwhite {
|
|
|
+ background: #FFF;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotred {
|
|
|
+ background: #fda4af;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotblack {
|
|
|
+ background: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotyellow {
|
|
|
+ background: #fcd34d;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotgreen {
|
|
|
+ background: #86efac;
|
|
|
+}
|
|
|
+
|
|
|
+.shHatDotblue {
|
|
|
+ background: #93c5fd;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityRow {
|
|
|
+ display: flex;
|
|
|
+ gap: 1rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左:帽子展示卡 */
|
|
|
+.shActivityHatCard {
|
|
|
+ flex: 0 0 45%;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1.5rem;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIcon {
|
|
|
+ width: 6rem;
|
|
|
+ height: 6rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ transition: background 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIconwhite {
|
|
|
+ background: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIconred {
|
|
|
+ background: #fef2f2;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIconblack {
|
|
|
+ background: #1f1f1f;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIconyellow {
|
|
|
+ background: #fffbeb;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIcongreen {
|
|
|
+ background: #f0fdf4;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatIconblue {
|
|
|
+ background: #eff6ff;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatName {
|
|
|
+ font-size: 1.25rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatDesc {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatProgress {
|
|
|
+ font-size: 0.8125rem;
|
|
|
+ color: #bbb;
|
|
|
+ margin-bottom: 1.25rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityHatPrompt {
|
|
|
+ background: #fff6e5;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ padding: 0.875rem 1rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #666;
|
|
|
+ line-height: 1.5;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右:输入卡 */
|
|
|
+.shActivityInputCard {
|
|
|
+ flex: 1;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1.5rem;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.shActivityInputCard .shTextarea {
|
|
|
+ min-height: 11rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shAssignee {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ margin-top: 1rem;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.shAssigneeLabel {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.shAssigneeTag {
|
|
|
+ background: #fff6e5;
|
|
|
+ color: #F5A623;
|
|
|
+ border-radius: 1rem;
|
|
|
+ padding: 0.25rem 0.75rem;
|
|
|
+ font-size: 0.8125rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shAssigneeTagEmpty {
|
|
|
+ color: #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+/* 底部栏 */
|
|
|
+.shActivityFooter {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 1rem;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ padding: 1rem 1.5rem;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.shAutoSwitchBtn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.375rem;
|
|
|
+ background: #fafafa;
|
|
|
+ padding: 0.5rem 0.875rem;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #999;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.shAutoSwitchBtn:hover {
|
|
|
+ border-color: #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.shAutoSwitchOn {
|
|
|
+ background: #fff6e5;
|
|
|
+ color: #F5A623;
|
|
|
+ border-color: #ffe7c2;
|
|
|
+}
|
|
|
+
|
|
|
+.shCountdown {
|
|
|
+ font-size: 1.75rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #F5A623;
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+}
|
|
|
+
|
|
|
+.shFooterBtns {
|
|
|
+ display: flex;
|
|
|
+ gap: 1rem;
|
|
|
+ margin-top: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========= 总结回顾 ========= */
|
|
|
+.shReviewStage {
|
|
|
+ padding-top: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewHeader {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewTitle {
|
|
|
+ font-size: 1.5rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin: 0 0 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewSub {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ color: #666;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewList {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0.75rem;
|
|
|
+ margin-bottom: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewCard {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 1rem;
|
|
|
+ padding: 1rem 1.25rem;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0.75rem;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: 2px solid transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewCard:hover {
|
|
|
+ border-color: #F5A623;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewIcon {
|
|
|
+ width: 2.5rem;
|
|
|
+ height: 2.5rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewInfo {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewName {
|
|
|
+ font-size: 0.9375rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f1f1f;
|
|
|
+ margin-bottom: 0.375rem;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewContent {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.6;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewEmpty {
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+
|
|
|
+.shReviewFooter {
|
|
|
+ display: flex;
|
|
|
+ gap: 1rem;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+
|
|
|
+ .shSetupStage,
|
|
|
+ .shActivityStage,
|
|
|
+ .shReviewStage {
|
|
|
+ padding-top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shStepRow {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shStepLeft {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shActivityRow {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shActivityHatCard,
|
|
|
+ .shActivityInputCard {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shHatsGrid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ .shCountdown {
|
|
|
+ font-size: 1.25rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|