feat(mobile): restructure worker home into scenario-based task flow
员工端首页从功能陈列改为情境任务流,回应甲方「学的时候怎么用、 工作的时候怎么用、碰到问题怎么用」反馈:五区依次为「今天要学」 (训练卡进度接真数据:practiceHistory 按今日 MM-dd 统计,完成后 CTA 变再练一次,接口失败保持静态兜底)、「练完要复盘」(最近一条评分+ 导师改写摘要,点击进复盘详情,含空态引导)、「工作中要用」(查SOP 升级为通栏大按钮卡)、「碰到问题要报」(素材上报)、「我的成长」 (进度+能力画像移至底部)。员工端 ToolGrid 移除,候选人/主管端零 改动,demo-check 14 条标记保留。
This commit is contained in:
+350
-62
@@ -52,7 +52,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Card :title="roleKey === 'supervisor' ? '今日处理流程' : '今日使用流程'">
|
<Card v-if="roleKey !== 'user'" :title="roleKey === 'supervisor' ? '今日处理流程' : '今日使用流程'">
|
||||||
<div class="flow-steps">
|
<div class="flow-steps">
|
||||||
<button v-for="item in roleFlowSteps" :key="`${item.phase}-${item.title}`" type="button" :class="{ active: isFlowActive(item) }" @click="openFlowStep(item)">
|
<button v-for="item in roleFlowSteps" :key="`${item.phase}-${item.title}`" type="button" :class="{ active: isFlowActive(item) }" @click="openFlowStep(item)">
|
||||||
<em>{{ item.phase }}</em>
|
<em>{{ item.phase }}</em>
|
||||||
@@ -278,73 +278,164 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Card title="今日任务" action="查看全部" @action="openUserAction('tasks')">
|
<template v-if="roleKey === 'user'">
|
||||||
<div class="task-card">
|
<section class="scenario-section">
|
||||||
<div class="task-copy">
|
<div class="scenario-title">
|
||||||
<strong>{{ workerRole.primary.badge }}</strong>
|
<h2>今天要学</h2>
|
||||||
<h3>{{ workerRole.primary.title }}</h3>
|
<p>跟着任务走,学完自动记录</p>
|
||||||
<p>{{ workerRole.primary.desc }}</p>
|
|
||||||
<span>进度 {{ workerRole.primary.progressText }}</span>
|
|
||||||
<ProgressBar :value="workerRole.primary.progress" />
|
|
||||||
</div>
|
|
||||||
<div class="headset-art">
|
|
||||||
<Icon :name="workerRole.primary.icon" size="104" stroke="1.7" />
|
|
||||||
</div>
|
|
||||||
<button type="button" @click="handlePrimaryAction">{{ workerRole.primary.cta }}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="review-card" type="button" @click="openUserAction('sop')">
|
|
||||||
<IconBubble icon="document" tone="amber" />
|
|
||||||
<span>
|
|
||||||
<b>{{ workerRole.review.title }}</b>
|
|
||||||
<small>{{ workerRole.review.desc }}</small>
|
|
||||||
</span>
|
|
||||||
<Icon name="chevron-right" size="26" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="roleKey === 'user' && practiceStatus !== 'idle'" class="practice-panel">
|
|
||||||
<div class="practice-panel-head">
|
|
||||||
<span>三角色对练</span>
|
|
||||||
<b v-if="practiceResult">{{ practiceResult.totalScore }}分</b>
|
|
||||||
<small v-else>{{ practiceStatus === 'starting' ? '准备中' : '进行中' }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="practiceResult" class="practice-result">
|
<div class="scenario-card-stack">
|
||||||
<strong>{{ practiceResult.aiComment }}</strong>
|
<div class="task-card">
|
||||||
<p>{{ practiceResult.mentorRewrite }}</p>
|
<div class="task-copy">
|
||||||
<div class="practice-result-actions">
|
<strong>{{ userPrimaryTask.badge }}</strong>
|
||||||
<button type="button" @click="startMobilePractice">再练一次</button>
|
<h3>{{ userPrimaryTask.title }}</h3>
|
||||||
<button type="button" @click="goSupervisor">查看主管端</button>
|
<p>{{ userPrimaryTask.desc }}</p>
|
||||||
|
<span>进度 {{ userPrimaryTask.progressText }}</span>
|
||||||
|
<ProgressBar :value="userPrimaryTask.progress" />
|
||||||
|
</div>
|
||||||
|
<div class="headset-art">
|
||||||
|
<Icon :name="userPrimaryTask.icon" size="104" stroke="1.7" />
|
||||||
|
</div>
|
||||||
|
<button type="button" @click="handlePrimaryAction">{{ userPrimaryTask.cta }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="review-card" type="button" @click="openUserAction('sop')">
|
||||||
|
<IconBubble icon="document" tone="amber" />
|
||||||
|
<span>
|
||||||
|
<b>{{ workerRole.review.title }}</b>
|
||||||
|
<small>{{ workerRole.review.desc }}</small>
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-right" size="26" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="practice-live">
|
<div v-if="practiceStatus !== 'idle'" class="practice-panel">
|
||||||
<div class="practice-dialogue">
|
<div class="practice-panel-head">
|
||||||
<p v-for="(turn, index) in practiceTurns" :key="`${turn.role}-${index}`" :class="turn.role">
|
<span>三角色对练</span>
|
||||||
<span>{{ turn.label }}</span>
|
<b v-if="practiceResult">{{ practiceResult.totalScore }}分</b>
|
||||||
{{ turn.text }}
|
<small v-else>{{ practiceStatus === 'starting' ? '准备中' : '进行中' }}</small>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<textarea v-model.trim="practiceDraft" rows="3" placeholder="输入你的回复" />
|
|
||||||
<div class="practice-actions">
|
|
||||||
<button type="button" :disabled="asrBusy" @click="toggleRecording">
|
|
||||||
{{ recording ? '停止录音' : asrBusy ? '识别中' : useAudioFileFallback ? '选择语音' : '语音输入' }}
|
|
||||||
</button>
|
|
||||||
<input ref="practiceAudioInput" class="practice-audio-input" type="file" accept="audio/*,.mp3,.wav,.m4a,.webm,.ogg" capture @change="handlePracticeAudioFile" />
|
|
||||||
<button type="button" @click="toggleVoice">{{ voiceEnabled ? '播报:开' : '播报:关' }}</button>
|
|
||||||
<button type="button" @click="fillPracticeReply">填入建议回复</button>
|
|
||||||
<button type="button" :disabled="practiceStatus === 'starting' || practiceStatus === 'submitting'" @click="submitMobilePractice">
|
|
||||||
{{ practiceStatus === 'submitting' ? '提交中' : '提交本轮' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<em v-if="practiceMessage">{{ practiceMessage }}</em>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card :title="workerRole.toolTitle">
|
<div v-if="practiceResult" class="practice-result">
|
||||||
<ToolGrid :items="workerRole.tools" :handler="openUserTool" />
|
<strong>{{ practiceResult.aiComment }}</strong>
|
||||||
</Card>
|
<p>{{ practiceResult.mentorRewrite }}</p>
|
||||||
|
<div class="practice-result-actions">
|
||||||
|
<button type="button" @click="startMobilePractice">再练一次</button>
|
||||||
|
<button type="button" @click="goSupervisor">查看主管端</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="practice-live">
|
||||||
|
<div class="practice-dialogue">
|
||||||
|
<p v-for="(turn, index) in practiceTurns" :key="`${turn.role}-${index}`" :class="turn.role">
|
||||||
|
<span>{{ turn.label }}</span>
|
||||||
|
{{ turn.text }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<textarea v-model.trim="practiceDraft" rows="3" placeholder="输入你的回复" />
|
||||||
|
<div class="practice-actions">
|
||||||
|
<button type="button" :disabled="asrBusy" @click="toggleRecording">
|
||||||
|
{{ recording ? '停止录音' : asrBusy ? '识别中' : useAudioFileFallback ? '选择语音' : '语音输入' }}
|
||||||
|
</button>
|
||||||
|
<input ref="practiceAudioInput" class="practice-audio-input" type="file" accept="audio/*,.mp3,.wav,.m4a,.webm,.ogg" capture @change="handlePracticeAudioFile" />
|
||||||
|
<button type="button" @click="toggleVoice">{{ voiceEnabled ? '播报:开' : '播报:关' }}</button>
|
||||||
|
<button type="button" @click="fillPracticeReply">填入建议回复</button>
|
||||||
|
<button type="button" :disabled="practiceStatus === 'starting' || practiceStatus === 'submitting'" @click="submitMobilePractice">
|
||||||
|
{{ practiceStatus === 'submitting' ? '提交中' : '提交本轮' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<em v-if="practiceMessage">{{ practiceMessage }}</em>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="scenario-section">
|
||||||
|
<div class="scenario-title">
|
||||||
|
<h2>练完要复盘</h2>
|
||||||
|
<p>看评分和导师改写,把话术改到位</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button v-if="latestPracticeReview" class="review-summary-card" type="button" @click="openUserHistory(latestPracticeReview)">
|
||||||
|
<IconBubble icon="target" tone="blue" />
|
||||||
|
<span>
|
||||||
|
<b>{{ latestPracticeReview.scene }} · {{ latestPracticeReview.score }}分</b>
|
||||||
|
<small>导师改写摘要:{{ latestPracticeReview.summary }}</small>
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-right" size="26" />
|
||||||
|
</button>
|
||||||
|
<div v-else class="review-summary-card empty">
|
||||||
|
<IconBubble icon="target" tone="blue" />
|
||||||
|
<span>
|
||||||
|
<b>我的复盘</b>
|
||||||
|
<small>完成第一次训练后,这里会出现你的评分和导师改写</small>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="scenario-section">
|
||||||
|
<div class="scenario-title">
|
||||||
|
<h2>工作中要用</h2>
|
||||||
|
<p>遇到不确定的现场问题,先查标准做法</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="sop-action-card" type="button" @click="openUserAction('sop')">
|
||||||
|
<IconBubble icon="book-search" tone="teal" />
|
||||||
|
<span>
|
||||||
|
<b>遇到问题,问一句</b>
|
||||||
|
<small>输入或语音提问,30秒拿到带出处的标准做法</small>
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-right" size="28" />
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="scenario-section">
|
||||||
|
<div class="scenario-title">
|
||||||
|
<h2>碰到问题要报</h2>
|
||||||
|
<p>现场情况拍下来说出来,沉淀成案例</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="report-card" type="button" @click="openUserAction('submit')">
|
||||||
|
<IconBubble icon="pen" tone="purple" />
|
||||||
|
<span>
|
||||||
|
<b>提交素材 / 案例上报</b>
|
||||||
|
<small>记录工作场景,上传后自动转写并整理案例</small>
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-right" size="26" />
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<Card title="今日任务" action="查看全部" @action="openUserAction('tasks')">
|
||||||
|
<div class="task-card">
|
||||||
|
<div class="task-copy">
|
||||||
|
<strong>{{ workerRole.primary.badge }}</strong>
|
||||||
|
<h3>{{ workerRole.primary.title }}</h3>
|
||||||
|
<p>{{ workerRole.primary.desc }}</p>
|
||||||
|
<span>进度 {{ workerRole.primary.progressText }}</span>
|
||||||
|
<ProgressBar :value="workerRole.primary.progress" />
|
||||||
|
</div>
|
||||||
|
<div class="headset-art">
|
||||||
|
<Icon :name="workerRole.primary.icon" size="104" stroke="1.7" />
|
||||||
|
</div>
|
||||||
|
<button type="button" @click="handlePrimaryAction">{{ workerRole.primary.cta }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="review-card" type="button" @click="openUserAction('sop')">
|
||||||
|
<IconBubble icon="document" tone="amber" />
|
||||||
|
<span>
|
||||||
|
<b>{{ workerRole.review.title }}</b>
|
||||||
|
<small>{{ workerRole.review.desc }}</small>
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-right" size="26" />
|
||||||
|
</button>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card :title="workerRole.toolTitle">
|
||||||
|
<ToolGrid :items="workerRole.tools" :handler="openUserTool" />
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
<Card v-if="roleKey !== 'supervisor' && activeUserPanel" :title="userPanelTitle">
|
<Card v-if="roleKey !== 'supervisor' && activeUserPanel" :title="userPanelTitle">
|
||||||
<div class="user-panel">
|
<div class="user-panel">
|
||||||
@@ -602,7 +693,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title="我的进度" :action="workerRole.recommend" compact-action @action="openUserAction('recommend')">
|
<section v-if="roleKey === 'user'" class="scenario-section growth-section">
|
||||||
|
<div class="scenario-title">
|
||||||
|
<h2>我的成长</h2>
|
||||||
|
<p>训练、复盘和能力画像集中在这里看</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card title="我的进度" :action="workerRole.recommend" compact-action @action="openUserAction('recommend')">
|
||||||
|
<div class="progress-grid">
|
||||||
|
<ProgressTile v-for="item in workerRole.metrics" :key="item.title" :item="item" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="competencyProfile" class="profile-card">
|
||||||
|
<span>
|
||||||
|
<small>综合能力</small>
|
||||||
|
<b>{{ competencyProfile.score }}</b>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<small>训练历史</small>
|
||||||
|
<b>{{ competencyProfile.completed }}</b>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<small>待复盘</small>
|
||||||
|
<b>{{ competencyProfile.pendingReview }}</b>
|
||||||
|
</span>
|
||||||
|
<div class="profile-dims">
|
||||||
|
<p v-for="item in competencyProfile.dimensions" :key="item.label">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<b>{{ item.value }}</b>
|
||||||
|
<small>{{ item.note }}</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="practiceHistory.length" class="record-list">
|
||||||
|
<h3>训练历史</h3>
|
||||||
|
<button v-for="item in practiceHistory" :key="`${item.time}-${item.scene}`" type="button" @click="openUserHistory(item)">
|
||||||
|
<span>
|
||||||
|
<b>{{ item.scene }} · {{ item.score }}分</b>
|
||||||
|
<small>{{ item.time }} · {{ item.summary }}</small>
|
||||||
|
</span>
|
||||||
|
<em>{{ item.status }}</em>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Card v-else title="我的进度" :action="workerRole.recommend" compact-action @action="openUserAction('recommend')">
|
||||||
<div class="progress-grid">
|
<div class="progress-grid">
|
||||||
<ProgressTile v-for="item in workerRole.metrics" :key="item.title" :item="item" />
|
<ProgressTile v-for="item in workerRole.metrics" :key="item.title" :item="item" />
|
||||||
</div>
|
</div>
|
||||||
@@ -1061,6 +1198,7 @@ const userReviewMessage = ref('');
|
|||||||
const reviewMessage = ref('');
|
const reviewMessage = ref('');
|
||||||
const markingReviewed = ref(false);
|
const markingReviewed = ref(false);
|
||||||
const competencyProfile = ref<CompetencyProfile | null>(null);
|
const competencyProfile = ref<CompetencyProfile | null>(null);
|
||||||
|
const practiceDataReady = ref(false);
|
||||||
const activeUserPanel = ref<UserPanelKey>('');
|
const activeUserPanel = ref<UserPanelKey>('');
|
||||||
const activeSupervisorPanel = ref<SupervisorPanelKey>('');
|
const activeSupervisorPanel = ref<SupervisorPanelKey>('');
|
||||||
const knowledgeQuery = ref('住宅投诉处理流程');
|
const knowledgeQuery = ref('住宅投诉处理流程');
|
||||||
@@ -2214,6 +2352,7 @@ const fetchMobileHome = async () => {
|
|||||||
|
|
||||||
const fetchPracticeData = async () => {
|
const fetchPracticeData = async () => {
|
||||||
if (!authToken.value || roleKey.value === 'candidate') {
|
if (!authToken.value || roleKey.value === 'candidate') {
|
||||||
|
practiceDataReady.value = false;
|
||||||
practiceHistory.value = [];
|
practiceHistory.value = [];
|
||||||
practiceReviews.value = [];
|
practiceReviews.value = [];
|
||||||
selectedReview.value = null;
|
selectedReview.value = null;
|
||||||
@@ -2231,16 +2370,19 @@ const fetchPracticeData = async () => {
|
|||||||
]);
|
]);
|
||||||
practiceHistory.value = history || [];
|
practiceHistory.value = history || [];
|
||||||
competencyProfile.value = profile || null;
|
competencyProfile.value = profile || null;
|
||||||
|
practiceDataReady.value = true;
|
||||||
practiceReviews.value = [];
|
practiceReviews.value = [];
|
||||||
selectedReview.value = null;
|
selectedReview.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
practiceDataReady.value = false;
|
||||||
practiceReviews.value =
|
practiceReviews.value =
|
||||||
(await readApi<PracticeRecord[]>(await fetch('/dev-api/api/aihr/mobile/practice/reviews', { headers: apiHeaders() }))) || [];
|
(await readApi<PracticeRecord[]>(await fetch('/dev-api/api/aihr/mobile/practice/reviews', { headers: apiHeaders() }))) || [];
|
||||||
practiceHistory.value = [];
|
practiceHistory.value = [];
|
||||||
competencyProfile.value = null;
|
competencyProfile.value = null;
|
||||||
} catch {
|
} catch {
|
||||||
|
practiceDataReady.value = false;
|
||||||
practiceHistory.value = [];
|
practiceHistory.value = [];
|
||||||
practiceReviews.value = [];
|
practiceReviews.value = [];
|
||||||
selectedReview.value = null;
|
selectedReview.value = null;
|
||||||
@@ -2307,6 +2449,29 @@ const supervisorTeamTiles = computed<MetricItem[]>(() =>
|
|||||||
progress: Number.parseInt(item.value, 10) || 66
|
progress: Number.parseInt(item.value, 10) || 66
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
const todayPracticeTarget = 1;
|
||||||
|
const todayPrefix = () => {
|
||||||
|
const date = new Date();
|
||||||
|
return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
const countTodayPracticeCompletions = () => {
|
||||||
|
const prefix = todayPrefix();
|
||||||
|
return practiceHistory.value.filter((item) => item.time.startsWith(prefix)).length;
|
||||||
|
};
|
||||||
|
const userPrimaryTask = computed<PrimaryTask>(() => {
|
||||||
|
const base = workerRole.value.primary;
|
||||||
|
if (roleKey.value !== 'user' || !practiceDataReady.value) return base;
|
||||||
|
|
||||||
|
const completed = countTodayPracticeCompletions();
|
||||||
|
const shownCompleted = Math.min(completed, todayPracticeTarget);
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
progressText: `${shownCompleted}/${todayPracticeTarget}`,
|
||||||
|
progress: Math.round((shownCompleted / todayPracticeTarget) * 100),
|
||||||
|
cta: completed >= todayPracticeTarget ? '再练一次' : '开始训练'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const latestPracticeReview = computed(() => practiceHistory.value[0] || null);
|
||||||
const ringStyle = (value: number) => ({ '--value': value } as Record<string, string | number>);
|
const ringStyle = (value: number) => ({ '--value': value } as Record<string, string | number>);
|
||||||
|
|
||||||
const iconPaths: Record<IconName, Array<[string, Record<string, string | number>?]>> = {
|
const iconPaths: Record<IconName, Array<[string, Record<string, string | number>?]>> = {
|
||||||
@@ -3081,6 +3246,129 @@ p {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scenario-section {
|
||||||
|
margin-top: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario-title {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 4px minmax(0, 1fr);
|
||||||
|
column-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario-title::before {
|
||||||
|
content: '';
|
||||||
|
width: 4px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 99px;
|
||||||
|
background: var(--primary-teal);
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario-title h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario-title p {
|
||||||
|
margin-top: 3px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.35;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario-card-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card,
|
||||||
|
.sop-action-card,
|
||||||
|
.report-card {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 13px;
|
||||||
|
text-align: left;
|
||||||
|
transition: var(--transition-spring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card:active,
|
||||||
|
.sop-action-card:active,
|
||||||
|
.report-card:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card span,
|
||||||
|
.sop-action-card span,
|
||||||
|
.report-card span {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card b,
|
||||||
|
.sop-action-card b,
|
||||||
|
.report-card b {
|
||||||
|
display: block;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card small,
|
||||||
|
.sop-action-card small,
|
||||||
|
.report-card small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-summary-card.empty {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sop-action-card {
|
||||||
|
min-height: 112px;
|
||||||
|
border-color: rgba(15, 174, 166, 0.28);
|
||||||
|
background: linear-gradient(135deg, #ecfffd 0%, #ffffff 72%);
|
||||||
|
box-shadow: 0 12px 28px rgba(9, 151, 144, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sop-action-card .icon-bubble {
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sop-action-card b {
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-card {
|
||||||
|
border-color: rgba(144, 131, 245, 0.18);
|
||||||
|
background: linear-gradient(135deg, #fbf7ff 0%, #ffffff 76%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.growth-section .section-card {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.practice-panel {
|
.practice-panel {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|||||||
Reference in New Issue
Block a user