739 lines
20 KiB
Vue
739 lines
20 KiB
Vue
<template>
|
||
<view class="page learning-page">
|
||
<view class="subpage-header">
|
||
<button
|
||
class="subpage-back-button"
|
||
aria-label="返回今日"
|
||
title="返回今日"
|
||
@click="navigateBackToToday"
|
||
>
|
||
←
|
||
</button>
|
||
<view class="subpage-heading">
|
||
<text class="page-title">学</text>
|
||
<text class="page-subtitle">按你的岗位读取今日任务、SOP 依据和训练前预习卡。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="message" class="status-message">{{ message }}</view>
|
||
|
||
<view v-if="!loggedIn" class="panel login-panel">
|
||
<view>
|
||
<text class="panel-title">需要登录</text>
|
||
<text class="muted block">登录后才能按手机号读取今日任务和岗位学习内容。</text>
|
||
</view>
|
||
<button class="primary-button login-button" @click="goLogin">去登录</button>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="learning-summary">
|
||
<view class="summary-item pending">
|
||
<text class="summary-value">{{ pendingCount }}</text>
|
||
<text class="summary-label">待完成</text>
|
||
</view>
|
||
<view class="summary-item completed">
|
||
<text class="summary-value">{{ completedCount }}</text>
|
||
<text class="summary-label">今日完成</text>
|
||
</view>
|
||
<view class="summary-item reference">
|
||
<text class="summary-value">{{ referenceCount }}</text>
|
||
<text class="summary-label">知识引用</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel">
|
||
<view class="panel-head">
|
||
<text class="panel-title">今日任务</text>
|
||
<button class="ghost-button compact-button" :disabled="assignmentLoading" @click="loadAssignments">
|
||
{{ assignmentLoading ? '刷新中' : '刷新' }}
|
||
</button>
|
||
</view>
|
||
<view v-if="assignments.length" class="task-list">
|
||
<view v-for="item in assignments" :key="item.id" class="task-row">
|
||
<view class="task-copy">
|
||
<text class="task-badge" :class="{ daily: item.source === 'daily' }">
|
||
{{ item.source === 'daily' ? '每日三题' : '专项训练' }}
|
||
</text>
|
||
<text class="task-title">{{ item.question || item.scenarioName }}</text>
|
||
<text v-if="assignmentDesc(item)" class="task-desc">{{ assignmentDesc(item) }}</text>
|
||
</view>
|
||
<button
|
||
class="secondary-button compact-button"
|
||
:disabled="item.status === '已完成' && item.source !== 'daily'"
|
||
@click="openAssignment(item)"
|
||
>
|
||
{{ item.status === '已完成' ? '查看' : '去完成' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty">暂无今日任务</view>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel mistake-panel">
|
||
<view class="panel-head">
|
||
<view>
|
||
<text class="panel-title">错题本</text>
|
||
<text class="panel-caption">从低分和红线回合中自动归集</text>
|
||
</view>
|
||
<button class="ghost-button compact-button" :disabled="mistakeLoading" @click="loadMistakes">
|
||
{{ mistakeLoading ? '加载中' : '刷新' }}
|
||
</button>
|
||
</view>
|
||
<view class="chip-row mistake-filter-row">
|
||
<button
|
||
v-for="cause in mistakeFilters"
|
||
:key="cause"
|
||
class="chip"
|
||
:class="{ active: cause === activeMistakeCause }"
|
||
:disabled="mistakeLoading"
|
||
@click="activeMistakeCause = cause"
|
||
>
|
||
{{ cause }}
|
||
</button>
|
||
</view>
|
||
<view v-if="visibleMistakes.length" class="mistake-list">
|
||
<view v-for="item in visibleMistakes" :key="item.id" class="mistake-row">
|
||
<view class="mistake-copy">
|
||
<view class="mistake-meta">
|
||
<text class="mistake-cause">{{ item.cause }}</text>
|
||
<text class="mistake-score">{{ item.score }} 分</text>
|
||
</view>
|
||
<text class="mistake-title">{{ item.scenarioName }}</text>
|
||
<text class="mistake-desc">{{ item.time }} · {{ item.summary }}</text>
|
||
<text class="mistake-status">再练状态:{{ item.retryStatus }}</text>
|
||
</view>
|
||
<button
|
||
v-if="item.retryAssignmentId"
|
||
class="secondary-button compact-button"
|
||
@click="openMistake(item)"
|
||
>
|
||
去再练
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<view v-else-if="!mistakeLoading" class="empty">
|
||
{{ mistakes.length ? '当前归因筛选暂无错题' : '完成训练后,低分和红线回合会自动出现在这里' }}
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel">
|
||
<view class="panel-head">
|
||
<text class="panel-title">岗位 SOP</text>
|
||
<button class="ghost-button compact-button" :disabled="knowledgeLoading" @click="loadKnowledge(activeTopic)">
|
||
{{ knowledgeLoading ? '检索中' : '重查' }}
|
||
</button>
|
||
</view>
|
||
<view v-if="positionSopSummary" class="position-sop-status">
|
||
<text class="position-sop-title">
|
||
{{ positionSopSummary.position }} · {{ positionSopSummary.topics.length }} 个学习主题
|
||
</text>
|
||
<text class="position-sop-note">{{ positionSopSummary.message }}</text>
|
||
<text v-if="!positionSopSummary.formalApplicabilityConfigured" class="position-sop-warning">当前摘要不代表正式上岗资格</text>
|
||
</view>
|
||
<view class="chip-row topic-row">
|
||
<button
|
||
v-for="topic in topics"
|
||
:key="topic"
|
||
class="chip"
|
||
:class="{ active: topic === activeTopic }"
|
||
:disabled="knowledgeLoading"
|
||
@click="loadKnowledge(topic)"
|
||
>
|
||
{{ topic }}
|
||
</button>
|
||
</view>
|
||
<text v-if="knowledgeResult?.answer" class="answer-text">{{ knowledgeResult.answer }}</text>
|
||
<view v-if="knowledgeResult?.keyPoints?.length" class="keypoints">
|
||
<text v-for="(point, index) in knowledgeResult.keyPoints" :key="point" class="keypoint">
|
||
{{ index + 1 }}. {{ cleanOrderedText(point) }}
|
||
</text>
|
||
</view>
|
||
<view v-if="knowledgeResult?.snippets?.length" class="snippet-list">
|
||
<view v-for="(snippet, index) in knowledgeResult.snippets?.slice(0, 3)" :key="`${snippet.docTitle}-${index}`" class="snippet-row">
|
||
<text class="snippet-title">{{ snippet.docTitle || '知识片段' }}</text>
|
||
<text class="snippet-content">{{ snippetExcerpt(snippet.content) }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else-if="!knowledgeLoading" class="empty">知识库暂未命中可引用内容</view>
|
||
<button class="secondary-button full-action" @click="askTopic(activeTopic)">继续问师傅</button>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel">
|
||
<view class="panel-head">
|
||
<text class="panel-title">训练前预习卡</text>
|
||
<button class="ghost-button compact-button" :disabled="prepLoading" @click="loadPrepCard">
|
||
{{ prepLoading ? '加载中' : '刷新' }}
|
||
</button>
|
||
</view>
|
||
<view v-if="prepCard" class="prep-grid">
|
||
<view class="prep-block">
|
||
<text class="prep-label">要点</text>
|
||
<text v-for="item in prepCard.keyPoints.slice(0, 3)" :key="`point-${item}`" class="prep-line">{{ item }}</text>
|
||
</view>
|
||
<view class="prep-block red">
|
||
<text class="prep-label">红线</text>
|
||
<text v-for="item in prepCard.redLines.slice(0, 3)" :key="`red-${item}`" class="prep-line">{{ item }}</text>
|
||
</view>
|
||
<view class="prep-block script">
|
||
<text class="prep-label">可说话术</text>
|
||
<text v-for="item in prepCard.scripts.slice(0, 3)" :key="`script-${item}`" class="prep-line">{{ item }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else-if="!prepLoading" class="empty">暂无可用预习卡</view>
|
||
<button class="primary-button full-action" @click="goPractice">练一遍</button>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel action-panel">
|
||
<text class="panel-title">学习后动作</text>
|
||
<view class="action-list">
|
||
<button class="secondary-button action-button" @click="goPractice">进入训练</button>
|
||
<button class="secondary-button action-button" @click="askTopic(activeTopic)">问师傅</button>
|
||
<button class="secondary-button action-button" @click="goCases">提交素材</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import type {
|
||
KnowledgeSearchResponse,
|
||
PositionSopSummary,
|
||
PracticeAssignment,
|
||
PracticeMistake,
|
||
PracticePrepCard
|
||
} from '@/types/api';
|
||
import { getPracticeAssignments, getPracticeMistakes, getPracticePrepCard } from '@/services/practice';
|
||
import { getPositionSopSummary, searchKnowledge } from '@/services/knowledge';
|
||
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||
import { buildLearningTopics, selectLearningTopic } from '@/services/learning';
|
||
import { navigateBackToToday, resetPageScroll } from '@/services/navigation';
|
||
import { ensureEmployeePosition } from '@/services/position';
|
||
|
||
const defaultScenarioId = 'complaint-water';
|
||
const pendingAssignmentKey = 'aihr_pending_assignment_id';
|
||
const pendingSopQueryKey = 'aihr_sop_pending_query';
|
||
|
||
const loggedIn = ref(false);
|
||
const message = ref('');
|
||
const assignments = ref<PracticeAssignment[]>([]);
|
||
const mistakes = ref<PracticeMistake[]>([]);
|
||
const prepCard = ref<PracticePrepCard | null>(null);
|
||
const knowledgeResult = ref<KnowledgeSearchResponse | null>(null);
|
||
const positionSopSummary = ref<PositionSopSummary | null>(null);
|
||
const activeTopic = ref('岗前 SOP');
|
||
const assignmentLoading = ref(false);
|
||
const mistakeLoading = ref(false);
|
||
const mistakeFilters = ['全部', '红线话术', '情绪承接', '流程合规', '综合得分不足'];
|
||
const activeMistakeCause = ref('全部');
|
||
const prepLoading = ref(false);
|
||
const knowledgeLoading = ref(false);
|
||
const positionProfile = ref(getSelectedPositionProfile());
|
||
|
||
const defaultTopic = computed(() => `${positionProfile.value.value} 岗前 SOP`);
|
||
const topics = computed(() => buildLearningTopics(
|
||
assignments.value,
|
||
defaultTopic.value,
|
||
positionProfile.value.quickQuestions
|
||
));
|
||
|
||
const pendingCount = computed(() => assignments.value.filter((item) => item.status !== '已完成').length);
|
||
const completedCount = computed(() => assignments.value.filter((item) => item.status === '已完成').length);
|
||
const referenceCount = computed(() => knowledgeResult.value?.snippets?.length || 0);
|
||
const firstPracticeAssignment = computed(() =>
|
||
assignments.value.find((item) => item.status !== '已完成' && item.source !== 'daily')
|
||
|| assignments.value.find((item) => item.source !== 'daily')
|
||
|| null
|
||
);
|
||
const prepScenarioId = computed(() => firstPracticeAssignment.value?.scenarioId || defaultScenarioId);
|
||
const visibleMistakes = computed(() => activeMistakeCause.value === '全部'
|
||
? mistakes.value
|
||
: mistakes.value.filter((item) => item.cause === activeMistakeCause.value));
|
||
|
||
const refreshAuthState = () => {
|
||
loggedIn.value = isLoggedIn();
|
||
positionProfile.value = getSelectedPositionProfile();
|
||
if (!topics.value.includes(activeTopic.value)) {
|
||
activeTopic.value = topics.value[0] || '岗前 SOP';
|
||
}
|
||
};
|
||
|
||
const handleRequestError = (error: unknown, fallback: string) => {
|
||
refreshAuthState();
|
||
if (!loggedIn.value) {
|
||
message.value = '登录已过期,请重新登录';
|
||
return;
|
||
}
|
||
message.value = error instanceof Error ? error.message : fallback;
|
||
};
|
||
|
||
const assignmentDesc = (item: PracticeAssignment) => {
|
||
if (item.source === 'daily') {
|
||
if (item.status === '已完成' && item.feedback) return item.feedback;
|
||
return '提交后显示反馈与参考答案';
|
||
}
|
||
return item.reason || item.feedback;
|
||
};
|
||
|
||
const snippetExcerpt = (text = '') => {
|
||
const value = text.trim();
|
||
return value.length > 180 ? `${value.slice(0, 180)}...` : value;
|
||
};
|
||
|
||
const cleanOrderedText = (text = '') =>
|
||
text
|
||
.trim()
|
||
.replace(/^(?:[一二三四五六七八九十]+[、..]|[0-9]+[.)、..])\s*/, '')
|
||
.trim();
|
||
|
||
const loadAssignments = async () => {
|
||
if (!loggedIn.value) return;
|
||
assignmentLoading.value = true;
|
||
try {
|
||
assignments.value = await getPracticeAssignments(true);
|
||
} catch (error) {
|
||
assignments.value = [];
|
||
handleRequestError(error, '学习任务加载失败');
|
||
} finally {
|
||
assignmentLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const loadMistakes = async () => {
|
||
if (!loggedIn.value) return;
|
||
mistakeLoading.value = true;
|
||
try {
|
||
mistakes.value = await getPracticeMistakes();
|
||
} catch (error) {
|
||
mistakes.value = [];
|
||
handleRequestError(error, '错题本加载失败');
|
||
} finally {
|
||
mistakeLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const loadPrepCard = async () => {
|
||
if (!loggedIn.value) return;
|
||
prepLoading.value = true;
|
||
try {
|
||
prepCard.value = await getPracticePrepCard(prepScenarioId.value);
|
||
} catch (error) {
|
||
prepCard.value = null;
|
||
handleRequestError(error, '预习卡加载失败');
|
||
} finally {
|
||
prepLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const loadKnowledge = async (topic: string) => {
|
||
if (!loggedIn.value || knowledgeLoading.value) return;
|
||
activeTopic.value = topic;
|
||
knowledgeLoading.value = true;
|
||
try {
|
||
knowledgeResult.value = await searchKnowledge(topic);
|
||
} catch (error) {
|
||
knowledgeResult.value = null;
|
||
handleRequestError(error, '岗位 SOP 检索失败');
|
||
} finally {
|
||
knowledgeLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const loadPositionSopSummary = async () => {
|
||
if (!loggedIn.value) return;
|
||
try {
|
||
positionSopSummary.value = await getPositionSopSummary(positionProfile.value.value);
|
||
} catch (error) {
|
||
positionSopSummary.value = null;
|
||
handleRequestError(error, '岗位学习范围加载失败');
|
||
}
|
||
};
|
||
|
||
const loadPage = async () => {
|
||
refreshAuthState();
|
||
message.value = '';
|
||
if (!loggedIn.value) {
|
||
assignments.value = [];
|
||
mistakes.value = [];
|
||
prepCard.value = null;
|
||
knowledgeResult.value = null;
|
||
positionSopSummary.value = null;
|
||
return;
|
||
}
|
||
if (!(await ensureEmployeePosition('/pages/user/learning/index'))) {
|
||
return;
|
||
}
|
||
refreshAuthState();
|
||
await Promise.all([loadAssignments(), loadMistakes()]);
|
||
activeTopic.value = selectLearningTopic(assignments.value, defaultTopic.value);
|
||
await Promise.all([
|
||
loadPositionSopSummary(),
|
||
loadKnowledge(activeTopic.value),
|
||
loadPrepCard()
|
||
]);
|
||
};
|
||
|
||
const goLogin = () => {
|
||
rememberLoginRedirect('/pages/user/learning/index');
|
||
uni.navigateTo({ url: '/pages/auth/login/index' });
|
||
};
|
||
|
||
const openAssignment = (item: PracticeAssignment) => {
|
||
uni.setStorageSync(pendingAssignmentKey, String(item.id));
|
||
uni.switchTab({ url: '/pages/user/practice/index' });
|
||
};
|
||
|
||
const openMistake = (item: PracticeMistake) => {
|
||
if (!item.retryAssignmentId) return;
|
||
uni.setStorageSync(pendingAssignmentKey, String(item.retryAssignmentId));
|
||
uni.switchTab({ url: '/pages/user/practice/index' });
|
||
};
|
||
|
||
const goPractice = () => {
|
||
if (firstPracticeAssignment.value) {
|
||
uni.setStorageSync(pendingAssignmentKey, String(firstPracticeAssignment.value.id));
|
||
}
|
||
uni.switchTab({ url: '/pages/user/practice/index' });
|
||
};
|
||
|
||
const askTopic = (topic: string) => {
|
||
uni.setStorageSync(pendingSopQueryKey, topic);
|
||
uni.switchTab({ url: '/pages/user/sop/index' });
|
||
};
|
||
|
||
const goCases = () => {
|
||
uni.navigateTo({ url: '/pages/user/cases/index' });
|
||
};
|
||
|
||
onShow(() => {
|
||
resetPageScroll();
|
||
void loadPage();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.learning-page {
|
||
padding-bottom: 42px;
|
||
}
|
||
|
||
.panel-caption {
|
||
display: block;
|
||
margin-top: 4px;
|
||
color: #7b8494;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.position-sop-status {
|
||
display: grid;
|
||
gap: 4px;
|
||
margin-bottom: 12px;
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
background: #f3f7ff;
|
||
}
|
||
|
||
.position-sop-title {
|
||
color: #183b70;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.position-sop-note {
|
||
color: #60708a;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.position-sop-warning {
|
||
color: #9a6700;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.mistake-list {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.mistake-filter-row {
|
||
margin-top: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.mistake-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px;
|
||
border: 1px solid #fee2e2;
|
||
border-radius: 12px;
|
||
background: #fff8f8;
|
||
}
|
||
|
||
.mistake-copy {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.mistake-meta {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.mistake-cause {
|
||
color: #bd0010;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mistake-score {
|
||
color: #7b8494;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.mistake-title,
|
||
.mistake-desc,
|
||
.mistake-status {
|
||
display: block;
|
||
}
|
||
|
||
.mistake-title {
|
||
margin-top: 4px;
|
||
color: #1f2937;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mistake-desc,
|
||
.mistake-status {
|
||
margin-top: 4px;
|
||
color: #7b8494;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.login-panel {
|
||
display: flex;
|
||
gap: 14px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.login-button {
|
||
min-width: 104px;
|
||
}
|
||
|
||
.block {
|
||
display: block;
|
||
margin-top: 8px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.learning-summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.summary-item {
|
||
min-height: 82px;
|
||
padding: 14px 10px;
|
||
border: 1px solid #dbeafe;
|
||
border-radius: 14px;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.summary-item.pending {
|
||
background: #fff0f1;
|
||
border-color: #fee2e2;
|
||
}
|
||
.summary-item.pending .summary-value {
|
||
color: #bd0010;
|
||
}
|
||
|
||
.summary-item.completed {
|
||
background: #edf7f0;
|
||
border-color: #dcfce7;
|
||
}
|
||
.summary-item.completed .summary-value {
|
||
color: #168243;
|
||
}
|
||
|
||
.summary-item.reference {
|
||
background: #eff6ff;
|
||
border-color: #dbeafe;
|
||
}
|
||
.summary-item.reference .summary-value {
|
||
color: #1d4ed8;
|
||
}
|
||
|
||
.summary-value {
|
||
display: block;
|
||
color: #1d4ed8;
|
||
font-size: 25px;
|
||
font-weight: 850;
|
||
line-height: 1;
|
||
}
|
||
|
||
.summary-label {
|
||
display: block;
|
||
margin-top: 10px;
|
||
color: #475569;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.panel-head {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.compact-button {
|
||
min-height: 36px;
|
||
padding: 0 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.task-list,
|
||
.snippet-list,
|
||
.action-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.task-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 13px;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.task-copy {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.task-badge {
|
||
display: inline-flex;
|
||
min-height: 24px;
|
||
align-items: center;
|
||
padding: 0 8px;
|
||
border-radius: 999px;
|
||
background: #fee2e2;
|
||
color: #991b1b;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.task-badge.daily {
|
||
background: #dcfce7;
|
||
color: #166534;
|
||
}
|
||
|
||
.task-title,
|
||
.snippet-title,
|
||
.prep-label {
|
||
display: block;
|
||
margin-top: 8px;
|
||
color: #0f172a;
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.task-desc,
|
||
.snippet-content,
|
||
.prep-line {
|
||
display: block;
|
||
margin-top: 6px;
|
||
color: #64748b;
|
||
font-size: 14px;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.topic-row {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.chip.active {
|
||
background: #e60012;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.answer-text {
|
||
display: block;
|
||
margin-top: 14px;
|
||
color: #1e293b;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.keypoints {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.keypoint {
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
background: #f8fafc;
|
||
color: #334155;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.snippet-row {
|
||
padding: 13px;
|
||
border-left: 4px solid #2563eb;
|
||
border-radius: 12px;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.full-action {
|
||
width: 100%;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.prep-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.prep-block {
|
||
padding: 13px;
|
||
border-radius: 12px;
|
||
background: #ecfeff;
|
||
}
|
||
|
||
.prep-block.red {
|
||
background: #fff1f2;
|
||
}
|
||
|
||
.prep-block.script {
|
||
background: #fefce8;
|
||
}
|
||
|
||
.action-panel {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.action-button {
|
||
width: 100%;
|
||
}
|
||
</style>
|