feat(mobile): 重做员工端首页、问答和训练页视觉

统一员工端 tabBar、共享卡片和三大核心页面的视觉基础,同时保持现有移动端业务流程不变。补充 H5 输入兼容、页面结构和训练回滚行为的回归测试。
This commit is contained in:
2026-07-11 01:13:14 +08:00
parent ebbc69eed6
commit ee396d1e7a
19 changed files with 2171 additions and 439 deletions
+299 -109
View File
@@ -1,13 +1,15 @@
<template>
<view class="page practice-page">
<view class="page practice-page employee-page-shell">
<view class="practice-head">
<text class="page-title">场景练</text>
<text class="page-subtitle">按当前场景完成三角色对练,结束后自动评分并写入训练记录。</text>
<text class="employee-kicker practice-eyebrow">数字师傅 · 场景训练</text>
<text class="page-title">场景模拟</text>
<text class="page-subtitle">与业主、老师傅完成一轮真实工作对话,结束后自动写入训练记录。</text>
<text class="practice-tip">先看预习卡,再开始练习;红线命中时按老师傅建议重说本轮。</text>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
<view v-if="!loggedIn" class="panel login-panel">
<view v-if="!loggedIn" class="panel login-panel employee-card">
<view>
<text class="panel-title">需要登录</text>
<text class="muted block">登录后查看今日任务、开始练习并保存训练记录。</text>
@@ -15,41 +17,12 @@
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<view v-if="loggedIn" class="panel assignment-panel">
<view class="assignment-head">
<text class="panel-title">今日任务</text>
<button class="ghost-button refresh-button" :disabled="assignmentLoading" @click="loadAssignments">刷新</button>
<view v-if="loggedIn" class="panel scenario-card employee-card">
<view class="scenario-headline">
<text class="scenario-badge">今日小训</text>
<text class="scenario-title">{{ activeScenarioName }}</text>
<text class="scenario-desc">{{ scenarioDescription }}</text>
</view>
<view v-if="assignments.length" class="assignment-list">
<view v-for="item in assignments" :key="item.id" class="assignment-row" :class="{ highlighted: highlightedAssignmentId === item.id }">
<view class="assignment-copy">
<text class="assignment-badge">{{ item.source === 'daily' ? '每日三题' : '主管专项' }}</text>
<text class="assignment-title">{{ item.question || item.scenarioName }}</text>
<text v-if="assignmentDesc(item)" class="assignment-desc">{{ assignmentDesc(item) }}</text>
<text v-if="item.status === '已完成' && item.referenceAnswer" class="assignment-reference">参考:{{ item.referenceAnswer }}</text>
</view>
<view v-if="item.source === 'daily'" class="daily-answer">
<textarea
v-model="drillAnswers[item.id]"
class="textarea daily-textarea"
maxlength="220"
:disabled="item.status === '已完成'"
placeholder="写一句你的处理话术"
/>
<button class="primary-button small-button" :disabled="Boolean(answeringId) || item.status === '已完成'" @click="submitDaily(item)">
{{ item.status === '已完成' ? '已完成' : '提交' }}
</button>
</view>
<button v-else class="secondary-button small-button" :disabled="!canStartPractice" @click="startAssignment(item)">开始专项</button>
</view>
</view>
<view v-else class="empty">暂无今日任务</view>
</view>
<view v-if="loggedIn" class="panel scenario-card">
<text class="scenario-badge">今日小训</text>
<text class="scenario-title">{{ activeScenarioName }}</text>
<text class="scenario-desc">{{ scenarioDescription }}</text>
<view v-if="prepCard" class="prep-card">
<view class="prep-head">
<text>训练前预习卡</text>
@@ -78,9 +51,43 @@
</button>
</view>
<view v-if="turns.length" class="panel dialogue-card">
<view v-if="loggedIn" class="panel assignment-panel">
<view class="assignment-head">
<text class="panel-title">今日任务</text>
<button class="ghost-button refresh-button" :disabled="assignmentLoading" @click="loadAssignments">刷新</button>
</view>
<view v-if="assignments.length" class="assignment-list">
<view v-for="item in assignments" :key="item.id" class="assignment-row" :class="{ highlighted: highlightedAssignmentId === item.id }">
<view class="assignment-copy">
<text class="assignment-badge" :class="{ daily: item.source === 'daily' }">{{ item.source === 'daily' ? '每日三题' : '主管专项' }}</text>
<text class="assignment-title">{{ item.question || item.scenarioName }}</text>
<text v-if="assignmentDesc(item)" class="assignment-desc">{{ assignmentDesc(item) }}</text>
<text v-if="item.status === '已完成' && item.referenceAnswer" class="assignment-reference">参考:{{ item.referenceAnswer }}</text>
</view>
<view v-if="item.source === 'daily'" class="daily-answer">
<textarea
v-model="drillAnswers[item.id]"
class="textarea daily-textarea"
maxlength="220"
:disabled="item.status === '已完成'"
placeholder="写一句你的处理话术"
/>
<button class="primary-button small-button" :disabled="Boolean(answeringId) || item.status === '已完成'" @click="submitDaily(item)">
{{ item.status === '已完成' ? '已完成' : '提交' }}
</button>
</view>
<button v-else class="secondary-button small-button" :disabled="!canStartPractice" @click="startAssignment(item)">开始专项</button>
</view>
</view>
<view v-else class="empty">暂无今日任务</view>
</view>
<view v-if="turns.length" class="panel dialogue-card employee-card">
<view class="dialogue-head">
<text class="panel-title">对话</text>
<view class="dialogue-heading-block">
<text class="panel-title">对话</text>
<text class="dialogue-caption">保持承接情绪、说明动作、给出反馈节点。</text>
</view>
<text class="round-label">第 {{ Math.min(roundIndex + 1, 3) }} 轮</text>
</view>
<view v-if="hasCoachState" class="coach-state">
@@ -117,16 +124,16 @@
</view>
</view>
<view v-if="status === 'active' || status === 'submitting'" class="panel reply-card">
<view v-if="status === 'active' || status === 'submitting'" class="panel reply-card employee-card">
<text class="panel-title">你的回应</text>
<textarea
v-model="draft"
class="textarea"
class="textarea practice-textarea"
maxlength="500"
placeholder="先承接业主情绪,再说明处理动作和反馈时间。"
/>
<view class="reply-actions">
<button class="secondary-button" :disabled="busy || transcribing" @click="toggleRecording">
<button class="secondary-button" :class="{ 'recording-active': recording }" :disabled="busy || transcribing" @click="toggleRecording">
{{ recording ? '停止录音' : '录音转写' }}
</button>
<button class="ghost-button" :disabled="busy || recording || transcribing" @click="pickAudio">
@@ -143,7 +150,7 @@
<text v-if="draftAudioUrl" class="voice-state">已附带语音回放</text>
</view>
<view v-if="helpAnswer || helpRefs.length" class="panel help-card">
<view v-if="helpAnswer || helpRefs.length" class="panel help-card employee-card">
<text class="panel-title">SOP 提示</text>
<text v-if="helpAnswer" class="help-answer">{{ helpAnswer }}</text>
<view v-if="helpRefs.length" class="help-refs">
@@ -151,7 +158,7 @@
</view>
</view>
<view v-if="result" class="panel result-card">
<view v-if="result" class="panel result-card employee-card">
<text class="result-label">本次评分</text>
<text class="result-score">{{ result.totalScore }}</text>
<text class="result-comment">{{ result.aiComment }}</text>
@@ -625,6 +632,7 @@ const submit = async () => {
message.value = '';
const audioUrl = draftAudioUrl.value || undefined;
const audioOssId = draftAudioOssId.value;
const previousTurns = turns.value.slice();
draft.value = '';
clearDraftAudio();
const retryingRedFlag = redFlagActive.value && lastCoachRoundIndex.value === roundIndex.value;
@@ -659,6 +667,7 @@ const submit = async () => {
status.value = 'active';
} catch (error) {
status.value = 'active';
turns.value = previousTurns;
draft.value = text;
draftAudioUrl.value = audioUrl || '';
draftAudioOssId.value = audioOssId;
@@ -704,21 +713,114 @@ onShow(() => {
<style scoped>
.practice-page {
padding-bottom: 92px;
background: #f4f7fb;
padding: 0 18px 96px;
background: linear-gradient(180deg, var(--employee-bg-soft) 0%, var(--employee-bg) 100%);
}
.practice-head {
padding-top: 8px;
position: relative;
margin: 0 -18px;
padding: 30px 28px 24px;
overflow: hidden;
background: linear-gradient(180deg, #eef4ff 0%, #f5f8fd 100%);
}
.practice-head::after {
position: absolute;
right: 28px;
bottom: 24px;
width: 56px;
height: 3px;
background: #e70014;
content: '';
}
.practice-eyebrow {
display: inline-flex;
margin-bottom: 12px;
}
.practice-head .page-title {
color: #1f2b3d;
font-size: 31px;
line-height: 1.12;
}
.practice-head .page-subtitle {
max-width: 274px;
margin-top: 10px;
color: #607086;
font-size: 14px;
line-height: 1.55;
}
.practice-tip {
display: block;
max-width: 298px;
margin-top: 14px;
color: #315b8f;
font-size: 12px;
line-height: 1.5;
}
.login-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-top: 22px;
border-left: 4px solid #e70014;
box-shadow: var(--employee-card-shadow);
}
.login-panel .muted {
display: block;
max-width: 218px;
margin-top: 6px;
font-size: 13px;
line-height: 1.5;
}
.login-panel .login-button {
min-width: 84px;
min-height: 46px;
padding: 0 12px;
}
.scenario-card,
.dialogue-card,
.reply-card,
.help-card,
.result-card {
display: flex;
flex-direction: column;
gap: 12px;
gap: 14px;
}
.scenario-card {
margin-top: 22px;
padding: 20px;
border-color: #d7e5dc;
border-left: 4px solid #168243;
background: #ffffff;
box-shadow: var(--employee-card-shadow);
}
.scenario-headline {
display: flex;
flex-direction: column;
gap: 10px;
}
.scenario-card .primary-button {
width: 100%;
margin-top: 2px;
background: #168243;
box-shadow: 0 7px 14px rgba(22, 130, 67, 0.2);
}
.scenario-card .primary-button:active {
background: #106b35;
}
.assignment-head,
@@ -734,39 +836,71 @@ onShow(() => {
min-width: 82px;
}
.assignment-panel {
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
}
.assignment-head {
margin-top: 26px;
}
.assignment-head .panel-title {
color: #25282d;
font-size: 20px;
}
.assignment-head .refresh-button {
min-height: 36px;
padding: 0 12px;
border: 1px solid #dedee1;
border-radius: 18px;
background: #ffffff;
color: #66696f;
font-size: 13px;
}
.assignment-list {
margin-top: 12px;
margin-top: 14px;
}
.assignment-row {
margin-top: 10px;
padding: 12px;
border: 1px solid transparent;
border-radius: 14px;
background: #f8fafc;
margin-top: 12px;
padding: 16px;
border: 1px solid #deebff;
border-radius: 20px;
background: #ffffff;
box-shadow: 0 10px 26px rgba(47, 107, 255, 0.08);
}
.assignment-row.highlighted {
border-color: #2563eb;
background: #eff6ff;
border-color: #e70014;
background: #fff7f7;
}
.assignment-badge {
display: inline-flex;
width: fit-content;
padding: 4px 8px;
border-radius: 999px;
background: #e0f2fe;
color: #0369a1;
border-radius: 5px;
background: #fff0f1;
color: #bd0010;
font-size: 12px;
font-weight: 800;
}
.assignment-badge.daily {
background: #fff5df;
color: #a86100;
}
.assignment-title {
display: block;
margin-top: 8px;
color: #0f172a;
font-size: 15px;
color: #25282d;
font-size: 16px;
font-weight: 900;
line-height: 1.45;
}
@@ -775,7 +909,7 @@ onShow(() => {
.assignment-reference {
display: block;
margin-top: 6px;
color: #64748b;
color: #6d7076;
font-size: 13px;
line-height: 1.45;
}
@@ -792,21 +926,21 @@ onShow(() => {
.scenario-badge {
width: fit-content;
padding: 5px 10px;
border-radius: 999px;
background: #dbeafe;
color: #1d4ed8;
border-radius: 5px;
background: #edf7f0;
color: #168243;
font-size: 13px;
font-weight: 900;
}
.scenario-title {
color: #0f172a;
font-size: 24px;
color: #25282d;
font-size: 25px;
font-weight: 900;
}
.scenario-desc {
color: #64748b;
color: #696c72;
font-size: 15px;
line-height: 1.55;
}
@@ -814,10 +948,10 @@ onShow(() => {
.prep-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
border-radius: 14px;
background: #f8fafc;
gap: 12px;
padding: 14px 0 0;
border-top: 1px solid #dfe8e2;
background: transparent;
}
.prep-head {
@@ -829,13 +963,13 @@ onShow(() => {
.prep-head text:first-child,
.prep-label {
color: #0f172a;
color: #34373c;
font-size: 13px;
font-weight: 900;
}
.prep-head text:last-child {
color: #94a3b8;
color: #898c92;
font-size: 12px;
}
@@ -846,7 +980,7 @@ onShow(() => {
}
.prep-line {
color: #475569;
color: #5e6167;
font-size: 13px;
line-height: 1.45;
}
@@ -858,26 +992,53 @@ onShow(() => {
gap: 10px;
}
.dialogue-head {
justify-content: space-between;
}
.dialogue-heading-block {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
gap: 6px;
}
.dialogue-caption {
color: #6d7a8d;
font-size: 12px;
line-height: 1.45;
}
.dialogue-card {
border-color: #e1e2e5;
box-shadow: var(--employee-card-shadow);
}
.reply-actions {
flex-wrap: wrap;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.reply-actions button {
min-height: 48px;
min-width: 92px;
flex: 1;
min-width: 0;
padding: 0 8px;
white-space: nowrap;
word-break: keep-all;
}
.reply-actions .primary-button {
grid-column: span 2;
}
.voice-state {
color: #2563eb;
color: #168243;
font-size: 13px;
font-weight: 800;
}
.round-label {
color: #64748b;
color: #6d7076;
font-size: 13px;
}
@@ -893,7 +1054,7 @@ onShow(() => {
align-items: center;
grid-template-columns: 72px 1fr;
gap: 10px;
color: #475569;
color: #5f6268;
font-size: 13px;
font-weight: 800;
}
@@ -901,28 +1062,30 @@ onShow(() => {
.state-bar {
height: 8px;
overflow: hidden;
border-radius: 999px;
background: #e2e8f0;
border-radius: 4px;
background: #e3e4e6;
}
.state-fill {
height: 100%;
border-radius: 999px;
transition: width 0.3s ease-in-out;
}
.emotion-fill {
background: #f97316;
background: #d98200;
}
.trust-fill {
background: #16a34a;
background: #168243;
}
.red-flag {
padding: 9px 10px;
border-radius: 12px;
background: #fef2f2;
color: #b91c1c;
border-left: 3px solid #e70014;
border-radius: 4px;
background: #fff2f3;
color: #bd0010;
font-size: 13px;
font-weight: 800;
line-height: 1.45;
@@ -939,27 +1102,42 @@ onShow(() => {
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
border-radius: 14px;
background: #f8fafc;
padding: 14px;
border: 1px solid #e5edf7;
border-left: 3px solid #9ca0a6;
border-radius: 18px;
background: #f8fbff;
}
.turn-row.customer {
border-left-color: #315b8f;
background: #f0f4f8;
}
.turn-row.trainee {
background: #eff6ff;
border-left-color: #e70014;
background: #fff6f6;
}
.turn-row.coach {
background: #fff7ed;
border-left-color: #168243;
background: #edf7f0;
}
.reply-actions button.recording-active {
border-color: #e70014;
background: #fff5f5;
color: #e70014;
}
.turn-role {
color: #334155;
color: #42454b;
font-size: 13px;
font-weight: 900;
}
.turn-text {
color: #172033;
color: #282b30;
font-size: 15px;
line-height: 1.55;
}
@@ -973,14 +1151,25 @@ onShow(() => {
.style-chip {
width: auto;
border-color: #c9d9ce;
background: #ffffff;
color: #168243;
}
.reply-actions button {
flex: 1;
.reply-card {
border-top: 0;
box-shadow: var(--employee-card-shadow);
}
.practice-textarea {
min-height: 124px;
border-color: #d6deea;
border-radius: 18px;
background: #f9fbff;
}
.help-answer {
color: #334155;
color: #45484e;
font-size: 15px;
line-height: 1.6;
}
@@ -993,21 +1182,21 @@ onShow(() => {
.help-ref {
padding: 5px 8px;
border-radius: 999px;
background: #e0f2fe;
color: #0369a1;
border-radius: 5px;
background: #edf7f0;
color: #168243;
font-size: 12px;
font-weight: 800;
}
.result-label {
color: #64748b;
color: #6d7076;
font-size: 14px;
font-weight: 800;
}
.result-score {
color: #1d4ed8;
color: #2f6bff;
font-size: 44px;
font-weight: 900;
line-height: 1;
@@ -1015,7 +1204,7 @@ onShow(() => {
.result-comment,
.mentor-rewrite text:last-child {
color: #334155;
color: #45484e;
font-size: 15px;
line-height: 1.55;
}
@@ -1025,12 +1214,13 @@ onShow(() => {
flex-direction: column;
gap: 6px;
padding: 12px;
border-radius: 12px;
background: #f8fafc;
border-left: 3px solid #d98200;
border-radius: 6px;
background: #fff9ed;
}
.mentor-rewrite text:first-child {
color: #0f172a;
color: #855000;
font-weight: 900;
}
</style>
+534 -60
View File
@@ -1,27 +1,32 @@
<template>
<view class="page sop-page">
<text class="page-title">问师傅</text>
<text class="page-subtitle">直接问工作中的 SOP、话术和流程。答案来自知识库检索,引用在后面。</text>
<view class="page sop-page employee-page-shell">
<view class="sop-head">
<text class="employee-kicker sop-eyebrow">SOP · 话术 · 工作流程</text>
<text class="page-title">问师傅</text>
<text class="page-subtitle">直接问工作中的 SOP、话术和流程。答案附带可追溯的知识库依据。</text>
<text class="toolbar-tip">先把问题问清楚,再按需生成总结卡,不伪造分类能力。</text>
</view>
<view class="panel query-panel">
<view class="panel query-panel employee-card">
<textarea
v-model="query"
class="textarea"
class="textarea sop-query-textarea"
maxlength="200"
:placeholder="quickQuestionPlaceholder"
/>
<text class="query-caption">常见问题可以直接点下面快捷问法,结果会保留引用依据。</text>
<view class="chip-row">
<button v-for="item in quickQuestions" :key="item" class="chip" :disabled="loading" @click="useQuickQuestion(item)">
{{ item }}
</button>
</view>
<button class="primary-button" :disabled="loading" @click="search">
{{ loading ? '检索中' : '查询' }}
<button class="primary-button query-submit" :disabled="loading" @click="search">
{{ loading ? '检索中' : '问老师傅' }}
</button>
<text v-if="message" class="status-message">{{ message }}</text>
</view>
<view v-if="!loggedIn" class="panel login-panel">
<view v-if="!loggedIn" class="panel login-panel employee-card">
<view>
<text class="panel-title">需要登录</text>
<text class="muted block">登录后才能查询 SOP、生成总结卡并反馈答案。</text>
@@ -29,7 +34,7 @@
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<view v-if="result" class="panel answer-panel">
<view v-if="result" class="panel answer-panel employee-card">
<text class="panel-title">直给答案</text>
<text class="answer-text">{{ result.answer || '未生成直接答案,请查看下方引用。' }}</text>
@@ -54,16 +59,16 @@
</button>
<button
class="secondary-button"
:class="{ active: feedbackVerdict === 'down' }"
:class="{ active: feedbackVerdict === 'down', negative: feedbackVerdict === 'down' }"
:disabled="feedbackSubmitting || Boolean(feedbackVerdict)"
@click="feedback('down')"
>
不准
需要复核
</button>
</view>
</view>
<view v-if="result?.snippets?.length" class="panel">
<view v-if="result?.snippets?.length" class="panel sources-panel">
<text class="panel-title">引用依据</text>
<view v-for="(snippet, index) in result.snippets" :key="`${snippet.docTitle}-${index}`" class="snippet">
<text class="snippet-title">{{ snippet.docTitle }}</text>
@@ -72,35 +77,81 @@
</view>
</view>
<view v-if="result" class="panel summary-panel">
<view v-if="result" class="panel summary-panel employee-card">
<text class="panel-title">总结卡</text>
<text class="muted">需要时再生成,避免每次查询都调用模型。</text>
<text class="muted summary-copy">需要时再生成,适合把一段 SOP 整理成步骤卡、话术卡和提醒卡。</text>
<button class="ghost-button" :disabled="summaryLoading" @click="buildSummaryCard">
{{ summaryLoading ? '生成中' : '生成总结卡' }}
</button>
</view>
<view v-if="summaryCard" class="panel">
<text class="panel-title">{{ summaryCard.title }}</text>
<view class="summary-block">
<text class="summary-heading">步骤</text>
<text v-for="(step, index) in summaryCard.steps" :key="`${step.label}-${index}`" class="summary-line">
{{ index + 1 }}. {{ step.label }}: {{ step.desc }}
</text>
</view>
<view v-if="summaryCard.scripts.length" class="summary-block">
<text class="summary-heading">可说话术</text>
<text v-for="item in summaryCard.scripts" :key="item" class="summary-line">{{ item }}</text>
</view>
<view v-if="summaryCard.objections.length" class="summary-block">
<text class="summary-heading">异议回应</text>
<text v-for="item in summaryCard.objections" :key="item.concern" class="summary-line">
{{ item.concern }}: {{ item.response }}
</text>
</view>
<view v-if="summaryCard.reminders.length" class="summary-block">
<text class="summary-heading">提醒</text>
<text v-for="item in summaryCard.reminders" :key="item" class="summary-line">{{ item }}</text>
<view v-if="summaryCard" class="modal-mask" @click="closeSummary">
<view class="modal-card" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ summaryCard.title }}</text>
<button class="modal-close" @click="closeSummary">×</button>
</view>
<scroll-view scroll-y class="modal-body">
<view v-if="summaryCard.steps?.length" class="info-section">
<view class="section-title-row">
<text class="badge-num">1</text>
<text class="info-section-title">核心步骤</text>
</view>
<view class="timeline">
<view v-for="(step, index) in summaryCard.steps" :key="index" class="timeline-item">
<view class="timeline-node">{{ index + 1 }}</view>
<view class="timeline-content">
<text class="timeline-label">{{ step.label }}</text>
<text class="timeline-desc">{{ step.desc }}</text>
</view>
</view>
</view>
</view>
<view v-if="summaryCard.scripts?.length" class="info-section speech-section">
<view class="section-title-row">
<text class="badge-num">2</text>
<text class="info-section-title">推荐话术</text>
</view>
<view class="speech-bubbles">
<view v-for="(script, index) in summaryCard.scripts" :key="index" class="speech-bubble">
<text class="quote-mark">“</text>
<text class="speech-text">{{ script }}</text>
</view>
</view>
</view>
<view v-if="summaryCard.objections?.length" class="info-section objection-section">
<view class="section-title-row">
<text class="badge-num">3</text>
<text class="info-section-title">常见异议与回应</text>
</view>
<view class="objection-list">
<view v-for="(obj, index) in summaryCard.objections" :key="index" class="objection-card">
<view class="objection-q">
<text class="q-badge">问</text>
<text class="q-text">{{ obj.concern }}</text>
</view>
<view class="objection-a">
<text class="a-badge">答</text>
<text class="a-text">{{ obj.response }}</text>
</view>
</view>
</view>
</view>
<view v-if="summaryCard.reminders?.length" class="info-section reminder-section">
<view class="section-title-row">
<text class="badge-num">4</text>
<text class="info-section-title">师傅提醒</text>
</view>
<view class="reminder-list">
<view v-for="(rem, index) in summaryCard.reminders" :key="index" class="reminder-item">
<text class="reminder-text">{{ rem }}</text>
</view>
</view>
</view>
</scroll-view>
<view class="modal-footer">
<button class="primary-button footer-btn" @click="closeSummary">我知道了</button>
</view>
</view>
</view>
</view>
@@ -259,6 +310,10 @@ const buildSummaryCard = async () => {
}
};
const closeSummary = () => {
summaryCard.value = null;
};
const goLogin = () => {
rememberLoginRedirect('/pages/user/sop/index');
uni.navigateTo({ url: '/pages/auth/login/index' });
@@ -297,7 +352,53 @@ onShow(() => {
<style scoped>
.sop-page {
padding-bottom: 48px;
padding: 0 18px 96px;
background: linear-gradient(180deg, var(--employee-bg-soft) 0%, var(--employee-bg) 100%);
}
.sop-head {
position: relative;
margin: 0 -18px;
padding: 30px 28px 24px;
background: linear-gradient(180deg, #eef4ff 0%, #f5f8fd 100%);
}
.sop-head::after {
position: absolute;
right: 28px;
bottom: 24px;
width: 56px;
height: 3px;
background: #e70014;
content: '';
}
.sop-eyebrow {
display: inline-flex;
margin-bottom: 12px;
}
.sop-head .page-title {
color: #1f2b3d;
font-size: 31px;
line-height: 1.12;
}
.sop-head .page-subtitle {
max-width: 292px;
margin-top: 10px;
color: #607086;
font-size: 14px;
line-height: 1.55;
}
.toolbar-tip {
display: block;
max-width: 286px;
margin-top: 14px;
color: #315b8f;
font-size: 12px;
line-height: 1.5;
}
.query-panel,
@@ -308,30 +409,103 @@ onShow(() => {
gap: 14px;
}
.query-panel {
margin-top: 22px;
padding: 20px;
border-top: 0;
box-shadow: var(--employee-card-shadow);
}
.sop-query-textarea {
min-height: 112px;
border-color: #d6deea;
background: #f9fbff;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.query-caption {
color: #6f7b8f;
font-size: 12px;
line-height: 1.5;
}
.query-panel .textarea {
min-height: 104px;
height: 104px;
border-color: #d9dade;
border-radius: 7px;
background: #fbfbfb;
color: #25282d;
}
.query-panel .chip-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 7px;
}
.query-panel .chip {
min-height: 38px;
padding: 0 10px;
border-color: #dbe5f4;
border-radius: 999px;
background: #f7faff;
color: #47607f;
font-size: 13px;
white-space: nowrap;
}
.query-submit {
width: 100%;
margin-top: 2px;
min-height: 50px;
border-radius: 18px;
background: linear-gradient(135deg, #2f6bff, #5a8cff);
box-shadow: 0 14px 28px rgba(47, 107, 255, 0.22);
}
.login-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
gap: 14px;
margin-top: 22px;
border-left: 4px solid #e70014;
box-shadow: var(--employee-card-shadow);
}
.block {
display: block;
margin-top: 8px;
font-size: 14px;
font-size: 13px;
line-height: 1.5;
}
.login-button {
min-width: 84px;
width: 96px;
min-width: 96px;
flex: 0 0 96px;
padding: 0 8px;
white-space: nowrap;
}
.answer-text {
color: #0f172a;
color: #243246;
font-size: 18px;
font-weight: 700;
line-height: 1.65;
}
.answer-panel {
border-color: #d9e7de;
border-left: 4px solid #168243;
box-shadow: var(--employee-card-shadow);
}
.answer-panel .panel-title {
color: #168243;
}
.keypoints {
display: flex;
flex-direction: column;
@@ -340,9 +514,10 @@ onShow(() => {
.keypoint {
padding: 10px 12px;
border-radius: 10px;
background: #f8fafc;
color: #334155;
border-left: 3px solid #168243;
border-radius: 5px;
background: #f2f8f4;
color: #45484e;
font-size: 15px;
line-height: 1.5;
}
@@ -352,9 +527,10 @@ onShow(() => {
flex-direction: column;
gap: 6px;
padding: 10px 12px;
border-radius: 10px;
background: #fff7ed;
color: #9a3412;
border-left: 3px solid #d98200;
border-radius: 5px;
background: #fff8e9;
color: #855000;
font-size: 13px;
line-height: 1.45;
}
@@ -366,9 +542,29 @@ onShow(() => {
}
.feedback-actions .active {
border-color: #16a34a;
background: #dcfce7;
color: #166534;
border-color: #168243;
background: #edf7f0;
color: #168243;
}
.feedback-actions .negative {
border-color: #e70014;
background: #fff1f2;
color: #bd0010;
}
.sources-panel {
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
}
.sources-panel .panel-title {
display: block;
margin-top: 26px;
color: #25282d;
font-size: 20px;
}
.snippet {
@@ -376,24 +572,26 @@ onShow(() => {
margin-top: 12px;
flex-direction: column;
gap: 6px;
padding: 12px;
border-radius: 12px;
background: #f8fafc;
padding: 14px;
border: 1px solid #e3e4e7;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 5px 14px rgba(37, 40, 45, 0.04);
}
.snippet-title {
color: #0f172a;
color: #34373c;
font-weight: 800;
}
.snippet-content {
color: #475569;
color: #60636a;
font-size: 14px;
line-height: 1.55;
}
.snippet-id {
color: #94a3b8;
color: #96999f;
font-size: 12px;
}
@@ -404,14 +602,290 @@ onShow(() => {
gap: 8px;
}
.summary-heading {
color: #1d4ed8;
.summary-panel {
border-color: #eadfbd;
background: #fffdf7;
box-shadow: var(--employee-card-shadow);
}
.summary-panel .muted {
font-size: 13px;
line-height: 1.5;
}
.summary-copy {
max-width: 278px;
}
.summary-panel .ghost-button {
min-height: 48px;
border: 1px solid #d7e1f5;
border-radius: 18px;
background: #f6f9ff;
color: #315b8f;
}
/* Modal mask overlay */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
padding: 20px;
}
.modal-card {
width: 100%;
max-width: 400px;
max-height: 80vh;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
overflow: hidden;
border-top: 5px solid #168243;
}
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #f1f5f9;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-size: 18px;
font-weight: 900;
color: #0f172a;
}
.modal-close {
background: transparent;
border: none;
font-size: 24px;
color: #94a3b8;
cursor: pointer;
padding: 0;
line-height: 1;
width: auto;
min-height: auto;
}
.modal-body {
flex: 1;
padding: 20px;
overflow-y: auto;
max-height: 55vh;
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #f1f5f9;
}
.footer-btn {
width: 100%;
background: #168243;
color: #ffffff;
}
/* Infographic Sections */
.info-section {
margin-bottom: 24px;
}
.section-title-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
}
.badge-num {
width: 20px;
height: 20px;
background: #168243;
color: #ffffff;
border-radius: 50%;
font-size: 11px;
font-weight: 800;
display: flex;
align-items: center;
justify-content: center;
}
.info-section-title {
font-size: 15px;
font-weight: 900;
color: #1e293b;
}
/* Steps Timeline */
.timeline {
position: relative;
padding-left: 14px;
}
.timeline::before {
content: '';
position: absolute;
left: 5px;
top: 6px;
bottom: 6px;
width: 2px;
background: #e2e8f0;
}
.timeline-item {
position: relative;
padding-bottom: 16px;
display: flex;
gap: 12px;
}
.timeline-item:last-child {
padding-bottom: 0;
}
.timeline-node {
position: absolute;
left: -14px;
width: 12px;
height: 12px;
background: #168243;
color: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 8px;
font-weight: 800;
}
.summary-line {
.timeline-content {
flex: 1;
}
.timeline-label {
display: block;
font-size: 14px;
font-weight: 800;
color: #334155;
font-size: 15px;
line-height: 1.55;
margin-bottom: 4px;
}
.timeline-desc {
display: block;
font-size: 12px;
color: #64748b;
line-height: 1.45;
}
/* Speech bubbles/scripts */
.speech-bubble {
position: relative;
background: #f2f8f4;
border-left: 3px solid #168243;
border-radius: 8px;
padding: 12px 14px;
margin-bottom: 10px;
}
.quote-mark {
position: absolute;
left: 6px;
top: -2px;
font-size: 24px;
color: rgba(22, 130, 67, 0.15);
font-family: Georgia, serif;
line-height: 1;
}
.speech-text {
font-size: 13.5px;
color: #1e293b;
line-height: 1.5;
position: relative;
z-index: 1;
}
/* Objections Q&A */
.objection-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
}
.objection-q,
.objection-a {
display: flex;
gap: 8px;
}
.objection-q {
margin-bottom: 8px;
}
.q-badge,
.a-badge {
flex: 0 0 18px;
height: 18px;
font-size: 10px;
font-weight: 800;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.q-badge {
background: #fee2e2;
color: #b91c1c;
}
.a-badge {
background: #dcfce7;
color: #166534;
}
.q-text {
font-size: 13px;
font-weight: 800;
color: #334155;
line-height: 1.4;
}
.a-text {
font-size: 13px;
color: #475569;
line-height: 1.45;
}
/* Reminders alert box */
.reminder-item {
display: flex;
gap: 8px;
background: #fffbeb;
border-radius: 8px;
padding: 10px 12px;
margin-bottom: 8px;
align-items: flex-start;
border-left: 3px solid #d97706;
}
.reminder-text {
font-size: 13px;
color: #78350f;
line-height: 1.45;
}
</style>
+367 -199
View File
@@ -1,9 +1,12 @@
<template>
<view class="page today-page">
<view class="page today-page employee-page-shell">
<view v-if="message" class="status-message">{{ message }}</view>
<view class="mentor-hero">
<image class="hero-scene" src="/static/mentor.png" mode="aspectFill" />
<view class="hero-scene-scrim" />
<view class="hero-copy">
<text class="employee-kicker hero-kicker">数字师傅 · 今日带练</text>
<text class="hero-role">{{ positionProfile.value }}</text>
<text class="hero-title">{{ positionProfile.heroTitle }}</text>
<text class="hero-sub">有问题,随时问老师傅</text>
@@ -16,8 +19,13 @@
<view class="voice-panel">
<button class="voice-primary" @click="goSop">
<text class="voice-mark">问</text>
<text>问老师傅</text>
<view class="voice-emblem">
<text class="voice-mark">问</text>
</view>
<view class="voice-copy">
<text>问老师傅</text>
<text>语音或文字都可以</text>
</view>
</button>
<button class="voice-secondary" @click="goSop">
<text class="keyboard-mark">键</text>
@@ -33,7 +41,7 @@
</view>
</view>
<view class="section-card login-section" v-if="!loggedIn">
<view class="section-card employee-card login-section" v-if="!loggedIn">
<view>
<text class="login-title">登录后查看今日安排</text>
<text class="login-desc">训练任务、复习提醒和成长数据会按你的手机号展示。</text>
@@ -41,11 +49,14 @@
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<view class="section-card" v-else-if="hasSchedule">
<view class="section-card employee-card" v-else-if="hasSchedule">
<view class="section-head">
<view class="section-title">
<text class="section-icon">日</text>
<text>今日安排</text>
<view class="section-heading-block">
<view class="section-title">
<text class="section-icon">日</text>
<text>今日安排</text>
</view>
<text class="section-subtitle">先完成训练,再补一轮复习,今天的学习节奏就完整了。</text>
</view>
<button class="section-link" @click="openTool('训练')">查看全部</button>
</view>
@@ -69,11 +80,14 @@
</view>
</view>
<view v-if="loggedIn" class="section-card">
<view v-if="loggedIn" class="section-card employee-card">
<view class="section-head">
<view class="section-title">
<text class="section-icon">图</text>
<text>{{ loggedIn ? '我的地图' : '常用入口' }}</text>
<view class="section-heading-block">
<view class="section-title">
<text class="section-icon">图</text>
<text>{{ loggedIn ? '我的地图' : '常用入口' }}</text>
</view>
<text class="section-subtitle">把今天最常用的问、练、学、报都放在手边。</text>
</view>
</view>
<view v-if="mapTiles.length" class="map-grid">
@@ -84,7 +98,7 @@
:class="tile.tone"
@click="openTool(tile.title)"
>
<image class="map-icon" :src="tile.icon" mode="aspectFit" />
<text class="map-symbol">{{ tile.shortTitle }}</text>
<view class="map-copy">
<text class="map-title">{{ tile.title }}</text>
<text class="map-desc">{{ tile.desc }}</text>
@@ -123,13 +137,6 @@ const primaryTipQuestion = computed(() => quickQuestions.value[0] || '岗位SOP'
const pendingSopQueryKey = 'aihr_sop_pending_query';
const pendingAssignmentKey = 'aihr_pending_assignment_id';
const iconFor = (title: string) => {
if (title.includes('SOP') || title.includes('查')) return '/static/map-icons/ask.png';
if (title.includes('练')) return '/static/map-icons/practice.png';
if (title.includes('学') || title.includes('案例')) return '/static/map-icons/learn.png';
return '/static/map-icons/report.png';
};
const toneFor = (title: string) => {
if (title.includes('SOP') || title.includes('查')) return 'ask';
if (title.includes('练')) return 'practice';
@@ -137,14 +144,28 @@ const toneFor = (title: string) => {
return 'report';
};
const shortTitleFor = (tone: string) => {
if (tone === 'ask') return '问';
if (tone === 'practice') return '练';
if (tone === 'learn') return '学';
return '报';
};
const toolOrder = ['ask', 'practice', 'learn', 'report'];
const mapTiles = computed(() => {
if (!home.value?.tools?.length) return [];
return home.value.tools.map((item) => ({
title: item.title,
desc: item.desc.replace(/\n/g, ' / '),
tone: toneFor(item.title),
icon: iconFor(item.title)
}));
return home.value.tools
.map((item) => {
const tone = toneFor(item.title);
return {
title: item.title,
shortTitle: shortTitleFor(tone),
desc: item.desc.replace(/\n/g, ' / '),
tone
};
})
.sort((left, right) => toolOrder.indexOf(left.tone) - toolOrder.indexOf(right.tone));
});
const primaryAssignment = computed(() =>
@@ -261,169 +282,261 @@ onShow(() => {
</script>
<style scoped>
/* Homepage composition follows the approved employee-home visual reference. */
.today-page {
min-height: 100vh;
padding: 0 14px 86px;
background: #f4f7fb;
padding: 0 18px 96px;
background: linear-gradient(180deg, var(--employee-bg-soft) 0%, var(--employee-bg) 100%);
}
.mentor-hero {
position: relative;
height: 224px;
margin: 0 -14px;
height: 244px;
margin: 0 -18px;
overflow: hidden;
background:
linear-gradient(180deg, rgba(237, 247, 255, 0.72), rgba(244, 247, 251, 0.98)),
linear-gradient(135deg, #d9ecff 0%, #f7fbff 45%, #dfeff2 100%);
border: 0;
background: #f0f1ee;
}
.mentor-hero::before {
display: none;
}
.hero-scene,
.hero-scene-scrim {
position: absolute;
right: -36px;
bottom: 12px;
width: 190px;
height: 96px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.46);
content: '';
inset: -18px;
width: calc(100% + 36px);
height: calc(100% + 36px);
}
.hero-scene {
object-fit: cover;
opacity: 0.18;
filter: blur(13px) saturate(0.34) brightness(1.28);
transform: scale(1.12);
}
.hero-scene-scrim {
background: rgba(247, 247, 248, 0.76);
}
.hero-copy {
position: absolute;
left: 22px;
left: 28px;
top: 34px;
z-index: 2;
display: flex;
flex-direction: column;
}
.hero-kicker {
margin-bottom: 12px;
}
.hero-role,
.hero-title {
color: #11235a;
font-size: 22px;
font-weight: 900;
line-height: 1.08;
color: #25282d;
font-size: 29px;
line-height: 1.04;
letter-spacing: 0;
}
.hero-sub {
margin-top: 8px;
color: #1e3a8a;
font-size: 13px;
font-weight: 700;
margin-top: 12px;
color: #6a6d72;
font-size: 15px;
line-height: 1.35;
}
.mentor-image {
position: absolute;
right: 88px;
bottom: 14px;
right: 105px;
bottom: 0;
z-index: 1;
width: 104px;
height: 138px;
border-radius: 24px;
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.14);
width: 132px;
height: 176px;
border-radius: 0;
filter: drop-shadow(0 8px 8px rgba(37, 40, 45, 0.16));
-webkit-clip-path: ellipse(44% 56% at 50% 48%);
clip-path: ellipse(44% 56% at 50% 48%);
-webkit-mask-image: radial-gradient(ellipse 68% 61% at 50% 42%, #000 58%, transparent 100%);
mask-image: radial-gradient(ellipse 68% 61% at 50% 42%, #000 58%, transparent 100%);
}
.mentor-bubble {
position: absolute;
right: 16px;
top: 80px;
right: 18px;
top: 96px;
z-index: 3;
width: 128px;
padding: 12px 14px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.12);
width: 142px;
padding: 14px 16px;
border: 1px solid rgba(255, 255, 255, 0.9);
border-radius: 22px;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 10px 24px rgba(37, 40, 45, 0.14);
}
.mentor-bubble text {
color: #111827;
color: #1c2940;
font-size: 17px;
font-weight: 700;
line-height: 1.45;
font-weight: 800;
line-height: 1.4;
}
.voice-panel {
position: relative;
z-index: 4;
display: grid;
margin: -34px 8px 0;
padding: 16px;
grid-template-columns: 1fr 1fr;
gap: 14px;
border-radius: 22px;
min-height: 106px;
margin: -42px 10px 0;
padding: 15px;
grid-template-columns: minmax(0, 1fr) 112px;
gap: 0;
border: 1px solid rgba(47, 74, 117, 0.08);
border-radius: 25px;
background: #ffffff;
box-shadow: 0 14px 38px rgba(15, 23, 42, 0.12);
box-shadow: 0 14px 36px rgba(47, 107, 255, 0.12);
}
.voice-primary,
.voice-secondary {
display: flex;
min-height: 64px;
min-height: 76px;
align-items: center;
justify-content: center;
gap: 10px;
border-radius: 18px;
border-radius: 0;
background: transparent;
color: #1d4ed8;
font-size: 20px;
font-weight: 800;
color: #e70014;
}
.voice-secondary {
color: #4b5563;
background: #f1f5f9;
.voice-primary {
justify-content: flex-start;
gap: 14px;
padding: 0 16px 0 4px;
border-right: 1px solid #e7e7e9;
}
.voice-primary text:last-child,
.voice-secondary text:last-child {
white-space: nowrap;
}
.voice-mark,
.keyboard-mark {
.voice-emblem {
display: flex;
width: 46px;
height: 46px;
width: 60px;
height: 60px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 50%;
background: linear-gradient(135deg, #2563eb, #1d9bf0);
background: #e70014;
box-shadow: 0 0 0 9px #fff0f1, 0 7px 14px rgba(231, 0, 20, 0.22);
}
.voice-mark {
width: auto;
height: auto;
border-radius: 0;
background: transparent;
color: #ffffff;
font-size: 18px;
font-size: 26px;
}
.voice-copy {
display: flex;
min-width: 0;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.voice-copy text:first-child {
font-size: 21px;
font-weight: 900;
white-space: nowrap;
}
.voice-copy text:last-child {
color: #777a80;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.voice-secondary {
gap: 7px;
padding: 0 8px;
border-radius: 24px;
background: #f1f1f2;
color: #45474d;
font-size: 17px;
}
.keyboard-mark {
background: #e5e7eb;
color: #4b5563;
display: flex;
width: 30px;
height: 30px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 9px;
background: #dedee1;
color: #45474d;
font-size: 14px;
}
.mentor-chips {
margin-top: 14px;
margin: 22px 0 0;
}
.mentor-chips .chip-row {
justify-content: space-between;
gap: 8px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.mentor-chips .chip {
min-height: 42px;
flex: 1;
border: 1px solid rgba(203, 213, 225, 0.7);
position: relative;
display: flex;
min-height: 48px;
align-items: center;
justify-content: center;
gap: 5px;
padding: 0 6px;
border: 0;
border-radius: 24px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
color: #1f2937;
font-size: 15px;
box-shadow: 0 7px 18px rgba(37, 40, 45, 0.08);
color: #34373c;
font-size: 14px;
font-weight: 800;
white-space: nowrap;
}
.mentor-chips .chip::before {
width: 8px;
height: 8px;
flex: none;
border-radius: 50%;
background: #e70014;
content: '';
}
.mentor-chips .chip::after {
content: none;
}
.mentor-chips .chip:nth-child(2)::before {
background: #d98200;
}
.mentor-chips .chip:nth-child(3)::before {
background: #168243;
}
.section-card {
margin-top: 18px;
margin-top: 24px;
padding: 18px;
border-radius: 18px;
border: 1px solid rgba(47, 74, 117, 0.08);
border-radius: var(--employee-card-radius);
background: #ffffff;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.07);
box-shadow: var(--employee-card-shadow);
}
.login-section {
@@ -431,6 +544,7 @@ onShow(() => {
align-items: center;
justify-content: space-between;
gap: 12px;
border-left: 4px solid #e70014;
}
.login-title,
@@ -439,74 +553,84 @@ onShow(() => {
}
.login-title {
color: #111827;
font-size: 18px;
font-weight: 900;
font-size: 19px;
}
.login-desc {
margin-top: 6px;
color: #64748b;
font-size: 13px;
line-height: 1.4;
max-width: 190px;
font-size: 14px;
line-height: 1.48;
}
.today-page .primary-button,
.login-button {
flex: 0 0 84px;
min-width: 84px;
white-space: nowrap;
width: 96px;
min-width: 96px;
min-height: 50px;
flex: 0 0 96px;
border-radius: 14px;
background: #e70014;
box-shadow: 0 8px 16px rgba(231, 0, 20, 0.2);
}
.section-head,
.section-title {
display: flex;
align-items: center;
gap: 10px;
color: #25282d;
font-size: 22px;
}
.section-head {
justify-content: space-between;
.section-heading-block {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
gap: 8px;
}
.section-title {
gap: 9px;
color: #111827;
font-size: 21px;
font-weight: 900;
.section-subtitle {
color: #707682;
font-size: 13px;
line-height: 1.5;
}
.section-icon {
width: 28px;
height: 28px;
border-radius: 9px;
background: #fff0f1;
color: #e70014;
font-size: 14px;
}
.section-head {
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: 8px;
background: #dbeafe;
color: #2563eb;
font-size: 13px;
font-weight: 900;
justify-content: space-between;
}
.section-link {
color: #64748b;
font-size: 14px;
flex: none;
font-size: 15px;
}
.schedule-list {
display: flex;
margin-top: 16px;
margin-top: 18px;
flex-direction: column;
gap: 10px;
gap: 12px;
}
.schedule-row {
display: flex;
min-height: 82px;
min-height: 94px;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: 15px;
background: #f8fafc;
padding: 14px;
border: 0;
border-radius: 18px;
background: #f4f4f5;
}
.step-dot {
@@ -523,11 +647,11 @@ onShow(() => {
}
.step-dot.blue {
background: #2563eb;
background: #e70014;
}
.step-dot.orange {
background: #f97316;
background: #d98200;
}
.schedule-copy {
@@ -539,26 +663,22 @@ onShow(() => {
}
.schedule-title {
color: #111827;
font-size: 16px;
font-weight: 900;
line-height: 1.25;
font-size: 17px;
}
.schedule-desc {
color: #64748b;
font-size: 13px;
line-height: 1.35;
font-size: 14px;
}
.schedule-action {
display: flex;
width: 68px;
min-height: 42px;
width: 76px;
min-height: 48px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 12px;
border-radius: 14px;
box-shadow: 0 7px 14px rgba(231, 0, 20, 0.2);
color: #ffffff;
font-size: 16px;
font-weight: 900;
@@ -566,117 +686,165 @@ onShow(() => {
}
.blue-action {
background: #2563eb;
background: #e70014;
}
.orange-action {
background: #f97316;
background: #d98200;
box-shadow: 0 7px 14px rgba(217, 130, 0, 0.18);
}
.map-grid {
display: grid;
margin-top: 16px;
margin-top: 18px;
grid-template-columns: 1fr 1fr;
gap: 10px;
gap: 12px;
}
.map-tile {
position: relative;
display: flex;
min-height: 108px;
display: grid;
min-height: 128px;
grid-template-columns: 42px minmax(0, 1fr) 14px;
align-items: center;
gap: 12px;
gap: 9px;
padding: 16px 12px;
border-radius: 17px;
border: 0;
border-radius: 19px;
color: #ffffff;
overflow: hidden;
text-align: left;
}
.map-tile.ask {
background: linear-gradient(135deg, #1d4ed8, #2682ff);
background: linear-gradient(135deg, #c90012, #f02135);
}
.map-tile.practice {
background: linear-gradient(135deg, #16a34a, #22c55e);
background: linear-gradient(135deg, #0d7137, #239954);
}
.map-tile.learn {
background: linear-gradient(135deg, #f59e0b, #fb923c);
background: linear-gradient(135deg, #ba6d00, #e89410);
}
.map-tile.report {
background: linear-gradient(135deg, #ef4444, #f97316);
background: linear-gradient(135deg, #5a5d63, #85888d);
}
.map-icon {
width: 42px;
height: 42px;
flex: none;
filter: brightness(0) invert(1);
.map-symbol {
display: flex;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
border: 2px solid rgba(255, 255, 255, 0.76);
border-radius: 14px;
color: #ffffff;
font-size: 22px;
font-weight: 900;
}
.map-title {
color: #ffffff;
font-size: 26px;
line-height: 1;
}
.map-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
}
.map-title {
color: #ffffff;
font-size: 24px;
font-weight: 900;
line-height: 1.1;
}
.map-desc {
margin-top: 6px;
color: rgba(255, 255, 255, 0.9);
margin-top: 7px;
color: rgba(255, 255, 255, 0.92);
font-size: 13px;
line-height: 1.35;
line-height: 1.28;
}
.map-arrow {
color: #ffffff;
font-size: 28px;
font-weight: 500;
font-size: 25px;
}
.tip-card {
display: flex;
margin-top: 18px;
min-height: 58px;
margin-top: 22px;
min-height: 64px;
align-items: center;
gap: 10px;
padding: 0 14px;
border-radius: 16px;
background: #ffffff;
color: #475569;
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.06);
padding: 0 16px;
border: 1px solid rgba(255, 179, 64, 0.18);
border-radius: 20px;
background: #fff7eb;
color: #64666b;
box-shadow: 0 10px 24px rgba(255, 155, 61, 0.08);
}
.tip-mark {
display: flex;
width: 28px;
height: 28px;
width: 30px;
height: 30px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #fef3c7;
color: #d97706;
font-size: 13px;
font-weight: 900;
border-radius: 10px;
background: #fff0c4;
color: #bf7000;
}
.tip-card text:nth-child(2) {
flex: 1;
font-size: 14px;
line-height: 1.45;
font-size: 15px;
}
.tip-arrow {
color: #94a3b8;
color: #85878c;
font-size: 24px;
}
@media (max-width: 350px) {
.hero-role,
.hero-title {
font-size: 26px;
}
.mentor-image {
right: 63px;
width: 122px;
height: 164px;
}
.mentor-bubble {
right: 12px;
width: 128px;
padding: 12px;
}
.voice-panel {
grid-template-columns: minmax(0, 1fr) 98px;
margin-right: 4px;
margin-left: 4px;
padding: 12px;
}
.voice-primary {
gap: 10px;
padding-right: 10px;
}
.voice-emblem {
width: 52px;
height: 52px;
}
.voice-copy text:first-child {
font-size: 18px;
}
.mentor-chips .chip {
font-size: 13px;
}
}
</style>