诉讼信息自动归集至对应项目;
中标信息自动关联至对应项目;
所有销售合同数据均按项维度归集,作为项目管理模块中销售合同汇总的数据来源。
已修改1个文件
249 ■■■■■ 文件已修改
src/views/projectManagement/Management/projectDetail.vue 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/projectManagement/Management/projectDetail.vue
@@ -160,6 +160,88 @@
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <el-tab-pane label="诉讼信息" name="lawsuit">
          <el-table :data="lawsuitRows" border v-loading="lawsuitLoading" style="margin-top: 14px;">
            <el-table-column align="center" label="序号" type="index" width="60" />
            <el-table-column label="案件编号" prop="caseNo" width="160" show-overflow-tooltip />
            <el-table-column label="所属项目" prop="projectName" min-width="160" show-overflow-tooltip />
            <el-table-column label="合同编号" prop="contractNo" width="160" show-overflow-tooltip />
            <el-table-column label="诉讼状态" width="110" align="center">
              <template #default="{ row }">
                <el-tag :type="lawsuitStatusTypeMap[row.lawsuitStatus] || 'info'">{{ row.lawsuitStatus || '-' }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column label="立案时间" prop="filingDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="承办律师/法务负责人" prop="lawyer" width="160" show-overflow-tooltip />
            <el-table-column label="开庭时间" prop="hearingDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="诉讼标的金额(元)" prop="claimAmount" width="150" align="right" :formatter="formatAmountColumn" />
            <el-table-column label="判决结果摘要" prop="judgmentSummary" min-width="180" show-overflow-tooltip />
            <el-table-column label="诉讼进展备注" prop="progressRemark" min-width="160" show-overflow-tooltip />
            <el-table-column label="附件" min-width="160">
              <template #default="{ row }">
                <div v-if="getLawsuitAttachments(row).length > 0" class="attachment-list">
                  <el-button
                    v-for="att in getLawsuitAttachments(row)"
                    :key="att.id || att.url || att.name || att.fileName"
                    link
                    type="primary"
                    size="small"
                    @click="downloadAttachment(att)"
                  >{{ att.name || att.fileName || '附件' }}</el-button>
                </div>
                <span v-else>-</span>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <el-tab-pane label="中标信息" name="bidding">
          <el-table :data="biddingRows" border v-loading="biddingLoading" style="margin-top: 14px;">
            <el-table-column align="center" label="序号" type="index" width="60" />
            <el-table-column label="所属项目" prop="projectName" min-width="160" show-overflow-tooltip />
            <el-table-column label="标书名称" prop="bidDocName" min-width="160" show-overflow-tooltip />
            <el-table-column label="招标单位" prop="tenderUnit" width="160" show-overflow-tooltip />
            <el-table-column label="投标单位" prop="bidUnit" width="160" show-overflow-tooltip />
            <el-table-column label="招标日期" prop="tenderDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="投标日期" prop="bidDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="标书款金额(元)" prop="bidDocFee" width="130" align="right" :formatter="formatAmountColumn" />
            <el-table-column label="标书款状态" width="110" align="center">
              <template #default="{ row }">
                <el-tag :type="bidDocFeeStatusTypeMap[row.bidDocFeeStatus] || 'info'">{{ row.bidDocFeeStatus || '-' }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column label="标书款状态变更日期" prop="bidDocFeeStatusChangeDate" width="160" :formatter="formatDateColumn" />
            <el-table-column label="投标保证金(元)" prop="bidDeposit" width="130" align="right" :formatter="formatAmountColumn" />
            <el-table-column label="招投标结果" width="150" align="center">
              <template #default="{ row }">
                <el-tag :type="bidResultTypeMap[row.bidResult] || 'info'">{{ row.bidResult || '-' }}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <el-tab-pane label="销售合同" name="contract">
          <el-table :data="contractRows" border v-loading="contractLoading" style="margin-top: 14px;">
            <el-table-column align="center" label="序号" type="index" width="60" />
            <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip />
            <el-table-column label="客户名称" prop="customerName" min-width="180" show-overflow-tooltip />
            <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip />
            <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip />
            <el-table-column label="付款方式" prop="paymentMethod" width="120">
              <template #default="{ row }">
                <dict-tag :options="checkout_payment" :value="row.paymentMethod" />
              </template>
            </el-table-column>
            <el-table-column label="合同金额(元)" prop="contractAmount" width="140" align="right" :formatter="formatAmountColumn" />
            <el-table-column label="实际合同金额(元)" prop="netContractAmount" width="150" align="right" :formatter="formatAmountColumn" />
            <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip />
            <el-table-column label="录入日期" prop="entryDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="签订日期" prop="executionDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="交付日期" prop="deliveryDate" width="110" :formatter="formatDateColumn" />
            <el-table-column label="备注" prop="remarks" min-width="160" show-overflow-tooltip />
          </el-table>
        </el-tab-pane>
      </el-tabs>
    </el-card>
  </div>
@@ -188,6 +270,9 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import { getProject, saveStage, listStage, deleteStage } from '@/api/projectManagement/project'
import { listPlan } from '@/api/projectManagement/projectType'
import { lawsuitListPage } from '@/api/salesManagement/lawsuitManagement'
import { biddingListPage } from '@/api/salesManagement/biddingManagement'
import { ledgerListPage } from '@/api/salesManagement/salesLedger'
import ProgressReportDialog from '@/components/ProjectManagement/ProgressReportDialog.vue'
import DiscussProgressDialog from '@/components/ProjectManagement/DiscussProgressDialog.vue'
import useUserStore from '@/store/modules/user'
@@ -196,6 +281,7 @@
const route = useRoute()
const router = useRouter()
const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status')
const { checkout_payment } = proxy.useDict('checkout_payment')
const loading = ref(false)
const activeTab = ref('base')
@@ -214,6 +300,13 @@
const planNodeRows = ref([])
const planAttachments = ref([])
const stageNodeRows = ref([])
const lawsuitRows = ref([])
const biddingRows = ref([])
const contractRows = ref([])
const lawsuitLoading = ref(false)
const biddingLoading = ref(false)
const contractLoading = ref(false)
const estimatedDays = computed(() => {
  const raw = info.value?.estimatedDays
@@ -374,6 +467,161 @@
  ElMessage.warning('附件暂无下载地址')
}
const lawsuitStatusTypeMap = {
  立案中: 'warning',
  审理中: 'primary',
  已判决: 'success',
  已撤诉: 'info',
  已和解: 'success'
}
const bidDocFeeStatusTypeMap = {
  未交: 'warning',
  已交: 'success',
  已退: 'info'
}
const bidResultTypeMap = {
  废标: 'info',
  流标: 'info',
  未中标: 'warning',
  中标待出通知书: 'primary',
  已出中标通知书: 'success',
  已签合同: 'success'
}
function formatDate(val) {
  if (val === undefined || val === null || val === '') return '-'
  return String(val).substring(0, 10)
}
function formatAmount(val) {
  if (val === undefined || val === null || val === '') return '0.00'
  const num = Number(val)
  return Number.isNaN(num) ? '0.00' : num.toFixed(2)
}
function formatDateColumn(row, column, cellValue) {
  return formatDate(cellValue)
}
function formatAmountColumn(row, column, cellValue) {
  return formatAmount(cellValue)
}
function getLawsuitAttachments(row) {
  if (Array.isArray(row?.storageBlobVOs)) return row.storageBlobVOs
  if (Array.isArray(row?.attachmentList)) return row.attachmentList
  return []
}
function matchProject(record, projectId, projectTitle) {
  if (!record) return false
  if (record.projectId !== undefined && record.projectId !== null && record.projectId !== '') {
    if (String(record.projectId) === String(projectId)) return true
  }
  const name = String(record.projectName || '').trim()
  const title = String(projectTitle || '').trim()
  if (name && title) {
    return name === title || name.includes(title) || title.includes(name)
  }
  return false
}
function filterByProject(records, projectId, projectTitle) {
  const list = Array.isArray(records) ? records : []
  if (list.length === 0) return []
  const matched = list.filter(r => matchProject(r, projectId, projectTitle))
  return matched.length > 0 ? matched : list
}
function extractRecords(res) {
  return (
    res?.data?.records ||
    res?.records ||
    res?.rows ||
    res?.data?.rows ||
    res?.data?.list ||
    (Array.isArray(res?.data) ? res.data : []) ||
    []
  )
}
async function loadLawsuitList() {
  const projectId = info.value?.id
  const projectTitle = info.value?.title
  if (!projectId) {
    lawsuitRows.value = []
    return
  }
  lawsuitLoading.value = true
  try {
    let res = await lawsuitListPage({ projectName: projectTitle, current: 1, size: 999 })
    let records = extractRecords(res)
    let matched = records.filter(r => matchProject(r, projectId, projectTitle))
    if (matched.length === 0) {
      res = await lawsuitListPage({ current: 1, size: 999 })
      records = extractRecords(res)
      matched = records.filter(r => matchProject(r, projectId, projectTitle))
    }
    lawsuitRows.value = matched
  } catch {
    lawsuitRows.value = []
  } finally {
    lawsuitLoading.value = false
  }
}
async function loadBiddingList() {
  const projectId = info.value?.id
  const projectTitle = info.value?.title
  if (!projectId) {
    biddingRows.value = []
    return
  }
  biddingLoading.value = true
  try {
    let res = await biddingListPage({ projectName: projectTitle, current: 1, size: 999 })
    let records = extractRecords(res)
    let matched = records.filter(r => matchProject(r, projectId, projectTitle))
    if (matched.length === 0) {
      res = await biddingListPage({ current: 1, size: 999 })
      records = extractRecords(res)
      matched = records.filter(r => matchProject(r, projectId, projectTitle))
    }
    biddingRows.value = matched
  } catch {
    biddingRows.value = []
  } finally {
    biddingLoading.value = false
  }
}
async function loadContractList() {
  const projectId = info.value?.id
  if (!projectId) {
    contractRows.value = []
    return
  }
  contractLoading.value = true
  try {
    const res = await ledgerListPage({ projectId, current: 1, size: 999 })
    const records = extractRecords(res)
    contractRows.value = filterByProject(records, projectId, info.value?.title).sort((a, b) => {
      const da = String(a.executionDate || '').substring(0, 10)
      const db = String(b.executionDate || '').substring(0, 10)
      if (!da && !db) return 0
      if (!da) return 1
      if (!db) return -1
      return db.localeCompare(da)
    })
  } catch {
    contractRows.value = []
  } finally {
    contractLoading.value = false
  }
}
async function loadProjectTypeMap() {
  try {
    const res = await listPlan({ current: 1, size: 999 })
@@ -471,6 +719,7 @@
onMounted(async () => {
  await loadProjectTypeMap()
  await loadDetail()
  await Promise.all([loadLawsuitList(), loadBiddingList(), loadContractList()])
})
</script>