Просмотр исходного кода

✨ feat(运行记录填报): 非生产时间

Zimo 1 неделя назад
Родитель
Сommit
7c20440214

+ 686 - 0
src/views/pms/iotopeationfill/index1 copy.vue

@@ -0,0 +1,686 @@
+<template>
+  <ContentWrap>
+    <el-tabs type="border-card" tab-position="left" v-loading="loading" style="height: 84vh">
+      <el-tab-pane
+        style="height: 100%"
+        v-for="(deviceItem, deviceIndex) in list"
+        :key="deviceIndex"
+      >
+        <template #label>
+          <span
+            :class="['custom-label', { 'has-border': deviceItem.deviceName === '生产日报' }]"
+            v-if="deviceItem.isFill === 1"
+            @click="
+              openFill(
+                deviceItem.deviceCategoryId,
+                deviceItem.deviceId,
+                deviceItem.deptId,
+                deviceItem.deviceName,
+                deviceItem.deviceCode
+              )
+            "
+          >
+            {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
+          </span>
+          <span
+            :class="['custom-label1', { 'has-border': deviceItem.deviceName === '生产日报' }]"
+            v-else
+            @click="
+              openFill(
+                deviceItem.deviceCategoryId,
+                deviceItem.deviceId,
+                deviceItem.deptId,
+                deviceItem.deviceName,
+                deviceItem.deviceCode
+              )
+            "
+          >
+            {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
+          </span>
+        </template>
+        <div class="form-wrapper">
+          <el-form label-width="120px" class="scrollable-form">
+            <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>
+                </el-form-item>
+                <el-form-item :label="t('operationFillForm.team')" class="custom-label1">
+                  <span style="text-decoration: underline">
+                    {{ deviceItem.orgName }}
+                  </span>
+                </el-form-item>
+                <el-row :gutter="20">
+                  <el-col
+                    v-for="(summaryItem, summaryIndex) in attrList1"
+                    :key="summaryIndex"
+                    :span="24"
+                  >
+                    <el-form-item :label="summaryItem.name" class="custom-label1">
+                      <span style="text-decoration: underline">
+                        {{ summaryItem.totalRunTime }}
+                      </span>
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+                <!--                <el-form-item :label="t('operationFillForm.sumTime')" class="custom-label1">
+                  <span style="text-decoration: underline;">
+                  {{totalRunTime1}}h
+                  </span>
+                </el-form-item>-->
+              </el-form>
+            </div>
+
+            <div
+              v-for="(attrItem, attrIndex) in attrList"
+              :key="attrIndex"
+              style="margin-left: 24px"
+            >
+              {{ attrItem }}
+              <!-- 添加提示文字 -->
+              <div v-if="attrItem.isCollection === 1" class="plc-tip">
+                <el-alert
+                  :title="t('operationFillForm.alert')"
+                  type="warning"
+                  :closable="false"
+                  center
+                  show-icon
+                  style="width: 320px"
+                />
+              </div>
+              <el-form-item :label="attrItem.name" prop="deviceId" label-position="top">
+                <div v-if="fillStatus === '1'">
+                  <el-select
+                    disabled
+                    v-model="attrItem.fillContent"
+                    v-if="attrItem.type === 'enum' && attrItem.description !== null"
+                    style="width: 200px"
+                  >
+                    <el-option
+                      v-for="dict in attrItem.name === '非生产原因'
+                        ? getIntDictOptions(attrItem.description)
+                        : getStrDictOptions(attrItem.description)"
+                      :key="dict.label"
+                      :label="dict.label"
+                      :value="
+                        attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
+                      "
+                    />
+                  </el-select>
+                  <el-input
+                    v-else
+                    v-model="attrItem.fillContent"
+                    clearable
+                    style="width: 200px; margin-right: 10px"
+                    disabled
+                  />
+                </div>
+
+                <el-input
+                  v-else-if="attrItem.type === 'textarea'"
+                  v-model="attrItem.fillContent"
+                  type="textarea"
+                  clearable
+                  style="width: 200px"
+                />
+                <el-select
+                  v-model="attrItem.fillContent"
+                  clearable
+                  v-else-if="attrItem.type === 'enum' && attrItem.description !== null"
+                  style="width: 200px"
+                  filterable
+                >
+                  <el-option
+                    v-for="dict in attrItem.name === '非生产原因'
+                      ? getIntDictOptions(attrItem.description)
+                      : getStrDictOptions(attrItem.description)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="
+                      attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
+                    "
+                  />
+                </el-select>
+                <el-input
+                  v-else
+                  v-model="attrItem.fillContent"
+                  clearable
+                  style="width: 200px"
+                  :placeholder="
+                    attrItem.type === 'double'
+                      ? t('operationFillForm.enterNumber')
+                      : t('operationFillForm.enterContent')
+                  "
+                  @input="handleInput(attrItem)"
+                  :maxlength="attrItem.type === 'double' ? calculateMaxLength(attrItem) : 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-form-item>
+          </el-form>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </ContentWrap>
+</template>
+
+<script setup lang="ts">
+import { IotOpeationFillApi, IotOpeationFillVO } from '@/api/pms/iotopeationfill'
+import { ElMessage } from 'element-plus'
+import moment from 'moment'
+import { getIntDictOptions, getStrDictOptions } from '@/utils/dict'
+import { useRoute } from 'vue-router'
+
+/** 运行记录填报 列表 */
+defineOptions({ name: 'FillOrderInfo' })
+
+const route = useRoute()
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const loading = ref(true) // 列表的加载中
+const { params } = useRoute() // 查询参数
+const deptId = params.id as string
+const list = ref<IotOpeationFillVO[]>([]) // 列表的数据
+const attrList = ref<IotOpeationFillVO[]>([]) // 非累计属性集合
+const attrList1 = ref<IotOpeationFillVO[]>([]) // 累计属性集合
+const attrList2 = ref<IotOpeationFillVO[]>([]) // 属性集合
+let companyName = ref('')
+
+let fillStatus = deptId.split(',')[4]
+let createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
+let showStatus = true
+const queryParams = reactive<any>({
+  pageNo: 1,
+  pageSize: 10,
+  deviceCode: undefined,
+  deviceName: undefined,
+  fillContent: undefined,
+  deviceType: undefined,
+  deviceComponent: undefined,
+  deptId: undefined,
+  orgName: undefined,
+  proId: undefined,
+  proName: undefined,
+  teamId: undefined,
+  teamName: undefined,
+  dutyName: undefined,
+  creDate: [],
+  createTime: [],
+  deviceCategoryId: 1,
+  deviceId: undefined,
+  threshold: undefined,
+  defaultValue: undefined,
+  isSum: undefined
+})
+
+let cxStatus = true
+
+// 计算数字输入的最大长度(根据阈值动态计算)
+const calculateMaxLength = (item: any) => {
+  if (item.type !== 'double' || !item.threshold) return undefined
+
+  const max = parseFloat(item.threshold)
+  if (isNaN(max)) return undefined
+
+  // 整数部分长度 + 可能的小数点 + 两位小数
+  return max.toString().length + (max.toString().includes('.') ? 0 : 3)
+}
+
+// 简单的节流函数,避免提示信息过于频繁
+const throttle = <T extends (...args: any[]) => any>(fn: T, delay: number) => {
+  let lastTime = 0
+
+  return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
+    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
+
+    // 1. 格式验证:只允许数字和小数点
+    item.fillContent = item.fillContent.replace(/[^\d.]/g, '')
+    // 确保只有一个小数点
+    item.fillContent = item.fillContent.replace(/\.{2,}/g, '.')
+    // 确保小数点不在开头
+    item.fillContent = item.fillContent.replace(/^\./g, '')
+    // 限制小数位数为两位
+    item.fillContent = item.fillContent.replace(/(\d+)\.(\d{2}).*/, '$1.$2')
+
+    // 2. 最大值验证
+    if (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, '')
+          .replace(/\.{2,}/g, '.')
+          .replace(/^\./g, '')
+          .replace(/(\d+)\.(\d{2}).*/, '$1.$2')
+
+        if (parseFloat(item.fillContent) > max) {
+          item.fillContent = max.toString()
+        }
+
+        throttle(() => {
+          ElMessage.warning(t('operationFillForm.exceedMax', { max }))
+        }, 1000)()
+      }
+    }
+
+    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`
+          }
+
+          ElMessage.warning(
+            `填报值 ${value} 超过限制 ${item.maxAllowedValue} (${limitDescription}),请确认是否正确!`
+          )
+        }
+      }
+    }
+  }
+}
+
+const getAttrList = async () => {
+  loading.value = true
+  try {
+    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
+
+    // 建立累计数据映射,用于后续验证
+    const sumMap = new Map()
+    attrList1.value.forEach((item) => {
+      // 创建匹配规则:移除"填报"字样的差异,保留核心名称
+      const coreName = item.name.replace(/填报/g, '')
+      sumMap.set(coreName, item)
+    })
+
+    // 为非累计数据添加最大值限制
+    attrList.value.forEach(function (item) {
+      if (item.fillContent !== '' && item.fillContent !== null) {
+        const num = Number(item.fillContent)
+        if (!isNaN(num)) {
+          if (item.fillContent.includes('.')) {
+            item.fillContent = Number(num.toFixed(2))
+          } else {
+            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
+      console.log(item.fillContent)
+    })
+
+    attrList1.value.forEach(function (item) {
+      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 {
+    const company = await IotOpeationFillApi.getOrgName(route.params.id.toString().split(',')[0])
+
+    if (devName != '生产日报') {
+      // 检查必填字段
+      const emptyFields = attrList.value.filter((item) => {
+        // 只检查非disabled的字段
+        return (
+          !(item.isCollection === 1 || fillStatus === '1') &&
+          (item.fillContent === undefined || item.fillContent === '')
+        )
+      })
+      if (emptyFields.length > 0) {
+        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,
+          '以下填报项超出限制,是否继续保存?',
+          '继续保存',
+          '取消'
+        )
+        if (!confirmResult) {
+          return // 用户取消保存
+        }
+      }
+    }
+
+    attrList2.value = attrList.value.concat(attrList1.value)
+
+    attrList2.value.forEach(function (item) {
+      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()
+  } catch (error) {
+    console.error('保存失败:', error)
+  }
+}
+
+/**清空填写信息*/
+const deleteFillInfo = () => {
+  attrList.value.forEach(function (item) {
+    item.fillContent = ''
+  })
+}
+
+/** 初始化 **/
+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; /* 根据你的需求调整 */
+
+  /* 可选:添加内边距和边框美化 */
+  padding: 16px;
+
+  /* 超出部分显示垂直滚动条 */
+  overflow-y: auto;
+  border: 1px solid #e5e7eb;
+  border-radius: 4px;
+}
+
+/* 可选:美化滚动条 */
+.scrollable-form::-webkit-scrollbar {
+  width: 6px;
+}
+
+.scrollable-form::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.scrollable-form::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.scrollable-form::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+.back-red {
+  /* 红色背景 */
+  background-color: red;
+}
+
+.back-blue {
+  background-color: grey;
+}
+
+.step-container {
+  display: grid;
+  grid-template-columns: 220px 1fr;
+  gap: 10px;
+  height: 100%;
+  min-height: 600px;
+}
+
+.steps-nav {
+  padding-right: 15px;
+  overflow-y: auto;
+}
+
+.form-wrapper {
+  padding: 30px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
+}
+
+.navigation-controls {
+  margin-top: 40px;
+  text-align: center;
+}
+
+.custom-label {
+  padding: 0 10px;
+  font-size: 17px;
+  font-weight: 1000;
+  color: forestgreen;
+}
+
+.custom-label1 {
+  padding: 0 10px;
+  font-size: 17px;
+  font-weight: 1000;
+}
+
+.has-border {
+  padding: 3px 8px; /* 适当增加内边距,避免文字太贴近边框 */
+  font-size: 20px;
+  border: 2px solid #333; /* 加粗到2px,使用深灰色#333让边框更清晰 */
+  border-radius: 2px; /* 轻微圆角,可选 */
+}
+
+::v-deep .el-step__icon {
+  color: #fff;
+  background-color: #409eff;
+  border: 0;
+}
+
+.step-title-wrapper {
+  position: relative;
+  display: inline-flex;
+  padding-right: 25px;
+  align-items: center;
+  gap: 8px;
+}
+
+/* 覆盖步骤条默认样式 */
+:deep(.custom-steps) {
+  /* 调整头部位置 */
+  .el-step__head {
+    top: 3px;
+  }
+
+  /* 标题容器定位 */
+  .el-step__title {
+    display: inline-block;
+    padding-right: 0;
+    margin-left: 10px;
+  }
+
+  /* 步骤连接线 */
+  .el-step__line {
+    left: 11px;
+    background-color: #ebeef5;
+  }
+
+  /* 当前步骤样式 */
+  .is-process .title-text {
+    font-weight: 600;
+    color: #409eff;
+  }
+
+  /* 完成状态图标 */
+  .is-finish .tip-icon {
+    color: #67c23a;
+  }
+}
+
+.horizontal-container {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  gap: 20px; /* 项目间距 */
+}
+
+.form-item-container {
+  flex: 1; /* 等宽分布 */
+  min-width: 200px; /* 最小宽度防止挤压 */
+}
+
+/* 新增日报填报项的边框样式 */
+.report-border {
+  padding: 2px 4px;
+  border: 2px solid #42b983; /* 使用Vue标志性的绿色边框 */
+  border-radius: 4px;
+}
+</style>

+ 229 - 7
src/views/pms/iotopeationfill/index1.vue

@@ -38,8 +38,13 @@
             {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
           </span>
         </template>
-        <div class="form-wrapper">
-          <el-form label-width="120px" class="scrollable-form">
+        <div class="form-wrapper h-full">
+          <el-form
+            size="default"
+            label-width="120px"
+            class="scrollable-form"
+            :model="{ attrList: attrList }"
+          >
             <div style="margin-left: 24px">
               <el-form class="demo-form-inline" :inline="true">
                 <el-form-item :label="t('common.createTime')" class="custom-label1">
@@ -74,11 +79,12 @@
             </div>
 
             <div
-              v-for="(attrItem, attrIndex) in attrList"
+              v-for="(attrItem, attrIndex) in attrList.filter(
+                (item) => !keys.includes(item.description)
+              )"
               :key="attrIndex"
               style="margin-left: 24px"
             >
-              {{ attrItem }}
               <!-- 添加提示文字 -->
               <div v-if="attrItem.isCollection === 1" class="plc-tip">
                 <el-alert
@@ -158,6 +164,49 @@
                 />
               </el-form-item>
             </div>
+
+            <div
+              v-for="(attrItem, attrIndex) in attrList"
+              :key="attrIndex"
+              style="margin-left: 24px"
+            >
+              <el-divider v-if="attrItem.description === 'repairTime'" content-position="left"
+                >非生产时间</el-divider
+              >
+              <el-form-item
+                v-if="
+                  keys.includes(attrItem.description) && attrItem.description !== 'otherNptReason'
+                "
+                label-position="top"
+                :label="attrItem.name"
+                :prop="'attrList.' + attrIndex + '.fillContent'"
+                :rules="rules[attrItem.description]"
+              >
+                <el-input-number
+                  class="w-80!"
+                  :min="0"
+                  :max="24"
+                  v-model="attrItem.fillContent"
+                  :controls="false"
+                  align="left"
+                  placeholder="请输入数字"
+                />
+              </el-form-item>
+
+              <el-form-item
+                v-else-if="attrItem.description === 'otherNptReason'"
+                label-position="top"
+                :label="attrItem.name"
+                :prop="'attrList.' + attrIndex + '.fillContent'"
+                :rules="rules[attrItem.description]"
+              >
+                <el-input
+                  class="w-80!"
+                  v-model="attrItem.fillContent"
+                  placeholder="请输入其他非生产原因"
+                />
+              </el-form-item>
+            </div>
             <el-form-item>
               <el-button type="primary" @click="getFillInfo" v-show="showStatus">{{
                 t('operationFillForm.confirm')
@@ -175,7 +224,7 @@
 
 <script setup lang="ts">
 import { IotOpeationFillApi, IotOpeationFillVO } from '@/api/pms/iotopeationfill'
-import { ElMessage } from 'element-plus'
+import { ElMessage, FormRules } from 'element-plus'
 import moment from 'moment'
 import { getIntDictOptions, getStrDictOptions } from '@/utils/dict'
 import { useRoute } from 'vue-router'
@@ -225,6 +274,170 @@ const queryParams = reactive<any>({
   isSum: undefined
 })
 
+const NON_KEYS = [
+  'repairTime',
+  'selfStopTime',
+  'accidentTime',
+  'complexityTime',
+  'rectificationTime',
+  'waitingStopTime',
+  'partyaDesign',
+  'partyaPrepare',
+  'partyaResource',
+  'relocationTime',
+  'winterBreakTime',
+  'otherNptTime'
+]
+
+const keys = [
+  'repairTime',
+  'selfStopTime',
+  'accidentTime',
+  'complexityTime',
+  'rectificationTime',
+  'waitingStopTime',
+  'partyaDesign',
+  'partyaPrepare',
+  'partyaResource',
+  'relocationTime',
+  'winterBreakTime',
+  'otherNptTime',
+  'drillingWorkingTime',
+  'otherProductionTime',
+  'ratedProductionTime',
+  'productionTime',
+  'dailyInjectGasTime',
+  'otherNptReason'
+]
+
+const sumNonProdTimes = () => {
+  let sum = 0
+  NON_KEYS.forEach((field) => {
+    sum += attrList.value.find((item) => item.description === field)?.fillContent || 0
+  })
+  return sum
+}
+
+const rhValidateTotalTime =
+  (isNon: boolean = false) =>
+  (_rule: any, _value: any, callback: any) => {
+    console.log('11 :>> ', 11)
+    const gasTime =
+      attrList.value.find((item) => item.description === 'dailyInjectGasTime')?.fillContent || 0
+    const nonProdSum = sumNonProdTimes()
+
+    let total = 0
+    let msg = ''
+
+    total = parseFloat((gasTime + nonProdSum).toFixed(2))
+    msg = `注气(${gasTime})+非生产(${nonProdSum})=${total}H,必须为24H`
+
+    if (Math.abs(total - 24) > 0.01) {
+      if (!isNon) callback(new Error(msg))
+      else callback(new Error())
+    } else {
+      callback()
+    }
+  }
+
+// const ryXjValidateTotalTime =
+//   (isNon: boolean = false) =>
+//   (_rule: any, _value: any, callback: any) => {
+//     const rateTime =
+//       attrList.value.find((item) => item.description === 'ratedProductionTime')?.fillContent || 0
+//     const time =
+//       attrList.value.find((item) => item.description === 'productionTime')?.fillContent || 0
+
+//     const nonProdSum = sumNonProdTimes()
+
+//     let total = 0
+//     let msg = ''
+
+//     total = parseFloat((time + nonProdSum).toFixed(2))
+//     msg = `生产(${time})+非生产(${nonProdSum})=${total}H,必须等于额定${rateTime}H`
+
+//     if (Math.abs(total - rateTime) > 0.01) {
+//       if (!isNon) callback(new Error(msg))
+//       else callback(new Error())
+//     } else {
+//       callback()
+//     }
+//   }
+
+const ryValidateTotalTime =
+  (isNon: boolean = false) =>
+  (_rule: any, _value: any, callback: any) => {
+    const drillingTime =
+      attrList.value.find((item) => item.description === 'drillingWorkingTime')?.fillContent || 0
+    const otherTime =
+      attrList.value.find((item) => item.description === 'otherProductionTime')?.fillContent || 0
+
+    const nonProdSum = sumNonProdTimes()
+
+    let total = 0
+    let msg = ''
+
+    total = parseFloat((drillingTime + otherTime + nonProdSum).toFixed(2))
+    msg = `进尺(${drillingTime})+其他(${otherTime})+非生产(${nonProdSum})=${total}H,必须为24H`
+
+    if (Math.abs(total - 24) > 0.01) {
+      if (!isNon) callback(new Error(msg))
+      else callback(new Error())
+    } else {
+      callback()
+    }
+  }
+
+const validateOtherReason = (_rule: any, value: any, callback: any) => {
+  const time = attrList.value.find((item) => item.description === 'otherNptTime')?.fillContent || 0
+  if (time > 0 && !value) {
+    callback(new Error('填写了其他时间,必须说明原因'))
+  } else {
+    callback()
+  }
+}
+
+const rules = reactive<FormRules>({
+  dailyInjectGasTime: [
+    { required: true, message: '请输入当日注气时间', trigger: 'blur' },
+    { validator: rhValidateTotalTime(), trigger: 'blur' }
+  ],
+  drillingWorkingTime: [
+    { required: true, message: '请输入进尺工作时间', trigger: 'blur' },
+    { validator: ryValidateTotalTime(), trigger: 'blur' }
+  ],
+  otherProductionTime: [{ validator: ryValidateTotalTime(), trigger: 'blur' }],
+  // ratedProductionTime: [
+  //   { required: true, message: '请输入额定生产时间', trigger: 'blur' },
+  //   { validator: ryXjValidateTotalTime(), trigger: 'blur' }
+  // ],
+  // productionTime: [
+  //   { required: true, message: '请输入生产时间', trigger: 'blur' },
+  //   { validator: ryXjValidateTotalTime(), trigger: 'blur' }
+  // ],
+  otherNptReason: [{ validator: validateOtherReason, trigger: 'blur' }]
+})
+
+const totalValidatorComputed = computed(() => {
+  if (attrList.value.some((item) => item.description === 'dailyInjectGasTime')) {
+    return rhValidateTotalTime
+  }
+  //  else if (attrList.value.some((item) => item.description === 'ratedProductionTime')) {
+  //   return ryXjValidateTotalTime
+  // }
+  else if (attrList.value.some((item) => item.description === 'drillingWorkingTime')) {
+    return ryValidateTotalTime
+  }
+})
+
+nextTick(() => {
+  const validator = totalValidatorComputed.value
+  if (!validator) return
+  NON_KEYS.forEach((field) => {
+    rules[field] = [{ validator: validator(true), trigger: 'blur' }]
+  })
+})
+
 let cxStatus = true
 
 // 计算数字输入的最大长度(根据阈值动态计算)
@@ -372,7 +585,14 @@ const getAttrList = async () => {
     queryParams.createTime = formatTimestamp(JSON.parse(deptId.split(',')[2].substring(0, 10)))
     const data = await IotOpeationFillApi.getAttrs(queryParams)
 
-    attrList.value = data[0].nonSumList
+    const timeKeys = keys.filter((k) => k !== 'otherNptReason')
+
+    attrList.value = data[0].nonSumList.map((item) => {
+      if (timeKeys.includes(item.description)) {
+        item.fillContent = item.fillContent ?? 0
+      }
+      return item
+    })
     attrList1.value = data[0].sumList
 
     // 建立累计数据映射,用于后续验证
@@ -506,6 +726,8 @@ const getFillInfo = async () => {
       item.id = deptId.split(',')[3]
     })
     const data = attrList2.value as unknown as IotOpeationFillVO
+
+    console.log('data :>> ', data)
     await IotOpeationFillApi.insertLog(data)
     message.success(t('common.createSuccess'))
     // 发送操作成功的事件
@@ -536,7 +758,7 @@ onMounted(async () => {
 <style scoped>
 .scrollable-form {
   /* 设置最大高度,超过这个高度会出现滚动条 */
-  max-height: 500px; /* 根据你的需求调整 */
+  max-height: 100%; /* 根据你的需求调整 */
 
   /* 可选:添加内边距和边框美化 */
   padding: 16px;

Разница между файлами не показана из-за своего большого размера
+ 343 - 189
src/views/pms/iotrddailyreport/FillDailyReportForm.vue


Некоторые файлы не были показаны из-за большого количества измененных файлов