feat: add project-scoped work assistant results
This commit is contained in:
@@ -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:100vh;padding:18px 16px 100px;background:#f4f5f7;color:#171b23;box-sizing:border-box}.page-head{display:flex;align-items:center;gap:12px;margin-bottom:16px}.back{width:42px;height:42px;padding:0;border:1px solid #dfe2e7;border-radius:14px;background:#fff;font-size:32px;line-height:38px}.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:25px;font-weight:800}.subtitle{margin-top:3px;color:#7d8592;font-size:14px}.refresh{padding:8px 12px;border:1px solid #ef3340;border-radius:18px;background:#fff;color:#d9081c}.message,.empty{margin-bottom:14px;padding:14px;border-radius:14px;background:#fff0f1;color:#c6091b}.metrics{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:14px}.metrics>view{display:flex;flex-direction:column;padding:16px;border:1px solid #e5e7eb;border-radius:18px;background:#fff;color:#6f7784}.num{color:#171b23;font-size:30px;font-weight:800}.num.warn{color:#d98200}.num.danger{color:#e60012}.member-card{margin-bottom:14px;padding:16px;border:1px solid #e5e7eb;border-radius:20px;background:#fff;box-shadow:0 8px 26px rgba(31,38,50,.05)}.member-head{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px;border-bottom:1px solid #eceef1}.member-head>view{display:flex;flex-direction:column}.member-name{font-size:19px;font-weight:800}.member-meta{margin-top:4px;color:#7d8592;font-size:13px}.formed{padding:4px 8px;border-radius:9px;background:#fff0f1;color:#d9081c;font-size:12px}.member-item{display:flex;align-items:center;gap:10px;padding:12px 0;border-bottom:1px 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:12px}.item-summary{margin-top:4px;font-size:15px;line-height:1.5}.item-source{margin-top:5px;color:#8a919d;font-size:12px}.item-actions{display:flex;align-items:flex-end;flex-direction:column;gap:6px}.source-action,.action{padding:7px 12px;border:1px solid #dfe2e7;border-radius:14px;background:#fff;color:#59616f;font-size:13px}.action{border-color:#ef3340;color:#d9081c}
|
||||
</style>
|
||||
Reference in New Issue
Block a user