| | |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { |
| | |
| | | const materialLoading = ref(false); |
| | | const materialFormVisible = ref(false); |
| | | const materialForm = ref<SrmTenderApi.TenderMaterialVO>({ tenderProjectId: projectId }); |
| | | |
| | | // 已添加的物料产品ID集合,避免重复添加 |
| | | const addedProductIds = computed(() => { |
| | | const ids = new Set( |
| | | materialList.value |
| | | .filter((m) => m.productId !== undefined) |
| | | .map((m) => m.productId!), |
| | | ); |
| | | // 编辑时允许保留当前物料的 productId |
| | | if (materialForm.value.id && materialForm.value.productId) { |
| | | ids.delete(materialForm.value.productId); |
| | | } |
| | | return ids; |
| | | }); |
| | | |
| | | // 物料下拉可选列表(排除已添加的) |
| | | const availableMdmItems = computed(() => |
| | | mdmItemList.value.filter((item) => !addedProductIds.value.has(item.id!)), |
| | | ); |
| | | |
| | | async function loadProject() { |
| | | project.value = await getTenderProject(projectId); |
| | |
| | | try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; } |
| | | } |
| | | async function handleSaveQuote() { |
| | | // 校验:报价金额与投标金额一致性 |
| | | const bid = bidList.value.find((b) => b.id === quoteForm.value.bidId); |
| | | if (bid?.bidTotalAmount != null) { |
| | | const otherQuotesTotal = quoteList.value |
| | | .filter((q) => q.id !== quoteForm.value.id) |
| | | .reduce((sum, q) => sum + (q.quotePrice ?? 0), 0); |
| | | const newTotal = otherQuotesTotal + (quoteForm.value.quotePrice ?? 0); |
| | | if (newTotal !== bid.bidTotalAmount) { |
| | | await new Promise<void>((resolve, reject) => { |
| | | Modal.confirm({ |
| | | title: '金额不一致提示', |
| | | content: `报价总金额(${newTotal.toLocaleString()})与投标金额(${bid.bidTotalAmount!.toLocaleString()})不一致,是否继续提交?`, |
| | | okText: '继续提交', |
| | | cancelText: '取消', |
| | | onOk: () => { resolve(); }, |
| | | onCancel: () => { reject(new Error('cancel')); }, |
| | | }); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | if (quoteForm.value.id) { |
| | | await updateQuote(quoteForm.value); |
| | | } else { |
| | |
| | | const statusLabel = computed(() => getLabel(TENDER_STATUS_CELLTAG.labels, project.value?.tenderStatus)); |
| | | const statusColor = computed(() => TENDER_STATUS_COLORS[project.value?.tenderStatus!] || 'default'); |
| | | const typeLabel = computed(() => getLabel(TENDER_TYPE_CELLTAG.labels, project.value?.tenderType)); |
| | | const isAwarded = computed(() => project.value?.tenderStatus === TENDER_STATUS.AWARDED); |
| | | |
| | | const stepItems = [ |
| | | { title: '草稿', status: TENDER_STATUS.DRAFT }, |
| | |
| | | <a-space> |
| | | <span class="text-xs text-gray-400">共 <b class="text-gray-600">{{ materialList.length }}</b> 项,预估总金额 <b class="text-blue-500">{{ materialTotalAmount.toLocaleString() }}</b> 元</span> |
| | | </a-space> |
| | | <a-button type="primary" size="small" @click="handleAddMaterial"> |
| | | <a-button v-if="!isAwarded" type="primary" size="small" @click="handleAddMaterial"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | 添加物料 |
| | | </a-button> |
| | |
| | | {{ record.estimatedPrice ? record.estimatedPrice.toLocaleString() : '-' }} |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-space> |
| | | <a-space v-if="!isAwarded"> |
| | | <a-button size="small" type="link" @click="handleEditMaterial(record)"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button> |
| | | <a-popconfirm title="确认删除该物料?" @confirm="handleDeleteMaterial(record.id)"> |
| | | <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:delete-outlined" />删除</a-button> |
| | |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <span class="text-xs text-gray-400">共 <b class="text-gray-600">{{ bidCount }}</b> 个投标</span> |
| | | <a-button type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true"> |
| | | <a-button v-if="!isAwarded" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | 新增投标 |
| | | </a-button> |
| | |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-space> |
| | | <a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />报价</a-button> |
| | | <a-popconfirm v-if="record.bidStatus === BID_STATUS.REGISTERED" title="确认撤标?" @confirm="handleWithdrawBid(record.id)"> |
| | | <a-popconfirm v-if="!isAwarded && record.bidStatus === BID_STATUS.REGISTERED" title="确认撤标?" @confirm="handleWithdrawBid(record.id)"> |
| | | <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />撤标</a-button> |
| | | </a-popconfirm> |
| | | </a-space> |
| | |
| | | <a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">切换投标</a-button> |
| | | </div> |
| | | <div class="text-right mb-3"> |
| | | <a-button v-if="selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote"> |
| | | <a-button v-if="!isAwarded && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | 新增报价 |
| | | </a-button> |
| | |
| | | {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 个月` : '-' }} |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-button v-if="selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button> |
| | | <a-button v-if="!isAwarded && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | |
| | | <div class="text-center py-8"> |
| | | <a-result status="info" title="尚未开标" sub-title="执行开标后项目进入投标阶段,供应商可正式投标" /> |
| | | <br /> |
| | | <a-button type="primary" @click="handleCreateBidOpen"> |
| | | <a-button v-if="!isAwarded" type="primary" @click="handleCreateBidOpen"> |
| | | <template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template> |
| | | 执行开标 |
| | | </a-button> |
| | |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <span class="text-xs text-gray-400">共 <b class="text-gray-600">{{ evalCount }}</b> 条,已排名 <b class="text-green-500">{{ rankedCount }}</b> 条</span> |
| | | <a-space> |
| | | <a-space v-if="!isAwarded"> |
| | | <a-button size="small" @click="handleCalculateRanking"> |
| | | <template #icon><IconifyIcon icon="ant-design:bar-chart-outlined" /></template> |
| | | 计算排名 |
| | |
| | | <span v-else class="text-gray-300">-</span> |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-button size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button> |
| | | <a-button v-if="!isAwarded" size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | |
| | | <span class="text-sm">采购订单 ID: {{ award.purchaseOrderId }}</span> |
| | | </template> |
| | | </a-result> |
| | | <a-popconfirm title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-popconfirm v-if="!isAwarded" title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-button type="link" danger size="small" class="mt-2">删除定标</a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | <template v-else-if="award.awardStatus === AWARD_STATUS.AWARDED"> |
| | | <div class="text-center"> |
| | | <a-result status="success" title="定标完成" class="p-0 mb-3" /> |
| | | <a-button type="primary" block @click="handleGeneratePurchaseOrder"> |
| | | <a-button v-if="!isAwarded" type="primary" block @click="handleGeneratePurchaseOrder"> |
| | | <template #icon><IconifyIcon icon="ant-design:shopping-cart-outlined" /></template> |
| | | 生成采购订单 |
| | | </a-button> |
| | | <a-popconfirm title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-popconfirm v-if="!isAwarded" title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-button type="link" danger size="small" class="mt-2">删除定标</a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | <template v-else> |
| | | <div class="text-center"> |
| | | <div class="text-gray-500 text-sm mb-3">定标信息已创建,请审批</div> |
| | | <a-button type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;"> |
| | | <a-button v-if="!isAwarded" type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;"> |
| | | <template #icon><IconifyIcon icon="ant-design:check-outlined" /></template> |
| | | 审批通过 |
| | | </a-button> |
| | | <a-popconfirm title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-popconfirm v-if="!isAwarded" title="确定删除该定标记录?" ok-text="确定" cancel-text="取消" @confirm="handleDeleteAward"> |
| | | <a-button type="link" danger size="small" class="mt-2">删除定标</a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | <div class="text-center py-8"> |
| | | <a-result status="info" title="尚未定标" sub-title="基于评标排名选择中标供应商并创建定标" /> |
| | | <br /> |
| | | <a-button type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true"> |
| | | <a-button v-if="!isAwarded" type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | 创建定标 |
| | | </a-button> |
| | |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="MDM物料" required> |
| | | <a-select v-model:value="materialForm.productId" show-search placeholder="搜索并选择MDM物料" :filter-option="filterOption" option-label-prop="label" @change="onMaterialSelect"> |
| | | <a-select-option v-for="item in mdmItemList" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`"> |
| | | <a-select-option v-for="item in availableMdmItems" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`"> |
| | | <div class="flex flex-col"> |
| | | <span class="font-medium">{{ item.code }}</span> |
| | | <span class="text-xs text-gray-400">{{ item.name }}{{ item.specification ? ` · ${item.specification}` : '' }}</span> |