<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>
|