gaoluyang
2026-03-12 fe167dd71a1300aeae07522db990d6b3fdb77a0e
src/views/projectManagement/Management/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,430 @@
<template>
  <div class="app-container">
    <SearchPanel
      v-model="queryParams"
      :schema="searchSchema"
      @search="handleQuery"
      @reset="resetQuery"
    >
      <template #billStatus="{ item }">
        <el-select v-model="queryParams[item.prop]" placeholder="请选择单据状态" clearable style="width: 100%">
          <el-option v-for="dict in bill_status" :key="dict.value" :label="dict.label" :value="dict.value" />
        </el-select>
      </template>
      <template #auditStatus="{ item }">
        <el-select v-model="queryParams[item.prop]" placeholder="请选择计划状态" clearable style="width: 100%">
          <el-option v-for="dict in project_management" :key="dict.value" :label="dict.label" :value="dict.value" />
        </el-select>
      </template>
      <template #projectStage="{ item }">
        <el-select v-model="queryParams[item.prop]" placeholder="请选择审核状态" clearable style="width: 100%">
          <el-option v-for="dict in plan_status" :key="dict.value" :label="dict.label" :value="dict.value" />
        </el-select>
      </template>
    </SearchPanel>
    <div class="table-container">
      <div class="table-actions">
        <el-button style="background-color: #002FA7; color: #fff" @click="handleAdd">新增</el-button>
        <!-- <el-dropdown split-button type="default" @command="handleGenerateBill" style="margin-left: 10px;">
          ç”Ÿæˆå•据
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="1">生成单据1</el-dropdown-item>
              <el-dropdown-item command="2">生成单据2</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown> -->
        <el-button :loading="submitLoading" @click="handleSubmit">提交</el-button>
        <el-button :loading="auditLoading" @click="handleAudit">审核</el-button>
        <el-button :loading="reverseAuditLoading" @click="handleReverseAudit">反审核</el-button>
        <el-button :loading="deleteLoading" @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
        :column="columns"
        :tableData="tableData"
        :page="pagination"
        :tableLoading="loading"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
      >
        <template #auditStatus="{ row }">
          <dict-tag :options="project_management" :value="row.auditStatus" />
        </template>
        <template #projectStage="{ row }">
          <dict-tag :options="plan_status" :value="row.projectStage" />
        </template>
        <template #action="{ row }">
          <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
          <el-button link type="primary" :loading="progressBtnLoadingId===row.id" @click="handleProgressReport(row)">进度汇报</el-button>
          <el-button link type="primary" @click="handleDetail(row)">详情</el-button>
        </template>
      </PIMTable>
    </div>
    <FormDia ref="formDiaRef" @completed="getList" />
    <ProgressReportDialog
      v-model="progressReportVisible"
      :project-id="progressProjectId"
      :project-info="progressProjectInfo"
      :plan-nodes="progressPlanNodes"
      :default-plan-node-id="progressDefaultPlanNodeId"
      @submitted="handleProgressSubmitted"
    />
  </div>
</template>
<script setup name="ProjectManagement">
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { useRouter } from 'vue-router'
import SearchPanel from '@/components/SearchPanel/index.vue'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import FormDia from './components/formDia.vue'
import ProgressReportDialog from '@/components/ProjectManagement/ProgressReportDialog.vue'
import {
  listProject,
  delProject,
  submitProject,
  auditProject,
  reverseAuditProject,
  getProject,
  saveStage
} from '@/api/projectManagement/project'
import { listPlan } from '@/api/projectManagement/projectType'
import { ElMessage, ElMessageBox } from 'element-plus'
import useUserStore from '@/store/modules/user'
const { proxy } = getCurrentInstance()
const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status')
const router = useRouter()
const userStore = useUserStore()
const loading = ref(false)
const ids = ref([])
const tableData = ref([])
const formDiaRef = ref()
const progressReportVisible = ref(false)
const progressProjectId = ref(undefined)
const progressProjectInfo = ref({})
const progressPlanNodes = ref([])
const progressDefaultPlanNodeId = ref(undefined)
const progressBtnLoadingId = ref(null)
const submitLoading = ref(false)
const auditLoading = ref(false)
const reverseAuditLoading = ref(false)
const deleteLoading = ref(false)
const data = reactive({
  queryParams: {
    projectNameOrCode: undefined,
    customerName: undefined,
    billStatus: undefined,
    projectStage: undefined,
    auditStatus: undefined,
    salesperson: undefined,
    pageNum: 1,
    pageSize: 10
  },
  pagination: {
    current: 1,
    size: 10,
    total: 0,
    layout: 'total, sizes, prev, pager, next, jumper'
  }
})
const { queryParams, pagination } = toRefs(data)
const searchSchema = [
  { prop: 'projectNameOrCode', label: '项目名称/编号', type: 'input', placeholder: '请输入项目名称/编号' },
  { prop: 'customerName', label: '客户名称', type: 'input', placeholder: '请输入客户名称' },
  { prop: 'billStatus', label: '单据状态', slot: 'billStatus' },
  { prop: 'projectStage', label: '计划状态', slot: 'projectStage' },
  { prop: 'auditStatus', label: '审核状态', slot: 'auditStatus' },
  { prop: 'salesperson', label: '业务人员', type: 'input', placeholder: '请输入业务人员' }
]
const columns = [
  { label: '单据编号', prop: 'billNo', align: 'center', width: '150' },
  { label: '项目名称', prop: 'projectName', align: 'center' },
  { label: '审核状态', prop: 'auditStatus', align: 'center', dataType: 'slot', slot: 'auditStatus' },
  { label: '客户名称', prop: 'customerName', align: 'center' },
  { label: '立项日期', prop: 'setupDate', align: 'center', width: '120' },
  { label: '项目来源', prop: 'projectSource', align: 'center' },
  { label: '项目分类', prop: 'projectClassification', align: 'center' },
  { label: '操作', prop: 'action', align: 'center', width: '250', dataType: 'slot', slot: 'action', fixed: 'right' }
]
function getList() {
  loading.value = true
  const params = {
    noOrName: queryParams.value.projectNameOrCode,
    clientName: queryParams.value.customerName,
    salesmanName: queryParams.value.salesperson,
    reviewStatus: queryParams.value.auditStatus,
    stage: queryParams.value.projectStage,
    current: queryParams.value.pageNum,
    size: queryParams.value.pageSize
  }
  listProject(params)
    .then(response => {
      const records = response?.data?.records || response?.rows || response?.records || []
      const billFilter = queryParams.value.billStatus
      const filtered = billFilter === undefined || billFilter === null || billFilter === ''
        ? records
        : records.filter(r => String(r.billStatus ?? r.status) === String(billFilter))
      tableData.value = filtered.map(r => ({
        id: r.id,
        billNo: r.no ?? r.billNo,
        projectName: r.title ?? r.projectName,
        billStatus: r.billStatus ?? r.status,
        auditStatus: r.reviewStatus ?? r.auditStatus,
        projectStage: r.stage ?? r.projectStage,
        customerName: r.clientName ?? r.customerName,
        parentProject: r.parentTitle ?? r.parentName ?? r.parentProject,
        setupDate: r.establishTime ?? r.setupDate,
        projectType: r.planName ?? r.projectType,
        projectSource: r.source ?? r.projectSource,
        projectClassification: r.departmentName ?? r.projectClassification,
        raw: r
      }))
      pagination.value.total = response?.total || response?.data?.total || 0
    })
    .finally(() => {
      loading.value = false
    })
}
function handleQuery() {
  queryParams.value.pageNum = 1
  pagination.value.current = 1
  getList()
}
function resetQuery() {
  queryParams.value = {
    projectNameOrCode: undefined,
    customerName: undefined,
    billStatus: undefined,
    projectStage: undefined,
    auditStatus: undefined,
    salesperson: undefined,
    pageNum: 1,
    pageSize: 10
  }
  handleQuery()
}
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.id)
}
function handlePagination({ page, limit }) {
  queryParams.value.pageNum = page
  queryParams.value.pageSize = limit
  pagination.value.current = page
  pagination.value.size = limit
  getList()
}
function handleAdd() {
  formDiaRef.value?.openDialog({ operationType: 'add' })
}
async function handleDelete() {
  const delIds = ids.value
  if (delIds.length === 0) {
    ElMessage.warning('请选择要删除的数据项')
    return
  }
  try {
    await ElMessageBox.confirm('是否确认删除所选数据项?', '警告', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    deleteLoading.value = true
    await delProject(delIds)
    getList()
    ElMessage.success('删除成功')
  } catch {} finally {
    deleteLoading.value = false
  }
}
async function handleSubmit() {
  const submitIds = ids.value
  if (submitIds.length === 0) {
    ElMessage.warning('请选择要提交的数据项')
    return
  }
  try {
    await ElMessageBox.confirm('是否确认提交所选数据项?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    submitLoading.value = true
    await Promise.all(submitIds.map(id => submitProject({ id })))
    getList()
    ElMessage.success('提交成功')
  } catch {} finally {
    submitLoading.value = false
  }
}
async function handleAudit() {
  const auditIds = ids.value
  if (auditIds.length === 0) {
    ElMessage.warning('请选择要审核的数据项')
    return
  }
  try {
    await ElMessageBox.confirm('是否确认审核所选数据项?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    auditLoading.value = true
    await Promise.all(auditIds.map(id => auditProject({ id })))
    getList()
    ElMessage.success('审核成功')
  } catch {} finally {
    auditLoading.value = false
  }
}
async function handleReverseAudit() {
  const reverseAuditIds = ids.value
  if (reverseAuditIds.length === 0) {
    ElMessage.warning('请选择要反审核的数据项')
    return
  }
  try {
    await ElMessageBox.confirm('是否确认反审核所选数据项?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    reverseAuditLoading.value = true
    await Promise.all(reverseAuditIds.map(id => reverseAuditProject({ id })))
    getList()
    ElMessage.success('反审核成功')
  } catch {} finally {
    reverseAuditLoading.value = false
  }
}
function handleGenerateBill(command) {
  ElMessage.info(`生成单据: ${command}`)
}
function computeDefaultPlanNodeId(stageVal, nodes) {
  const list = Array.isArray(nodes) ? nodes : []
  if (list.length === 0) return undefined
  const direct = list.find(n => String(n.id) === String(stageVal))
  if (direct?.id) return direct.id
  const sorted = [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0))
  const idx = Number(stageVal)
  if (Number.isFinite(idx)) {
    const byIndex = sorted[idx - 1] || sorted[idx] || sorted[0]
    if (byIndex?.id) return byIndex.id
  }
  return sorted[0]?.id
}
async function handleProgressReport(row) {
  if (!row?.id) return
  try {
    progressBtnLoadingId.value = row.id
    const res = await getProject(row.id)
    const detail = res?.data?.data ?? res?.data ?? res
    const info = detail?.info || {}
    progressProjectId.value = info.id
    progressProjectInfo.value = info
    const planId = info.projectManagementPlanId
    if (planId) {
      const planRes = await listPlan({ current: 1, size: 999 })
      const records = planRes?.data?.records || planRes?.records || planRes?.rows || []
      const plan = (records || []).find(p => String(p.id) === String(planId)) || {}
      progressPlanNodes.value = Array.isArray(plan?.planNodeList) ? plan.planNodeList : []
    } else {
      progressPlanNodes.value = []
    }
    progressDefaultPlanNodeId.value = computeDefaultPlanNodeId(info.stage, progressPlanNodes.value)
    progressReportVisible.value = true
  } catch (e) {
    ElMessage.error('获取项目详情失败')
  } finally {
    progressBtnLoadingId.value = null
  }
}
async function handleProgressSubmitted(payload) {
  try {
    const nodes = Array.isArray(progressPlanNodes.value) ? progressPlanNodes.value : []
    const node = nodes.find(n => String(n.id) === String(payload.planNodeId)) || {}
    const description = payload.remark
      ? `${payload.reportDate || ''} ${payload.remark}`.trim()
      : `${payload.reportDate || ''} è¿›åº¦æ±‡æŠ¥`.trim()
    const req = {
      id: null,
      projectManagementPlanNodeId: payload.planNodeId,
      projectManagementInfoId: progressProjectId.value,
      description,
      actualLeaderId: userStore.id || progressProjectInfo.value?.managerId,
      actualLeaderName: userStore.nickName || progressProjectInfo.value?.managerName,
      estimatedDuration: Number(node.estimatedDuration ?? 0) || 0,
      planStartTime: payload.planStartTime || progressProjectInfo.value?.planStartTime,
      planEndTime: payload.planEndTime || progressProjectInfo.value?.planEndTime,
      actualStartTime: payload.actualStartTime || null,
      actualEndTime: payload.actualEndTime || null,
      progress: Number(payload.totalProgress ?? payload.completionProgress ?? 0) || 0,
      attachmentIds: Array.isArray(payload.attachmentIds) ? payload.attachmentIds : []
    }
    const res = await saveStage(req)
    if (res?.code === 200) {
      ElMessage.success('提交成功')
      getList()
      return
    }
    ElMessage.error(res?.msg || '提交失败')
  } catch (e) {
    ElMessage.error('提交失败')
  }
}
function handleDetail(row) {
  if (!row?.id) return
  router.push(`/projectManagement/Management/detail/${row.id}`)
}
function handleEdit(row) {
  formDiaRef.value?.openDialog({ operationType: 'edit', row })
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
}
.table-container {
  background-color: #fff;
  padding: 20px;
  border-radius: 4px;
}
.table-actions {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
}
</style>