Explorar el Código

fix: 运行记录

yanghao hace 21 horas
padre
commit
4b0a1af985

+ 24 - 19
src/api/pms/iotopeationfill/index.ts

@@ -4,7 +4,7 @@ import request from '@/config/axios'
 export interface IotOpeationFillVO {
   id: number // 主键id
   deviceCode: string // 资产编号
-  deviceCategoryId:number
+  deviceCategoryId: number
   deviceName: string // 设备名称
   fillContent: string // 填写内容
   deviceType: string // 设备类别
@@ -17,21 +17,21 @@ export interface IotOpeationFillVO {
   teamName: string // 所属小队
   dutyName: string // 设备负责人
   creDate: Date // 填写日期
-  name:string
-  type:string
-  modelAttr:string,
-  modelId:number,
-  isFill:number,
-  fillContent:string
-  pointCode:string
-  pointName:string
-  orderName:string
-  orderType:string
-  orderStatus:number
-  createTime:Date
-  totalRunTime:number
-  userId:number,
-  isSum:number,
+  name: string
+  type: string
+  modelAttr: string
+  modelId: number
+  isFill: number
+  fillContent: string
+  pointCode: string
+  pointName: string
+  orderName: string
+  orderType: string
+  orderStatus: number
+  createTime: Date
+  totalRunTime: number
+  userId: number
+  isSum: number
 }
 
 // 运行记录填报 API
@@ -56,15 +56,15 @@ export const IotOpeationFillApi = {
     return await request.get({ url: `/rq/iot-opeation-fill/fillRecordPage`, params })
   },
 
-  getAttrs:async (params: any) => {
+  getAttrs: async (params: any) => {
     return await request.get({ url: `/rq/iot-opeation-fill/getAttrs`, params })
   },
 
-  getAttrs1:async (params: any) => {
+  getAttrs1: async (params: any) => {
     return await request.get({ url: `/rq/iot-opeation-fill/getAttrs1`, params })
   },
 
-  getDeivceFillInfo:async (params: any) => {
+  getDeivceFillInfo: async (params: any) => {
     return await request.get({ url: `/rq/iot-opeation-fill/getDeivceFillInfo`, params })
   },
 
@@ -102,4 +102,9 @@ export const IotOpeationFillApi = {
   exportIotOpeationFill: async (params) => {
     return await request.download({ url: `/rq/iot-opeation-fill/export-excel`, params })
   },
+
+  // 获取公司
+  getOrgName: async (id) => {
+    return await request.get({ url: `/rq/report/dept/${id}` })
+  }
 }

+ 387 - 319
src/views/pms/iotopeationfill/index1.vue

@@ -1,25 +1,43 @@
 <template>
   <ContentWrap>
-    <el-tabs v-model="activeTab" type="border-card" tab-position="left" v-loading="loading" style="height: 84vh">
-      <el-tab-pane
-        style="height: 100%"
-        v-for="(item,index) in list"
-        :key="index"
-      >
+    <el-tabs
+      v-model="activeTab"
+      type="border-card"
+      tab-position="left"
+      v-loading="loading"
+      style="height: 84vh"
+    >
+      <el-tab-pane style="height: 100%" v-for="(item, index) in list" :key="index">
         <template #label>
           <span
             :class="['custom-label', { 'has-border': item.deviceName === '生产日报' }]"
-            v-if='item.isFill === 1'
-            @click="openFill(item.deviceCategoryId,item.deviceId,item.deptId,item.deviceName,item.deviceCode)"
+            v-if="item.isFill === 1"
+            @click="
+              openFill(
+                item.deviceCategoryId,
+                item.deviceId,
+                item.deptId,
+                item.deviceName,
+                item.deviceCode
+              )
+            "
           >
-            {{item.deviceCode}} ({{ item.deviceName }})
+            {{ item.deviceCode }} ({{ item.deviceName }})
           </span>
-                  <span
-                    :class="['custom-label1', { 'has-border': item.deviceName === '生产日报' }]"
-                    v-else
-                    @click="openFill(item.deviceCategoryId,item.deviceId,item.deptId,item.deviceName,item.deviceCode)"
-                  >
-            {{item.deviceCode}} ({{ item.deviceName }})
+          <span
+            :class="['custom-label1', { 'has-border': item.deviceName === '生产日报' }]"
+            v-else
+            @click="
+              openFill(
+                item.deviceCategoryId,
+                item.deviceId,
+                item.deptId,
+                item.deviceName,
+                item.deviceCode
+              )
+            "
+          >
+            {{ item.deviceCode }} ({{ item.deviceName }})
           </span>
         </template>
         <div class="form-wrapper">
@@ -27,29 +45,25 @@
             <div style="margin-left: 24px">
               <el-form class="demo-form-inline" :inline="true">
                 <el-form-item :label="t('common.createTime')" class="custom-label1">
-                  <span style="text-decoration: underline;">
-                    {{createTime}}
+                  <span style="text-decoration: underline">
+                    {{ createTime }}
                   </span>
                 </el-form-item>
                 <el-form-item :label="t('operationFillForm.team')" class="custom-label1">
-                  <span style="text-decoration: underline;">
-                    {{item.orgName}}
+                  <span style="text-decoration: underline">
+                    {{ item.orgName }}
                   </span>
                 </el-form-item>
                 <el-row :gutter="20">
-                  <el-col
-                    v-for="(item,index) in attrList1"
-                    :key="index"
-                    :span="24"
-                  >
-                    <el-form-item :label='item.name' class="custom-label1">
-                      <span style="text-decoration: underline;">
-                      {{item.totalRunTime}}
+                  <el-col v-for="(item, index) in attrList1" :key="index" :span="24">
+                    <el-form-item :label="item.name" class="custom-label1">
+                      <span style="text-decoration: underline">
+                        {{ item.totalRunTime }}
                       </span>
                     </el-form-item>
                   </el-col>
                 </el-row>
-<!--                <el-form-item :label="t('operationFillForm.sumTime')" class="custom-label1">
+                <!--                <el-form-item :label="t('operationFillForm.sumTime')" class="custom-label1">
                   <span style="text-decoration: underline;">
                   {{totalRunTime1}}h
                   </span>
@@ -57,83 +71,97 @@
               </el-form>
             </div>
 
-            <div v-for="(item,index) in attrList" :key="index" style="margin-left: 24px">
+            <div v-for="(item, index) in attrList" :key="index" style="margin-left: 24px">
               <!-- 添加提示文字 -->
-              <div v-if="item.isCollection===1" class="plc-tip">
+              <div v-if="item.isCollection === 1" class="plc-tip">
                 <el-alert
                   :title="t('operationFillForm.alert')"
                   type="warning"
                   :closable="false"
                   center
                   show-icon
-                  style="width: 320px;"
+                  style="width: 320px"
                 />
               </div>
-                <el-form-item :label='item.name' prop="deviceId"  label-position="top">
-                  <div v-if="fillStatus === '1'">
-
-                    <el-select
-                      disabled
-                      v-model="item.fillContent"
-                      v-if="item.type === 'enum' && item.description !== null"
-                      style="width: 200px">
-                      <el-option
-                        v-for="dict in (item.name === '非生产原因' ? getIntDictOptions(item.description) : getStrDictOptions(item.description))"
-                        :key="dict.label"
-                        :label="dict.label"
-                        :value="item.name === '非生产原因' ? Number(dict.value) : dict.value.toString()"
-                      />
-                    </el-select>
-                    <el-input
-                      v-else
-                      v-model="item.fillContent"
-                      clearable
-                      style="width: 200px; margin-right: 10px"
-                      disabled
-                    />
-                  </div>
-
-
-                    <el-input
-                      v-else-if="item.type === 'textarea'"
-                      v-model="item.fillContent"
-                      type="textarea"
-                      clearable
-                      style="width: 200px"
-                    />
-                    <el-select  v-model="item.fillContent"
-                                clearable
-                                v-else-if="item.type === 'enum' && item.description !== null"
-                                style="width: 200px"
-                                filterable>
-                      <el-option
-                        v-for="dict in (item.name === '非生产原因' ? getIntDictOptions(item.description) : getStrDictOptions(item.description))"
-                        :key="dict.label"
-                        :label="dict.label"
-                        :value="item.name === '非生产原因' ? Number(dict.value) : dict.value.toString()"
-                      />
-                    </el-select>
-                    <el-input
-                      v-else
-                      v-model="item.fillContent"
-                      clearable
-                      style="width: 200px"
-                      :placeholder="item.type === 'double' ? t('operationFillForm.enterNumber') : t('operationFillForm.enterContent')"
-                      @input="handleInput(item)"
-                      :maxlength="item.type === 'double' ? calculateMaxLength(item) : undefined"
+              <el-form-item :label="item.name" prop="deviceId" label-position="top">
+                <div v-if="fillStatus === '1'">
+                  <el-select
+                    disabled
+                    v-model="item.fillContent"
+                    v-if="item.type === 'enum' && item.description !== null"
+                    style="width: 200px"
+                  >
+                    <el-option
+                      v-for="dict in item.name === '非生产原因'
+                        ? getIntDictOptions(item.description)
+                        : getStrDictOptions(item.description)"
+                      :key="dict.label"
+                      :label="dict.label"
+                      :value="
+                        item.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
+                      "
                     />
-                </el-form-item>
+                  </el-select>
+                  <el-input
+                    v-else
+                    v-model="item.fillContent"
+                    clearable
+                    style="width: 200px; margin-right: 10px"
+                    disabled
+                  />
+                </div>
+
+                <el-input
+                  v-else-if="item.type === 'textarea'"
+                  v-model="item.fillContent"
+                  type="textarea"
+                  clearable
+                  style="width: 200px"
+                />
+                <el-select
+                  v-model="item.fillContent"
+                  clearable
+                  v-else-if="item.type === 'enum' && item.description !== null"
+                  style="width: 200px"
+                  filterable
+                >
+                  <el-option
+                    v-for="dict in item.name === '非生产原因'
+                      ? getIntDictOptions(item.description)
+                      : getStrDictOptions(item.description)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="item.name === '非生产原因' ? Number(dict.value) : dict.value.toString()"
+                  />
+                </el-select>
+                <el-input
+                  v-else
+                  v-model="item.fillContent"
+                  clearable
+                  style="width: 200px"
+                  :placeholder="
+                    item.type === 'double'
+                      ? t('operationFillForm.enterNumber')
+                      : t('operationFillForm.enterContent')
+                  "
+                  @input="handleInput(item)"
+                  :maxlength="item.type === 'double' ? calculateMaxLength(item) : undefined"
+                />
+              </el-form-item>
             </div>
             <el-form-item>
-              <el-button type="primary" @click="getFillInfo" v-show="showStatus">{{t('operationFillForm.confirm')}}</el-button>
-              <el-button type="info" @click="deleteFillInfo" v-show="showStatus">{{t('operationFill.clear')}}</el-button>
+              <el-button type="primary" @click="getFillInfo" v-show="showStatus">{{
+                t('operationFillForm.confirm')
+              }}</el-button>
+              <el-button type="info" @click="deleteFillInfo" v-show="showStatus">{{
+                t('operationFill.clear')
+              }}</el-button>
             </el-form-item>
           </el-form>
         </div>
       </el-tab-pane>
     </el-tabs>
   </ContentWrap>
-
 </template>
 
 <script setup lang="ts">
@@ -141,17 +169,20 @@ import { dateFormatter2 } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotOpeationFillApi, IotOpeationFillVO } from '@/api/pms/iotopeationfill'
 import IotOpeationFillForm from './IotOpeationFillForm.vue'
-import Vue from "@vitejs/plugin-vue";
-import {useUserStore} from "@/store/modules/user";
+import Vue from '@vitejs/plugin-vue'
+import { useUserStore } from '@/store/modules/user'
 import { ElMessage } from 'element-plus'
-import moment from 'moment';
-import { format } from 'date-fns';
-import {cx} from "@fullcalendar/core/internal-common";
-import { DICT_TYPE, getIntDictOptions,getStrDictOptions} from '@/utils/dict'
+import moment from 'moment'
+import { format } from 'date-fns'
+import { cx } from '@fullcalendar/core/internal-common'
+import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
+import { useRoute } from 'vue-router'
 
 /** 运行记录填报 列表 */
 defineOptions({ name: 'FillOrderInfo' })
 
+const route = useRoute()
+
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 /** 提交表单 */
@@ -163,12 +194,11 @@ const list = ref<IotOpeationFillVO[]>([]) // 列表的数据
 const attrList = ref<IotOpeationFillVO[]>([]) // 非累计属性集合
 const attrList1 = ref<IotOpeationFillVO[]>([]) // 累计属性集合
 const attrList2 = ref<IotOpeationFillVO[]>([]) // 属性集合
-const total = ref(0) // 列表的总页数
-const arry1 =ref([]);
-let totalRunTime1: string = '123'
-let fillStatus = params.id.split(",")[4];
-let createTime = formatTimestamp(JSON.parse(deptId.split(",")[2].substring(0,10)));
-let showStatus = true;
+let companyName = ref('')
+
+let fillStatus = params.id.split(',')[4]
+let createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
+let showStatus = true
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -186,282 +216,324 @@ const queryParams = reactive({
   dutyName: undefined,
   creDate: [],
   createTime: [],
-  deviceCategoryId:1,
-  deviceId:undefined,
-  threshold:undefined,
-  defaultValue:undefined,
-  isSum:undefined,
+  deviceCategoryId: 1,
+  deviceId: undefined,
+  threshold: undefined,
+  defaultValue: undefined,
+  isSum: undefined
 })
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-let cxStatus = true;
 
-const formatDescription = async(row, column, cellValue) =>{
-  return cellValue.split(',').map(part => `<div>${part}</div>`).join('');
-}
+let cxStatus = true
 
 // 计算数字输入的最大长度(根据阈值动态计算)
 const calculateMaxLength = (item: any) => {
-  if (item.type !== 'double' || !item.threshold) return undefined;
+  if (item.type !== 'double' || !item.threshold) return undefined
 
-  const max = parseFloat(item.threshold);
-  if (isNaN(max)) return undefined;
+  const max = parseFloat(item.threshold)
+  if (isNaN(max)) return undefined
 
   // 整数部分长度 + 可能的小数点 + 两位小数
-  return max.toString().length + (max.toString().includes('.') ? 0 : 3);
-};
+  return max.toString().length + (max.toString().includes('.') ? 0 : 3)
+}
+
+// 简单的节流函数,避免提示信息过于频繁
+const throttle = (fn: Function, delay: number) => {
+  let lastTime = 0
+  return function (...args: any[]) {
+    const now = Date.now()
+    if (now - lastTime >= delay) {
+      fn.apply(this, args)
+      lastTime = now
+    }
+  }
+}
+
+const showComponent = () => {
+  if (JSON.parse(fillStatus) === 1 || JSON.parse(fillStatus) === 3) {
+    showStatus = false
+  }
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    queryParams.deptId = deptId.split(',')[0]
+    queryParams.userId = deptId.split(',')[1]
+    queryParams.createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
+    queryParams.orderId = deptId.split(',')[3]
+    const data = await IotOpeationFillApi.getIotOpeationFillPage(queryParams)
+    list.value = data
+    if (cxStatus) {
+      queryParams.deviceCategoryId = list.value[0].deviceCategoryId
+      queryParams.deptId = list.value[0].deptId
+      queryParams.deviceCode = list.value[0].deviceCode
+      queryParams.deviceName = list.value[0].deviceName
+      queryParams.deviceId = list.value[0].deviceId
+    }
+    getAttrList()
+  } finally {
+    loading.value = false
+  }
+}
+function formatTimestamp(timestamp) {
+  const date = new Date(timestamp * 1000)
+  return moment.unix(timestamp).format('YYYY-MM-DD')
+}
 
+const open = async (type: string, id?: number) => {
+  alert(id)
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+let devName = ''
+const openFill = (
+  deviceCategoryId?: number,
+  deviceId?: number,
+  deptId?: number,
+  deviceName?: string,
+  deviceCode?: string
+) => {
+  queryParams.deviceCategoryId = deviceCategoryId
+  queryParams.deptId = deptId
+  queryParams.deviceCode = deviceCode
+  queryParams.deviceName = deviceName
+  if (queryParams.deviceName == '生产日报') {
+    devName = '生产日报'
+  }
+  queryParams.deviceId = deviceId
+  getAttrList()
+}
 // 处理输入事件,实时限制输入格式和最大值
 const handleInput = (item: any) => {
   if (item.type === 'double') {
     // 保存原始值用于后续比较
-    const originalValue = item.fillContent;
+    const originalValue = item.fillContent
 
     // 1. 格式验证:只允许数字和小数点
-    item.fillContent = item.fillContent.replace(/[^\d.]/g, '');
+    item.fillContent = item.fillContent.replace(/[^\d.]/g, '')
     // 确保只有一个小数点
-    item.fillContent = item.fillContent.replace(/\.{2,}/g, '.');
+    item.fillContent = item.fillContent.replace(/\.{2,}/g, '.')
     // 确保小数点不在开头
-    item.fillContent = item.fillContent.replace(/^\./g, '');
+    item.fillContent = item.fillContent.replace(/^\./g, '')
     // 限制小数位数为两位
-    item.fillContent = item.fillContent.replace(/(\d+)\.(\d{2}).*/, '$1.$2');
+    item.fillContent = item.fillContent.replace(/(\d+)\.(\d{2}).*/, '$1.$2')
 
     // 2. 最大值验证
     if (item.threshold) {
-      const value = parseFloat(item.fillContent);
-      const max = parseFloat(item.threshold);
+      const value = parseFloat(item.fillContent)
+      const max = parseFloat(item.threshold)
 
       if (!isNaN(value) && !isNaN(max) && value > max) {
         // 输入值超过阈值时,恢复到修改前的值
-        item.fillContent = originalValue.replace(/[^\d.]/g, '')
+        item.fillContent = originalValue
+          .replace(/[^\d.]/g, '')
           .replace(/\.{2,}/g, '.')
           .replace(/^\./g, '')
-          .replace(/(\d+)\.(\d{2}).*/, '$1.$2');
+          .replace(/(\d+)\.(\d{2}).*/, '$1.$2')
 
-        // 如果修正后的值仍然超过阈值,则设置为最大值
         if (parseFloat(item.fillContent) > max) {
-          item.fillContent = max.toString();
+          item.fillContent = max.toString()
         }
 
-        // 显示提示信息(使用节流避免频繁提示)
         throttle(() => {
-          ElMessage.warning(t('operationFillForm.exceedMax', { max }));
-        }, 1000)();
+          ElMessage.warning(t('operationFillForm.exceedMax', { max }))
+        }, 1000)()
       }
     }
-  }
-};
-
-// 简单的节流函数,避免提示信息过于频繁
-const throttle = (fn: Function, delay: number) => {
-  let lastTime = 0;
-  return function(...args: any[]) {
-    const now = Date.now();
-    if (now - lastTime >= delay) {
-      fn.apply(this, args);
-      lastTime = now;
-    }
-  };
-};
-
-const showComponent = () => {
-  if(JSON.parse(fillStatus)=== 1||JSON.parse(fillStatus)===3){
-    showStatus = false;
-  }
-};
 
+    if (companyName.value === 'rd') {
+      // 3. 累计值限制验证(改为弹窗提示但允许继续)
+      if (item.maxAllowedValue !== undefined) {
+        const value = parseFloat(item.fillContent)
+        if (!isNaN(value) && value > item.maxAllowedValue) {
+          // 不自动修改值,而是显示警告弹窗
+          let limitDescription = ''
+          if (item.limitType === 'km') {
+            limitDescription = `当前累计值${item.currentSumValue} + 3000`
+          } else if (item.limitType === 'time') {
+            limitDescription = `当前累计值${item.currentSumValue} + 100`
+          }
 
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    queryParams.deptId = deptId.split(",")[0];
-    queryParams.userId = deptId.split(",")[1];
-    queryParams.createTime = formatTimestamp(JSON.parse(deptId.split(",")[2].substring(0,10)));
-    queryParams.orderId = deptId.split(",")[3];
-    const data = await IotOpeationFillApi.getIotOpeationFillPage(queryParams);
-      list.value = data;
-      if(cxStatus){
-        queryParams.deviceCategoryId = list.value[0].deviceCategoryId
-        queryParams.deptId = list.value[0].deptId;
-        queryParams.deviceCode = list.value[0].deviceCode;
-        queryParams.deviceName = list.value[0].deviceName;
-        queryParams.deviceId = list.value[0].deviceId;
+          ElMessage.warning(
+            `填报值 ${value} 超过限制 ${item.maxAllowedValue} (${limitDescription}),请确认是否正确!`
+          )
+        }
       }
-      getAttrList();
-
-
-  } finally {
-    loading.value = false
-  }
-}
-function formatTimestamp(timestamp) {
-  const date = new Date(timestamp*1000);
-  return moment.unix(timestamp).format('YYYY-MM-DD');
-
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-const open = async (type: string, id?: number) => {
-  alert(id)
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-let devName = "";
-const openFill = (deviceCategoryId?:number,deviceId?:number,deptId?:number,deviceName?:string,deviceCode?:string) =>{
-  queryParams.deviceCategoryId = deviceCategoryId;
-  queryParams.deptId = deptId;
-  queryParams.deviceCode = deviceCode;
-  queryParams.deviceName = deviceName;
-  if(queryParams.deviceName=='生产日报'){
-    devName = '生产日报';
+    }
   }
-  queryParams.deviceId = deviceId;
-  getAttrList();
 }
 
-
 const getAttrList = async () => {
   loading.value = true
   try {
-    queryParams.createTime = formatTimestamp(JSON.parse(deptId.split(",")[2].substring(0,10)));
+    queryParams.createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
     const data = await IotOpeationFillApi.getAttrs(queryParams)
 
-    attrList.value = data[0].nonSumList;
-    attrList1.value = data[0].sumList;
-    attrList.value.forEach(function (item,index){
-      if(item.fillContent!=''&& item.fillContent !== null){
-        // 尝试将字符串转换为数字
-        const num = Number(item.fillContent);
+    attrList.value = data[0].nonSumList
+    attrList1.value = data[0].sumList
 
-        // 检查是否是有效的数字
+    // 建立累计数据映射,用于后续验证
+    const sumMap = new Map()
+    attrList1.value.forEach((item) => {
+      // 创建匹配规则:移除"填报"字样的差异,保留核心名称
+      const coreName = item.name.replace(/填报/g, '')
+      sumMap.set(coreName, item)
+    })
+
+    // 为非累计数据添加最大值限制
+    attrList.value.forEach(function (item, index) {
+      if (item.fillContent !== '' && item.fillContent !== null) {
+        const num = Number(item.fillContent)
         if (!isNaN(num)) {
-          // 检查是否包含小数
           if (item.fillContent.includes('.')) {
-            // 保留两位小数
-            item.fillContent = Number(num.toFixed(2));
+            item.fillContent = Number(num.toFixed(2))
           } else {
-            // 转换为整数
-            item.fillContent = Math.floor(num);
+            item.fillContent = Math.floor(num)
+          }
+        }
+      }
+
+      if (companyName.value === 'rd') {
+        // 添加最大值限制逻辑
+        const coreName = item.name.replace(/填报/g, '')
+        const sumItem = sumMap.get(coreName)
+        if (sumItem) {
+          // 根据字段名称判断使用哪种限制规则
+          if (item.name.includes('公里数填报')) {
+            // 公里数限制:当前累计值 + 3000
+            item.maxAllowedValue = sumItem.totalRunTime + 3000
+            item.currentSumValue = sumItem.totalRunTime // 保存当前累计值用于提示
+            item.limitType = 'km' // 标记为公里数限制
+          } else if (item.name.includes('运转时长填报')) {
+            // 运转时长限制:当前累计值 + 100
+            item.maxAllowedValue = sumItem.totalRunTime + 100
+            item.currentSumValue = sumItem.totalRunTime // 保存当前累计值用于提示
+            item.limitType = 'time' // 标记为时长限制
           }
         }
-        // 如果不是有效的数字,则保持原文本不变
       }
-      item.deviceCode = queryParams.deviceCode;
-      item.deptId = queryParams.deptId;
-      item.deviceId = queryParams.deviceId;
-      item.deviceCategoryId = queryParams.deviceCategoryId;
-      item.modelId = item.id;
+
+      item.deviceCode = queryParams.deviceCode
+      item.deptId = queryParams.deptId
+      item.deviceId = queryParams.deviceId
+      item.deviceCategoryId = queryParams.deviceCategoryId
+      item.modelId = item.id
       console.log(item.fillContent)
     })
-    attrList1.value.forEach(function (item,index){
-      item.deviceCode = queryParams.deviceCode;
-      item.deptId = queryParams.deptId;
-      item.deviceId = queryParams.deviceId;
-      item.deviceCategoryId = queryParams.deviceCategoryId;
-      item.modelId = item.id;
+
+    attrList1.value.forEach(function (item, index) {
+      item.deviceCode = queryParams.deviceCode
+      item.deptId = queryParams.deptId
+      item.deviceId = queryParams.deviceId
+      item.deviceCategoryId = queryParams.deviceCategoryId
+      item.modelId = item.id
     })
   } finally {
     loading.value = false
   }
-
 }
 /** 获取填写信息保存到后台*/
 const getFillInfo = async () => {
   try {
-    if(devName!='生产日报'){
+    const company = await IotOpeationFillApi.getOrgName(route.params.id.toString().split(',')[0])
+
+    if (devName != '生产日报') {
       // 检查必填字段
-      const emptyFields = attrList.value.filter(item => {
+      const emptyFields = attrList.value.filter((item) => {
         // 只检查非disabled的字段
-        return !(item.isCollection===1||fillStatus === '1') &&
-          (item.fillContent === undefined || item.fillContent === '');
-      });
+        return (
+          !(item.isCollection === 1 || fillStatus === '1') &&
+          (item.fillContent === undefined || item.fillContent === '')
+        )
+      })
       if (emptyFields.length > 0) {
-        ElMessage.error(t('operationFillForm.fill'));
-        return;
+        ElMessage.error(t('operationFillForm.fill'))
+        return
       }
     }
 
+    if (company === 'rd') {
+      // 检查是否有超出累计值限制的字段
+      const exceededFields = attrList.value.filter((item) => {
+        if (
+          item.type === 'double' &&
+          item.maxAllowedValue !== undefined &&
+          item.fillContent !== '' &&
+          item.fillContent !== null
+        ) {
+          const value = parseFloat(item.fillContent)
+          return !isNaN(value) && value > item.maxAllowedValue
+        }
+        return false
+      })
+
+      // 如果有超出限制的字段,提示用户确认
+      if (exceededFields.length > 0) {
+        let exceededMessage = ''
+        exceededFields.forEach((field) => {
+          let limitDescription = ''
+          if (field.limitType === 'km') {
+            limitDescription = `(${field.currentSumValue} + 3000)`
+          } else if (field.limitType === 'time') {
+            limitDescription = `(${field.currentSumValue} + 100)`
+          }
+          exceededMessage += `${field.name};\n`
+        })
+
+        // exceededMessage += '\n是否继续保存?'
+
+        const confirmResult = await message.confirm(
+          exceededMessage,
+          '以下填报项超出限制,是否继续保存?',
+          {
+            confirmButtonText: '继续保存',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }
+        )
+        if (!confirmResult) {
+          return // 用户取消保存
+        }
+      }
+    }
 
     attrList2.value = attrList.value.concat(attrList1.value)
 
-    attrList2.value.forEach(function (item,index){
-      item.pointName = item.name;
-      item.createTime = formatTimestamp(JSON.parse(deptId.split(",")[2].substring(0,10)));
-      item.userId = deptId.split(",")[1];
-      item.id = deptId.split(",")[3];
+    attrList2.value.forEach(function (item, index) {
+      item.pointName = item.name
+      item.createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
+      item.userId = deptId.split(',')[1]
+      item.id = deptId.split(',')[3]
     })
     const data = attrList2.value as unknown as IotOpeationFillVO
     await IotOpeationFillApi.insertLog(data)
     message.success(t('common.createSuccess'))
     // 发送操作成功的事件
     emit('success')
-    cxStatus = false;
-    getList();
-  } finally {
-
+    cxStatus = false
+    getList()
+  } catch (error) {
+    console.error('保存失败:', error)
   }
 }
 
 /**清空填写信息*/
-const deleteFillInfo = () =>{
-  attrList.value.forEach(function (item, index){
-    item.fillContent = '';
-  });
-}
-
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await IotOpeationFillApi.deleteIotOpeationFill(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await IotOpeationFillApi.exportIotOpeationFill(queryParams)
-    download.excel(data, '运行记录填报.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
+const deleteFillInfo = () => {
+  attrList.value.forEach(function (item, index) {
+    item.fillContent = ''
+  })
 }
 
 /** 初始化 **/
-onMounted(() => {
+onMounted(async () => {
+  const company = await IotOpeationFillApi.getOrgName(route.params.id.toString().split(',')[0])
+  companyName.value = company
+
   getList()
   showComponent()
 })
 </script>
 <style scoped>
-
 .scrollable-form {
   /* 设置最大高度,超过这个高度会出现滚动条 */
   max-height: 500px; /* 根据你的需求调整 */
@@ -492,12 +564,11 @@ onMounted(() => {
   background: #a8a8a8;
 }
 
-
-
-.back-red{		/* 红色背景 */
+.back-red {
+  /* 红色背景 */
   background-color: red;
 }
-.back-blue{
+.back-blue {
   background-color: grey;
 }
 .step-container {
@@ -517,7 +588,7 @@ onMounted(() => {
   background: #fff;
   padding: 30px;
   border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
 }
 
 .navigation-controls {
@@ -557,36 +628,34 @@ onMounted(() => {
 
 /* 覆盖步骤条默认样式 */
 :deep(.custom-steps) {
+  /* 调整头部位置 */
+  .el-step__head {
+    top: 3px;
+  }
 
-/* 调整头部位置 */
-.el-step__head {
-  top: 3px;
-}
-
-/* 标题容器定位 */
-.el-step__title {
-  display: inline-block;
-  margin-left: 10px;
-  padding-right: 0;
-}
-
-/* 步骤连接线 */
-.el-step__line {
-  left: 11px;
-  background-color: #ebeef5;
-}
+  /* 标题容器定位 */
+  .el-step__title {
+    display: inline-block;
+    margin-left: 10px;
+    padding-right: 0;
+  }
 
-/* 当前步骤样式 */
-.is-process .title-text {
-  font-weight: 600;
-  color: #409eff;
-}
+  /* 步骤连接线 */
+  .el-step__line {
+    left: 11px;
+    background-color: #ebeef5;
+  }
 
-/* 完成状态图标 */
-.is-finish .tip-icon {
-  color: #67c23a;
-}
+  /* 当前步骤样式 */
+  .is-process .title-text {
+    font-weight: 600;
+    color: #409eff;
+  }
 
+  /* 完成状态图标 */
+  .is-finish .tip-icon {
+    color: #67c23a;
+  }
 }
 
 .horizontal-container {
@@ -605,5 +674,4 @@ onMounted(() => {
   padding: 2px 4px;
   border-radius: 4px;
 }
-
 </style>

+ 136 - 28
src/views/report-statistics/fault_report/index.vue

@@ -8,49 +8,116 @@
     <el-col :span="20" :xs="24">
       <!-- 统计卡片 -->
       <el-row :gutter="18" class="mb-4">
-        <el-col :span="5">
+        <el-col :span="4">
           <div style="background-color: #fff; border-radius: 10px; cursor: pointer">
-            <div class="stat-card bg-blue-gradient">
+            <div
+              class="stat-card bg-blue-gradient"
+              :class="{ 'stat-card-selected': statusList.all }"
+              @click="
+                () => {
+                  queryParams.pageNo = 1
+                  getList('all')
+                }
+              "
+            >
               <Icon icon="ep:histogram" :size="40" />
               <div class="card-title">故障总数</div>
-              <div class="card-value pt-5">{{
-                statusCount.finished + statusCount.trans + statusCount.reporting
-              }}</div>
+              <div class="card-value pt-5">
+                <CountTo
+                  class="text-3xl"
+                  :end-val="statusCount.finished + statusCount.trans + statusCount.reporting || 0"
+                  :decimals="0"
+                />
+              </div>
             </div>
           </div>
         </el-col>
 
         <el-col :span="4">
           <div style="background-color: #fff; border-radius: 10px">
-            <div class="stat-card bg-green-gradient">
+            <div
+              class="stat-card bg-green-gradient"
+              :class="{ 'stat-card-selected': statusList.finished }"
+              @click="
+                () => {
+                  queryParams.pageNo = 1
+                  getList('finished')
+                }
+              "
+            >
               <Icon icon="ep:finished" :size="40" />
               <div class="card-title">维修完成</div>
-              <div class="card-value pt-5">{{ statusCount.finished }}</div>
+              <div class="card-value pt-5">
+                <CountTo class="text-3xl" :end-val="statusCount.finished || 0" :decimals="0" />
+              </div>
             </div>
           </div>
         </el-col>
 
         <el-col :span="4">
           <div style="background-color: #fff; border-radius: 10px">
-            <div class="stat-card bg-orange-gradient">
+            <div
+              class="stat-card bg-orange-gradient"
+              :class="{ 'stat-card-selected': statusList.trans }"
+              @click="
+                () => {
+                  queryParams.pageNo = 1
+                  getList('trans')
+                }
+              "
+            >
               <Icon icon="ep:more-filled" :size="40" />
               <div class="card-title">未完成</div>
-              <div class="card-value pt-5">{{ statusCount.trans }}</div>
+              <div class="card-value pt-5">
+                <CountTo class="text-3xl" :end-val="statusCount.trans || 0" :decimals="0" />
+              </div>
             </div>
           </div>
         </el-col>
 
         <el-col :span="4">
           <div style="background-color: #fff; border-radius: 10px">
-            <div class="stat-card bg-red-gradient">
-              <Icon icon="ep:hide" :size="40" />
+            <div
+              class="stat-card bg-red-gradient"
+              :class="{ 'stat-card-selected': statusList.reporting }"
+              @click="
+                () => {
+                  queryParams.pageNo = 1
+                  getList('reporting')
+                }
+              "
+            >
+              <Icon icon="ep:promotion" :size="40" />
               <div class="card-title">上报中</div>
-              <div class="card-value pt-5">{{ statusCount.reporting }}</div>
+              <div class="card-value pt-5">
+                <CountTo class="text-3xl" :end-val="statusCount.reporting || 0" :decimals="0" />
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div
+              class="stat-card bg-red-gradient"
+              :class="{ 'stat-card-selected': statusList.close }"
+              @click="
+                () => {
+                  queryParams.pageNo = 1
+                  getList('close')
+                }
+              "
+            >
+              <Icon icon="ep:close" :size="40" />
+              <div class="card-title">关闭</div>
+              <div class="card-value pt-5">
+                <CountTo class="text-3xl" :end-val="statusCount.close || 0" :decimals="0" />
+              </div>
             </div>
           </div>
         </el-col>
 
-        <el-col :span="7">
+        <el-col :span="4">
           <div class="bg-[#fff] p-2 py-4 rounded-lg">
             <el-form ref="queryFormRef" :model="queryParams">
               <el-form-item label="上报时间" prop="createTime">
@@ -175,7 +242,7 @@
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
-          @pagination="getList"
+          @pagination="getAllList"
         />
       </ContentWrap>
     </el-col>
@@ -203,7 +270,7 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-
+  status: undefined,
   createTime: [],
   deptId: undefined
 })
@@ -211,15 +278,45 @@ const queryFormRef = ref() // 搜索的表单
 
 /** 部门树节点点击 */
 const handleDeptNodeClick = async (row) => {
+  queryParams.status = undefined
   queryParams.deptId = row.id
-  await getList()
+
+  const currentStatus = Object.keys(statusList.value).find((key) => statusList.value[key])
+  queryParams.pageNo = 1
+  await getList(currentStatus || 'all', false) // 不重置状态
+
   await getCounts()
 }
 
+const statusList = ref({
+  all: false,
+  reporting: false,
+  finished: false,
+  trans: false,
+  close: false
+})
+
 /** 查询列表 */
-const getList = async () => {
+const getList = async (status: string = '', shouldResetStatus = true) => {
+  if (shouldResetStatus) {
+    Object.keys(statusList.value).forEach((key) => {
+      statusList.value[key] = false
+    })
+    statusList.value[status] = true
+  }
   loading.value = true
   try {
+    if (status === 'all') {
+      queryParams.status = undefined
+    } else if (status === 'trans') {
+      queryParams.status = 'trans'
+    } else if (status === 'finished') {
+      queryParams.status = 'finished'
+    } else if (status === 'reporting') {
+      queryParams.status = 'reporting'
+    } else if (status === 'close') {
+      queryParams.status = 'close'
+    }
     const data = await IotInspectOrderApi.getFaultReportList(queryParams)
     list.value = data.list
     total.value = data.total
@@ -228,10 +325,17 @@ const getList = async () => {
   }
 }
 
+const getAllList = async () => {
+  // 获取当前选中的状态
+  const currentStatus = Object.keys(statusList.value).find((key) => statusList.value[key])
+  await getList(currentStatus || '', false) // 不重置状态
+}
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
-  getList()
+  const currentStatus = Object.keys(statusList.value).find((key) => statusList.value[key])
+  getList(currentStatus || '', false)
 }
 
 /** 重置按钮操作 */
@@ -243,7 +347,8 @@ const resetQuery = () => {
 let statusCount = ref({
   finished: 0,
   trans: 0,
-  reporting: 0
+  reporting: 0,
+  close: 0
 })
 async function getCounts() {
   const res = await IotInspectOrderApi.getFaultReportStatus({
@@ -300,13 +405,14 @@ onMounted(() => {
   color: white;
   text-align: center;
   font-size: 14px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  /* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); */
   transition:
     transform 0.3s ease,
     box-shadow 0.3s ease;
   backdrop-filter: blur(12px);
   height: 200px;
   cursor: pointer;
+  overflow: hidden; /* 防止闪光效果溢出 */
 }
 .stat-card::before {
   position: absolute;
@@ -314,12 +420,6 @@ onMounted(() => {
   z-index: -1;
 }
 
-.stat-card:hover {
-  transform: translateY(-4px);
-  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
-  cursor: pointer;
-}
-
 .card-title {
   margin: 8px 0;
   font-size: 16px;
@@ -342,8 +442,7 @@ onMounted(() => {
 }
 
 .bg-green-gradient {
-  background: linear-gradient(135deg, rgba(101, 226, 136, 0.1), #52d7a2);
-  background-color: rgba(76, 175, 80, 0.1);
+  background: linear-gradient(135deg, rgba(101, 226, 136, 0.3), #52d7a2);
 }
 
 .bg-orange-gradient {
@@ -375,4 +474,13 @@ onMounted(() => {
   top: 0px;
   z-index: 2000;
 }
+
+.stat-card-selected {
+  position: relative;
+  transform: scale(1.06);
+  transition: all 0.2s;
+  box-shadow:
+    0 10px 10px rgba(0, 80, 179, 0.5),
+    0 0 10px rgba(0, 120, 255, 0.4) inset;
+}
 </style>