fix(mobile): prioritize practice actions above guidance
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<text class="page-subtitle">{{ practicePageSubtitle }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="message" class="status-message">{{ message }}</view>
|
||||
<view v-if="message && (!loggedIn || practiceView !== 'prep')" class="status-message">{{ message }}</view>
|
||||
|
||||
<view v-if="!loggedIn" class="panel login-panel employee-card">
|
||||
<view>
|
||||
@@ -15,38 +15,46 @@
|
||||
<button class="primary-button login-button" @click="goLogin">去登录</button>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel growth-card employee-card">
|
||||
<view class="growth-card-head">
|
||||
<view>
|
||||
<text class="panel-title">我的成长位置</text>
|
||||
<text class="growth-meta">住宅物业 · {{ positionLabel }} · {{ currentGrowthLevel }}</text>
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel scenario-card realtime-entry-card employee-card">
|
||||
<view class="realtime-entry-head">
|
||||
<view class="realtime-title-row">
|
||||
<text class="scenario-title">实时陪练</text>
|
||||
<text class="realtime-beta-badge">Beta</text>
|
||||
</view>
|
||||
<text class="growth-level-badge">{{ currentGrowthLevel }}</text>
|
||||
<text class="scenario-desc">选一个角色,直接开口练;无需等待场景发布。</text>
|
||||
</view>
|
||||
<text v-if="growthConfirmation" class="growth-confirmation">
|
||||
主管已确认可进入{{ growthConfirmation.confirmedGrowthLevel }}{{ growthConfirmation.confirmedTime ? ` · ${growthConfirmation.confirmedTime}` : '' }}
|
||||
</text>
|
||||
<view v-if="growthPath.length" class="growth-path-list">
|
||||
<view v-for="item in growthPath" :key="item.level" class="growth-path-row" :class="{ reached: item.status === '已达成' }">
|
||||
<view class="growth-path-copy">
|
||||
<text class="growth-path-level">{{ item.level }}</text>
|
||||
<text class="growth-path-evidence">{{ item.evidence }}</text>
|
||||
<view v-if="realtimePersonaOptions.length" class="realtime-persona-picker">
|
||||
<text class="realtime-persona-title">选择陪练角色</text>
|
||||
<scroll-view class="scene-option-scroll realtime-persona-scroll" scroll-x>
|
||||
<view class="scene-option-list">
|
||||
<button
|
||||
v-for="item in realtimePersonaOptions"
|
||||
:key="item.id"
|
||||
class="scene-option realtime-persona-option"
|
||||
:class="{ selected: selectedRealtimePersonaId === item.id }"
|
||||
:disabled="busy"
|
||||
@click="selectRealtimePersona(item)"
|
||||
>
|
||||
<text class="scene-option-name">{{ item.name }}</text>
|
||||
<text class="scene-option-capability">{{ item.hasTools ? '可查询知识库' : '纯对练' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
<text class="growth-path-status">{{ item.status }} · {{ item.progress }}%</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="capabilityProgress.length" class="capability-progress-list">
|
||||
<text class="capability-progress-title">已覆盖能力项</text>
|
||||
<view v-for="item in capabilityProgress.slice(0, 3)" :key="`${item.growthLevel}-${item.competencyCode}`" class="capability-progress-row">
|
||||
<text>{{ item.growthLevel }} · {{ item.competencyCode }}</text>
|
||||
<text>{{ item.completed }} 次 · 均分 {{ item.averageScore }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-else class="growth-empty">完成后会按“岗位 · 层级 · 能力项”记录训练证据;历史未归类记录不会被倒推改写。</text>
|
||||
<text class="growth-note">这是训练建议与证据展示,不自动改变职级、薪酬或晋升资格。</text>
|
||||
<button
|
||||
v-if="status === 'idle' || status === 'finished'"
|
||||
class="primary-button realtime-start-button"
|
||||
:disabled="!canStartRealtime || busy"
|
||||
@click="startRealtime"
|
||||
>
|
||||
开始实时陪练
|
||||
</button>
|
||||
<text class="realtime-beta-note">不计入成绩,可随时结束</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel scene-picker-card employee-card">
|
||||
<view v-if="message && loggedIn && practiceView === 'prep'" class="status-message status-message-compact">{{ message }}</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep' && (curriculumLoading || availableScenarios.length)" class="panel scene-picker-card employee-card">
|
||||
<view class="scene-picker-head">
|
||||
<view>
|
||||
<text class="panel-title">场景训练</text>
|
||||
@@ -71,7 +79,14 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text v-else-if="!curriculumLoading" class="scene-picker-empty">当前岗位暂无已发布场景,请联系内容运营完成审核发布。</text>
|
||||
<text class="scene-picker-note">复杂场景的正式前置解锁规则仍待业务确认;系统不会以估算分数硬性拦截已发布训练。</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="loggedIn && practiceView === 'prep'" class="panel scene-empty-card employee-card">
|
||||
<view>
|
||||
<text class="panel-title">常规场景</text>
|
||||
<text class="scene-picker-subtitle">发布后可录音对练并保存成绩</text>
|
||||
</view>
|
||||
<text class="scene-empty-state">暂未开放</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep' && availableScenarios.length" class="panel scenario-card practice-scenario-card employee-card">
|
||||
@@ -112,41 +127,50 @@
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel scenario-card realtime-entry-card employee-card">
|
||||
<view class="scenario-headline">
|
||||
<text class="scenario-title">实时陪练 Beta</text>
|
||||
<text class="scenario-desc">直接与数字师傅或模拟业主对话,不依赖场景审核发布。</text>
|
||||
</view>
|
||||
<view v-if="realtimePersonaOptions.length" class="realtime-persona-picker">
|
||||
<view class="realtime-persona-heading">
|
||||
<text class="realtime-persona-title">实时陪练人设</text>
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel growth-card employee-card">
|
||||
<view class="growth-card-head">
|
||||
<view>
|
||||
<text class="panel-title">我的成长</text>
|
||||
<text class="growth-meta">住宅物业 · {{ positionLabel }}</text>
|
||||
</view>
|
||||
<scroll-view class="scene-option-scroll" scroll-x>
|
||||
<view class="scene-option-list">
|
||||
<button
|
||||
v-for="item in realtimePersonaOptions"
|
||||
:key="item.id"
|
||||
class="scene-option"
|
||||
:class="{ selected: selectedRealtimePersonaId === item.id }"
|
||||
:disabled="busy"
|
||||
@click="selectRealtimePersona(item)"
|
||||
>
|
||||
<text class="scene-option-level">{{ item.hasTools ? '可查询知识库' : '纯对练' }}</text>
|
||||
<text class="scene-option-name">{{ item.name }}</text>
|
||||
<text class="scene-option-capability">{{ item.description }}</text>
|
||||
</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text class="growth-level-badge">{{ currentGrowthLevel }}</text>
|
||||
</view>
|
||||
<button
|
||||
v-if="status === 'idle' || status === 'finished'"
|
||||
class="secondary-button realtime-start-button"
|
||||
:disabled="!canStartRealtime || busy"
|
||||
@click="startRealtime"
|
||||
>
|
||||
开始实时陪练
|
||||
<view class="growth-summary">
|
||||
<view class="growth-summary-copy">
|
||||
<text>当前进度</text>
|
||||
<text>{{ currentGrowthProgress }}%</text>
|
||||
</view>
|
||||
<view class="growth-summary-track" aria-hidden="true">
|
||||
<view class="growth-summary-fill" :style="{ width: `${currentGrowthProgress}%` }" />
|
||||
</view>
|
||||
</view>
|
||||
<button class="growth-toggle" :aria-expanded="growthDetailsExpanded" @click="growthDetailsExpanded = !growthDetailsExpanded">
|
||||
{{ growthDetailsExpanded ? '收起成长规则' : '查看成长规则' }}
|
||||
<text aria-hidden="true">{{ growthDetailsExpanded ? '⌃' : '⌄' }}</text>
|
||||
</button>
|
||||
<text class="realtime-beta-note">自然打断、边听边说;本次体验不计入训练成绩。</text>
|
||||
<view v-if="growthDetailsExpanded" class="growth-details">
|
||||
<text v-if="growthConfirmation" class="growth-confirmation">
|
||||
主管已确认可进入{{ growthConfirmation.confirmedGrowthLevel }}{{ growthConfirmation.confirmedTime ? ` · ${growthConfirmation.confirmedTime}` : '' }}
|
||||
</text>
|
||||
<view v-if="growthPath.length" class="growth-path-list">
|
||||
<view v-for="item in growthPath" :key="item.level" class="growth-path-row" :class="{ reached: item.status === '已达成' }">
|
||||
<view class="growth-path-copy">
|
||||
<text class="growth-path-level">{{ item.level }}</text>
|
||||
<text class="growth-path-evidence">{{ item.evidence }}</text>
|
||||
</view>
|
||||
<text class="growth-path-status">{{ item.status }} · {{ item.progress }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="capabilityProgress.length" class="capability-progress-list">
|
||||
<text class="capability-progress-title">已覆盖能力项</text>
|
||||
<view v-for="item in capabilityProgress.slice(0, 3)" :key="`${item.growthLevel}-${item.competencyCode}`" class="capability-progress-row">
|
||||
<text>{{ item.growthLevel }} · {{ item.competencyCode }}</text>
|
||||
<text>{{ item.completed }} 次 · 均分 {{ item.averageScore }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-else class="growth-empty">完成训练后,这里会显示岗位、层级和能力项证据。</text>
|
||||
<text class="growth-note">成长记录仅作训练建议,不自动改变职级、薪酬或晋升资格。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn && practiceView === 'prep'" class="panel assignment-panel recent-panel">
|
||||
@@ -272,6 +296,11 @@
|
||||
|
||||
<scroll-view scroll-y class="chat-thread" :scroll-top="threadScrollTop" :scroll-with-animation="true">
|
||||
<view class="thread-inner">
|
||||
<view v-if="realtimeActive && !turns.length" class="realtime-listening-empty">
|
||||
<text class="realtime-listening-label">{{ realtimeStatusLabel }}</text>
|
||||
<text class="realtime-listening-title">直接开口说话</text>
|
||||
<text class="realtime-listening-copy">实时对话会显示在这里</text>
|
||||
</view>
|
||||
<template v-for="(turn, index) in turns" :key="index">
|
||||
<view v-if="turn.role === 'customer'" class="bubble-row left">
|
||||
<view class="avatar customer-avatar">业</view>
|
||||
@@ -536,6 +565,7 @@ const realtimeAudioBlocked = ref(false);
|
||||
const realtimeSessionId = ref('');
|
||||
const realtimePersonaOptions = ref<RealtimePersonaOption[]>([]);
|
||||
const selectedRealtimePersonaId = ref(typeof storedRealtimePersona === 'string' ? storedRealtimePersona : '');
|
||||
const growthDetailsExpanded = ref(false);
|
||||
const assignments = ref<PracticeAssignment[]>([]);
|
||||
const practiceHistory = ref<PracticeRecord[]>([]);
|
||||
const drillAnswers = ref<Record<number, string>>({});
|
||||
@@ -585,12 +615,12 @@ const practiceView = computed<'prep' | 'active' | 'result'>(() => {
|
||||
});
|
||||
const practicePageTitle = computed(() => {
|
||||
if (practiceView.value === 'result') return '训练完成';
|
||||
return practiceView.value === 'active' ? '场景对练' : '训练前预习';
|
||||
return practiceView.value === 'active' ? '场景对练' : 'AI 陪练';
|
||||
});
|
||||
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, activeRoundCount.value)}/${activeRoundCount.value} 轮`;
|
||||
return '先掌握要点,再开始场景练习';
|
||||
return '选一种方式,马上开始';
|
||||
});
|
||||
const selectedScenario = computed(() =>
|
||||
availableScenarios.value.find((item) => item.id === selectedScenarioId.value)
|
||||
@@ -624,6 +654,9 @@ const currentGrowthLevel = computed(() => {
|
||||
return ['初级', '中级', '高级'].includes(level) ? level : '初级';
|
||||
});
|
||||
const growthPath = computed<GrowthStage[]>(() => competencyProfile.value?.growthPath || []);
|
||||
const currentGrowthProgress = computed(() =>
|
||||
growthPath.value.find((item) => item.level === currentGrowthLevel.value)?.progress || 0
|
||||
);
|
||||
const capabilityProgress = computed<CapabilityProgress[]>(() =>
|
||||
(competencyProfile.value?.capabilityProgress || [])
|
||||
.filter((item) => !item.position || item.position === positionLabel.value)
|
||||
@@ -1033,6 +1066,7 @@ const startRealtime = async () => {
|
||||
const session = await createRealtimeSession(selectedRealtimePersonaId.value || undefined, selectedScenarioId.value || undefined);
|
||||
if (!realtimeActive.value) return;
|
||||
realtimeSessionId.value = session.sessionId;
|
||||
activeScenarioName.value = session.personaName || '实时陪练';
|
||||
await realtimePractice.start(session.sessionUpdate, session.sessionId);
|
||||
if (!realtimeActive.value) return;
|
||||
status.value = 'active';
|
||||
@@ -3552,4 +3586,244 @@ onUnload(() => {
|
||||
.practice-chat .chat-composer-wrap {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* 首屏只承载开始训练;规则和运营说明按需展开。 */
|
||||
.practice-head {
|
||||
padding: 18px 20px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.practice-head .page-title {
|
||||
font-size: 24px;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.practice-head .page-subtitle {
|
||||
margin-top: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.realtime-entry-card {
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
padding: 18px;
|
||||
border: 1px solid #f2d9dc;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(150deg, #ffffff 0%, #fff7f7 100%);
|
||||
box-shadow: 0 8px 22px rgba(41, 29, 31, 0.06);
|
||||
}
|
||||
|
||||
.realtime-entry-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.realtime-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.realtime-entry-card .scenario-title {
|
||||
color: #25282d;
|
||||
font-size: 20px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.realtime-entry-card .scenario-desc {
|
||||
color: #6f7784;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.realtime-beta-badge {
|
||||
padding: 3px 7px;
|
||||
border-radius: 999px;
|
||||
background: #fff0f1;
|
||||
color: var(--employee-primary);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.realtime-persona-picker {
|
||||
margin-top: 0;
|
||||
padding: 10px 0 0;
|
||||
border: 0;
|
||||
border-top: 1px solid #f1e3e4;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.realtime-persona-title {
|
||||
color: #5e6672;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.realtime-persona-scroll {
|
||||
margin: 8px -18px 0;
|
||||
}
|
||||
|
||||
.realtime-persona-option {
|
||||
width: 124px;
|
||||
min-height: 66px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 13px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.realtime-persona-option .scene-option-name {
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.realtime-persona-option .scene-option-capability {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.scenario-card .realtime-start-button {
|
||||
min-height: 48px;
|
||||
margin-top: 0;
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, #ed1f28, #ff4b4d);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8px 16px rgba(231, 0, 20, 0.18);
|
||||
}
|
||||
|
||||
.realtime-beta-note {
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: #fff8e8;
|
||||
color: #875300;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.status-message-compact {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.scene-picker-card,
|
||||
.scene-empty-card,
|
||||
.growth-card {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.scene-empty-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.scene-empty-state {
|
||||
flex: 0 0 auto;
|
||||
color: #8a919d;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.growth-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.growth-summary {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.growth-summary-copy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #697488;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.growth-summary-track {
|
||||
height: 6px;
|
||||
margin-top: 7px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: #edf1f5;
|
||||
}
|
||||
|
||||
.growth-summary-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--employee-primary);
|
||||
}
|
||||
|
||||
.growth-toggle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 10px 0 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-top: 1px solid #eef1f4;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #4e5d70;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.growth-toggle::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.growth-details {
|
||||
border-top: 1px solid #eef1f4;
|
||||
}
|
||||
|
||||
.growth-details .growth-path-evidence,
|
||||
.growth-details .growth-path-status,
|
||||
.growth-details .growth-empty,
|
||||
.growth-details .growth-note {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.realtime-listening-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #687487;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.realtime-listening-label {
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: #edf8f2;
|
||||
color: #168243;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.realtime-listening-title {
|
||||
margin-top: 14px;
|
||||
color: #25282d;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.realtime-listening-copy {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -378,9 +378,15 @@ test('场景目录加载失败时清空旧场景并禁止开始训练', async ()
|
||||
|
||||
test('无已发布场景时隐藏常规训练,但仍展示独立实时陪练入口', async () => {
|
||||
const source = await pageSource('../src/pages/user/practice/index.vue');
|
||||
const realtimeIndex = source.indexOf('class="panel scenario-card realtime-entry-card employee-card"');
|
||||
const sceneIndex = source.indexOf('class="panel scene-picker-card employee-card"');
|
||||
const growthIndex = source.indexOf('class="panel growth-card employee-card"');
|
||||
|
||||
assert.match(source, /v-if="loggedIn && practiceView === 'prep' && availableScenarios\.length" class="panel scenario-card practice-scenario-card employee-card"/);
|
||||
assert.match(source, /v-if="loggedIn && practiceView === 'prep'" class="panel scenario-card realtime-entry-card employee-card"/);
|
||||
assert.ok(realtimeIndex > -1 && realtimeIndex < sceneIndex && sceneIndex < growthIndex, '核心陪练入口必须排在场景和成长说明之前');
|
||||
assert.match(source, /v-if="growthDetailsExpanded" class="growth-details"/);
|
||||
assert.match(source, /return '选一种方式,马上开始';/);
|
||||
assert.match(source, /:disabled="!canStartRealtime \|\| busy"/);
|
||||
assert.match(source, /if \(!canStartRealtime\.value \|\| !requireLogin\(\)\) return;/);
|
||||
assert.doesNotMatch(source, /if \(!canStartPractice\.value \|\| !requireLogin\(\)\) return;\s*speechPlayback\.stop\(\);\s*cancelRecording\(\);\s*turnVoiceGeneration \+= 1;/);
|
||||
|
||||
@@ -50,12 +50,14 @@ test('实时陪练通过受认证代理接入,前端不携带供应商密钥
|
||||
|
||||
// 页面:人设卡片选择 + 本地持久化;音色 picker 已删除
|
||||
assert.match(page, /实时陪练 Beta/);
|
||||
assert.match(page, /实时陪练人设/);
|
||||
assert.match(page, /选择陪练角色/);
|
||||
assert.match(page, /aihr_realtime_practice_persona/);
|
||||
assert.match(page, /selectRealtimePersona/);
|
||||
assert.match(page, /fetchRealtimePersonas/);
|
||||
assert.match(page, /createRealtimeSession/);
|
||||
assert.match(page, /正在查知识库/);
|
||||
assert.match(page, /activeScenarioName\.value = session\.personaName \|\| '实时陪练'/);
|
||||
assert.match(page, /realtimeActive && !turns\.length/);
|
||||
assert.match(page, /class="panel scenario-card realtime-entry-card employee-card"/);
|
||||
assert.match(page, /const canStartRealtime = computed/);
|
||||
assert.match(page, /if \(!canStartRealtime\.value \|\| !requireLogin\(\)\) return;/);
|
||||
|
||||
Reference in New Issue
Block a user