155 lines
7.1 KiB
Vue
155 lines
7.1 KiB
Vue
<template>
|
||
<div v-loading="loading">
|
||
<div class="page-header">
|
||
<div>
|
||
<el-button text @click="$router.push('/projects')"><el-icon><ArrowLeft /></el-icon> 返回</el-button>
|
||
<h2 style="display:inline; margin-left:8px">{{ project.name }}</h2>
|
||
<el-tag :type="typeTagMap[project.project_type]" style="margin-left:8px">{{ project.project_type }}</el-tag>
|
||
<el-tag :type="project.status === '已完成' ? 'success' : ''" style="margin-left:4px">{{ project.status }}</el-tag>
|
||
</div>
|
||
<el-space>
|
||
<el-button v-if="authStore.isOwner() && project.status === '制作中'" type="danger" @click="handleComplete">
|
||
确认完成结算
|
||
</el-button>
|
||
<el-button v-if="authStore.isOwner() && project.status === '已完成'" type="primary" @click="$router.push(`/settlement/${project.id}`)">
|
||
查看结算报告
|
||
</el-button>
|
||
</el-space>
|
||
</div>
|
||
|
||
<!-- 项目概览 -->
|
||
<el-row :gutter="16" class="stat-row">
|
||
<el-col :span="6">
|
||
<el-card shadow="hover"><div class="stat-label">目标时长</div><div class="stat-value">{{ formatSecs(project.target_total_seconds) }}</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover"><div class="stat-label">已提交</div><div class="stat-value">{{ formatSecs(project.total_submitted_seconds) }}</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover"><div class="stat-label">完成进度</div><div class="stat-value" :style="{color: project.progress_percent > 100 ? '#e6a23c' : '#409eff'}">{{ project.progress_percent }}%</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover"><div class="stat-label">损耗率</div><div class="stat-value" :style="{color: project.waste_rate > 30 ? '#f56c6c' : '#67c23a'}">{{ project.waste_rate }}%</div></el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 进度条 -->
|
||
<el-card class="section-card">
|
||
<template #header><span class="section-title">项目进度</span></template>
|
||
<el-progress :percentage="Math.min(project.progress_percent, 100)" :stroke-width="20" :text-inside="true"
|
||
:format="() => project.progress_percent + '%'"
|
||
:color="progressColor" style="margin-bottom:12px" />
|
||
<div class="meta-row">
|
||
<span>目标:{{ project.episode_count }}集 × {{ project.episode_duration_minutes }}分 = {{ formatSecs(project.target_total_seconds) }}</span>
|
||
<span v-if="project.estimated_completion_date">预估完成:{{ project.estimated_completion_date }}</span>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 团队效率 -->
|
||
<el-card v-if="authStore.isLeaderOrAbove() && efficiency.length" class="section-card">
|
||
<template #header><span class="section-title">团队效率(人均基准对比)</span></template>
|
||
<el-table :data="efficiency" size="small" stripe>
|
||
<el-table-column prop="user_name" label="成员" width="100" />
|
||
<el-table-column label="提交总秒数" align="right"><template #default="{row}">{{ formatSecs(row.total_seconds) }}</template></el-table-column>
|
||
<el-table-column label="人均基准" align="right"><template #default="{row}">{{ formatSecs(row.baseline) }}</template></el-table-column>
|
||
<el-table-column label="超出基准" align="right">
|
||
<template #default="{row}">
|
||
<span :style="{color: row.excess_seconds > 0 ? '#f56c6c' : '#67c23a'}">
|
||
{{ row.excess_seconds > 0 ? '+' : '' }}{{ formatSecs(row.excess_seconds) }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="超出比例" align="right" width="100">
|
||
<template #default="{row}">
|
||
<span :style="{color: row.excess_rate > 20 ? '#f56c6c' : '#333'}">{{ row.excess_rate > 0 ? '+' : '' }}{{ row.excess_rate }}%</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<!-- 提交记录 -->
|
||
<el-card class="section-card">
|
||
<template #header><span class="section-title">提交记录</span></template>
|
||
<el-table :data="submissions" size="small" stripe>
|
||
<el-table-column prop="submit_date" label="日期" width="110" />
|
||
<el-table-column prop="user_name" label="提交人" width="80" />
|
||
<el-table-column prop="project_phase" label="阶段" width="70" />
|
||
<el-table-column prop="work_type" label="工作类型" width="80">
|
||
<template #default="{row}">
|
||
<el-tag :type="row.work_type === '测试' ? 'warning' : ''" size="small">{{ row.work_type }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="content_type" label="内容类型" width="90" />
|
||
<el-table-column label="产出时长" width="90" align="right">
|
||
<template #default="{row}">{{ row.total_seconds > 0 ? formatSecs(row.total_seconds) : '—' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="description" label="描述" show-overflow-tooltip />
|
||
</el-table>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { projectApi, submissionApi } from '../api'
|
||
import { useAuthStore } from '../stores/auth'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const authStore = useAuthStore()
|
||
const loading = ref(false)
|
||
const project = ref({})
|
||
const submissions = ref([])
|
||
const efficiency = ref([])
|
||
const typeTagMap = { '客户正式项目': 'success', '客户测试项目': 'warning', '内部原创项目': '', '内部测试项目': 'info' }
|
||
|
||
const progressColor = computed(() => {
|
||
if (project.value.progress_percent > 100) return '#e6a23c'
|
||
return '#67c23a'
|
||
})
|
||
|
||
function formatSecs(s) {
|
||
if (!s) return '0秒'
|
||
const abs = Math.abs(s)
|
||
const m = Math.floor(abs / 60)
|
||
const sec = Math.round(abs % 60)
|
||
const sign = s < 0 ? '-' : ''
|
||
return m > 0 ? `${sign}${m}分${sec > 0 ? sec + '秒' : ''}` : `${sign}${sec}秒`
|
||
}
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const id = route.params.id
|
||
project.value = await projectApi.get(id)
|
||
submissions.value = await submissionApi.list({ project_id: id })
|
||
if (authStore.isLeaderOrAbove()) {
|
||
try { efficiency.value = await projectApi.efficiency(id) } catch {}
|
||
}
|
||
} finally { loading.value = false }
|
||
}
|
||
|
||
async function handleComplete() {
|
||
try {
|
||
await ElMessageBox.confirm('确认将此项目标记为完成并进行结算?此操作不可撤销。', '确认完成', { type: 'warning' })
|
||
await projectApi.complete(route.params.id)
|
||
ElMessage.success('项目已完成')
|
||
load()
|
||
} catch {}
|
||
}
|
||
|
||
onMounted(load)
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||
.stat-row { margin-bottom: 16px; }
|
||
.stat-label { font-size: 13px; color: #909399; margin-bottom: 4px; }
|
||
.stat-value { font-size: 24px; font-weight: 700; }
|
||
.section-card { margin-bottom: 16px; }
|
||
.section-title { font-weight: 600; }
|
||
.meta-row { display: flex; justify-content: space-between; font-size: 13px; color: #909399; }
|
||
</style>
|