From fe167dd71a1300aeae07522db990d6b3fdb77a0e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 12 三月 2026 13:26:02 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New' into dev_中兴实强

---
 src/views/projectManagement/Management/index.vue |  430 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 430 insertions(+), 0 deletions(-)

diff --git a/src/views/projectManagement/Management/index.vue b/src/views/projectManagement/Management/index.vue
new file mode 100644
index 0000000..7547209
--- /dev/null
+++ b/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>

--
Gitblit v1.9.3