liu
2026-09-07 a7dc7b230fcc6abc6ee5a7bfadb90789b03d2ca8
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>