@@ -0,0 +1,83 @@
< template >
< view class = "page supervisor-result-page" >
< view class = "page-head" >
< button class = "back" @click ="goBack" > ‹ < / button >
< view class = "head-copy" > < text class = "title" > 团队今日成果 < / text > < text class = "subtitle" > { { result ? . projectName || currentProject ? . projectName || '当前项目' } } · { { workDate } } < / text > < / view >
< button class = "refresh" @click ="load" > 刷新 < / button >
< / view >
< view v-if = "message" class="message" > {{ message }} < / view >
< view v-if = "result" class="metrics" >
< view > < text class = "num" > { { result . contributorCount } } < / text > < text > 今日记录人数 < / text > < / view >
< view > < text class = "num" > { { result . resultCount } } < / text > < text > 已形成成果 < / text > < / view >
< view > < text class = "num warn" > { { result . pendingCount } } < / text > < text > 待跟进 < / text > < / view >
< view > < text class = "num danger" > { { result . highPriorityCount } } < / text > < text > 高优问题 < / text > < / view >
< / view >
< view v-for = "member in result?.members || []" :key="member.ownerUserId" class="member-card" >
< view class = "member-head" >
< view > < text class = "member-name" > { { member . ownerName } } < / text > < text class = "member-meta" > { { member . total } } 条记录 · { { member . pending } } 项待跟进 < / text > < / view >
< text class = "formed" > { { member . total ? '已形成成果' : '暂无记录' } } < / text >
< / view >
< view v-for = "item in memberItems(member)" :key="item.captureId" class="member-item" >
< view class = "item-copy" >
< text class = "item-type" > { { item . category } } < / text >
< text class = "item-summary" > { { item . summary } } < / text >
< text class = "item-source" > { { sourceLabel ( item . sourceType ) } } < / text >
< / view >
< view class = "item-actions" >
< button v-if = "item.sourceType !== 'TEXT'" class="source-action" @click="openSource(item)" > 原始来源 < / button >
< button v-if = "['PENDING','IN_PROGRESS'].includes(item.businessStatus)" class="action" @click="advance(item)" > {{ item.businessStatus = = = ' IN_PROGRESS ' ? ' 完成 ' : ' 处理 ' }} < / button >
< / view >
< / view >
< / view >
< view v-if = "result && !result.members.length" class="empty" > 当前项目今天还没有已确认的工作记录 。 < / view >
< SupervisorTabBar active = "today" / >
< / view >
< / template >
< script setup lang = "ts" >
import { ref } from 'vue' ;
import { onShow } from '@dcloudio/uni-app' ;
import type { MobileProject , ProjectWorkResult , WorkResult , WorkResultItem } from '@/types/api' ;
import { getCurrentProject , reconcileCurrentProject } from '@/services/auth' ;
import { fetchMobileIdentity } from '@/services/position' ;
import { updateAssistantCaptureStatus } from '@/services/personal-assistant' ;
import { openProtectedSource } from '@/services/source-preview' ;
import { getProjectWorkResult } from '@/services/work-results' ;
import SupervisorTabBar from '@/components/SupervisorTabBar.vue' ;
const result = ref < ProjectWorkResult | null > ( null ) ;
const currentProject = ref < MobileProject | null > ( getCurrentProject ( ) ) ;
const message = ref ( '' ) ;
const loading = ref ( false ) ;
const workDate = new Date ( ) . toLocaleDateString ( 'sv-SE' , { timeZone : 'Asia/Shanghai' } ) ;
const memberItems = ( member : WorkResult ) => Object . values ( member . sections || { } ) . flat ( ) ;
const load = async ( ) => {
if ( loading . value ) return ;
loading . value = true ;
message . value = '' ;
try {
const identity = await fetchMobileIdentity ( ) ;
currentProject . value = reconcileCurrentProject ( identity . projects || [ ] ) ;
if ( ! currentProject . value ) throw new Error ( '请先选择当前项目' ) ;
result . value = await getProjectWorkResult ( currentProject . value . projectCode , workDate ) ;
} catch ( error ) {
message . value = error instanceof Error ? error . message : '团队成果读取失败' ;
} finally {
loading . value = false ;
}
} ;
const advance = async ( item : WorkResultItem ) => {
const next = item . businessStatus === 'IN_PROGRESS' ? 'COMPLETED' : 'IN_PROGRESS' ;
try { await updateAssistantCaptureStatus ( item . captureId , next ) ; await load ( ) ; }
catch ( error ) { uni . showToast ( { title : error instanceof Error ? error . message : '状态更新失败' , icon : 'none' } ) ; }
} ;
const openSource = ( item : WorkResultItem ) => openProtectedSource ( item . sourceUrl , item . sourceType ) ;
const sourceLabel = ( value : string ) => ( { TEXT : '文字来源' , VOICE : '原始语音' , IMAGE : '现场照片' , VIDEO : '现场视频' } [ value ] || '原始来源' ) ;
const goBack = ( ) => uni . navigateBack ( { fail : ( ) => uni . redirectTo ( { url : '/pages/supervisor/index/index' } ) } ) ;
onShow ( load ) ;
< / script >
< style scoped >
. supervisor - result - page { min - height : 100 vh ; padding : 18 px 16 px 100 px ; background : # f4f5f7 ; color : # 171 b23 ; box - sizing : border - box } . page - head { display : flex ; align - items : center ; gap : 12 px ; margin - bottom : 16 px } . back { width : 42 px ; height : 42 px ; padding : 0 ; border : 1 px solid # dfe2e7 ; border - radius : 14 px ; background : # fff ; font - size : 32 px ; line - height : 38 px } . back : : after , . refresh : : after , . action : : after , . source - action : : after { border : 0 } . head - copy { display : flex ; min - width : 0 ; flex : 1 ; flex - direction : column } . title { font - size : 25 px ; font - weight : 800 } . subtitle { margin - top : 3 px ; color : # 7 d8592 ; font - size : 14 px } . refresh { padding : 8 px 12 px ; border : 1 px solid # ef3340 ; border - radius : 18 px ; background : # fff ; color : # d9081c } . message , . empty { margin - bottom : 14 px ; padding : 14 px ; border - radius : 14 px ; background : # fff0f1 ; color : # c6091b } . metrics { display : grid ; grid - template - columns : repeat ( 2 , 1 fr ) ; gap : 10 px ; margin - bottom : 14 px } . metrics > view { display : flex ; flex - direction : column ; padding : 16 px ; border : 1 px solid # e5e7eb ; border - radius : 18 px ; background : # fff ; color : # 6 f7784 } . num { color : # 171 b23 ; font - size : 30 px ; font - weight : 800 } . num . warn { color : # d98200 } . num . danger { color : # e60012 } . member - card { margin - bottom : 14 px ; padding : 16 px ; border : 1 px solid # e5e7eb ; border - radius : 20 px ; background : # fff ; box - shadow : 0 8 px 26 px rgba ( 31 , 38 , 50 , .05 ) } . member - head { display : flex ; align - items : center ; justify - content : space - between ; padding - bottom : 12 px ; border - bottom : 1 px solid # eceef1 } . member - head > view { display : flex ; flex - direction : column } . member - name { font - size : 19 px ; font - weight : 800 } . member - meta { margin - top : 4 px ; color : # 7 d8592 ; font - size : 13 px } . formed { padding : 4 px 8 px ; border - radius : 9 px ; background : # fff0f1 ; color : # d9081c ; font - size : 12 px } . member - item { display : flex ; align - items : center ; gap : 10 px ; padding : 12 px 0 ; border - bottom : 1 px solid # eff0f2 } . member - item : last - child { border : 0 } . item - copy { display : flex ; min - width : 0 ; flex : 1 ; flex - direction : column } . item - type { color : # d9081c ; font - size : 12 px } . item - summary { margin - top : 4 px ; font - size : 15 px ; line - height : 1.5 } . item - source { margin - top : 5 px ; color : # 8 a919d ; font - size : 12 px } . item - actions { display : flex ; align - items : flex - end ; flex - direction : column ; gap : 6 px } . source - action , . action { padding : 7 px 12 px ; border : 1 px solid # dfe2e7 ; border - radius : 14 px ; background : # fff ; color : # 59616 f ; font - size : 13 px } . action { border - color : # ef3340 ; color : # d9081c }
< / style >