fix:施工立项新建项目的时候的字段修改

This commit is contained in:
Maple 2025-08-13 16:21:33 +08:00
parent 79b8eb340f
commit a3aaf7da07
3 changed files with 295 additions and 109 deletions

View File

@ -18,6 +18,8 @@ export interface ProjectResp {
projectCategory?: string // 项目类型/服务
projectManagerId?: string // 项目经理ID
projectManagerName?: string // 项目经理姓名
projectOrigin?: string // 项目来源
projectStaff?: string[] // 施工人员
startDate?: string // 开始日期
endDate?: string // 结束日期
@ -28,10 +30,10 @@ export interface ProjectResp {
coverUrl?: string // 封面URL
createDt?: Date
updateDt?: Date
// 为了保持向后兼容,添加一些别名字段
id?: string // projectId的别名
fieldName?: string // farmName的别名
fieldName?: string // farmName的别名
fieldLocation?: string // farmAddress的别名
commissionUnit?: string // client的别名
commissionContact?: string // clientContact的别名
@ -85,7 +87,7 @@ export interface TaskQuery {
status?: string
}
export interface TaskPageQuery extends TaskQuery, PageQuery {}
export interface TaskPageQuery extends TaskQuery, PageQuery {}
// ==================== 人员调度相关类型 ====================
@ -339,4 +341,4 @@ export interface PageRes<T> {
total: number
page: number
pageSize: number
}
}

View File

@ -70,6 +70,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}

View File

@ -2,19 +2,21 @@
项目管理页面
已完成接口对接:
1. 项目列表查询 (listProject) - 支持分页和条件查询
2. 项目新增 (addProject)
2. 项目新增 (addProject)
3. 项目修改 (updateProject)
4. 项目删除 (deleteProject)
5. 项目导出 (exportProject)
6. 项目导入 (importProject)
所有API调用都已添加错误处理和类型安全检查
-->
<template>
<GiPageLayout>
<GiTable row-key="id" :data="dataList" :columns="tableColumns" :loading="loading"
<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">
@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>
@ -71,10 +73,14 @@
</GiTable>
<!-- 新增/编辑项目弹窗 -->
<a-modal v-model:visible="addModalVisible" :title="modalTitle" @cancel="resetForm"
:ok-button-props="{ loading: submitLoading }" @ok="handleSubmit" width="800px" modal-class="project-form-modal">
<a-form ref="formRef" :model="form" :rules="formRules" layout="vertical"
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }">
<a-modal
v-model:visible="addModalVisible" :title="modalTitle" :ok-button-props="{ loading: submitLoading }"
width="800px" modal-class="project-form-modal" @cancel="resetForm" @ok="handleSubmit"
>
<a-form
ref="formRef" :model="form" :rules="formRules" layout="vertical"
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }"
>
<!-- 基本信息 -->
<a-divider orientation="left">基本信息</a-divider>
<a-row :gutter="16">
@ -88,10 +94,12 @@
<a-input v-model="form.farmAddress" placeholder="请输入地址" />
</a-form-item>
</a-col>
<a-col><a-button size="mini" @click="() => { Message.info(`待开发`) }">
<a-col>
<a-button size="mini" @click="() => { Message.info(`待开发`) }">
<template #icon><icon-location /></template>
地图选点
</a-button></a-col>
</a-button>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
@ -108,7 +116,7 @@
<a-col :span="12">
<a-form-item field="inspectionUnit" label="业主">
<a-input v-model="form.inspectionUnit" placeholder="请输入业主单位" @input="(val) => (form.farmName = val)" />
<!--风场名称同步业主 -->
<!-- 风场名称同步业主 -->
</a-form-item>
</a-col>
<a-col :span="12">
@ -145,10 +153,133 @@
</a-col>
</a-row>
<a-row :gutter="16">
<a-form-item field="projectContent" label="项目内容">
<a-textarea v-model="form.coverUrl" placeholder="请输入项目内容" :rows="4" />
</a-form-item>
<a-col :span="12">
<a-form-item field="projectOrigin" label="项目来源" :rules="[{ required: true, message: '请输入项目来源' }]">
<a-input v-model="form.projectOrigin" placeholder="请输入项目来源" />
</a-form-item>
</a-col>
</a-row>
<a-divider orientation="left">任务设置</a-divider>
<div class="mb-2">
<a-button type="dashed" size="small" @click="addTask">
<template #icon><icon-plus /></template>
新增任务
</a-button>
</div>
<div v-if="form.tasks.length === 0" class="text-gray-500 mb-2">暂无任务请点击新增任务</div>
<a-space direction="vertical" fill>
<a-card v-for="(task, tIndex) in form.tasks" :key="tIndex" size="small">
<template #title>
<div class="flex items-center justify-between">
<span>任务 {{ tIndex + 1 }}</span>
<a-space>
<a-button size="mini" @click="addSubtask(tIndex)">新增子任务</a-button>
<a-button size="mini" status="danger" @click="removeTask(tIndex)">删除</a-button>
</a-space>
</div>
</template>
<a-row :gutter="12">
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.taskName`" label="任务名称" required>
<a-input v-model="task.taskName" placeholder="请输入任务名称" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item :field="`tasks.${tIndex}.taskCode`" label="任务编号">
<a-input v-model="task.taskCode" placeholder="编号" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item :field="`tasks.${tIndex}.mainUserId`" label="负责人">
<a-select v-model="task.mainUserId" placeholder="选择负责人" :loading="userLoading">
<a-option v-for="u in userOptions" :key="u.value" :value="u.value">{{ u.label }}</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="4">
<a-form-item :field="`tasks.${tIndex}.scales`" label="工量">
<a-input-number v-model="task.scales" :min="0" :max="9999" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.planStartDate`" label="计划开始">
<a-date-picker v-model="task.planStartDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.planEndDate`" label="计划结束">
<a-date-picker v-model="task.planEndDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.taskGroupId`" label="任务组">
<a-input-number v-model="task.taskGroupId" :min="0" placeholder="可选" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
<!-- 子任务 -->
<div v-if="task.children && task.children.length">
<a-divider orientation="left">子任务</a-divider>
<a-card
v-for="(sub, sIndex) in task.children"
:key="sIndex"
size="small"
class="mb-2"
>
<template #title>
<div class="flex items-center justify-between">
<span>子任务 {{ tIndex + 1 }}-{{ sIndex + 1 }}</span>
<a-button size="mini" status="danger" @click="removeSubtask(tIndex, sIndex)">删除</a-button>
</div>
</template>
<a-row :gutter="12">
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskName`" label="任务名称" required>
<a-input v-model="sub.taskName" placeholder="请输入任务名称" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskCode`" label="任务编号">
<a-input v-model="sub.taskCode" placeholder="编号" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.mainUserId`" label="负责人">
<a-select v-model="sub.mainUserId" placeholder="选择负责人" :loading="userLoading">
<a-option v-for="u in userOptions" :key="u.value" :value="u.value">{{ u.label }}</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="4">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.scales`" label="工量">
<a-input-number v-model="sub.scales" :min="0" :max="9999" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.planStartDate`" label="计划开始">
<a-date-picker v-model="sub.planStartDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.planEndDate`" label="计划结束">
<a-date-picker v-model="sub.planEndDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskGroupId`" label="任务组">
<a-input-number v-model="sub.taskGroupId" :min="0" placeholder="可选" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
</a-card>
</div>
</a-card>
</a-space>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="status" label="项目状态">
@ -198,7 +329,6 @@
</a-col>
</a-row>
<a-divider orientation="middle">地图</a-divider>
</a-form>
</a-modal>
@ -221,53 +351,53 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Message, Modal } from '@arco-design/web-vue'
import { addProject, deleteProject, listProject, updateProject, exportProject, importProject } from '@/apis/project'
import type { TableColumnData } from '@arco-design/web-vue'
import TurbineGrid from './TurbineGrid.vue'
import { addProject, deleteProject, exportProject, importProject, listProject, updateProject } from '@/apis/project'
import { isMobile } from '@/utils'
import has from '@/utils/has'
import http from '@/utils/http'
import type { ColumnItem } from '@/components/GiForm'
import type { TableColumnData } from '@arco-design/web-vue'
import type { ProjectResp, ProjectPageQuery } from '@/apis/project/type'
import * as T from '@/apis/project/type'
import TurbineGrid from './TurbineGrid.vue'
import type { ProjectPageQuery } from '@/apis/project/type'
import type * as T from '@/apis/project/type'
defineOptions({ name: 'ProjectManagement' })
// (API)
const PROJECT_STATUS = {
NOT_STARTED: 0, // /
IN_PROGRESS: 1, //
COMPLETED: 2, //
NOT_STARTED: 0, // /
IN_PROGRESS: 1, //
COMPLETED: 2, //
} as const
//
const PROJECT_STATUS_MAP = {
0: '待施工',
1: '施工中',
2: '已完成'
2: '已完成',
} as const
//
const PROJECT_STATUS_OPTIONS = [
{ label: '待施工', value: 0 },
{ label: '施工中', value: 1 },
{ label: '已完成', value: 2 }
{ label: '已完成', value: 2 },
]
//
const PROJECT_CATEGORY = {
EXTERNAL_WORK: '外部工作',
INTERNAL_PROJECT: '内部项目',
TECHNICAL_SERVICE: '技术服务'
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 }
{ label: PROJECT_CATEGORY.TECHNICAL_SERVICE, value: PROJECT_CATEGORY.TECHNICAL_SERVICE },
]
const router = useRouter()
@ -280,12 +410,12 @@ const currentId = ref<string | null>(null)
const fileList = ref([])
const dataList = ref<T.ProjectResp[]>([])
const userLoading = ref(false)
const userOptions = ref<{ label: string; value: string }[]>([])
const userOptions = ref<{ label: string, value: string }[]>([])
let searchForm = reactive<Partial<ProjectPageQuery>>({
const searchForm = reactive<Partial<ProjectPageQuery>>({
projectName: '',
status: undefined,
fieldName: '', // 使fieldNameAPI使
fieldName: '', // 使fieldNameAPI使
})
const queryFormColumns: ColumnItem[] = reactive([
@ -320,28 +450,48 @@ const queryFormColumns: ColumnItem[] = reactive([
])
const form = reactive({
projectId: '', // id
projectName: '', //
projectManagerId: '', // id
client: '', //
clientContact: '', //
clientPhone: '', //
inspectionUnit: '', //
inspectionContact: '', //
inspectionPhone: '', //
farmName: '', //
farmAddress: '', //
scale: '', //
turbineModel: '', //
status: '', // 01234
startDate: '', //
endDate: '', //
coverUrl: '', //
projectId: '', // id
projectName: '', //
projectManagerId: '', // id
client: '', //
clientContact: '', //
clientPhone: '', //
inspectionUnit: '', //
inspectionContact: '', //
inspectionPhone: '', //
farmName: '', //
farmAddress: '', //
projectOrigin: '', //
scale: '', //
turbineModel: '', //
status: '', // 01234
startDate: '', //
endDate: '', //
// coverUrl: '', // 使
constructionTeamLeaderId: '', // id
constructorIds: '', // id
qualityOfficerId: '', // id
auditorId: '', // id
turbineList: [] as { id: number; turbineNo: string; lat?: number; lng?: number; status: 0 | 1 | 2 }[], //
constructorIds: '', // id
qualityOfficerId: '', // id
auditorId: '', // id
//
tasks: [] as Array<{
taskName: string
taskCode?: string
mainUserId?: string | number
planStartDate?: string
planEndDate?: string
scales?: number
taskGroupId?: number | string
children?: Array<{
taskName: string
taskCode?: string
mainUserId?: string | number
planStartDate?: string
planEndDate?: string
scales?: number
taskGroupId?: number | string
}>
}>,
turbineList: [] as { id: number, turbineNo: string, lat?: number, lng?: number, status: 0 | 1 | 2 }[], //
})
const pagination = reactive({
@ -350,7 +500,7 @@ const pagination = reactive({
total: 0,
showTotal: true,
showJumper: true,
showPageSize: true
showPageSize: true,
})
const openMapModal = (item: any) => {
Message.info(`地图选点功能待开发,当前机组编号:${item.turbineNo}`)
@ -384,70 +534,70 @@ const tableColumns = ref<TableColumnData[]>([
slotName: 'fieldInfo',
minWidth: 180,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '状态',
dataIndex: 'status',
slotName: 'status',
align: 'center',
width: 100
width: 100,
},
{
title: '委托单位',
dataIndex: 'commissionUnit',
minWidth: 140,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '委托单位联系人/电话',
slotName: 'commissionInfo',
minWidth: 160,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '业主',
dataIndex: 'inspectionUnit',
minWidth: 140,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '业主联系人/电话',
slotName: 'inspectionInfo',
minWidth: 160,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '项目规模',
dataIndex: 'projectScale',
width: 100,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '机组型号',
dataIndex: 'orgNumber',
width: 100,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '项目经理/施工人员',
slotName: 'projectManager',
minWidth: 160,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '项目周期',
slotName: 'projectPeriod',
minWidth: 180,
ellipsis: true,
tooltip: true
tooltip: true,
},
{
title: '操作',
@ -492,7 +642,7 @@ const fetchData = async () => {
const params: ProjectPageQuery = {
...searchForm,
page: pagination.current,
size: pagination.pageSize
size: pagination.pageSize,
}
const res = await listProject(params)
@ -516,9 +666,9 @@ const fetchData = async () => {
projectManager: item.projectManagerName,
projectScale: item.scale,
//
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : []
};
return mappedItem;
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : [],
}
return mappedItem
})
// APItotal使
@ -553,7 +703,7 @@ const reset = () => {
//
Object.assign(searchForm, {
projectName: '',
fieldName: '', // 使fieldNameAPI使
fieldName: '', // 使fieldNameAPI使
status: undefined,
})
@ -576,27 +726,29 @@ const onPageSizeChange = (pageSize: number) => {
const resetForm = () => {
//
Object.assign(form, {
projectId: '', // id
projectName: '', //
projectManagerId: '', // id
client: '', //
clientContact: '', //
clientPhone: '', //
inspectionUnit: '', //
inspectionContact: '', //
inspectionPhone: '', //
farmName: '', //
farmAddress: '', //
scale: '', //
turbineModel: '', //
status: 0, // 01234
startDate: '', //
endDate: '', //
coverUrl: '', //
projectId: '', // id
projectName: '', //
projectManagerId: '', // id
client: '', //
clientContact: '', //
clientPhone: '', //
inspectionUnit: '', //
inspectionContact: '', //
inspectionPhone: '', //
farmName: '', //
farmAddress: '', //
projectOrigin: '', //
scale: '', //
turbineModel: '', //
status: 0, // 01234
startDate: '', //
endDate: '', //
// coverUrl: '', //
constructionTeamLeaderId: '', // id
constructorIds: '', // id
qualityOfficerId: '', // id
auditorId: '' // id
constructorIds: '', // id
qualityOfficerId: '', // id
auditorId: '', // id
tasks: [],
})
isEdit.value = false
@ -607,21 +759,35 @@ const openAddModal = () => {
resetForm()
addModalVisible.value = true
}
// /使
const addTask = () => {
;(form.tasks as any[]).push({ taskName: '', taskCode: '', mainUserId: undefined, planStartDate: '', planEndDate: '', scales: undefined, taskGroupId: undefined, children: [] })
}
const removeTask = (index: number) => {
;(form.tasks as any[]).splice(index, 1)
}
const addSubtask = (parentIndex: number) => {
const list = (form.tasks as any[])
if (!list[parentIndex].children) list[parentIndex].children = []
list[parentIndex].children!.push({ taskName: '', taskCode: '', mainUserId: undefined, planStartDate: '', planEndDate: '', scales: undefined, taskGroupId: undefined })
}
const removeSubtask = (parentIndex: number, index: number) => {
const list = (form.tasks as any[])
list[parentIndex].children!.splice(index, 1)
}
const openEditModal = (record: T.ProjectResp) => {
isEdit.value = true
currentId.value = record.id || record.projectId || null
//
Object.keys(form).forEach(key => {
// @ts-ignore
form[key] = ''
})
// tasksturbineList
resetForm()
//
Object.keys(form).forEach(key => {
Object.keys(form).forEach((key) => {
if (key in record && record[key as keyof T.ProjectResp] !== undefined) {
// @ts-ignore -
// @ts-expect-error -
form[key] = record[key as keyof T.ProjectResp]
}
})
@ -645,6 +811,7 @@ const openEditModal = (record: T.ProjectResp) => {
//
const formRules = {
projectName: [{ required: true, message: '请输入项目名称' }],
projectOrigin: [{ required: true, message: '请输入项目来源' }],
}
//
@ -660,15 +827,32 @@ const handleSubmit = async () => {
await formRef.value.validate()
//
const normalizeDate = (d: any) => (d ? (typeof d === 'string' ? d : new Date(d).toISOString().split('T')[0]) : '')
//
const mapTasks = (tasks: any[]) =>
(tasks || []).map(t => ({
...t,
planStartDate: normalizeDate(t.planStartDate),
planEndDate: normalizeDate(t.planEndDate),
children: (t.children || []).map((c: any) => ({
...c,
planStartDate: normalizeDate(c.planStartDate),
planEndDate: normalizeDate(c.planEndDate),
}))
}))
const submitData = {
...form,
// projectId
projectId: isEdit.value && currentId.value ? currentId.value : form.projectId,
// - YYYY-MM-DD
startDate: form.startDate ? (typeof form.startDate === 'string' ? form.startDate : new Date(form.startDate).toISOString().split('T')[0]) : '',
endDate: form.endDate ? (typeof form.endDate === 'string' ? form.endDate : new Date(form.endDate).toISOString().split('T')[0]) : '',
startDate: normalizeDate(form.startDate),
endDate: normalizeDate(form.endDate),
// ID -
constructorIds: Array.isArray(form.constructorIds) ? form.constructorIds.join(',') : form.constructorIds
constructorIds: Array.isArray(form.constructorIds) ? form.constructorIds.join(',') : form.constructorIds,
//
tasks: mapTasks(form.tasks as any[]),
}
console.log('提交数据:', submitData)
@ -753,8 +937,8 @@ const viewDetail = (record: T.ProjectResp) => {
router.push({
name: 'ProjectDetail',
params: {
id: projectId.toString()
}
id: projectId.toString(),
},
})
}
@ -811,7 +995,7 @@ const exportData = async () => {
const params = {
projectName: searchForm.projectName,
status: searchForm.status,
fieldName: searchForm.fieldName, // 使fieldNameAPI使
fieldName: searchForm.fieldName, // 使fieldNameAPI使
}
await exportProject(params)
@ -829,9 +1013,9 @@ const fetchUserList = async () => {
//
const res = await http.get('/user/list')
if (res.data && Array.isArray(res.data)) {
userOptions.value = res.data.map(item => ({
userOptions.value = res.data.map((item) => ({
label: item.userName || item.username || item.name || item.nickName || item.account || '未命名用户',
value: item.userId || item.id || ''
value: item.userId || item.id || '',
}))
} else {
userOptions.value = []