feat(mobile): align employee practice screens for release

This commit is contained in:
2026-07-15 11:23:08 +08:00
parent 3541aab3fe
commit eacf1a0aea
16 changed files with 307 additions and 191 deletions
+1 -1
View File
@@ -143,7 +143,7 @@
},
"tabBar": {
"color": "#7b8494",
"selectedColor": "#2f6bff",
"selectedColor": "#e60012",
"backgroundColor": "#ffffff",
"borderStyle": "white",
"height": "66px",
+103 -63
View File
@@ -1,8 +1,8 @@
<template>
<view class="page practice-page employee-page-shell">
<view class="practice-head">
<text class="page-title">训练前预习</text>
<text class="page-subtitle">先掌握要点,再开始场景练习</text>
<text class="page-title">{{ practicePageTitle }}</text>
<text class="page-subtitle">{{ practicePageSubtitle }}</text>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
@@ -15,7 +15,7 @@
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<view v-if="loggedIn" class="panel scenario-card employee-card">
<view v-if="loggedIn && practiceView === 'prep'" class="panel scenario-card employee-card">
<view class="scenario-headline">
<text class="scenario-title">{{ activeScenarioName }}</text>
<text class="scenario-desc">{{ positionLabel }} · 业主服务场景</text>
@@ -53,7 +53,7 @@
</button>
</view>
<view v-if="loggedIn" class="panel assignment-panel recent-panel">
<view v-if="loggedIn && practiceView === 'prep'" class="panel assignment-panel recent-panel">
<view class="assignment-head">
<text class="panel-title">最近练过</text>
<text class="recent-link">全部 ›</text>
@@ -74,7 +74,7 @@
<view v-else class="empty">暂无最近练习</view>
</view>
<view v-if="loggedIn" class="panel assignment-panel daily-panel">
<view v-if="loggedIn && practiceView === 'prep'" class="panel assignment-panel daily-panel">
<view class="assignment-head">
<view><text class="panel-title">每日三题</text><text class="daily-subtitle">今日安排</text></view>
<text class="daily-action-label">去作答 ›</text>
@@ -123,13 +123,13 @@
</view>
</view>
<view v-if="loggedIn" class="capability-strip">
<view v-if="loggedIn && practiceView === 'prep'" class="capability-strip">
<view><text>情绪</text><text>识别与安抚</text></view>
<view><text>信任</text><text>建立与维护</text></view>
<view><text>红线纠偏</text><text>识别与规避</text></view>
</view>
<view v-if="turns.length" class="panel dialogue-card employee-card">
<view v-if="practiceView === 'active' && turns.length" class="panel dialogue-card employee-card">
<view class="dialogue-head">
<view class="dialogue-heading-block">
<text class="panel-title">对话</text>
@@ -197,7 +197,7 @@
<text v-if="draftAudioUrl" class="voice-state">已附带语音回放</text>
</view>
<view v-if="helpAnswer || helpRefs.length" class="panel help-card employee-card">
<view v-if="practiceView === 'active' && (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">
@@ -248,6 +248,11 @@
</button>
<text v-else class="satisfaction-thanks">感谢反馈,已记录本次训练体验。</text>
</view>
<view v-if="result" class="result-actions">
<button class="secondary-button" @click="openPracticeHistory">查看训练记录</button>
<button class="primary-button" @click="start(selectedScenarioId)">再练一次</button>
</view>
</view>
</template>
@@ -327,6 +332,19 @@ const draftAudioOssId = ref<number | undefined>();
const mediaRecorder = ref<MediaRecorder | null>(null);
const mediaStream = ref<MediaStream | null>(null);
const busy = computed(() => status.value === 'starting' || status.value === 'submitting');
const practiceView = computed<'prep' | 'active' | 'result'>(() => {
if (result.value) return 'result';
return status.value === 'active' || status.value === 'submitting' ? 'active' : 'prep';
});
const practicePageTitle = computed(() => {
if (practiceView.value === 'result') return '训练完成';
return practiceView.value === 'active' ? '场景对练' : '训练前预习';
});
const practicePageSubtitle = computed(() => {
if (practiceView.value === 'result') return `${activeScenarioName.value} · ${Math.max(roundIndex.value, 1)} 轮对话`;
if (practiceView.value === 'active') return `${activeScenarioName.value} · 第 ${Math.min(roundIndex.value + 1, 3)}/3 轮`;
return '先掌握要点,再开始场景练习';
});
const canStartPractice = computed(() => status.value === 'idle' || status.value === 'finished');
const hasTraineeReply = computed(() => turns.value.some((item) => item.role === 'trainee'));
const canFinishPractice = computed(() => hasTraineeReply.value && !redFlagActive.value);
@@ -643,6 +661,10 @@ const openRecentItem = (item: RecentPracticeItem) => {
uni.navigateTo({ url: '/pages/user/profile/index' });
};
const openPracticeHistory = () => {
uni.navigateTo({ url: '/pages/user/profile/index' });
};
const consumePendingAssignment = () => {
const raw = uni.getStorageSync(pendingAssignmentKey);
if (!raw) return;
@@ -1244,7 +1266,7 @@ onShow(() => {
}
.dialogue-card {
border-color: #dbe7ff;
border-color: #f3dadd;
box-shadow: var(--employee-card-shadow);
}
@@ -1306,11 +1328,11 @@ onShow(() => {
}
.emotion-fill {
background: #6f9cff;
background: #e60012;
}
.trust-fill {
background: #2f6bff;
background: #8c929a;
}
.red-flag {
@@ -1348,19 +1370,19 @@ onShow(() => {
}
.turn-row.trainee {
border-left-color: #2f6bff;
background: #eef4ff;
border-left-color: #e60012;
background: #fff0f1;
}
.turn-row.coach {
border-left-color: #8bb1ff;
background: #f4f7ff;
border-left-color: #ef8992;
background: #fff7f7;
}
.reply-actions button.recording-active {
border-color: #2f6bff;
background: #eef4ff;
color: #2f6bff;
border-color: #e60012;
background: #fff0f1;
color: #e60012;
}
.turn-role {
@@ -1384,9 +1406,9 @@ onShow(() => {
.style-chip {
width: auto;
border-color: #c9d9ff;
border-color: #f0c7cb;
background: #ffffff;
color: #2f6bff;
color: #e60012;
}
.reply-card {
@@ -1396,7 +1418,7 @@ onShow(() => {
.practice-textarea {
min-height: 124px;
border-color: #dbe7ff;
border-color: #f3dadd;
border-radius: 18px;
background: #f9fbff;
}
@@ -1416,8 +1438,8 @@ onShow(() => {
.help-ref {
padding: 5px 8px;
border-radius: 5px;
background: #eef4ff;
color: #2f6bff;
background: #fff0f1;
color: #e60012;
font-size: 12px;
font-weight: 800;
}
@@ -1429,7 +1451,7 @@ onShow(() => {
}
.result-score {
color: #2f6bff;
color: #e60012;
font-size: 44px;
font-weight: 900;
line-height: 1;
@@ -1447,13 +1469,13 @@ onShow(() => {
flex-direction: column;
gap: 6px;
padding: 12px;
border-left: 3px solid #2f6bff;
border-left: 3px solid #e60012;
border-radius: 6px;
background: #eef4ff;
background: #fff0f1;
}
.mentor-rewrite text:first-child {
color: #2f6bff;
color: #e60012;
font-weight: 900;
}
@@ -1481,9 +1503,9 @@ onShow(() => {
}
.satisfaction-option.selected {
border-color: #2f6bff;
background: #eef4ff;
color: #2f6bff;
border-color: #e60012;
background: #fff0f1;
color: #e60012;
}
.satisfaction-textarea {
@@ -1496,6 +1518,18 @@ onShow(() => {
font-weight: 800;
}
.result-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.result-actions button {
width: 100%;
min-width: 0;
}
/* 练页入口态:与员工端蓝白轻卡片体系保持一致 */
.practice-page {
padding: 0 16px 108px;
@@ -1504,16 +1538,16 @@ onShow(() => {
.practice-head {
margin: 0 -16px;
padding: 30px 20px 24px;
border-bottom: 1px solid var(--employee-line);
background: linear-gradient(180deg, #eef4ff 0%, #f5f8fd 100%);
text-align: left;
padding: 28px 20px 16px;
border-bottom: 0;
background: #f7f7f7;
text-align: center;
}
.practice-head::after,
.practice-eyebrow,
.practice-tip {
display: block;
display: none;
}
.practice-head .page-title {
@@ -1665,9 +1699,9 @@ onShow(() => {
}
.scenario-card .primary-button {
margin-top: 18px;
background: linear-gradient(135deg, #2f6bff, #5a8cff);
box-shadow: 0 8px 16px rgba(47, 107, 255, 0.18);
margin-top: 2px;
background: var(--employee-primary);
box-shadow: 0 8px 16px rgba(230, 0, 18, 0.18);
}
.login-panel {
@@ -1675,12 +1709,12 @@ onShow(() => {
}
.assignment-row {
border-color: #dbe7ff;
border-color: #f3dadd;
}
.assignment-row.highlighted {
border-color: #9bbaff;
background: #f4f7ff;
border-color: #ef8992;
background: #fff7f7;
}
.assignment-badge,
@@ -1803,8 +1837,8 @@ onShow(() => {
}
.daily-row .small-button {
background: linear-gradient(135deg, #2f6bff, #5a8cff);
box-shadow: 0 6px 12px rgba(47, 107, 255, 0.16);
background: linear-gradient(135deg, #ed1f28, #ff4b4d);
box-shadow: 0 6px 12px rgba(230, 0, 18, 0.16);
}
.capability-strip {
@@ -1843,7 +1877,7 @@ onShow(() => {
.practice-head .page-title {
color: #1f2024;
font-size: 29px;
font-size: 30px;
letter-spacing: 0.5px;
}
@@ -1852,36 +1886,43 @@ onShow(() => {
}
.scenario-card {
padding: 20px 16px 16px;
border-radius: 24px;
background: #ffffff;
box-shadow: 0 10px 26px rgba(24, 28, 36, 0.06);
padding: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.scenario-headline {
max-width: none;
min-height: 42px;
flex-direction: row;
align-items: center;
gap: 6px;
padding: 0 14px;
border: 1px solid #ffd1d5;
border-radius: 22px;
background: #fff7f7;
}
.scenario-title {
color: #25282d;
font-size: 27px;
color: var(--employee-primary);
font-size: 14px;
}
.scenario-desc {
color: #8a8d93;
font-size: 14px;
color: var(--employee-primary);
font-size: 13px;
}
.scenario-persona {
display: flex;
align-items: center;
gap: 12px;
margin-top: 2px;
margin-top: 14px;
padding: 14px;
border: 1px solid #ffd9dc;
border-radius: 16px;
background: #fff8f8;
border: 0;
border-radius: 20px;
background: #ffffff;
}
.persona-avatar {
@@ -1930,15 +1971,15 @@ onShow(() => {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 2px;
margin-top: 14px;
}
.prep-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px;
border: 1px solid #f0f1f3;
gap: 7px;
padding: 15px 16px;
border: 0;
border-radius: 20px;
background: #ffffff;
}
@@ -1950,8 +1991,7 @@ onShow(() => {
}
.prep-section-redline {
border-color: #ffe0e2;
background: #fffafa;
background: #ffffff;
}
.prep-section-redline .prep-section-title,
@@ -1964,7 +2004,7 @@ onShow(() => {
margin-top: 0;
color: #858990;
font-size: 13px;
line-height: 1.5;
line-height: 1.42;
}
.prep-mark {
+102 -100
View File
@@ -18,6 +18,7 @@
<text class="hero-role">{{ positionProfile.value }}</text>
<text class="hero-title">{{ positionProfile.heroTitle }}</text>
<text class="hero-sub">有问题,随时问老师傅</text>
<text class="hero-tip">{{ mentorBubbleText }}</text>
</view>
<view class="mentor-stage">
<view class="mentor-bubble">
@@ -95,7 +96,7 @@
<view class="schedule-list">
<view class="schedule-row" v-if="primaryScheduleTitle">
<view class="schedule-icon-wrap">
<uni-icons class="schedule-icon" type="compose" size="26" color="#2f6bff" />
<uni-icons class="schedule-icon" type="compose" size="26" color="#e60012" />
</view>
<view class="schedule-copy">
<text v-if="primaryAssignmentSourceLabel" class="schedule-source" :class="primaryAssignmentSourceClass">
@@ -190,8 +191,7 @@ const pendingAssignmentKey = 'aihr_pending_assignment_id';
const chipIcons = ['chatbubble', 'help', 'wallet'];
const chipIcon = (index: number) => chipIcons[index] || 'chatbubble';
const chipColors = ['#2f6bff', '#ff9b3d', '#34b368'];
const chipColor = (index: number) => chipColors[index] || '#2f6bff';
const chipColor = (_index: number) => '#e60012';
const mapTiles = computed(() => [
{
@@ -390,18 +390,15 @@ onShow(() => {
<style scoped>
.today-page {
min-height: 100vh;
padding: 0 24px 112px;
--employee-primary: #2f6bff;
--employee-primary-soft: #eaf1ff;
--employee-card-shadow: 0 16px 40px rgba(47, 107, 255, 0.1);
background: linear-gradient(180deg, #f4f8ff 0%, #f7faff 58%, #f3f5f7 100%);
padding: 0 16px 104px;
background: #f7f7f7;
}
.brand-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: calc(env(safe-area-inset-top) + 20px) 2px 10px;
padding: calc(env(safe-area-inset-top) + 12px) 2px 8px;
}
.brand-left {
@@ -411,8 +408,8 @@ onShow(() => {
}
.brand-logo {
width: 34px;
height: 34px;
width: 24px;
height: 24px;
}
.brand-name {
@@ -458,11 +455,13 @@ onShow(() => {
.mentor-hero {
position: relative;
display: flex;
min-height: 232px;
min-height: 196px;
align-items: flex-start;
justify-content: space-between;
margin-top: 2px;
padding: 6px 0 0;
padding: 0;
border-radius: 20px;
background: #ffffff;
overflow: hidden;
}
@@ -470,50 +469,58 @@ onShow(() => {
position: relative;
z-index: 2;
display: flex;
max-width: 45%;
width: 52%;
max-width: 52%;
flex-direction: column;
padding-top: 11px;
padding: 22px 0 0 20px;
}
.hero-role,
.hero-title {
color: #111111;
font-size: 42px;
font-weight: 900;
line-height: 1.12;
font-size: 30px;
font-weight: 500;
line-height: 1.5;
letter-spacing: 0;
}
.hero-sub {
margin-top: 18px;
margin-top: 2px;
color: #8a8d93;
font-size: 17px;
font-size: 14px;
white-space: nowrap;
line-height: 1.4;
}
.hero-tip {
margin-top: 7px;
color: var(--employee-primary);
font-size: 13px;
line-height: 1.4;
white-space: nowrap;
}
.mentor-stage {
position: relative;
z-index: 1;
width: 240px;
height: 227px;
width: 178px;
height: 196px;
flex: none;
}
.mentor-image {
position: absolute;
right: -20px;
bottom: 0;
width: 240px;
height: 240px;
transform: translateY(50px);
right: 4px;
bottom: 4px;
width: 140px;
height: 140px;
filter: drop-shadow(0 10px 16px rgba(20, 24, 32, 0.12));
}
.mentor-bubble {
position: absolute;
top: 38px;
right: 130px;
top: 10px;
right: 84px;
z-index: 3;
width: 112px;
padding: 10px 12px;
@@ -544,8 +551,8 @@ onShow(() => {
.ask-card {
position: relative;
z-index: 4;
margin-top: 0;
padding: 16px 14px 25px;
margin-top: 10px;
padding: 12px 14px 10px;
border: 0;
border-radius: 20px;
background: #ffffff;
@@ -557,7 +564,6 @@ onShow(() => {
align-items: center;
min-height: 82px;
gap: 10px;
transform: translateY(7px);
}
.voice-primary,
@@ -579,46 +585,37 @@ onShow(() => {
}
.voice-primary {
flex: 1.7;
min-height: 74px;
gap: 14px;
padding: 0 12px;
border-radius: 16px;
background: #2f6bff;
box-shadow: 0 10px 20px rgba(47, 107, 255, 0.22);
flex: 1.45;
min-height: 70px;
gap: 10px;
padding: 0;
}
.voice-secondary {
flex: 1;
gap: 10px;
min-height: 74px;
padding: 0 8px;
border: 1px solid #e4eaf4;
border-radius: 16px;
background: #ffffff;
min-height: 70px;
padding: 0;
}
.voice-divider {
width: 1px;
height: 42px;
flex: none;
background: #ececee;
display: none;
}
.voice-emblem {
display: flex;
width: 52px;
height: 52px;
width: 64px;
height: 64px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #ffffff;
background: var(--employee-primary);
box-shadow: none;
}
.voice-mark {
color: #2f6bff;
color: #ffffff;
font-size: 24px;
font-weight: 900;
line-height: 1;
@@ -640,7 +637,7 @@ onShow(() => {
}
.voice-primary .voice-title {
color: #ffffff;
color: #1a1a1a;
}
.secondary-title {
@@ -656,7 +653,7 @@ onShow(() => {
}
.voice-primary .voice-desc {
color: rgba(255, 255, 255, 0.78);
color: #9a9da3;
}
.secondary-copy {
@@ -671,12 +668,12 @@ onShow(() => {
.keyboard-mark {
display: flex;
width: 40px;
height: 40px;
width: 64px;
height: 64px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 50%;
border-radius: 16px;
background: #f3f4f6;
}
@@ -686,8 +683,8 @@ onShow(() => {
}
.mentor-chips {
margin-top: 14px;
padding-top: 12px;
margin-top: 4px;
padding-top: 8px;
border-top: 1px solid #f0f1f3;
}
@@ -709,17 +706,17 @@ onShow(() => {
border: 1px solid #ececee;
border-radius: 12px;
background: #ffffff;
color: #2f6bff;
color: var(--employee-primary);
font-size: 13px;
font-weight: 700;
}
.chip-1 {
color: #ff9b3d;
color: var(--employee-primary);
}
.chip-2 {
color: #34b368;
color: var(--employee-primary);
}
.chip-icon {
@@ -741,8 +738,8 @@ onShow(() => {
}
.section-card {
margin-top: 12px;
padding: 22px 18px;
margin-top: 10px;
padding: 16px;
border: 0;
border-radius: 22px;
background: #ffffff;
@@ -789,7 +786,7 @@ onShow(() => {
color: #ffffff;
font-size: 15px;
font-weight: 800;
box-shadow: 0 8px 16px rgba(47, 107, 255, 0.2);
box-shadow: 0 8px 16px rgba(230, 0, 18, 0.18);
}
.section-head {
@@ -804,7 +801,7 @@ onShow(() => {
align-items: center;
gap: 8px;
color: #1a1a1a;
font-size: 22px;
font-size: 20px;
font-weight: 800;
}
@@ -812,7 +809,7 @@ onShow(() => {
width: 4px;
height: 22px;
border-radius: 2px;
background: #2f6bff;
background: var(--employee-primary);
}
.section-link {
@@ -839,21 +836,21 @@ onShow(() => {
.schedule-row {
display: flex;
align-items: center;
gap: 16px;
padding: 14px 12px;
border-radius: 18px;
background: #f8fbff;
gap: 12px;
padding: 12px 10px;
border-radius: 16px;
background: #fafafa;
}
.schedule-icon-wrap {
display: flex;
width: 60px;
height: 60px;
width: 52px;
height: 52px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #eaf1ff;
background: var(--employee-primary-soft);
}
.review-icon {
@@ -874,7 +871,7 @@ onShow(() => {
.schedule-title {
color: #1a1a1a;
font-size: 19px;
font-size: 17px;
font-weight: 800;
line-height: 1.25;
}
@@ -933,8 +930,8 @@ onShow(() => {
align-items: center;
justify-content: center;
border-radius: 12px;
background: #2f6bff;
box-shadow: 0 8px 16px rgba(47, 107, 255, 0.22);
background: var(--employee-primary);
box-shadow: 0 8px 16px rgba(230, 0, 18, 0.18);
color: #ffffff;
font-size: 18px;
font-weight: 800;
@@ -953,12 +950,12 @@ onShow(() => {
.map-tile {
display: flex;
min-height: 108px;
min-height: 70px;
align-items: center;
gap: 10px;
padding: 16px 14px;
border: 0;
border-radius: 16px;
padding: 10px 8px;
border: 1px solid #e2e4e8;
border-radius: 14px;
background: #ffffff;
text-align: left;
overflow: hidden;
@@ -968,46 +965,51 @@ onShow(() => {
.map-tile.practice,
.map-tile.learn,
.map-tile.report {
color: #ffffff;
border-color: transparent;
box-shadow: 0 10px 18px rgba(24, 28, 36, 0.08);
color: #25282d;
background: #ffffff;
box-shadow: none;
}
.map-tile.ask { background: #2f6bff; }
.map-tile.practice { background: #2ab673; }
.map-tile.learn { background: #ff961f; }
.map-tile.report { background: #ff6357; }
.map-tile.ask,
.map-symbol.ask { --map-accent: #e60012; }
.map-tile.practice,
.map-symbol.practice { --map-accent: #16a052; }
.map-tile.learn,
.map-symbol.learn { --map-accent: #f28b00; }
.map-tile.report,
.map-symbol.report { --map-accent: #526075; }
.map-symbol {
display: flex;
width: 56px;
height: 56px;
width: 44px;
height: 44px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 16px;
border-radius: 50%;
background: var(--map-accent);
color: #ffffff;
font-size: 24px;
font-weight: 900;
}
.map-symbol.ask {
background: rgba(255, 255, 255, 0.2);
background: var(--map-accent);
box-shadow: none;
}
.map-symbol.practice {
background: rgba(255, 255, 255, 0.2);
background: var(--map-accent);
box-shadow: none;
}
.map-symbol.learn {
background: rgba(255, 255, 255, 0.2);
background: var(--map-accent);
box-shadow: none;
}
.map-symbol.report {
background: rgba(255, 255, 255, 0.2);
background: var(--map-accent);
box-shadow: none;
}
@@ -1020,21 +1022,21 @@ onShow(() => {
}
.map-title {
color: #ffffff;
font-size: 18px;
color: #25282d;
font-size: 16px;
font-weight: 800;
line-height: 1.2;
}
.map-desc {
color: rgba(255, 255, 255, 0.82);
font-size: 13px;
color: #8a8d93;
font-size: 11px;
line-height: 1.35;
white-space: pre-line;
}
.map-arrow {
color: #ffffff;
color: #9da1a8;
font-size: 18px;
flex: none;
}
+3 -3
View File
@@ -21,8 +21,8 @@ page {
--employee-bg: #f4f6fb;
--employee-bg-soft: #f8fbff;
--employee-surface: #ffffff;
--employee-primary: #2f6bff;
--employee-primary-soft: #eaf1ff;
--employee-primary: #e60012;
--employee-primary-soft: #fff0f1;
--employee-success: #34b368;
--employee-warning: #ff9b3d;
--employee-danger: #ff6b5f;
@@ -31,7 +31,7 @@ page {
--employee-line: rgba(47, 74, 117, 0.08);
--employee-card-radius: 24px;
--employee-chip-radius: 18px;
--employee-card-shadow: 0 16px 40px rgba(47, 107, 255, 0.1);
--employee-card-shadow: 0 16px 40px rgba(230, 0, 18, 0.08);
}
view,