diff --git a/mobile-uni/src/pages/supervisor/profile/index.vue b/mobile-uni/src/pages/supervisor/profile/index.vue index 5bba77b6..f7d5682d 100644 --- a/mobile-uni/src/pages/supervisor/profile/index.vue +++ b/mobile-uni/src/pages/supervisor/profile/index.vue @@ -2,30 +2,69 @@ - 我的主管工作台 - 账号与主管身份 + 我的工作台 + 主管角色、项目范围与业务入口 + {{ message }} + + + 管 + + 项目主管 + + 团队画像、复盘、专项派发 + + + + + + {{ loading ? '--' : teamCount }} + 团队成员 + + + {{ loading ? '--' : pendingReviewCount }} + 待复盘 + + + {{ loading ? '--' : alertCount }} + 风险预警 + + + + + 主管业务入口 + + + + + - 当前账号 - - - - - - 主管身份 - - 角色 - 项目主管 - - - 可操作范围 - 团队画像、复盘、专项派发 + + 角色与账号 + + - - @@ -36,74 +75,218 @@ import { onShow } from '@dcloudio/uni-app'; import SupervisorTabBar from '@/components/SupervisorTabBar.vue'; import { clearAuth, getAuth, isLoggedIn, maskPhone, rememberLoginRedirect } from '@/services/auth'; import { resetPageScroll } from '@/services/navigation'; +import { getPracticeAlerts, getPracticeReviews, getPracticeTeamSnapshot } from '@/services/practice'; +import { actionablePracticeAlerts, buildTeamProfiles, isSupervisorAccessDenied } from '@/services/supervisor-view'; +const currentPath = '/pages/supervisor/profile/index'; const phone = ref(''); +const teamCount = ref(0); +const pendingReviewCount = ref(0); +const alertCount = ref(0); +const loading = ref(false); +const message = ref(''); const displayPhone = computed(() => maskPhone(phone.value)); const refreshAuth = () => { phone.value = getAuth().phone; }; +const clearMetrics = () => { + teamCount.value = 0; + pendingReviewCount.value = 0; + alertCount.value = 0; +}; + +const loadMetrics = async () => { + if (loading.value) return; + loading.value = true; + message.value = ''; + try { + const [reviews, alerts, snapshot] = await Promise.all([ + getPracticeReviews(), + getPracticeAlerts(), + getPracticeTeamSnapshot() + ]); + teamCount.value = buildTeamProfiles(snapshot.records, snapshot.assignments, snapshot.members).length; + pendingReviewCount.value = reviews.length; + alertCount.value = actionablePracticeAlerts(alerts).length; + } catch (error) { + clearMetrics(); + message.value = isSupervisorAccessDenied(error) + ? '当前账号无主管权限' + : error instanceof Error ? error.message : '主管数据加载失败'; + } finally { + loading.value = false; + } +}; + +const openPage = (url: string) => { + uni.navigateTo({ url }); +}; + const logout = () => { clearAuth(); - rememberLoginRedirect('/pages/supervisor/profile/index'); + rememberLoginRedirect(currentPath); uni.redirectTo({ url: '/pages/auth/login/index' }); }; onShow(() => { resetPageScroll(); if (!isLoggedIn()) { - rememberLoginRedirect('/pages/supervisor/profile/index'); + rememberLoginRedirect(currentPath); uni.redirectTo({ url: '/pages/auth/login/index' }); return; } refreshAuth(); + void loadMetrics(); }); diff --git a/mobile-uni/tests/supervisor-view.test.mjs b/mobile-uni/tests/supervisor-view.test.mjs index fcf5210f..004ceef3 100644 --- a/mobile-uni/tests/supervisor-view.test.mjs +++ b/mobile-uni/tests/supervisor-view.test.mjs @@ -289,6 +289,19 @@ test('员工和主管个人页只展示脱敏手机号', async () => { assert.doesNotMatch(supervisorProfile, /\{\{ phone \|\|/); }); +test('主管个人工作台展示真实指标和三项业务入口', async () => { + const source = await readFile(new URL('../src/pages/supervisor/profile/index.vue', import.meta.url), 'utf8'); + + assert.match(source, /\{\{ loading \? '--' : teamCount \}\}/); + assert.match(source, /\{\{ loading \? '--' : pendingReviewCount \}\}/); + assert.match(source, /\{\{ loading \? '--' : alertCount \}\}/); + assert.match(source, /主管业务入口/); + assert.match(source, /\/pages\/supervisor\/team\/index/); + assert.match(source, /\/pages\/supervisor\/reviews\/index/); + assert.match(source, /\/pages\/supervisor\/assign\/index/); + assert.match(source, /getPracticeTeamSnapshot/); +}); + test('组织快照姓名不可读时用稳定身份标识展示', () => { const profiles = buildTeamProfiles([], [], [ { extPartyId: '13900001111', name: '???????' },