2026-08-03 020a05d8d31565fc6fe2b0f76645cdabe8374e1c
refactor(srm): 优化招标项目功能实现

- 移除 ant-design-vue 的 Row 和 Col 组件依赖,改用 Tailwind CSS Grid 布局
- 为招标表单字段添加条件显示逻辑,支持部分更新场景
- 重构招标详情页面的状态判断逻辑,提高可读性和维护性
- 更新表单初始化逻辑以支持部分编辑状态
- 移除人力资源模块中的社保公积金档案组件
- 为销售通知单添加自动关联销售订单明细功能
- 增加招标项目的部分更新 API 接口
- 调整招标项目操作按钮的显示条件和权限控制
已修改8个文件
225 ■■■■ 文件已修改
src/api/srm/tender/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/analytics/modules/ai-insights.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/employee/modules/form.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/tender/data.ts 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/tender/detail.vue 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/tender/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/srm/tender/modules/form.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/modules/form.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/srm/tender/index.ts
@@ -99,6 +99,12 @@
    purchaseOrderId?: number;
    remark?: string;
  }
  export interface TenderProjectPartialUpdateVO {
    id: number;
    tenderDesc?: string;
    qualificationRequirements?: string;
  }
}
// ========== 招标项目 ==========
@@ -119,6 +125,10 @@
  return requestClient.put('/srm/tender-project/update', data);
}
export function updateTenderProjectPartial(data: SrmTenderApi.TenderProjectPartialUpdateVO) {
  return requestClient.put('/srm/tender-project/update-info', data);
}
export function deleteTenderProject(id: number) {
  return requestClient.delete(`/srm/tender-project/delete?id=${id}`);
}
src/views/dashboard/analytics/modules/ai-insights.vue
@@ -1,10 +1,5 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { CountTo } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Col, Row } from 'ant-design-vue';
defineOptions({ name: 'DashboardAiInsights' });
@@ -71,28 +66,28 @@
        {{ activeOrderCount }} 个工单可分析
      </span>
    </div>
    <Row :gutter="16">
      <Col v-for="cap in capabilities" :key="cap.key" :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
    <div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
      <div
        v-for="cap in capabilities"
        :key="cap.key"
        class="group flex cursor-pointer items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
        @click="emit('navigate', cap.route)"
      >
        <div
          class="group flex cursor-pointer items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
          @click="emit('navigate', cap.route)"
          class="flex size-10 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br shadow-md transition-transform duration-300 group-hover:scale-110"
          :class="cap.gradient"
        >
          <div
            class="flex size-10 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br shadow-md transition-transform duration-300 group-hover:scale-110"
            :class="cap.gradient"
          >
            <IconifyIcon :icon="cap.icon" class="size-5 text-white" />
          </div>
          <div class="min-w-0 flex-1">
            <div class="text-sm font-medium text-gray-800 dark:text-gray-100">
              {{ cap.title }}
            </div>
            <div class="text-xs text-gray-400">{{ cap.desc }}</div>
          </div>
          <IconifyIcon icon="lucide:chevron-right" class="size-4 text-gray-300 transition-transform duration-200 group-hover:translate-x-0.5" />
          <IconifyIcon :icon="cap.icon" class="size-5 text-white" />
        </div>
      </Col>
    </Row>
        <div class="min-w-0 flex-1">
          <div class="text-sm font-medium text-gray-800 dark:text-gray-100">
            {{ cap.title }}
          </div>
          <div class="text-xs text-gray-400">{{ cap.desc }}</div>
        </div>
        <IconifyIcon icon="lucide:chevron-right" class="size-4 text-gray-300 transition-transform duration-200 group-hover:translate-x-0.5" />
      </div>
    </div>
  </div>
</template>
src/views/hrm/employee/modules/form.vue
@@ -16,7 +16,6 @@
import EducationList from './education-list.vue';
import WorkHistoryList from './work-history-list.vue';
import EmergencyContactList from './emergency-contact-list.vue';
import SocialSecurityList from './social-security-list.vue';
import EmployeeDetail from './detail.vue';
const emit = defineEmits(['success']);
@@ -30,7 +29,6 @@
const educationListRef = ref<InstanceType<typeof EducationList>>();
const workHistoryListRef = ref<InstanceType<typeof WorkHistoryList>>();
const emergencyContactListRef = ref<InstanceType<typeof EmergencyContactList>>();
const socialSecurityListRef = ref<InstanceType<typeof SocialSecurityList>>();
const isDetail = computed(() => formType.value === 'detail');
const getTitle = computed(() => {
@@ -153,14 +151,6 @@
            ref="emergencyContactListRef"
            :form-type="formType"
            :employee-id="employeeId"
          />
        </Tabs.TabPane>
        <Tabs.TabPane key="socialSecurity" tab="社保公积金档案" v-if="employeeId">
          <SocialSecurityList
            ref="socialSecurityListRef"
            :form-type="formType"
            :employee-id="employeeId"
            :user-id="employeeData?.userId"
          />
        </Tabs.TabPane>
      </Tabs>
src/views/srm/tender/data.ts
@@ -20,11 +20,23 @@
      },
    },
    {
      component: 'Input',
      fieldName: '_partial',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'tenderNo',
      label: '招标编号',
      component: 'Input',
      rules: 'required',
      componentProps: { placeholder: '请输入招标编号' },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'tenderName',
@@ -32,6 +44,10 @@
      component: 'Input',
      rules: 'required',
      componentProps: { placeholder: '请输入招标项目名称' },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'tenderType',
@@ -42,18 +58,30 @@
        placeholder: '请选择招标类型',
        options: TENDER_TYPE_OPTIONS,
      },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'purchaseOrg',
      label: '采购组织',
      component: 'Input',
      componentProps: { placeholder: '请输入采购组织' },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'projectLeaderName',
      label: '项目负责人',
      component: 'Input',
      componentProps: { placeholder: '请输入项目负责人' },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'budgetAmount',
@@ -63,6 +91,10 @@
        class: '!w-full',
        min: 0,
        placeholder: '请输入预算金额',
      },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
@@ -76,6 +108,10 @@
        format: 'YYYY-MM-DD HH:mm:ss',
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'endTime',
@@ -88,6 +124,10 @@
        format: 'YYYY-MM-DD HH:mm:ss',
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'bidOpenTime',
@@ -100,6 +140,10 @@
        format: 'YYYY-MM-DD HH:mm:ss',
        valueFormat: 'YYYY-MM-DD HH:mm:ss',
      },
      dependencies: {
        triggerFields: ['_partial'],
        show: (partial: boolean) => !partial,
      },
    },
    {
      fieldName: 'tenderDesc',
src/views/srm/tender/detail.vue
@@ -126,7 +126,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,7 +146,7 @@
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 availableMaterials = computed(() => {
@@ -199,7 +199,7 @@
  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) {
@@ -226,7 +226,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 +251,7 @@
// ========== 定标 ==========
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: '' });
async function loadAward() {
  try { award.value = await getAwardByProject(projectId); } catch { award.value = undefined; }
@@ -295,7 +295,20 @@
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 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 },
@@ -462,7 +475,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 +486,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 +505,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="isPublishedOrBidding" 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 +523,7 @@
                  <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="isPublishedOrBidding && 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>
@@ -533,7 +546,7 @@
                  <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">
                  <a-button v-if="isPublishedOrBidding && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
                    <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                    新增报价
                  </a-button>
@@ -553,7 +566,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 && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />编辑</a-button>
                    </template>
                  </template>
                </a-table>
@@ -583,7 +596,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>
@@ -598,12 +611,12 @@
              </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 +643,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>
@@ -671,7 +684,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 +692,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 +704,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 +720,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>
src/views/srm/tender/index.vue
@@ -163,6 +163,14 @@
              onClick: handleEdit.bind(null, row),
            },
            {
              label: '编辑信息',
              type: 'link',
              icon: ACTION_ICON.EDIT,
              auth: ['srm:tender-project:update'],
              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING,
              onClick: handleEdit.bind(null, row),
            },
            {
              label: $t('common.delete'),
              type: 'link',
              danger: true,
@@ -189,7 +197,7 @@
              type: 'link',
              danger: true,
              auth: ['srm:tender-project:update'],
              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING,
              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING,
              popConfirm: {
                title: '确认要关闭该项目吗?',
                confirm: handleClose.bind(null, row),
src/views/srm/tender/modules/form.vue
@@ -12,14 +12,21 @@
  createTenderProject,
  getTenderProject,
  updateTenderProject,
  updateTenderProjectPartial,
} from '#/api/srm/tender';
import { $t } from '#/locales';
import { TENDER_STATUS } from '../../enums';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<SrmTenderApi.TenderProjectVO>();
const isPartial = computed(() => {
  const data = formData.value;
  return data?.id && data.tenderStatus !== undefined && data.tenderStatus !== TENDER_STATUS.DRAFT;
});
const getTitle = computed(() => {
  if (isPartial.value) return '编辑招标信息';
  return formData.value?.id
    ? $t('ui.actionTitle.edit', ['招标项目'])
    : $t('ui.actionTitle.create', ['招标项目']);
@@ -45,9 +52,17 @@
    modalApi.lock();
    const data = (await formApi.getValues()) as SrmTenderApi.TenderProjectVO;
    try {
      await (formData.value?.id
        ? updateTenderProject(data)
        : createTenderProject(data));
      if (isPartial.value) {
        await updateTenderProjectPartial({
          id: data.id!,
          tenderDesc: data.tenderDesc,
          qualificationRequirements: data.qualificationRequirements,
        });
      } else {
        await (formData.value?.id
          ? updateTenderProject(data)
          : createTenderProject(data));
      }
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
@@ -67,7 +82,9 @@
    modalApi.lock();
    try {
      formData.value = await getTenderProject(data.id);
      await formApi.setValues(formData.value);
      const partial = formData.value.tenderStatus !== undefined
        && formData.value.tenderStatus !== TENDER_STATUS.DRAFT;
      await formApi.setValues({ ...formData.value, _partial: partial || undefined });
    } finally {
      modalApi.unlock();
    }
src/views/wls/salesnotice/modules/form.vue
@@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { FormType } from '../data';
import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
import { computed, ref } from 'vue';
@@ -17,6 +18,8 @@
  submitSalesNotice,
  updateSalesNotice,
} from '#/api/mes/wm/salesnotice';
import { createSalesNoticeLine } from '#/api/mes/wm/salesnotice/line';
import { getSaleOrderItemListByOrderId } from '#/api/erp/sale/order';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
@@ -107,6 +110,29 @@
      }
      // 新增:保持弹窗打开,切换到编辑模式
      const id = await createSalesNotice(data);
      // 选择了销售订单时,自动带入订单产品明细
      if (data.saleOrderId) {
        try {
          const items = await getSaleOrderItemListByOrderId(data.saleOrderId);
          if (items?.length) {
            await Promise.all(items.map((item: ErpSaleOrderApi.SaleOrderItem) =>
              createSalesNoticeLine({
                noticeId: id,
                saleOrderItemId: item.id,
                itemId: item.productId,
                itemCode: item.productBarCode,
                itemName: item.productName,
                unitMeasureName: item.productUnitName,
                quantity: item.count,
                batchCode: item.batchCode,
                oqcCheckFlag: false,
              }),
            ));
          }
        } catch {
          // 获取订单明细失败不影响主流程
        }
      }
      formData.value = {
        ...data,
        id,