2025-06-30 09:14:46 +08:00
|
|
|
|
<!--
|
|
|
|
|
项目管理页面
|
|
|
|
|
已完成接口对接:
|
|
|
|
|
1. 项目列表查询 (listProject) - 支持分页和条件查询
|
|
|
|
|
2. 项目新增 (addProject)
|
|
|
|
|
3. 项目修改 (updateProject)
|
|
|
|
|
4. 项目删除 (deleteProject)
|
|
|
|
|
5. 项目导出 (exportProject)
|
|
|
|
|
6. 项目导入 (importProject)
|
|
|
|
|
|
|
|
|
|
所有API调用都已添加错误处理和类型安全检查
|
|
|
|
|
-->
|
2025-06-27 19:54:42 +08:00
|
|
|
|
<template>
|
|
|
|
|
<GiPageLayout>
|
|
|
|
|
<GiTable
|
|
|
|
|
row-key="id"
|
|
|
|
|
:data="dataList"
|
|
|
|
|
:columns="tableColumns"
|
|
|
|
|
:loading="loading"
|
|
|
|
|
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
|
|
|
|
:pagination="pagination"
|
|
|
|
|
:disabled-tools="['size']"
|
|
|
|
|
@page-change="onPageChange"
|
|
|
|
|
@page-size-change="onPageSizeChange"
|
|
|
|
|
@refresh="search"
|
|
|
|
|
>
|
|
|
|
|
<template #top>
|
|
|
|
|
<GiForm v-model="searchForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset"></GiForm>
|
|
|
|
|
</template>
|
|
|
|
|
<template #toolbar-left>
|
|
|
|
|
<a-button v-permission="['project:create']" type="primary" @click="openAddModal">
|
|
|
|
|
<template #icon><icon-plus /></template>
|
|
|
|
|
<template #default>新增项目</template>
|
|
|
|
|
</a-button>
|
|
|
|
|
<a-button v-permission="['project:import']" @click="openImportModal">
|
|
|
|
|
<template #icon><icon-upload /></template>
|
|
|
|
|
<template #default>导入</template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
<template #toolbar-right>
|
|
|
|
|
<a-button v-permission="['project:export']" @click="exportData">
|
|
|
|
|
<template #icon><icon-download /></template>
|
|
|
|
|
<template #default>导出</template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
<template #status="{ record }">
|
|
|
|
|
<a-tag :color="getStatusColor(record.status)">{{ record.status }}</a-tag>
|
|
|
|
|
</template>
|
|
|
|
|
<template #fieldInfo="{ record }">
|
|
|
|
|
<div>{{ record.fieldName }}</div>
|
|
|
|
|
<div class="text-xs text-gray-500">{{ record.fieldLocation }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template #commissionInfo="{ record }">
|
|
|
|
|
<div>{{ record.commissionContact }}</div>
|
|
|
|
|
<div class="text-xs text-gray-500">{{ record.commissionPhone }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template #inspectionInfo="{ record }">
|
|
|
|
|
<div>{{ record.inspectionContact }}</div>
|
|
|
|
|
<div class="text-xs text-gray-500">{{ record.inspectionPhone }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template #projectPeriod="{ record }">
|
|
|
|
|
<span v-if="record.projectPeriod?.length === 2">
|
|
|
|
|
{{ record.projectPeriod[0] }} 至 {{ record.projectPeriod[1] }}
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template #projectManager="{ record }">
|
|
|
|
|
<div>{{ record.projectManager }}</div>
|
|
|
|
|
<div class="text-xs text-gray-500">{{ record.projectStaff?.join(', ') }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template #action="{ record }">
|
|
|
|
|
<a-space>
|
|
|
|
|
<a-link v-permission="['project:detail']" title="详情" @click="viewDetail(record)">详情</a-link>
|
|
|
|
|
<a-link v-permission="['project:update']" title="修改" @click="openEditModal(record)">修改</a-link>
|
|
|
|
|
<a-link
|
|
|
|
|
v-permission="['project:delete']"
|
|
|
|
|
status="danger"
|
|
|
|
|
title="删除"
|
|
|
|
|
@click="confirmDelete(record)"
|
|
|
|
|
>
|
|
|
|
|
删除
|
|
|
|
|
</a-link>
|
|
|
|
|
</a-space>
|
|
|
|
|
</template>
|
|
|
|
|
</GiTable>
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑项目弹窗 -->
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:visible="addModalVisible"
|
|
|
|
|
:title="modalTitle"
|
|
|
|
|
@cancel="resetForm"
|
|
|
|
|
@before-ok="handleSubmit"
|
|
|
|
|
width="800px"
|
|
|
|
|
>
|
|
|
|
|
<a-form ref="formRef" :model="form" label-position="left" :label-col-props="{ span: 8 }" :wrapper-col-props="{ span: 16 }">
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="projectName" label="项目名称" required>
|
|
|
|
|
<a-input v-model="form.projectName" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="fieldName" label="风场名称" required>
|
|
|
|
|
<a-input v-model="form.fieldName" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="fieldLocation" label="风场地址" required>
|
|
|
|
|
<a-input v-model="form.fieldLocation" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="commissionUnit" label="委托单位" required>
|
|
|
|
|
<a-input v-model="form.commissionUnit" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="commissionContact" label="委托单位联系人" required>
|
|
|
|
|
<a-input v-model="form.commissionContact" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="commissionPhone" label="委托单位联系电话" required>
|
|
|
|
|
<a-input v-model="form.commissionPhone" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="inspectionUnit" label="检查单位" required>
|
|
|
|
|
<a-input v-model="form.inspectionUnit" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="inspectionContact" label="检查单位联系人" required>
|
|
|
|
|
<a-input v-model="form.inspectionContact" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="inspectionPhone" label="检查单位联系电话" required>
|
|
|
|
|
<a-input v-model="form.inspectionPhone" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="projectScale" label="项目规模" required>
|
|
|
|
|
<a-input v-model="form.projectScale" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="orgNumber" label="机组型号" required>
|
|
|
|
|
<a-input v-model="form.orgNumber" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="projectCategory" label="项目类型/服务" required>
|
|
|
|
|
<a-select v-model="form.projectCategory" placeholder="请选择">
|
2025-06-30 09:14:46 +08:00
|
|
|
|
<a-option v-for="option in PROJECT_CATEGORY_OPTIONS" :key="option.value" :value="option.value">
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
</a-option>
|
2025-06-27 19:54:42 +08:00
|
|
|
|
</a-select>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="projectManager" label="项目经理" required>
|
|
|
|
|
<a-select v-model="form.projectManager" placeholder="请选择">
|
|
|
|
|
<a-option value="请选择">请选择</a-option>
|
|
|
|
|
</a-select>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item field="status" label="状态" required>
|
|
|
|
|
<a-select v-model="form.status" placeholder="请选择">
|
2025-06-30 09:14:46 +08:00
|
|
|
|
<a-option v-for="option in PROJECT_STATUS_OPTIONS" :key="option.value" :value="option.value">
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
</a-option>
|
2025-06-27 19:54:42 +08:00
|
|
|
|
</a-select>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="24">
|
|
|
|
|
<a-form-item field="projectIntro" label="项目描述">
|
|
|
|
|
<a-textarea v-model="form.projectIntro" placeholder="请输入" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="24">
|
|
|
|
|
<a-form-item field="projectStaff" label="施工人员" required>
|
|
|
|
|
<a-select v-model="form.projectStaff" multiple placeholder="请选择">
|
|
|
|
|
<a-option value="全部员工">全部员工</a-option>
|
|
|
|
|
</a-select>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="24">
|
|
|
|
|
<a-form-item field="projectPeriod" label="项目周期" required>
|
|
|
|
|
<a-range-picker v-model="form.projectPeriod" style="width: 100%" />
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</a-form>
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
|
|
|
|
<!-- 导入项目弹窗 -->
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:visible="importModalVisible"
|
|
|
|
|
title="导入文件"
|
|
|
|
|
@cancel="handleCancelImport"
|
|
|
|
|
@before-ok="handleImport"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex flex-col items-center justify-center p-8">
|
|
|
|
|
<div class="text-primary text-4xl mb-4">
|
|
|
|
|
<icon-file-upload />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="text-lg font-medium mb-2">批量导入文件</div>
|
|
|
|
|
<div class="text-gray-500 mb-4">拖动文件到此处,或点击下方按钮上传</div>
|
|
|
|
|
<a-upload
|
|
|
|
|
:file-list="fileList"
|
|
|
|
|
:limit="1"
|
|
|
|
|
@change="handleFileChange"
|
|
|
|
|
>
|
|
|
|
|
<template #upload-button>
|
|
|
|
|
<a-button type="primary">选择文件</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-upload>
|
|
|
|
|
</div>
|
|
|
|
|
</a-modal>
|
|
|
|
|
</GiPageLayout>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import { Message, Modal } from '@arco-design/web-vue'
|
2025-06-30 09:14:46 +08:00
|
|
|
|
import { addProject, deleteProject, listProject, updateProject, exportProject, importProject } from '@/apis/project'
|
2025-06-27 19:54:42 +08:00
|
|
|
|
import { isMobile } from '@/utils'
|
|
|
|
|
import has from '@/utils/has'
|
|
|
|
|
import type { ColumnItem } from '@/components/GiForm'
|
|
|
|
|
import type { TableColumnData } from '@arco-design/web-vue'
|
2025-06-30 09:14:46 +08:00
|
|
|
|
import type { ProjectResp, ProjectPageQuery } from '@/apis/project/type'
|
2025-06-27 19:54:42 +08:00
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'ProjectManagement' })
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
// 项目状态常量定义 (API返回数字类型)
|
|
|
|
|
const PROJECT_STATUS = {
|
|
|
|
|
NOT_STARTED: 0, // 未开始/待施工
|
|
|
|
|
IN_PROGRESS: 1, // 施工中
|
|
|
|
|
COMPLETED: 2, // 已完成
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
// 项目状态映射
|
|
|
|
|
const PROJECT_STATUS_MAP = {
|
|
|
|
|
0: '待施工',
|
|
|
|
|
1: '施工中',
|
|
|
|
|
2: '已完成'
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
// 项目状态选项
|
|
|
|
|
const PROJECT_STATUS_OPTIONS = [
|
|
|
|
|
{ label: '待施工', value: 0 },
|
|
|
|
|
{ label: '施工中', value: 1 },
|
|
|
|
|
{ label: '已完成', value: 2 }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
// 项目类别常量定义
|
|
|
|
|
const PROJECT_CATEGORY = {
|
|
|
|
|
EXTERNAL_WORK: '外部工作',
|
|
|
|
|
INTERNAL_PROJECT: '内部项目',
|
|
|
|
|
TECHNICAL_SERVICE: '技术服务'
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
// 项目类别选项
|
|
|
|
|
const PROJECT_CATEGORY_OPTIONS = [
|
|
|
|
|
{ label: PROJECT_CATEGORY.EXTERNAL_WORK, value: PROJECT_CATEGORY.EXTERNAL_WORK },
|
|
|
|
|
{ label: PROJECT_CATEGORY.INTERNAL_PROJECT, value: PROJECT_CATEGORY.INTERNAL_PROJECT },
|
|
|
|
|
{ label: PROJECT_CATEGORY.TECHNICAL_SERVICE, value: PROJECT_CATEGORY.TECHNICAL_SERVICE }
|
|
|
|
|
]
|
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
const router = useRouter()
|
|
|
|
|
const formRef = ref()
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const addModalVisible = ref(false)
|
|
|
|
|
const importModalVisible = ref(false)
|
|
|
|
|
const isEdit = ref(false)
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const currentId = ref<string | null>(null)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
const fileList = ref([])
|
|
|
|
|
const dataList = ref<ProjectResp[]>([])
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const searchForm = reactive<Partial<ProjectPageQuery>>({
|
2025-06-27 19:54:42 +08:00
|
|
|
|
projectName: '',
|
|
|
|
|
status: undefined,
|
|
|
|
|
fieldName: '',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const queryFormColumns: ColumnItem[] = reactive([
|
|
|
|
|
{
|
|
|
|
|
type: 'input',
|
|
|
|
|
label: '项目名称',
|
|
|
|
|
field: 'projectName',
|
|
|
|
|
span: { xs: 24, sm: 8, xxl: 8 },
|
|
|
|
|
props: {
|
|
|
|
|
placeholder: '请输入项目名称',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'input',
|
|
|
|
|
label: '风场名称',
|
|
|
|
|
field: 'fieldName',
|
|
|
|
|
span: { xs: 24, sm: 8, xxl: 8 },
|
|
|
|
|
props: {
|
|
|
|
|
placeholder: '请输入风场名称',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'select',
|
|
|
|
|
label: '状态',
|
|
|
|
|
field: 'status',
|
|
|
|
|
span: { xs: 24, sm: 8, xxl: 8 },
|
|
|
|
|
props: {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
options: PROJECT_STATUS_OPTIONS,
|
2025-06-27 19:54:42 +08:00
|
|
|
|
placeholder: '请选择状态',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const form = reactive({
|
|
|
|
|
projectName: '',
|
|
|
|
|
projectIntro: '',
|
|
|
|
|
fieldName: '',
|
|
|
|
|
fieldLocation: '',
|
|
|
|
|
commissionUnit: '',
|
|
|
|
|
commissionContact: '',
|
|
|
|
|
commissionPhone: '',
|
|
|
|
|
inspectionUnit: '',
|
|
|
|
|
inspectionContact: '',
|
|
|
|
|
inspectionPhone: '',
|
|
|
|
|
projectScale: '',
|
|
|
|
|
orgNumber: '',
|
|
|
|
|
projectCategory: '',
|
|
|
|
|
projectManager: '',
|
|
|
|
|
projectStaff: [] as string[],
|
|
|
|
|
projectPeriod: [] as string[],
|
2025-06-30 09:14:46 +08:00
|
|
|
|
status: PROJECT_STATUS.IN_PROGRESS // 默认为施工中
|
2025-06-27 19:54:42 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
current: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
total: 0,
|
|
|
|
|
showTotal: true,
|
|
|
|
|
showJumper: true,
|
|
|
|
|
showPageSize: true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const tableColumns = ref<TableColumnData[]>([
|
|
|
|
|
{
|
|
|
|
|
title: '序号',
|
|
|
|
|
width: 66,
|
|
|
|
|
align: 'center',
|
|
|
|
|
render: ({ rowIndex }) => rowIndex + 1 + (pagination.current - 1) * pagination.pageSize,
|
|
|
|
|
fixed: !isMobile() ? 'left' : undefined,
|
|
|
|
|
},
|
2025-06-30 09:14:46 +08:00
|
|
|
|
{
|
|
|
|
|
title: '项目编号',
|
|
|
|
|
dataIndex: 'projectCode',
|
|
|
|
|
width: 120,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true,
|
|
|
|
|
fixed: !isMobile() ? 'left' : undefined,
|
|
|
|
|
},
|
2025-06-27 19:54:42 +08:00
|
|
|
|
{
|
|
|
|
|
title: '项目名称',
|
|
|
|
|
dataIndex: 'projectName',
|
|
|
|
|
minWidth: 140,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true,
|
|
|
|
|
fixed: !isMobile() ? 'left' : undefined,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '风场名称/风场地址',
|
|
|
|
|
slotName: 'fieldInfo',
|
|
|
|
|
minWidth: 180,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '状态',
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
slotName: 'status',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '委托单位',
|
|
|
|
|
dataIndex: 'commissionUnit',
|
|
|
|
|
minWidth: 140,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '委托单位联系人/电话',
|
|
|
|
|
slotName: 'commissionInfo',
|
|
|
|
|
minWidth: 160,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '检查单位',
|
|
|
|
|
dataIndex: 'inspectionUnit',
|
|
|
|
|
minWidth: 140,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '检查单位联系人/电话',
|
|
|
|
|
slotName: 'inspectionInfo',
|
|
|
|
|
minWidth: 160,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '项目规模',
|
|
|
|
|
dataIndex: 'projectScale',
|
|
|
|
|
width: 100,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '机组型号',
|
|
|
|
|
dataIndex: 'orgNumber',
|
|
|
|
|
width: 100,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '项目经理/施工人员',
|
|
|
|
|
slotName: 'projectManager',
|
|
|
|
|
minWidth: 160,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '项目周期',
|
|
|
|
|
slotName: 'projectPeriod',
|
|
|
|
|
minWidth: 180,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
tooltip: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '操作',
|
|
|
|
|
dataIndex: 'action',
|
|
|
|
|
slotName: 'action',
|
|
|
|
|
width: 180,
|
|
|
|
|
fixed: !isMobile() ? 'right' : undefined,
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const modalTitle = computed(() => isEdit.value ? '编辑项目' : '新增项目')
|
|
|
|
|
|
|
|
|
|
const getStatusColor = (status: string) => {
|
|
|
|
|
switch (status) {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
case PROJECT_STATUS.IN_PROGRESS:
|
2025-06-27 19:54:42 +08:00
|
|
|
|
return 'blue'
|
2025-06-30 09:14:46 +08:00
|
|
|
|
case PROJECT_STATUS.COMPLETED:
|
2025-06-27 19:54:42 +08:00
|
|
|
|
return 'green'
|
2025-06-30 09:14:46 +08:00
|
|
|
|
case PROJECT_STATUS.NOT_STARTED:
|
2025-06-27 19:54:42 +08:00
|
|
|
|
return 'orange'
|
|
|
|
|
default:
|
|
|
|
|
return 'gray'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const params: ProjectPageQuery = {
|
2025-06-27 19:54:42 +08:00
|
|
|
|
...searchForm,
|
|
|
|
|
page: pagination.current,
|
|
|
|
|
size: pagination.pageSize
|
2025-06-30 09:14:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = await listProject(params)
|
|
|
|
|
|
|
|
|
|
if (res.success && res.data) {
|
|
|
|
|
// API直接返回数组数据
|
|
|
|
|
const projects = Array.isArray(res.data) ? res.data : []
|
|
|
|
|
|
|
|
|
|
// 数据映射和兼容性处理
|
|
|
|
|
dataList.value = projects.map((item: any) => ({
|
|
|
|
|
...item,
|
|
|
|
|
// 添加别名字段以保持兼容性
|
|
|
|
|
id: item.projectId,
|
|
|
|
|
fieldName: item.farmName,
|
|
|
|
|
fieldLocation: item.farmAddress,
|
|
|
|
|
commissionUnit: item.client,
|
|
|
|
|
commissionContact: item.clientContact,
|
|
|
|
|
commissionPhone: item.clientPhone,
|
|
|
|
|
orgNumber: item.turbineModel,
|
|
|
|
|
projectManager: item.projectManagerName,
|
|
|
|
|
// 处理项目周期
|
|
|
|
|
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : [],
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
// 由于API没有返回total,使用当前数据长度
|
|
|
|
|
// 如果是完整数据,可以用数据长度;如果有分页,需要从其他地方获取total
|
|
|
|
|
pagination.total = projects.length
|
|
|
|
|
|
|
|
|
|
// 如果返回的数据少于每页大小,说明已经是最后一页
|
|
|
|
|
if (projects.length < pagination.pageSize) {
|
|
|
|
|
pagination.total = (pagination.current - 1) * pagination.pageSize + projects.length
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Message.error(res.msg || '获取数据失败')
|
|
|
|
|
dataList.value = []
|
|
|
|
|
pagination.total = 0
|
|
|
|
|
}
|
2025-06-27 19:54:42 +08:00
|
|
|
|
} catch (error) {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
console.error('获取项目列表失败:', error)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.error('获取数据失败')
|
2025-06-30 09:14:46 +08:00
|
|
|
|
dataList.value = []
|
|
|
|
|
pagination.total = 0
|
2025-06-27 19:54:42 +08:00
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const search = () => {
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reset = () => {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
// 重置搜索表单
|
|
|
|
|
Object.assign(searchForm, {
|
|
|
|
|
projectName: '',
|
|
|
|
|
fieldName: '',
|
|
|
|
|
status: undefined,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 重置分页并重新搜索
|
|
|
|
|
pagination.current = 1
|
2025-06-27 19:54:42 +08:00
|
|
|
|
search()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onPageChange = (current: number) => {
|
|
|
|
|
pagination.current = current
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onPageSizeChange = (pageSize: number) => {
|
|
|
|
|
pagination.pageSize = pageSize
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
formRef.value?.resetFields()
|
|
|
|
|
isEdit.value = false
|
|
|
|
|
currentId.value = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openAddModal = () => {
|
|
|
|
|
resetForm()
|
|
|
|
|
addModalVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const openEditModal = (record: ProjectResp) => {
|
2025-06-27 19:54:42 +08:00
|
|
|
|
isEdit.value = true
|
2025-06-30 09:14:46 +08:00
|
|
|
|
currentId.value = record.id || record.projectId || null
|
|
|
|
|
|
|
|
|
|
// 安全地填充表单数据
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Object.keys(form).forEach(key => {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
if (key in record && record[key as keyof ProjectResp] !== undefined) {
|
|
|
|
|
// @ts-ignore - 这里需要处理类型转换
|
|
|
|
|
form[key] = record[key as keyof ProjectResp]
|
2025-06-27 19:54:42 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
2025-06-30 09:14:46 +08:00
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
addModalVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
const valid = await formRef.value?.validate()
|
|
|
|
|
if (!valid) return false
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
let res
|
2025-06-27 19:54:42 +08:00
|
|
|
|
if (isEdit.value && currentId.value) {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
res = await updateProject(form, currentId.value)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.success('更新成功')
|
|
|
|
|
} else {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
res = await addProject(form)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.success('添加成功')
|
|
|
|
|
}
|
2025-06-30 09:14:46 +08:00
|
|
|
|
|
|
|
|
|
// 如果API返回success字段,检查操作是否真正成功
|
|
|
|
|
if (res && res.success === false) {
|
|
|
|
|
Message.error(res.msg || '操作失败')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
fetchData()
|
|
|
|
|
return true
|
|
|
|
|
} catch (error) {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
console.error('项目操作失败:', error)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.error('操作失败')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const confirmDelete = (record: ProjectResp) => {
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Modal.warning({
|
|
|
|
|
title: '确认删除',
|
|
|
|
|
content: `确定要删除项目"${record.projectName}"吗?`,
|
|
|
|
|
onOk: () => deleteItem(record),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const deleteItem = async (record: ProjectResp) => {
|
|
|
|
|
const projectId = record.id || record.projectId
|
|
|
|
|
if (!projectId) {
|
|
|
|
|
Message.error('项目ID不存在')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
try {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const res = await deleteProject(projectId)
|
|
|
|
|
|
|
|
|
|
// 检查删除操作是否成功
|
|
|
|
|
if (res && res.success === false) {
|
|
|
|
|
Message.error(res.msg || '删除失败')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.success('删除成功')
|
|
|
|
|
fetchData()
|
|
|
|
|
} catch (error) {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
console.error('删除项目失败:', error)
|
2025-06-27 19:54:42 +08:00
|
|
|
|
Message.error('删除失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const viewDetail = (record: ProjectResp) => {
|
|
|
|
|
const projectId = record.id || record.projectId
|
|
|
|
|
if (!projectId) {
|
|
|
|
|
Message.error('项目ID不存在')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-27 19:54:42 +08:00
|
|
|
|
router.push({
|
|
|
|
|
name: 'ProjectDetail',
|
|
|
|
|
params: {
|
2025-06-30 09:14:46 +08:00
|
|
|
|
id: projectId.toString()
|
2025-06-27 19:54:42 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openImportModal = () => {
|
|
|
|
|
fileList.value = []
|
|
|
|
|
importModalVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleFileChange = (files: any) => {
|
|
|
|
|
fileList.value = files
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCancelImport = () => {
|
|
|
|
|
fileList.value = []
|
|
|
|
|
importModalVisible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const handleImport = async () => {
|
2025-06-27 19:54:42 +08:00
|
|
|
|
if (fileList.value.length === 0) {
|
|
|
|
|
Message.warning('请选择文件')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
try {
|
|
|
|
|
const fileItem = fileList.value[0] as any
|
|
|
|
|
const file = fileItem?.file || fileItem
|
|
|
|
|
|
|
|
|
|
if (!file) {
|
|
|
|
|
Message.warning('请选择有效的文件')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用导入API
|
|
|
|
|
const res = await importProject(file)
|
|
|
|
|
|
|
|
|
|
if (res && res.success === false) {
|
|
|
|
|
Message.error(res.msg || '导入失败')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Message.success('导入成功')
|
|
|
|
|
handleCancelImport()
|
|
|
|
|
fetchData() // 重新获取数据
|
|
|
|
|
return true
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('导入项目失败:', error)
|
|
|
|
|
Message.error('导入失败')
|
|
|
|
|
return false
|
|
|
|
|
}
|
2025-06-27 19:54:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 09:14:46 +08:00
|
|
|
|
const exportData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
projectName: searchForm.projectName,
|
|
|
|
|
status: searchForm.status,
|
|
|
|
|
fieldName: searchForm.fieldName,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await exportProject(params)
|
|
|
|
|
Message.success('导出成功')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('导出项目失败:', error)
|
|
|
|
|
Message.error('导出失败')
|
|
|
|
|
}
|
2025-06-27 19:54:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
fetchData()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
:deep(.arco-tag) {
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|