|
@@ -0,0 +1,362 @@
|
|
|
+<template>
|
|
|
+ <ContentWrap v-loading="formLoading">
|
|
|
+ <!--
|
|
|
+ <el-page-header>
|
|
|
+ <template #content>
|
|
|
+ <span class="text-large font-600 mr-3"> 项目详情 </span>
|
|
|
+ </template>
|
|
|
+ </el-page-header> -->
|
|
|
+
|
|
|
+ <!-- 项目基本信息 -->
|
|
|
+ <el-card class="box-card" style="margin-top: 20px">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>项目基本信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ label-width="100px"
|
|
|
+ >
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="合同名称">
|
|
|
+ <el-input v-model="formData.contractName" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="合同编号">
|
|
|
+ <el-input v-model="formData.contractCode" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="客户名称">
|
|
|
+ <el-input v-model="formData.manufactureName" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="结算方式">
|
|
|
+ <el-input v-model="formData.payment" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="总数">
|
|
|
+ <el-input v-model="formData.workloadTotal" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="已完成">
|
|
|
+ <el-input v-model="formData.workloadFinish" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="开始时间">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formData.startTime"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择开始时间"
|
|
|
+ readonly
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="完成时间">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formData.endTime"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择完成时间"
|
|
|
+ readonly
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item label="备注">
|
|
|
+ <el-input v-model="formData.remark" type="textarea" readonly />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 任务列表 -->
|
|
|
+ <el-card class="box-card" style="margin-top: 20px">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>任务列表</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-table :data="taskList" v-loading="loading">
|
|
|
+ <el-table-column label="井号" prop="wellName" />
|
|
|
+ <el-table-column label="井型/井别" prop="wellType" />
|
|
|
+ <el-table-column label="施工地点" prop="location" />
|
|
|
+ <el-table-column label="施工工艺" prop="technique" />
|
|
|
+ <el-table-column label="设计工作量" prop="workloadDesign" />
|
|
|
+ <el-table-column label="施工队伍">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ getDeptNames(row.deptIds) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="施工设备">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tooltip
|
|
|
+ :content="getAllDeviceNames(row.deviceIds)"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <span class="device-names">
|
|
|
+ {{ getDeviceNames(row.deviceIds) }}
|
|
|
+ </span>
|
|
|
+ </el-tooltip>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="责任人">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tooltip
|
|
|
+ :content="getAllResponsiblePersonNames(row.responsiblePerson)"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <span class="responsible-names">
|
|
|
+ {{ getResponsiblePersonNames(row.responsiblePerson) }}
|
|
|
+ </span>
|
|
|
+ </el-tooltip>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="备注" prop="remark" />
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </ContentWrap>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, onMounted } from "vue";
|
|
|
+import { useRoute, useRouter } from "vue-router";
|
|
|
+import { IotProjectInfoApi } from '@/api/pms/iotprojectinfo'
|
|
|
+import { IotProjectTaskApi } from '@/api/pms/iotprojecttask'
|
|
|
+import { IotDeviceApi } from '@/api/pms/device'
|
|
|
+import * as UserApi from "@/api/system/user";
|
|
|
+import * as DeptApi from "@/api/system/dept";
|
|
|
+import { handleTree } from "@/utils/tree";
|
|
|
+
|
|
|
+/** 项目信息 详情 包含 项目下的任务列表 */
|
|
|
+defineOptions({ name: 'IotProjectInfoDetail' })
|
|
|
+
|
|
|
+const { currentRoute, push } = useRouter();
|
|
|
+const { params } = useRoute();
|
|
|
+const loading = ref(false);
|
|
|
+const formLoading = ref(false);
|
|
|
+
|
|
|
+// 项目基本信息
|
|
|
+const formData = ref({
|
|
|
+ id: undefined,
|
|
|
+ contractName: undefined,
|
|
|
+ contractCode: undefined,
|
|
|
+ manufactureName: undefined,
|
|
|
+ payment: undefined,
|
|
|
+ workloadTotal: undefined,
|
|
|
+ workloadFinish: undefined,
|
|
|
+ startTime: undefined,
|
|
|
+ endTime: undefined,
|
|
|
+ remark: undefined,
|
|
|
+});
|
|
|
+
|
|
|
+// 任务列表
|
|
|
+const taskList = ref([]);
|
|
|
+const deptList = ref([]);
|
|
|
+const deviceMap = ref({});
|
|
|
+const responsiblePersonList = ref([]);
|
|
|
+
|
|
|
+// 获取部门名称
|
|
|
+const getDeptNames = (deptIds) => {
|
|
|
+ if (!deptIds || deptIds.length === 0) return '';
|
|
|
+
|
|
|
+ const names = [];
|
|
|
+ const findDept = (list, id) => {
|
|
|
+ for (const item of list) {
|
|
|
+ if (item.id === id) {
|
|
|
+ names.push(item.name);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (item.children && findDept(item.children, id)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ deptIds.forEach(id => findDept(deptList.value, id));
|
|
|
+ return names.join(', ');
|
|
|
+};
|
|
|
+
|
|
|
+// 获取设备名称
|
|
|
+const getDeviceNames = (deviceIds) => {
|
|
|
+ if (!deviceIds || deviceIds.length === 0) return '';
|
|
|
+
|
|
|
+ const deviceNames = deviceIds
|
|
|
+ .map(id => deviceMap.value[id]?.deviceCode)
|
|
|
+ .filter(name => name);
|
|
|
+
|
|
|
+ if (deviceNames.length === 0) return '';
|
|
|
+ if (deviceNames.length > 2) {
|
|
|
+ return `${deviceNames[0]}, ${deviceNames[1]}...`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return deviceNames.join(', ');
|
|
|
+};
|
|
|
+
|
|
|
+// 获取所有设备名称
|
|
|
+const getAllDeviceNames = (deviceIds) => {
|
|
|
+ if (!deviceIds || deviceIds.length === 0) return '无设备';
|
|
|
+
|
|
|
+ const deviceNames = deviceIds
|
|
|
+ .map(id => deviceMap.value[id]?.deviceCode || '未知设备')
|
|
|
+ .filter(name => name !== '未知设备');
|
|
|
+
|
|
|
+ return deviceNames.join(', ') || '无有效设备';
|
|
|
+};
|
|
|
+
|
|
|
+// 获取责任人名称
|
|
|
+const getResponsiblePersonNames = (responsiblePersonIds) => {
|
|
|
+ if (!responsiblePersonIds || responsiblePersonIds.length === 0) return '';
|
|
|
+
|
|
|
+ const personNames = responsiblePersonIds
|
|
|
+ .map(id => {
|
|
|
+ const person = responsiblePersonList.value.find(p => p.id === id);
|
|
|
+ return person ? person.nickname : '';
|
|
|
+ })
|
|
|
+ .filter(name => name);
|
|
|
+
|
|
|
+ if (personNames.length === 0) return '';
|
|
|
+ if (personNames.length > 2) {
|
|
|
+ return `${personNames[0]}, ${personNames[1]}...`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return personNames.join(', ');
|
|
|
+};
|
|
|
+
|
|
|
+// 获取所有责任人名称
|
|
|
+const getAllResponsiblePersonNames = (responsiblePersonIds) => {
|
|
|
+ if (!responsiblePersonIds || responsiblePersonIds.length === 0) return '无责任人';
|
|
|
+
|
|
|
+ const personNames = responsiblePersonIds
|
|
|
+ .map(id => {
|
|
|
+ const person = responsiblePersonList.value.find(p => p.id === id);
|
|
|
+ return person ? person.nickname : '未知人员';
|
|
|
+ })
|
|
|
+ .filter(name => name !== '未知人员');
|
|
|
+
|
|
|
+ return personNames.join(', ') || '无有效责任人';
|
|
|
+};
|
|
|
+
|
|
|
+// 返回上一页
|
|
|
+const goBack = () => {
|
|
|
+ push({ name: 'IotProjectInfo' });
|
|
|
+};
|
|
|
+
|
|
|
+// 加载项目详情
|
|
|
+const loadProjectDetail = async () => {
|
|
|
+ formLoading.value = true;
|
|
|
+ try {
|
|
|
+ // 获取项目基本信息
|
|
|
+ const projectInfo = await IotProjectInfoApi.getIotProjectInfo(params.id as string);
|
|
|
+ formData.value = { ...projectInfo };
|
|
|
+
|
|
|
+ // 获取任务列表
|
|
|
+ const queryParams = {
|
|
|
+ projectId: params.id
|
|
|
+ };
|
|
|
+ const taskData = await IotProjectTaskApi.getIotProjectTaskPage(queryParams);
|
|
|
+ taskList.value = taskData.list;
|
|
|
+
|
|
|
+ // 收集所有设备ID和责任人ID
|
|
|
+ const allDeviceIds = new Set();
|
|
|
+ const allResponsiblePersonIds = new Set();
|
|
|
+
|
|
|
+ taskList.value.forEach(item => {
|
|
|
+ if (item.deviceIds?.length) {
|
|
|
+ item.deviceIds.forEach(id => allDeviceIds.add(id));
|
|
|
+ }
|
|
|
+ if (item.responsiblePerson?.length) {
|
|
|
+ item.responsiblePerson.forEach(id => allResponsiblePersonIds.add(id));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 批量获取设备信息
|
|
|
+ if (allDeviceIds.size > 0) {
|
|
|
+ const deviceIdsArray = Array.from(allDeviceIds);
|
|
|
+ const devices = await IotDeviceApi.getDevicesByDepts({
|
|
|
+ deviceIds: deviceIdsArray
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新设备映射表
|
|
|
+ devices.forEach(device => {
|
|
|
+ deviceMap.value[device.id] = device;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量获取责任人信息
|
|
|
+ if (allResponsiblePersonIds.size > 0) {
|
|
|
+ const personIdsArray = Array.from(allResponsiblePersonIds);
|
|
|
+ const persons = await UserApi.companyDeptsEmployee({
|
|
|
+ userIds: personIdsArray
|
|
|
+ });
|
|
|
+ responsiblePersonList.value = persons;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载项目详情失败:', error);
|
|
|
+ } finally {
|
|
|
+ formLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ // 加载部门列表
|
|
|
+ deptList.value = handleTree(await DeptApi.companyLevelChildrenDepts());
|
|
|
+ // 加载项目详情
|
|
|
+ await loadProjectDetail();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.device-names {
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ max-width: 120px;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.responsible-names {
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ max-width: 120px;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.text {
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.item {
|
|
|
+ margin-bottom: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+.box-card {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+</style>
|