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