项目中心
This commit is contained in:
@@ -8,8 +8,9 @@ import type { FileInfo } from '@/types/api'
|
||||
// 页面容器:数据只经 store,不直接 import api
|
||||
import { useProjectStore } from '@/stores/modules/project'
|
||||
import ProjectInfoCard from './cpns/ProjectInfoCard.vue'
|
||||
import ProjectTeam from './cpns/ProjectTeam.vue'
|
||||
import ProjectProgress from './cpns/ProjectProgress.vue'
|
||||
import ProjectMilestones from './cpns/ProjectMilestones.vue'
|
||||
import ProjectTeam from './cpns/ProjectTeam.vue'
|
||||
import ProjectFiles from './cpns/ProjectFiles.vue'
|
||||
import ProjectFinance from './cpns/ProjectFinance.vue'
|
||||
import ProjectAfterSales from './cpns/ProjectAfterSales.vue'
|
||||
@@ -19,12 +20,13 @@ type DetailTab = 'progress' | 'files' | 'finance' | 'afterSales'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const projectStore = useProjectStore()
|
||||
const { current } = storeToRefs(projectStore)
|
||||
const { current, loadingTab } = storeToRefs(projectStore)
|
||||
|
||||
const projectId = computed(() => String(route.params.id))
|
||||
const activeTab = ref<DetailTab>('progress')
|
||||
const cancelVisible = ref(false)
|
||||
|
||||
// 设计稿:页签是「文字 + 激活项蓝色下划线」(不是胶囊按钮组),顺序固定
|
||||
const tabs: Array<{ label: string; value: DetailTab }> = [
|
||||
{ label: '项目进度', value: 'progress' },
|
||||
{ label: '项目文件', value: 'files' },
|
||||
@@ -37,16 +39,26 @@ function load(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ 上传 / 删除附件、售后提交都还没有后端实现
|
||||
* (`ProjectRequirementUserController` 只有 create / list / get / serviceTypeList)。
|
||||
* 所以这里只提示「待接入」,**不往详情里塞本地假数据** —— 详情数据现在来自真实接口,
|
||||
* 混入前端自造条目会让刷新前后不一致。
|
||||
* 点击页签:**每次都调一次接口**,取该页签的最新数据。
|
||||
*
|
||||
* <p>后端详情本就是「4 个页签 4 个端点」,所以点一次 = 一次请求,且只请求被点的那一支;
|
||||
* 重复点击当前页签也会重取(用于拿到别处改动后的最新值)。竞态由 store 的请求序号兜住
|
||||
* ——切项目、连点同一页签都不会把旧响应盖回页面。</p>
|
||||
*/
|
||||
function onUpload(file: File): void {
|
||||
void file
|
||||
ElMessage.info('附件上传接口待接入')
|
||||
function onTabClick(tab: DetailTab): void {
|
||||
activeTab.value = tab
|
||||
void projectStore.refreshTab(tab)
|
||||
}
|
||||
|
||||
/**
|
||||
* 写操作的现状(2026-09-28):
|
||||
* · **售后已接真实接口** —— 申请跳 `/project/:id/after-sales`(`/afterSales/request`),
|
||||
* 取消走 `/afterSales/withdraw`;两者成功后都会重拉售后页签;
|
||||
* · **需求附件的删除 / 预览 / 下载仍无用户端接口**(上传入口在需求提报页),
|
||||
* 所以这里只提示「待接入」,**不往详情里塞本地假数据** —— 详情数据来自真实接口,
|
||||
* 混入前端自造条目会让刷新前后不一致。
|
||||
* 另外详情页「只展示文件」:上传入口在需求提报页,这里不提供。
|
||||
*/
|
||||
function onDeleteFile(index: number): void {
|
||||
void index
|
||||
ElMessage.info('附件删除接口待接入')
|
||||
@@ -82,7 +94,8 @@ function onApplyAfterSales(): void {
|
||||
}
|
||||
|
||||
function onConfirmCancel(): void {
|
||||
void projectStore.cancelAfterSales()
|
||||
// 撤回接口收的是**需求 id**(后端按需求校验归属)
|
||||
void projectStore.cancelAfterSales(projectId.value)
|
||||
}
|
||||
|
||||
watch(projectId, load)
|
||||
@@ -102,33 +115,37 @@ onMounted(load)
|
||||
|
||||
<template v-if="current">
|
||||
<ProjectInfoCard :project="current" />
|
||||
<ProjectTeam :team="current.team" />
|
||||
|
||||
<!-- 本地 Tab 切换 -->
|
||||
<!-- 本地 Tab 切换(设计稿:文字 + 激活项下划线) -->
|
||||
<div class="project-detail__tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
class="project-detail__tab"
|
||||
:class="{ 'is-active': activeTab === tab.value }"
|
||||
@click="activeTab = tab.value"
|
||||
:class="{
|
||||
'is-active': activeTab === tab.value,
|
||||
'is-loading': loadingTab === tab.value,
|
||||
}"
|
||||
@click="onTabClick(tab.value)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ProjectMilestones
|
||||
v-if="activeTab === 'progress'"
|
||||
:progress="current.progress"
|
||||
:milestones="current.milestones"
|
||||
:start-time="current.startTime"
|
||||
:end-time="current.endTime"
|
||||
/>
|
||||
<!-- 「项目进度」页签 = 开发进度 → 里程碑 → 项目团队(顺序按设计稿) -->
|
||||
<template v-if="activeTab === 'progress'">
|
||||
<ProjectProgress
|
||||
:progress="current.progress"
|
||||
:start-time="current.startTime"
|
||||
:end-time="current.endTime"
|
||||
/>
|
||||
<ProjectMilestones :milestones="current.milestones" />
|
||||
<ProjectTeam :team="current.team" />
|
||||
</template>
|
||||
|
||||
<ProjectFiles
|
||||
v-if="activeTab === 'files'"
|
||||
:files="current.files"
|
||||
@upload="onUpload"
|
||||
@delete="onDeleteFile"
|
||||
@preview="onPreviewFile"
|
||||
@download="onDownloadFile"
|
||||
@@ -144,10 +161,11 @@ onMounted(load)
|
||||
@download="onFinanceDownload"
|
||||
/>
|
||||
|
||||
<!-- 售后页签三形态:项目未完成 / 已完成未提交 / 已完成已提交(详见组件内注释) -->
|
||||
<ProjectAfterSales
|
||||
v-if="activeTab === 'afterSales'"
|
||||
:after-sales="current.afterSales"
|
||||
:project-id="current.id"
|
||||
:project-status="current.status"
|
||||
@apply="onApplyAfterSales"
|
||||
@cancel="cancelVisible = true"
|
||||
/>
|
||||
@@ -178,36 +196,49 @@ onMounted(load)
|
||||
padding-bottom: @spacing-xxl;
|
||||
|
||||
&__breadcrumb {
|
||||
font-size: @font-size-base;
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
|
||||
// 设计稿:页签无白底容器,仅一行文字 + 激活项 2px 下划线;相邻间距实测 ≈50px
|
||||
&__tabs {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-xs;
|
||||
padding: @spacing-xs;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
.flex(row, flex-start, center);
|
||||
gap: 50px;
|
||||
padding: 0 @spacing-base;
|
||||
}
|
||||
|
||||
&__tab {
|
||||
height: 34px;
|
||||
padding: 0 @spacing-base;
|
||||
position: relative;
|
||||
padding: 0 0 @spacing-sm;
|
||||
border: none;
|
||||
border-radius: @radius-base;
|
||||
background: transparent;
|
||||
color: @text-color;
|
||||
font-size: @font-size-base;
|
||||
transition: all 0.2s;
|
||||
line-height: 20px;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
color: @project-accent;
|
||||
}
|
||||
|
||||
// 刷新中:轻微变淡 + 进度光标(本地接口很快,做太重的 loading 反而闪屏)
|
||||
&.is-loading {
|
||||
opacity: 0.6;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: @primary-light;
|
||||
color: @primary-color;
|
||||
font-weight: 600;
|
||||
color: @project-accent;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
background: @project-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user