From baddb052ccd4a91ef1881ba663e6a4e55b05f4fb Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 15:51:55 +0800
Subject: [PATCH] feat(hrm): 社保公积金方案表单加「适用部门」与默认缴费基数,修比例输入精度
---
src/views/srm/tender/detail.vue | 370 ++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 296 insertions(+), 74 deletions(-)
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 7de94be..fda47ec 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -6,19 +6,20 @@
import { Page } from '@vben/common-ui';
-import { message, Modal } from 'ant-design-vue';
+import { message } from 'ant-design-vue';
import { IconifyIcon } from '@vben/icons';
import {
getTenderProject,
getMaterialList, createMaterial, updateMaterial, deleteMaterial,
- getBidListByProject, createBid, withdrawBid,
+ getBidListByProject, createBid, withdrawBid, updateBidTotal,
getQuoteList, createQuote, updateQuote,
createBidOpen, getBidOpen,
getEvaluationList, createBidEvaluation, updateBidEvaluation, calculateRanking,
createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder,
+ confirmTenderProject,
} from '#/api/srm/tender';
-import { getSupplierSimpleList } from '#/api/srm/supplier';
+import { getSupplierList } from '#/api/srm/supplier';
import { getItemSimpleList } from '#/api/mdm/item';
import type { MdmItemApi } from '#/api/mdm/item';
import {
@@ -31,6 +32,7 @@
AWARD_STATUS,
AWARD_STATUS_MAP,
getLabel,
+ BIDDING_MODE,
} from '../enums';
defineOptions({ name: 'SrmTenderDetail' });
@@ -49,7 +51,7 @@
onMounted(async () => {
const [items, suppliers] = await Promise.all([
getItemSimpleList(),
- getSupplierSimpleList(),
+ getSupplierList(),
]);
mdmItemList.value = items;
supplierList.value = suppliers;
@@ -126,7 +128,7 @@
const bidList = ref<SrmTenderApi.TenderBidVO[]>([]);
const bidLoading = ref(false);
const bidFormVisible = ref(false);
-const bidForm = ref<SrmTenderApi.TenderBidVO>({ tenderProjectId: projectId, supplierId: 0, bidNo: '' });
+const bidForm = ref<SrmTenderApi.TenderBidVO>({ tenderProjectId: projectId, supplierId: undefined!, bidNo: '' });
async function loadBids() {
bidLoading.value = true;
@@ -146,8 +148,10 @@
const quoteList = ref<SrmTenderApi.QuoteVO[]>([]);
const quoteLoading = ref(false);
const quoteFormVisible = ref(false);
-const quoteForm = ref<SrmTenderApi.QuoteVO>({ bidId: 0, tenderMaterialId: 0 });
+const quoteForm = ref<SrmTenderApi.QuoteVO>({ bidId: undefined!, tenderMaterialId: undefined! });
const selectedBidId = ref<number>();
+/** 鎶曟爣鎬婚噾棰濊崏绋匡細杩涘叆鎶ヤ环椤垫椂甯﹀嚭宸蹭繚瀛樻�讳环锛屽彲鑷姩甯﹀嚭鎶ヤ环鍚堣鍚庝汉宸ュ井璋� */
+const bidTotalDraft = ref<number>();
const availableMaterials = computed(() => {
const quotedIds = new Set(
@@ -167,29 +171,11 @@
selectedBidId.value = bidId;
quoteLoading.value = true;
try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; }
+ // 甯﹀嚭璇ユ姇鏍囧凡淇濆瓨鐨勬姇鏍囨�婚噾棰濓紙涓虹┖鍒欒鐢ㄦ埛褰曞叆/鑷姩甯﹀嚭锛�
+ const bid = bidList.value.find((b) => b.id === bidId);
+ bidTotalDraft.value = bid?.bidTotalAmount ?? undefined;
}
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 {
@@ -199,13 +185,69 @@
loadQuotes(selectedBidId.value);
}
function handleAddQuote() {
- quoteForm.value = { bidId: selectedBidId.value!, tenderMaterialId: 0 };
+ quoteForm.value = { bidId: selectedBidId.value!, tenderMaterialId: undefined! };
quoteFormVisible.value = true;
}
function handleEditQuote(row: SrmTenderApi.QuoteVO) {
quoteForm.value = { ...row };
quoteFormVisible.value = true;
}
+
+/** 鎷涙爣鐗╂枡 Map锛氭姤浠疯鎸� tenderMaterialId 鍙嶆煡鐗╂枡鏁伴噺 */
+const materialMap = computed(() => {
+ const map = new Map<number, SrmTenderApi.TenderMaterialVO>();
+ materialList.value.forEach((m) => {
+ if (m.id != null) map.set(m.id, m);
+ });
+ return map;
+});
+
+/** 鎶ヤ环鏄庣粏瀵瑰簲鐨勬嫑鏍囩墿鏂欐暟閲� */
+function materialQuantityOf(quote: SrmTenderApi.QuoteVO): number {
+ return materialMap.value.get(quote.tenderMaterialId!)?.quantity ?? 0;
+}
+
+/** 鎶ヤ环琛岄噾棰� = 鎶ヤ环鍗曚环 脳 鐗╂枡鏁伴噺 */
+function quoteAmountOf(quote: SrmTenderApi.QuoteVO): number {
+ return (quote.quotePrice ?? 0) * materialQuantityOf(quote);
+}
+
+/** 閫変腑鎶曟爣涓嬪凡褰曟姤浠风殑鍚堣閲戦 */
+const selectedQuoteTotalAmount = computed(() =>
+ quoteList.value.reduce((sum, q) => sum + quoteAmountOf(q), 0),
+);
+
+/** 缁存姢锛堟暣鍗曪級鎶曟爣鎬婚噾棰濓細鍙嚜鍔ㄥ甫鍑烘姤浠峰悎璁″悗浜哄伐寰皟锛堢敤浜庢暣鍗曡鍒╃瓑锛� */
+async function handleSaveBidTotal() {
+ if (!selectedBidId.value) return;
+ if (bidTotalDraft.value == null) {
+ message.warning('璇峰~鍐欐姇鏍囨�婚噾棰�');
+ return;
+ }
+ await updateBidTotal(selectedBidId.value, bidTotalDraft.value);
+ message.success('鎶曟爣鎬婚噾棰濆凡淇濆瓨');
+ loadBids();
+}
+
+/** 宸蹭繚瀛樼殑鎶曟爣鎬婚噾棰� 涓� 鎶ヤ环鍚堣 鐨勫樊寮傛彁绀猴紙鍙彁绀猴紝涓嶆嫤鎴級 */
+const bidTotalCompareHint = computed<{ text: string; tone: 'success' | 'warning' } | null>(() => {
+ const sum = selectedQuoteTotalAmount.value;
+ if (sum <= 0) {
+ return null; // 灏氭湭褰曟姤浠凤紝鏆備笉姣旇緝
+ }
+ const stored = selectedBid.value?.bidTotalAmount;
+ if (stored == null) {
+ return { text: '璇ユ姇鏍囧皻鏈淮鎶ゆ�婚噾棰濓細鍙偣銆岃嚜鍔ㄥ甫鍑烘姤浠峰悎璁°�嶅~鍏ュ悗淇濆瓨', tone: 'warning' };
+ }
+ const diff = Math.abs(stored - sum);
+ if (diff < 0.01) {
+ return { text: `鎶曟爣鎬婚噾棰� 楼${stored.toLocaleString()} 涓庢姤浠峰悎璁′竴鑷碻, tone: 'success' };
+ }
+ return {
+ text: `鎶曟爣鎬婚噾棰� 楼${stored.toLocaleString()} 涓庢姤浠峰悎璁� 楼${sum.toLocaleString()} 涓嶄竴鑷达紙宸 楼${diff.toLocaleString()}锛夈�傚闇�鏁村崟璁╁埄/璋冧环锛岀洿鎺ュ湪鎶曟爣鎬婚噾棰濆淇敼骞朵繚瀛樸�俙,
+ tone: 'warning',
+ };
+});
// ========== 寮�鏍� ==========
const bidOpen = ref<any>(null);
@@ -226,7 +268,7 @@
const evaluationList = ref<SrmTenderApi.BidEvaluationVO[]>([]);
const evalLoading = ref(false);
const evalFormVisible = ref(false);
-const evalForm = ref<SrmTenderApi.BidEvaluationVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0 });
+const evalForm = ref<SrmTenderApi.BidEvaluationVO>({ tenderProjectId: projectId, supplierId: undefined!, bidId: undefined! });
async function loadEvaluations() {
evalLoading.value = true;
@@ -251,7 +293,41 @@
// ========== 瀹氭爣 ==========
const award = ref<SrmTenderApi.BidAwardVO>();
const awardFormVisible = ref(false);
-const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' });
+const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: undefined!, bidId: undefined!, awardNo: '' });
+
+/** 宸叉姇鏍囦笖鏈挙鏍囩殑渚涘簲鍟嗗垪琛紙浠庢姇鏍囪褰曚腑鎻愬彇锛� */
+const bidSuppliers = computed(() => {
+ const activeBids = bidList.value.filter((b) => b.bidStatus !== BID_STATUS.WITHDRAWN);
+ const seen = new Map<number, { id: number; name: string }>();
+ activeBids.forEach((b) => {
+ if (b.supplierId && !seen.has(b.supplierId)) {
+ seen.set(b.supplierId, { id: b.supplierId, name: b.supplierName || '' });
+ }
+ });
+ return [...seen.values()];
+});
+
+/** 鍙�夋姇鏍囪褰曪細鎺掗櫎鎾ゆ爣锛屼笖濡傛灉閫変簡渚涘簲鍟嗗垯鍙樉绀鸿渚涘簲鍟嗙殑鎶曟爣 */
+const activeBids = computed(() => {
+ return bidList.value.filter((b) => {
+ if (b.bidStatus === BID_STATUS.WITHDRAWN) return false;
+ if (awardForm.value.supplierId && b.supplierId !== awardForm.value.supplierId) return false;
+ return true;
+ });
+});
+
+/** 閫夋嫨渚涘簲鍟嗘椂娓呯┖宸查�夌殑鎶曟爣 */
+function onAwardSupplierChange() {
+ awardForm.value.bidId = undefined!;
+}
+
+/** 閫夋嫨鎶曟爣鏃惰嚜鍔ㄥ甫鍏ヨ鎶曟爣鐨勬姤浠烽噾棰� */
+function onAwardBidChange(bidId: number) {
+ const bid = bidList.value.find((b) => b.id === bidId);
+ if (bid?.bidTotalAmount != null) {
+ awardForm.value.awardAmount = bid.bidTotalAmount;
+ }
+}
async function loadAward() {
try { award.value = await getAwardByProject(projectId); } catch { award.value = undefined; }
@@ -284,6 +360,36 @@
loadProject();
}
+// ========== 绠�鏄撴ā寮忥細纭涓爣 ==========
+const confirmBidId = ref<number>();
+const confirmModalVisible = ref(false);
+
+async function handleConfirmBidAsWinner(bidId: number) {
+ await confirmTenderProject({ id: projectId, bidId });
+ message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+ loadProject();
+ loadBids();
+ loadAward();
+}
+
+function openSimpleConfirmModal() {
+ confirmBidId.value = undefined;
+ confirmModalVisible.value = true;
+}
+
+async function handleSimpleConfirm() {
+ if (!confirmBidId.value) {
+ message.warning('璇烽�夋嫨涓爣鐨勬姇鏍囪褰�');
+ return;
+ }
+ await confirmTenderProject({ id: projectId, bidId: confirmBidId.value });
+ message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+ confirmModalVisible.value = false;
+ loadProject();
+ loadBids();
+ loadAward();
+}
+
// ========== 鍒濆鍖� ==========
loadProject();
loadMaterials();
@@ -295,25 +401,53 @@
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 isDraft = computed(() => project.value?.tenderStatus === TENDER_STATUS.DRAFT);
+const isPublished = computed(() => project.value?.tenderStatus === TENDER_STATUS.PUBLISHED);
+const isBidding = computed(() => project.value?.tenderStatus === TENDER_STATUS.BIDDING);
+const isPublishedOrBidding = computed(() => {
+ const s = project.value?.tenderStatus;
+ return s === TENDER_STATUS.PUBLISHED || s === TENDER_STATUS.BIDDING;
+});
+const isBiddingOrEvaluating = computed(() => {
+ const s = project.value?.tenderStatus;
+ return s === TENDER_STATUS.BIDDING || s === TENDER_STATUS.EVALUATING;
+});
+const isEvaluating = computed(() => project.value?.tenderStatus === TENDER_STATUS.EVALUATING);
+const canDeleteAward = computed(() => award.value?.awardStatus === AWARD_STATUS.PENDING_APPROVAL);
+const canApproveAward = computed(() => award.value?.awardStatus === AWARD_STATUS.PENDING_APPROVAL);
+const canGeneratePO = computed(() => award.value?.awardStatus === AWARD_STATUS.AWARDED);
-const stepItems = [
- { title: '鑽夌', status: TENDER_STATUS.DRAFT },
- { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
- { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
- { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
- { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
-];
+// 绠�鏄撴ā寮�
+const isSimpleMode = computed(() => project.value?.biddingMode === BIDDING_MODE.SIMPLE);
+const isPriceComparing = computed(() => project.value?.tenderStatus === TENDER_STATUS.PRICE_COMPARING);
+
+const stepItems = computed(() => {
+ if (isSimpleMode.value) {
+ return [
+ { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+ { title: '姣斾环涓�', status: TENDER_STATUS.PRICE_COMPARING },
+ { title: '宸茬‘璁�', status: TENDER_STATUS.CONFIRMED },
+ ];
+ }
+ return [
+ { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+ { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
+ { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
+ { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
+ { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
+ ];
+});
const currentStep = computed(() => {
const s = project.value?.tenderStatus ?? 0;
- if (s === TENDER_STATUS.CLOSED) return 4;
- const idx = stepItems.findIndex((item) => item.status >= s);
+ const items = stepItems.value;
+ if (s === TENDER_STATUS.CLOSED) return items.length - 1;
+ const idx = items.findIndex((item) => item.status >= s);
return idx === -1 ? 0 : idx;
});
const stepStatus = computed(() => {
const s = project.value?.tenderStatus;
if (s === TENDER_STATUS.CLOSED) return 'error';
- if (s === TENDER_STATUS.AWARDED) return 'finish';
+ if (s === TENDER_STATUS.AWARDED || s === TENDER_STATUS.CONFIRMED) return 'finish';
return 'process';
});
@@ -345,15 +479,17 @@
const bidColumns = [
{ title: '鎶曟爣缂栧彿', dataIndex: 'bidNo', width: 140 },
{ title: '渚涘簲鍟�', dataIndex: 'supplierName', width: 200 },
- { title: '鎶曟爣閲戦', dataIndex: 'bidTotalAmount', width: 120 },
+ { title: '鎶曟爣鎬婚噾棰�', dataIndex: 'bidTotalAmount', width: 120 },
{ title: '鎶曟爣鏃堕棿', dataIndex: 'bidTime', width: 160 },
{ title: '鐘舵��', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text },
- { title: '鎿嶄綔', key: 'actions', width: 180 },
+ { title: '鎿嶄綔', key: 'actions', width: 240 },
];
const quoteColumns = [
{ title: '鐗╂枡', dataIndex: 'materialName', width: 120 },
- { title: '鎶ヤ环浠锋牸', dataIndex: 'quotePrice', width: 100 },
+ { title: '鎶ヤ环鍗曚环', dataIndex: 'quotePrice', width: 100 },
+ { title: '鏁伴噺', dataIndex: 'quantity', width: 70 },
+ { title: '鎶ヤ环閲戦', dataIndex: 'quoteAmount', width: 110 },
{ title: '绋庣巼(%)', dataIndex: 'taxRate', width: 80 },
{ title: '浜ゆ湡(澶�)', dataIndex: 'deliveryCycle', width: 80 },
{ title: '浠樻鏉′欢', dataIndex: 'paymentTerms', width: 120 },
@@ -391,7 +527,16 @@
<span class="text-gray-400 text-xs">{{ project.tenderNo }}</span>
<a-tag :color="statusColor">{{ statusLabel }}</a-tag>
<a-tag color="blue">{{ typeLabel }}</a-tag>
+ <a-tag v-if="isSimpleMode" color="purple">绠�鏄撶増</a-tag>
<div class="flex-1" />
+ <a-button
+ v-if="isPriceComparing"
+ type="primary"
+ size="small"
+ @click="openSimpleConfirmModal"
+ >
+ 纭涓爣
+ </a-button>
<a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();">
<template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template>
鍒锋柊
@@ -435,14 +580,32 @@
</div>
<!-- 鎷涙爣璇存槑 / 璧勮川瑕佹眰 -->
- <template v-if="project.tenderDesc || project.qualificationRequirements">
+ <template
+ v-if="project.tenderDesc || project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)"
+ >
<a-divider class="!my-2" />
<a-collapse :bordered="false" ghost expand-icon-position="end">
<a-collapse-panel v-if="project.tenderDesc" key="desc" header="鎷涙爣璇存槑">
<p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.tenderDesc }}</p>
</a-collapse-panel>
- <a-collapse-panel v-if="project.qualificationRequirements" key="req" header="璧勮川瑕佹眰">
- <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p>
+ <a-collapse-panel
+ v-if="project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)"
+ key="req"
+ header="璧勮川瑕佹眰"
+ >
+ <p v-if="project.qualificationRequirements" class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p>
+ <div v-if="project.attachmentList && project.attachmentList.length" class="mt-2 flex flex-col gap-1">
+ <a
+ v-for="file in project.attachmentList"
+ :key="file.id"
+ :href="file.url"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-blue-500 text-xs hover:underline"
+ >
+ {{ file.name || '闄勪欢-' + file.id }}
+ </a>
+ </div>
</a-collapse-panel>
</a-collapse>
</template>
@@ -462,7 +625,7 @@
<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 v-if="!isAwarded" type="primary" size="small" @click="handleAddMaterial">
+ <a-button v-if="isDraft" type="primary" size="small" @click="handleAddMaterial">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
娣诲姞鐗╂枡
</a-button>
@@ -473,7 +636,7 @@
{{ record.estimatedPrice ? record.estimatedPrice.toLocaleString() : '-' }}
</template>
<template v-if="column.key === 'actions'">
- <a-space v-if="!isAwarded">
+ <a-space v-if="isDraft">
<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>
@@ -492,7 +655,7 @@
</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 v-if="!isAwarded" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true">
+ <a-button v-if="isBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鏂板鎶曟爣
</a-button>
@@ -510,7 +673,10 @@
<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="!isAwarded && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
+ <a-popconfirm v-if="isPriceComparing && record.bidStatus !== BID_STATUS.WITHDRAWN" title="纭閫夋嫨璇ユ姇鏍囦负涓爣锛熷皢鑷姩鐢熸垚閲囪喘璁㈠崟銆�" ok-text="纭涓爣" cancel-text="鍙栨秷" @confirm="handleConfirmBidAsWinner(record.id)">
+ <a-button size="small" type="link" style="color: #52c41a;"><IconifyIcon icon="ant-design:check-circle-outlined" />纭涓爣</a-button>
+ </a-popconfirm>
+ <a-popconfirm v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && 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>
@@ -532,8 +698,30 @@
<span class="text-gray-400">渚涘簲鍟嗭細<b class="text-gray-700">{{ selectedBid.supplierName }}</b></span>
<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="!isAwarded && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
+ <!-- 鎶曟爣鎬婚噾棰濓紙鏁村崟鎬讳环锛夛細鍙笌鎶ヤ环鍚堣鑷姩甯﹀嚭骞朵汉宸ヨ皟鏁� -->
+ <div v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" class="flex flex-wrap items-center gap-x-3 gap-y-2 mb-2 px-3 py-2 bg-blue-50/70 rounded text-xs">
+ <span class="text-gray-600 font-medium">鎶曟爣鎬婚噾棰�</span>
+ <a-input-number v-model:value="bidTotalDraft" :min="0" :precision="2" class="!w-40" placeholder="鏁村崟鎬讳环">
+ <template #addonAfter>鍏�</template>
+ </a-input-number>
+ <a-button size="small" type="link" :disabled="selectedQuoteTotalAmount <= 0" @click="bidTotalDraft = selectedQuoteTotalAmount">
+ 鑷姩甯﹀嚭鎶ヤ环鍚堣 楼{{ selectedQuoteTotalAmount.toLocaleString() }}
+ </a-button>
+ <a-button size="small" type="primary" @click="handleSaveBidTotal">淇濆瓨鎬讳环</a-button>
+ </div>
+ <a-alert
+ v-if="bidTotalCompareHint"
+ :type="bidTotalCompareHint.tone"
+ :show-icon="true"
+ class="mb-3"
+ :message="bidTotalCompareHint.text"
+ />
+ <div class="flex items-center justify-between mb-3">
+ <span class="text-xs text-gray-400">
+ 宸叉姤浠� <b class="text-gray-600">{{ quoteList.length }}</b> / <b class="text-gray-600">{{ materialList.length }}</b> 椤圭墿鏂欙紝鎶ヤ环鍚堣
+ <b class="text-blue-500">{{ selectedQuoteTotalAmount.toLocaleString() }}</b> 鍏�
+ </span>
+ <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鏂板鎶ヤ环
</a-button>
@@ -542,6 +730,12 @@
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'quotePrice'">
<span class="font-medium text-blue-600">{{ record.quotePrice?.toLocaleString() }}</span>
+ </template>
+ <template v-if="column.dataIndex === 'quantity'">
+ {{ materialQuantityOf(record) || '-' }}
+ </template>
+ <template v-if="column.dataIndex === 'quoteAmount'">
+ <span class="font-medium text-blue-600">{{ quoteAmountOf(record).toLocaleString() }}</span>
</template>
<template v-if="column.dataIndex === 'taxRate'">
{{ record.taxRate != null ? `${record.taxRate}%` : '-' }}
@@ -553,7 +747,7 @@
{{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 涓湀` : '-' }}
</template>
<template v-if="column.key === 'actions'">
- <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>
+ <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
</template>
</template>
</a-table>
@@ -565,7 +759,7 @@
</a-tab-pane>
<!-- Tab4: 寮�鏍� -->
- <a-tab-pane key="bidOpen">
+ <a-tab-pane v-if="!isSimpleMode" key="bidOpen">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />寮�鏍�</a-space>
</template>
@@ -583,7 +777,7 @@
<div class="text-center py-8">
<a-result status="info" title="灏氭湭寮�鏍�" sub-title="鎵ц寮�鏍囧悗椤圭洰杩涘叆鎶曟爣闃舵锛屼緵搴斿晢鍙寮忔姇鏍�" />
<br />
- <a-button v-if="!isAwarded" type="primary" @click="handleCreateBidOpen">
+ <a-button v-if="isPublished" type="primary" @click="handleCreateBidOpen">
<template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template>
鎵ц寮�鏍�
</a-button>
@@ -592,18 +786,18 @@
</a-tab-pane>
<!-- Tab5: 璇勬爣 -->
- <a-tab-pane key="evaluation">
+ <a-tab-pane v-if="!isSimpleMode" key="evaluation">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />璇勬爣</a-space>
</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 v-if="!isAwarded">
+ <a-space v-if="isBiddingOrEvaluating">
<a-button size="small" @click="handleCalculateRanking">
<template #icon><IconifyIcon icon="ant-design:bar-chart-outlined" /></template>
璁$畻鎺掑悕
</a-button>
- <a-button type="primary" size="small" @click="evalForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0 }; evalFormVisible = true">
+ <a-button type="primary" size="small" @click="evalForm = { tenderProjectId: projectId, supplierId: undefined!, bidId: undefined! }; evalFormVisible = true">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鏂板璇勬爣
</a-button>
@@ -630,7 +824,7 @@
<span v-else class="text-gray-300">-</span>
</template>
<template v-if="column.key === 'actions'">
- <a-button v-if="!isAwarded" size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+ <a-button v-if="isBiddingOrEvaluating" size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
</template>
</template>
</a-table>
@@ -638,7 +832,7 @@
</a-tab-pane>
<!-- Tab6: 瀹氭爣 -->
- <a-tab-pane key="award">
+ <a-tab-pane v-if="!isSimpleMode" key="award">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />瀹氭爣</a-space>
</template>
@@ -671,7 +865,7 @@
<span class="text-sm">閲囪喘璁㈠崟 ID: {{ award.purchaseOrderId }}</span>
</template>
</a-result>
- <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+ <a-popconfirm v-if="canDeleteAward" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
<a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
</a-popconfirm>
</div>
@@ -679,11 +873,11 @@
<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 v-if="!isAwarded" type="primary" block @click="handleGeneratePurchaseOrder">
+ <a-button v-if="canGeneratePO" type="primary" block @click="handleGeneratePurchaseOrder">
<template #icon><IconifyIcon icon="ant-design:shopping-cart-outlined" /></template>
鐢熸垚閲囪喘璁㈠崟
</a-button>
- <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+ <a-popconfirm v-if="canDeleteAward" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
<a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
</a-popconfirm>
</div>
@@ -691,11 +885,11 @@
<template v-else>
<div class="text-center">
<div class="text-gray-500 text-sm mb-3">瀹氭爣淇℃伅宸插垱寤猴紝璇峰鎵�</div>
- <a-button v-if="!isAwarded" type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;">
+ <a-button v-if="canApproveAward" type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;">
<template #icon><IconifyIcon icon="ant-design:check-outlined" /></template>
瀹℃壒閫氳繃
</a-button>
- <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+ <a-popconfirm v-if="canDeleteAward" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
<a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
</a-popconfirm>
</div>
@@ -707,7 +901,7 @@
<div class="text-center py-8">
<a-result status="info" title="灏氭湭瀹氭爣" sub-title="鍩轰簬璇勬爣鎺掑悕閫夋嫨涓爣渚涘簲鍟嗗苟鍒涘缓瀹氭爣" />
<br />
- <a-button v-if="!isAwarded" type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true">
+ <a-button v-if="isEvaluating" type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: undefined!, bidId: undefined!, awardNo: '' }; awardFormVisible = true">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鍒涘缓瀹氭爣
</a-button>
@@ -768,7 +962,9 @@
</a-select>
</a-form-item>
<a-form-item label="鎶曟爣鎬婚噾棰�">
- <a-input-number v-model:value="bidForm.bidTotalAmount" :min="0" class="!w-full" placeholder="鎶曟爣鎬婚噾棰�"><template #addonAfter>鍏�</template></a-input-number>
+ <div class="text-xs text-gray-400 leading-5">
+ 鍙殏涓嶅~锛氭姤浠锋槑缁嗗綍瀹屽悗锛屽湪銆屾姤浠风鐞嗐�嶉〉鎸夋姤浠峰悎璁¤嚜鍔ㄥ甫鍑恒�佸啀浜哄伐寰皟淇濆瓨銆�
+ </div>
</a-form-item>
</a-form>
</a-modal>
@@ -783,7 +979,7 @@
</a-form-item>
<a-row :gutter="12">
<a-col :span="8">
- <a-form-item label="鎶ヤ环浠锋牸" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="鎶ヤ环"><template #addonAfter>鍏�</template></a-input-number></a-form-item>
+ <a-form-item label="鎶ヤ环鍗曚环" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="鎶ヤ环"><template #addonAfter>鍏�</template></a-input-number></a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="绋庣巼(%)"><a-input-number v-model:value="quoteForm.taxRate" :min="0" :max="100" class="!w-full" placeholder="濡� 13" /></a-form-item>
@@ -839,6 +1035,32 @@
</a-form>
</a-modal>
+ <!-- 绠�鏄撴ā寮忥細纭涓爣寮圭獥 -->
+ <a-modal v-model:open="confirmModalVisible" title="纭涓爣" :width="460" @ok="handleSimpleConfirm">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="涓爣鎶曟爣璁板綍" required>
+ <a-select
+ v-model:value="confirmBidId"
+ show-search
+ placeholder="閫夋嫨涓爣鐨勬姇鏍囪褰�"
+ :filter-option="filterOption"
+ >
+ <a-select-option
+ v-for="b in bidList.filter((i) => i.bidStatus !== BID_STATUS.WITHDRAWN)"
+ :key="b.id"
+ :value="b.id"
+ :label="`${b.supplierName} - ${b.bidNo}`"
+ >
+ {{ b.supplierName }} - {{ b.bidNo }} (楼{{ b.bidTotalAmount?.toLocaleString?.() ?? b.bidTotalAmount }})
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <div class="text-gray-400 text-xs">
+ 纭鍚庡皢鑷姩鐢熸垚閲囪喘璁㈠崟锛岃鎿嶄綔涓嶅彲鎾ら攢銆�
+ </div>
+ </a-form>
+ </a-modal>
+
<!-- 瀹氭爣琛ㄥ崟 -->
<a-modal v-model:open="awardFormVisible" title="鍒涘缓瀹氭爣" :width="500" @ok="handleCreateAward">
<a-form layout="vertical" class="mt-4">
@@ -846,15 +1068,15 @@
<a-row :gutter="12">
<a-col :span="12">
<a-form-item label="渚涘簲鍟�" required>
- <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption">
- <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+ <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption" @change="onAwardSupplierChange">
+ <a-select-option v-for="s in bidSuppliers" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="鎶曟爣" required>
- <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption">
- <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
+ <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption" :disabled="!awardForm.supplierId" @change="onAwardBidChange">
+ <a-select-option v-for="b in activeBids" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
@@ -867,4 +1089,4 @@
</a-modal>
</template>
</Page>
-</template>
+</template>
\ No newline at end of file
--
Gitblit v1.9.3