feat(project): 项目基本信息修改和分包选择(FR-M2-03、04)
- 字段修改:人工值优先、撤销恢复 AI 值、按字段校验(金额可写「286万」,按元存)、 冗余列同步、审计留痕、只读项目 409;关键日期显示改后的投标截止时间 - 分包:增删改、选包(至少一个)、一键「不分包」;删包时条目归为通用,删到一个不剩回到不分包 - 清单列表 lot_scope 默认只列通用 + 所投的包,响应带 hidden_count - 检查记下当时用的项目依据(截止时间、限价、所投的包、法律体系):报告按它写, 之后改了信息检查结果接口给 basis_changed,查标页提示重新检查 - 前端:概览标签页(项目要点、采购包、关键日期)、字段修改对话框、选包页和手工调整; 解析完识别到多个包时进选包页;读标页清单范围和所属采购包;查标页写明查哪几个包 - 决策 PI1–PI7;方案和落地记录 docs/architecture/project-info-plan.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,14 +25,18 @@ import { groupCitations, type CitationGroup } from "@/lib/citations";
|
||||
import { useCrumbs } from "@/lib/crumbs";
|
||||
import { qk } from "@/lib/keys";
|
||||
import { category, CATEGORY_ORDER, fmtDateTime, MARKER, projectStatus } from "@/lib/labels";
|
||||
import { useChecklist, useChecklistItems, useProject, type ChecklistItem, type Citation, type MarkingRule } from "@/lib/queries";
|
||||
import { useChecklist, useChecklistItems, useLots, useProject, type ChecklistItem, type Citation, type MarkingRule } from "@/lib/queries";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
const props = defineProps<{ projectId: string }>();
|
||||
|
||||
const project = useProject(() => props.projectId);
|
||||
const checklist = useChecklist(() => props.projectId);
|
||||
const itemsQ = useChecklistItems(() => props.projectId);
|
||||
const lotScope = ref<"bidding" | "all">("bidding"); // 默认只列通用 + 所投的包(FR-M2-04)
|
||||
const itemsQ = useChecklistItems(() => props.projectId, lotScope);
|
||||
const lotsQ = useLots(() => props.projectId);
|
||||
const realLots = computed(() => (lotsQ.data.value?.items ?? []).filter((l) => !l.is_default));
|
||||
const biddingNos = computed(() => realLots.value.filter((l) => l.is_bidding).map((l) => `采购包 ${l.lot_no}`));
|
||||
const docsQ = useQuery({
|
||||
queryKey: computed(() => ["project-documents", props.projectId] as const),
|
||||
queryFn: () => unwrap(api.GET("/projects/{project_id}/documents", { params: { path: { project_id: props.projectId } } })),
|
||||
@@ -290,6 +294,20 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey));
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="realLots.length > 1" class="lot-scope" aria-label="清单范围">
|
||||
<span class="cap muted">采购包</span>
|
||||
<template v-if="lotScope === 'bidding'">
|
||||
<span>通用 + {{ biddingNos.join("、") }}</span>
|
||||
<span v-if="itemsQ.data.value?.hidden_count" class="cap muted">· 不投的包隐藏 <span class="fig">{{ itemsQ.data.value.hidden_count }}</span> 条</span>
|
||||
<button v-if="itemsQ.data.value?.hidden_count" type="button" class="lnk cap" @click="lotScope = 'all'">显示全部</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>全部采购包的条目</span>
|
||||
<button type="button" class="lnk cap" @click="lotScope = 'bidding'">只看所投的包</button>
|
||||
</template>
|
||||
<RouterLink v-if="!readOnly" class="lnk cap" :to="{ name: 'lots', params: { projectId } }">更改所投的包</RouterLink>
|
||||
</div>
|
||||
|
||||
<div v-if="cl?.response_modes?.length" class="modes" aria-label="招标文件的响应方式">
|
||||
<span class="cap muted">响应方式</span>
|
||||
<div v-for="m in cl.response_modes" :key="m.category" class="mode">
|
||||
@@ -455,7 +473,7 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey));
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ItemDialog v-model:open="dlg.open" :mode="dlg.mode" :item="dlg.item" :default-category="cat !== 'all' && cat !== 'pending' && cat !== 'deleted' ? cat : undefined" @submit="onSubmit" @pick="onDialogPick" />
|
||||
<ItemDialog v-model:open="dlg.open" :mode="dlg.mode" :item="dlg.item" :lots="realLots" :default-category="cat !== 'all' && cat !== 'pending' && cat !== 'deleted' ? cat : undefined" @submit="onSubmit" @pick="onDialogPick" />
|
||||
<HistoryDialog v-model:open="hist.open" :item="hist.item" />
|
||||
<ConfirmDialog v-model:open="confirmOpen" :title="`确认清单 v${nextVersion}?`" ok-text="确认清单" busy-text="正在确认…" :busy="confirming" @ok="doConfirm">
|
||||
<p>确认后,查标以这一版清单为依据。之后再改清单会回到草稿,历史版本保留。</p>
|
||||
@@ -467,6 +485,13 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey));
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lot-scope {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-2);
|
||||
align-items: baseline;
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
/* 响应方式(C12):清单栏下面一行,每类一个选择框 */
|
||||
.modes {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user