feat(mobile): unify page states with shared StateBlock component

This commit is contained in:
2026-07-21 01:02:39 +08:00
parent ee28465c30
commit 2198a92bae
14 changed files with 407 additions and 186 deletions
@@ -11,16 +11,16 @@
</button>
</view>
<view v-if="message && !accessDenied" class="status-message">{{ message }}</view>
<view v-if="message && !accessDenied && detail" class="status-message">{{ message }}</view>
<view v-if="accessDenied" class="panel supervisor-access-panel">
<text class="panel-title">无主管权限</text>
<text class="muted supervisor-access-copy">当前账号不是主管或项目经理,无法查看团队训练数据。</text>
<view class="supervisor-access-actions">
<button class="secondary-button" @click="goToEmployee">返回员工端</button>
<button class="primary-button" @click="switchAccount">切换账号</button>
</view>
</view>
<StateBlock
v-if="accessDenied"
type="access"
action-text="切换账号"
secondary-action-text="返回员工端"
@action="switchAccount"
@secondary-action="goToEmployee"
/>
<view v-else-if="!loggedIn" class="panel login-panel">
<view>
@@ -30,6 +30,10 @@
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<StateBlock v-else-if="loadFailed" type="error" action-text="重新加载" @action="loadDetail" />
<StateBlock v-else-if="firstLoading" type="loading" title="复盘详情加载中…" />
<template v-else-if="detail">
<view class="panel head-panel">
<view class="head-main">
@@ -206,10 +210,13 @@
</view>
</template>
<view v-else-if="loggedIn && !loading" class="panel empty-panel">
<view class="empty">{{ reviewId ? '复盘详情暂不可用' : '缺少有效的复盘ID' }}</view>
<button class="secondary-button back-list-button" @click="goToReviews">返回待复盘</button>
</view>
<StateBlock
v-else-if="loggedIn && !loading"
type="empty"
:title="reviewId ? '复盘详情暂不可用' : '缺少有效的复盘ID'"
action-text="返回待复盘"
@action="goToReviews"
/>
<SupervisorTabBar v-if="loggedIn && !accessDenied" active="review" />
</view>
</template>
@@ -223,6 +230,7 @@ import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { getPracticeReviewDetail, getPracticeScenarios, getPracticeTeamSnapshot, markPracticeReviewed } from '@/services/practice';
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
import StateBlock from '@/components/StateBlock.vue';
import {
createAsyncGenerationGuard,
isReviewEditable,
@@ -251,6 +259,8 @@ const audioErrors = ref<Record<string, string>>({});
const audioRequests = createAsyncGenerationGuard();
const memberNames = ref<Record<string, string>>({});
const editable = computed(() => isReviewEditable(detail.value?.status));
const loadFailed = computed(() => !loading.value && !!message.value && !detail.value);
const firstLoading = computed(() => loading.value && !detail.value && !message.value);
const displayName = computed(() => {
const identity = (detail.value?.extPartyId || '').trim();
@@ -588,19 +598,6 @@ onBeforeUnmount(() => {
line-height: 1.5;
}
.supervisor-access-copy {
display: block;
margin-top: 8px;
font-size: 14px;
line-height: 1.5;
}
.supervisor-access-actions {
display: flex;
gap: 10px;
margin-top: 14px;
}
.panel-head {
display: flex;
align-items: center;
@@ -1161,12 +1158,4 @@ onBeforeUnmount(() => {
font-size: 13px;
line-height: 1.5;
}
.empty-panel {
padding-bottom: 18px;
}
.back-list-button {
width: 100%;
}
</style>