yanghao 13 timmar sedan
förälder
incheckning
c8ad4712f5
2 ändrade filer med 534 tillägg och 60 borttagningar
  1. 230 53
      src/views/pms/qhse/approve/index.vue
  2. 304 7
      src/views/pms/qhse/faultReport/index.vue

+ 230 - 53
src/views/pms/qhse/approve/index.vue

@@ -115,73 +115,86 @@
       </div>
     </ContentWrap>
 
-    <!-- 审批流程弹窗 -->
-    <!-- 审批流程弹窗 -->
-    <!-- 审批流程抽屉 -->
     <el-drawer
       :title="approvalDialogTitle"
       v-model="approvalDialogVisible"
       direction="rtl"
-      size="600px"
+      size="650px"
       :with-header="true"
       :close-on-click-modal="false"
       destroy-on-close
     >
       <template #header>
-        <div
-          style="
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            border-bottom: 1px solid #ebeef5;
-            padding-bottom: 20px;
-            padding-left: 20px;
-            width: 108%;
-            margin-left: -15px;
-          "
-        >
+        <div class="drawer-header">
           <span>{{ approvalDialogTitle }}</span>
         </div>
       </template>
 
-      <!-- 时间线展示审批记录 -->
-      <el-timeline>
-        <el-timeline-item
-          v-for="(item, index) in approvalProcessList"
-          :key="index"
-          :timestamp="formatDate(item.createTime)"
-          placement="top"
-          :color="getStatusColor(item.status)"
-          :icon="Check"
-        >
-          <el-card shadow="never" class="text-sm">
-            <p>节点名称: {{ item.nodeName }}</p>
-            <p>操作人: {{ item.operator }}</p>
-            <p class="pt-1">审批状态:{{ item.status }}</p>
-            <p class="pt-1">描述:{{ item.description }}</p>
-          </el-card>
-        </el-timeline-item>
-      </el-timeline>
-
-      <!-- 审批建议输入框 -->
-      <!-- <el-form-item label="审批建议" prop="suggestion">
-        <el-input
-          v-model="approvalSuggestion"
-          type="textarea"
-          :rows="3"
-          placeholder="请输入审批建议"
-          maxlength="500"
-          show-word-limit
-        />
-      </el-form-item> -->
-
-      <!-- <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="handleApprovalCancel">取 消</el-button>
-
-          <el-button type="primary" @click="handleApprovalSubmit">上 报</el-button>
+      <!-- 专业化的审批流程时间线 -->
+      <div class="approval-process-container">
+        <el-timeline v-if="approvalProcessList.length > 0">
+          <el-timeline-item
+            v-for="(item, index) in approvalProcessList"
+            :key="index"
+            placement="top"
+            :color="getNodeStatusColor(item.status)"
+            :icon="Check"
+          >
+            <el-card
+              shadow="never"
+              class="approval-card"
+              :class="{ 'status-completed': item.status === '已完成' || item.status === '已批准' }"
+            >
+              <div class="card-header">
+                <div class="node-info">
+                  <el-tag :type="getTagTypeByStatus(item.status)" size="small" class="status-tag">
+                    {{ item.status }}
+                  </el-tag>
+                  <span class="node-name">{{ item.nodeName }}</span>
+                </div>
+                <div class="operator-time">
+                  <span class="time">{{ formatDate(item.createTime) }}</span>
+                </div>
+              </div>
+
+              <div class="card-content">
+                <div class="opinion-section">
+                  <span class="label">操作人:</span>
+                  <span class="opinion">{{ item.operator }}</span>
+                </div>
+
+                <div class="description-section mt-1" v-if="item.description">
+                  <span class="label">审批意见:</span>
+                  <span class="description">{{ item.description }}</span>
+                </div>
+                <div
+                  class="attachment-section"
+                  v-if="item.attachments && item.attachments.length > 0"
+                >
+                  <span class="label">附件:</span>
+                  <div class="attachments">
+                    <el-link
+                      v-for="(attachment, idx) in item.attachments"
+                      :key="idx"
+                      type="primary"
+                      :href="attachment.url"
+                      target="_blank"
+                      class="attachment-link"
+                    >
+                      {{ attachment.name }}
+                    </el-link>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </el-timeline-item>
+        </el-timeline>
+
+        <!-- 无数据提示 -->
+        <div v-else class="no-data">
+          <el-empty description="暂无审批流程信息" :image-size="100" />
         </div>
-      </template> -->
+      </div>
     </el-drawer>
 
     <!-- 审批建议弹窗 -->
@@ -420,6 +433,35 @@ const handleSizeChange = (val) => {
   getList()
 }
 
+const getTagTypeByStatus = (status) => {
+  const tagTypeMap = {
+    待处理: 'warning',
+    处理中: 'info',
+    已完成: 'success',
+    已驳回: 'danger',
+    已撤销: 'info',
+    已批准: 'success',
+    审批中: 'primary',
+    上报: 'success',
+    提交上报: 'primary'
+  }
+  return tagTypeMap[status] || 'info'
+}
+const getNodeStatusColor = (status) => {
+  const colorMap = {
+    待处理: '#E6A23C',
+    处理中: '#409EFF',
+    已完成: '#67C23A',
+    已驳回: '#F56C6C',
+    已撤销: '#909399',
+    已批准: '#67C23A',
+    审批中: '#409EFF',
+    上报: '#229242',
+    提交上报: '#409EFF'
+  }
+  return colorMap[status] || '#90939'
+}
+
 const handleCurrentChange = (val) => {
   queryParams.value.pageNo = val
   getList()
@@ -659,4 +701,139 @@ onUnmounted(() => {
     width: 90% !important;
   }
 }
+
+.drawer-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #ebeef5;
+  padding-bottom: 20px;
+  padding-left: 20px;
+  width: 108%;
+  margin-left: -15px;
+}
+
+.approval-process-container {
+  padding: 10px 15px;
+
+  .no-data {
+    text-align: center;
+    padding: 40px 0;
+  }
+
+  .approval-card {
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    transition: all 0.3s ease;
+
+    &.status-completed {
+      border-left: 4px solid #67c23a;
+    }
+
+    &:hover {
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    }
+
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-bottom: 12px;
+      border-bottom: 1px dashed #ebeef5;
+
+      .node-info {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+
+        .status-tag {
+          font-weight: bold;
+        }
+
+        .node-name {
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .operator-time {
+        .time {
+          color: #909399;
+          font-size: 13px;
+        }
+      }
+    }
+
+    .card-content {
+      padding: 12px 0;
+
+      .label {
+        font-weight: 600;
+        color: #606266;
+        display: inline-block;
+        width: 70px;
+        vertical-align: top;
+      }
+
+      .opinion,
+      .description {
+        color: #303133;
+        line-height: 1.6;
+      }
+
+      .attachment-section {
+        margin-top: 8px;
+
+        .attachments {
+          display: flex;
+          flex-direction: column;
+          gap: 5px;
+          margin-top: 5px;
+
+          .attachment-link {
+            display: inline-flex;
+            align-items: center;
+            padding-left: 70px;
+            text-decoration: none;
+
+            &:hover {
+              text-decoration: underline;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .approval-process-container {
+    padding: 5px;
+
+    .approval-card {
+      .card-header {
+        flex-direction: column;
+        gap: 8px;
+        align-items: flex-start;
+
+        .node-info {
+          flex-wrap: wrap;
+        }
+      }
+
+      .card-content {
+        .label {
+          width: auto;
+          display: block;
+          margin-bottom: 4px;
+        }
+
+        .attachment-section .attachment-link {
+          padding-left: 0;
+        }
+      }
+    }
+  }
+}
 </style>

+ 304 - 7
src/views/pms/qhse/faultReport/index.vue

@@ -63,7 +63,7 @@
         height="70vh"
         :max-height="tableHeight"
       >
-        <el-table-column prop="actualTime" label="事件时间" align="center" min-width="120">
+        <el-table-column prop="actualTime" label="事件时间" align="center" min-width="150">
           <template #default="{ row }">
             {{ formatDate(row.actualTime) }}
           </template>
@@ -92,8 +92,13 @@
         />
         <el-table-column prop="deptName" label="部门名称" align="center" />
         <el-table-column prop="dutyPerson" label="现场负责人" align="center" width="100" />
+        <el-table-column prop="actualTime" label="创建时间" align="center" min-width="150">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
 
-        <el-table-column label="操作" align="center" width="200" fixed="right">
+        <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="{ row }">
             <!-- <el-button
               link
@@ -111,22 +116,23 @@
               v-hasPermi="['rq:iot-accident-report:query']"
               >详情</el-button
             >
-            <el-button
+            <el-button link type="primary" @click="openApprovalDialog(row)"> 流转信息 </el-button>
+            <!-- <el-button
               link
               type="primary"
               @click="openForm('update', row)"
               :icon="Edit"
               v-hasPermi="['rq:iot-accident-report:update']"
               >编辑</el-button
-            >
-            <el-button
+            > -->
+            <!-- <el-button
               link
               type="danger"
               @click="handleDelete(row)"
               :icon="Delete"
               v-hasPermi="['rq:iot-accident-report:delete']"
               >删除</el-button
-            >
+            > -->
           </template>
         </el-table-column>
       </el-table>
@@ -326,13 +332,95 @@
         </div>
       </template>
     </el-dialog>
+
+    <el-drawer
+      :title="approvalDialogTitle"
+      v-model="approvalDialogVisible"
+      direction="rtl"
+      size="650px"
+      :with-header="true"
+      :close-on-click-modal="false"
+      destroy-on-close
+    >
+      <template #header>
+        <div class="drawer-header">
+          <span>{{ approvalDialogTitle }}</span>
+        </div>
+      </template>
+
+      <!-- 专业化的审批流程时间线 -->
+      <div class="approval-process-container">
+        <el-timeline v-if="approvalProcessList.length > 0">
+          <el-timeline-item
+            v-for="(item, index) in approvalProcessList"
+            :key="index"
+            placement="top"
+            :color="getNodeStatusColor(item.status)"
+            :icon="Check"
+          >
+            <el-card
+              shadow="never"
+              class="approval-card"
+              :class="{ 'status-completed': item.status === '已完成' || item.status === '已批准' }"
+            >
+              <div class="card-header">
+                <div class="node-info">
+                  <el-tag :type="getTagTypeByStatus(item.status)" size="small" class="status-tag">
+                    {{ item.status }}
+                  </el-tag>
+                  <span class="node-name">{{ item.nodeName }}</span>
+                </div>
+                <div class="operator-time">
+                  <span class="time">{{ formatDate(item.createTime) }}</span>
+                </div>
+              </div>
+
+              <div class="card-content">
+                <div class="opinion-section">
+                  <span class="label">操作人:</span>
+                  <span class="opinion">{{ item.operator }}</span>
+                </div>
+
+                <div class="description-section mt-1" v-if="item.description">
+                  <span class="label">审批意见:</span>
+                  <span class="description">{{ item.description }}</span>
+                </div>
+                <div
+                  class="attachment-section"
+                  v-if="item.attachments && item.attachments.length > 0"
+                >
+                  <span class="label">附件:</span>
+                  <div class="attachments">
+                    <el-link
+                      v-for="(attachment, idx) in item.attachments"
+                      :key="idx"
+                      type="primary"
+                      :href="attachment.url"
+                      target="_blank"
+                      class="attachment-link"
+                    >
+                      {{ attachment.name }}
+                    </el-link>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </el-timeline-item>
+        </el-timeline>
+
+        <!-- 无数据提示 -->
+        <div v-else class="no-data">
+          <el-empty description="暂无审批流程信息" :image-size="100" />
+        </div>
+      </div>
+    </el-drawer>
   </div>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Search, Refresh, Plus, Edit, Delete, TopRight, View } from '@element-plus/icons-vue'
+import { Search, Refresh, Plus, Edit, Delete, TopRight, View, Check } from '@element-plus/icons-vue'
 import { defaultProps } from '@/utils/tree'
 import { handleTree } from '@/utils/tree'
 import * as DeptApi from '@/api/system/dept'
@@ -340,6 +428,7 @@ import { IotFailureApi } from '@/api/pms/qhse/index'
 import UploadImage from '@/components/UploadFile/src/UploadImg.vue'
 import { formatDate } from '@/utils/formatTime'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { IotApprovalApi } from '@/api/pms/qhse/index'
 
 // Data
 const loading = ref(false)
@@ -617,6 +706,79 @@ const cancel = () => {
   reset()
 }
 
+const approvalDialogVisible = ref(false)
+const approvalDialogTitle = ref('审批流程')
+const approvalProcessList = ref([]) // 存储审批流程信息
+const approvalSuggestion = ref('')
+// 根据状态返回标签类型
+const getTagTypeByStatus = (status) => {
+  const tagTypeMap = {
+    待处理: 'warning',
+    处理中: 'info',
+    已完成: 'success',
+    已驳回: 'danger',
+    已撤销: 'info',
+    已批准: 'success',
+    审批中: 'primary',
+    上报: 'success',
+    提交上报: 'primary'
+  }
+  return tagTypeMap[status] || 'info'
+}
+const getNodeStatusColor = (status) => {
+  const colorMap = {
+    待处理: '#E6A23C',
+    处理中: '#409EFF',
+    已完成: '#67C23A',
+    已驳回: '#F56C6C',
+    已撤销: '#909399',
+    已批准: '#67C23A',
+    审批中: '#409EFF',
+    上报: '#229242',
+    提交上报: '#409EFF'
+  }
+  return colorMap[status] || '#90939'
+}
+
+const openApprovalDialog = async (row) => {
+  approvalDialogVisible.value = true
+  approvalDialogTitle.value = `审批流程详情`
+  approvalSuggestion.value = '' // 清空审批建议
+
+  try {
+    const response = await IotApprovalApi.getApprovalProcess(row.id)
+    const processList = response.list || []
+
+    // 按 createTime 升序排序(时间正序)
+    approvalProcessList.value = processList.sort((a, b) => a.createTime - b.createTime)
+
+    // 如果没有数据,添加一条默认的“发起人”记录
+    if (approvalProcessList.value.length === 0) {
+      approvalProcessList.value.push({
+        createTime: Date.now(),
+        operator: '系统管理员',
+        status: '提交上报',
+        description: '发起事故事件上报申请',
+        nodeName: '上报发起',
+        opinion: ''
+      })
+    }
+  } catch (error) {
+    ElMessage.error('获取审批流程失败')
+    // 即使出错也显示一个基本记录
+    approvalProcessList.value = [
+      {
+        createTime: Date.now(),
+        operator: '系统',
+        status: '错误',
+        description: '无法获取审批流程信息',
+        nodeName: '系统通知',
+        opinion: ''
+      }
+    ]
+  }
+}
+
 // 监听窗口大小变化
 const handleResize = () => {
   // 这里可以添加响应式逻辑
@@ -695,4 +857,139 @@ onUnmounted(() => {
 ::v-deep .el-button {
   border-radius: 3px;
 }
+
+.drawer-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #ebeef5;
+  padding-bottom: 20px;
+  padding-left: 20px;
+  width: 108%;
+  margin-left: -15px;
+}
+
+.approval-process-container {
+  padding: 10px 15px;
+
+  .no-data {
+    text-align: center;
+    padding: 40px 0;
+  }
+
+  .approval-card {
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    transition: all 0.3s ease;
+
+    &.status-completed {
+      border-left: 4px solid #67c23a;
+    }
+
+    &:hover {
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    }
+
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-bottom: 12px;
+      border-bottom: 1px dashed #ebeef5;
+
+      .node-info {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+
+        .status-tag {
+          font-weight: bold;
+        }
+
+        .node-name {
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .operator-time {
+        .time {
+          color: #909399;
+          font-size: 13px;
+        }
+      }
+    }
+
+    .card-content {
+      padding: 12px 0;
+
+      .label {
+        font-weight: 600;
+        color: #606266;
+        display: inline-block;
+        width: 70px;
+        vertical-align: top;
+      }
+
+      .opinion,
+      .description {
+        color: #303133;
+        line-height: 1.6;
+      }
+
+      .attachment-section {
+        margin-top: 8px;
+
+        .attachments {
+          display: flex;
+          flex-direction: column;
+          gap: 5px;
+          margin-top: 5px;
+
+          .attachment-link {
+            display: inline-flex;
+            align-items: center;
+            padding-left: 70px;
+            text-decoration: none;
+
+            &:hover {
+              text-decoration: underline;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .approval-process-container {
+    padding: 5px;
+
+    .approval-card {
+      .card-header {
+        flex-direction: column;
+        gap: 8px;
+        align-items: flex-start;
+
+        .node-info {
+          flex-wrap: wrap;
+        }
+      }
+
+      .card-content {
+        .label {
+          width: auto;
+          display: block;
+          margin-bottom: 4px;
+        }
+
+        .attachment-section .attachment-link {
+          padding-left: 0;
+        }
+      }
+    }
+  }
+}
 </style>