Răsfoiți Sursa

拆分生产动态

Zimo 1 săptămână în urmă
părinte
comite
8c6c2f7ba1

+ 72 - 68
api/recordFilling.js

@@ -1,109 +1,113 @@
-import {
-	request
-} from '@/utils/request.js';
+import { request } from "@/utils/request.js";
 
 export function getRecordFillingList(params) {
-	return request({
-		url: '/rq/iot-opeation-fill/page1',
-		method: 'GET',
-		params
-	});
+  return request({
+    url: "/rq/iot-opeation-fill/page1",
+    method: "GET",
+    params,
+  });
 }
 export function getRecordFillingDetailGetPage(params) {
-	return request({
-		url: '/rq/iot-opeation-fill/page',
-		method: 'GET',
-		params
-	});
+  return request({
+    url: "/rq/iot-opeation-fill/page",
+    method: "GET",
+    params,
+  });
 }
 export function getRecordFillingDetailGetAttrs(params) {
-	return request({
-		url: '/rq/iot-opeation-fill/getAttrs',
-		method: 'GET',
-		params
-	});
+  return request({
+    url: "/rq/iot-opeation-fill/getAttrs",
+    method: "GET",
+    params,
+  });
 }
 // 运行记录填报
 export function recordFillingDetailInsertLog(data) {
-	return request({
-		url: '/rq/iot-opeation-fill/insertLog',
-		method: 'POST',
-		data
-	});
+  return request({
+    url: "/rq/iot-opeation-fill/insertLog",
+    method: "POST",
+    data,
+  });
 }
 
 /**
  * 运气记录详情
  * @param id
  */
-export const getRecordFillingDetail = (id) =>
-	request({
-		url: '/rq/iot-opeation-fill/get',
-		method: 'GET',
-		params: { id }
-	})
+export const getRecordFillingDetail = id =>
+  request({
+    url: "/rq/iot-opeation-fill/get",
+    method: "GET",
+    params: { id },
+  });
 
 /**
  * 运行记录模板列表
  */
 export const getRecordFillingTemplateList = () =>
-	request({
-		url: '/rq/iot-model-template/page',
-		method: 'GET',
-		params: { pageNo: 1, pageSize: 100, status: 0 },
-	})
+  request({
+    url: "/rq/iot-model-template/page",
+    method: "GET",
+    params: { pageNo: 1, pageSize: 100, status: 0 },
+  });
 
 /**
  * 获取设备模板属性列表
  * @param deviceCategoryId
  */
-export const getRecordFillingOptionList = (deviceCategoryId) =>
-	request({
-		url: 'rq/iot-model-template-attrs/page',
-		method: 'GET',
-		params: { pageNo: 1, pageSize: 100, deviceCategoryId },
-	})
-	
-	// 忽略运行工单
+export const getRecordFillingOptionList = deviceCategoryId =>
+  request({
+    url: "rq/iot-model-template-attrs/page",
+    method: "GET",
+    params: { pageNo: 1, pageSize: 100, deviceCategoryId },
+  });
+
+// 忽略运行工单
 export function recordFillingIgnore(data) {
-	return request({
-		url: '/rq/iot-opeation-fill/update1',
-		method: 'put',
-		data: data
-	})
+  return request({
+    url: "/rq/iot-opeation-fill/update1",
+    method: "put",
+    data: data,
+  });
 }
 
 // 运行记录填报后更新工单状态
 export function recordFillingUpOperationOrder(data) {
-	return request({
-		url: '/rq/iot-opeation-fill/upOperationOrder',
-		method: 'POST',
-		data: data
-	})
+  return request({
+    url: "/rq/iot-opeation-fill/upOperationOrder",
+    method: "POST",
+    data: data,
+  });
 }
 
 // 获取运行记录填报工单详情-设备列表+属性
 export function recordFillingDetailGetPageAndAttrs(params) {
-	return request({
-		url: '/rq/iot-opeation-fill/orderFillpage1',
-		method: 'GET',
-		params
-	});
+  return request({
+    url: "/rq/iot-opeation-fill/orderFillpage1",
+    method: "GET",
+    params,
+  });
+}
+
+export function reportDetailsGet(id) {
+  return request({
+    url: `rq/iot-opeation-fill/prod/detail/${id}`,
+    method: "GET",
+  });
 }
 // 保存运行记录填报工单详情填报内容
 export function recordFillingDetailInsertDataList(data) {
-	return request({
-		url: '/rq/iot-opeation-fill/insertDataList',
-		method: 'POST',
-		data: data
-	})
+  return request({
+    url: "/rq/iot-opeation-fill/insertDataList",
+    method: "POST",
+    data: data,
+  });
 }
 
-
 // 获取部门名称
 export function getDeptName(deptId) {
-	return request({
-		url: `/rq/report/dept/${deptId}`,
-		method: 'GET'
-	});
-}
+  return request({
+    url: `/rq/report/dept/${deptId}`,
+    method: "GET",
+  });
+}

+ 399 - 345
pages/home/index.vue

@@ -5,9 +5,13 @@
       <uni-row style="width: 100%; display: flex">
         <uni-col :span="6" />
         <uni-col :span="12">
-          <view class="nav-title" style="text-align: center">{{ $t('app.appName') }}</view>
+          <view class="nav-title" style="text-align: center">{{
+            $t("app.appName")
+          }}</view>
         </uni-col>
-        <uni-col :span="6" style="display: flex; justify-content: end; padding-right: 20px">
+        <uni-col
+          :span="6"
+          style="display: flex; justify-content: end; padding-right: 20px">
           <uni-badge absolute="rightTop" size="small" :text="messageCount">
             <image
               src="~@/static/home/message.png"
@@ -40,28 +44,33 @@
               class="flex-col justify-between"
               v-for="{ item1, item2 } in overtimeTaskList"
               @click="navigatorTo('/pages/overtime/index')">
-              <view class="daiban flex-row align-center justify-around" style="margin-top: 10px">
+              <view
+                class="daiban flex-row align-center justify-around"
+                style="margin-top: 10px">
                 <view class="dt-status green">
                   <!-- 超时未保养 -->
                   {{ item1.status }}
                 </view>
                 <view class="dt-content">
                   <!--  增压机十年使用到期保养  -->
-                  {{ item1.type + '-' + item1.title }}
+                  {{ item1.type + "-" + item1.title }}
                 </view>
                 <!--								<view class="dt-time">-->
                 <!--                  &lt;!&ndash;  1小时前  &ndash;&gt;-->
                 <!--                  {{ item1.createTime }}-->
                 <!--								</view>-->
               </view>
-              <view v-if="item2" class="daiban flex-row align-center justify-around" style="margin-bottom: 10px">
+              <view
+                v-if="item2"
+                class="daiban flex-row align-center justify-around"
+                style="margin-bottom: 10px">
                 <view class="dt-status green">
                   <!--  超时未保养  -->
                   {{ item2?.status }}
                 </view>
                 <view class="dt-content">
                   <!--  增压机十年使用到期保养  -->
-                  {{ item2?.type + '-' + item2?.title }}
+                  {{ item2?.type + "-" + item2?.title }}
                 </view>
                 <!--								<view class="dt-time">-->
                 <!--                &lt;!&ndash; 1小时前  &ndash;&gt;-->
@@ -80,50 +89,61 @@
           class="yunxingjilu flex-col justify-between"
           @click="navigatorTo('/pages/recordFilling/list')">
           <view class="half-title">
-            {{ $t('home.operationRecordFilling') }}
+            {{ $t("home.operationRecordFilling") }}
           </view>
           <view class="half-subtitle">
-            {{ $t('home.fillDailyOperationRecord') }}
+            {{ $t("home.fillDailyOperationRecord") }}
           </view>
         </uni-col>
         <!-- 保养工单 -->
-        <uni-col :span="12" class="baoyang flex-col justify-between" @click="navigatorTo('/pages/maintenance/index')">
+        <uni-col
+          :span="12"
+          class="baoyang flex-col justify-between"
+          @click="navigatorTo('/pages/maintenance/index')">
           <view class="half-title">
-            {{ $t('home.maintenanceWorkOrder') }}
+            {{ $t("home.maintenanceWorkOrder") }}
           </view>
           <view class="half-subtitle">
-            {{ $t('home.receiveMaintenanceWorkOrderAndSubmit') }}
+            {{ $t("home.receiveMaintenanceWorkOrderAndSubmit") }}
           </view>
         </uni-col>
       </uni-row>
       <uni-row class="row-half" :gutter="5">
         <!-- 设备维修 -->
-        <uni-col :span="12" class="shebeiweixiu flex-col justify-between" @click="navigatorTo('/pages/repair/index')">
+        <uni-col
+          :span="12"
+          class="shebeiweixiu flex-col justify-between"
+          @click="navigatorTo('/pages/repair/index')">
           <view class="half-title">
-            {{ $t('home.equipmentMaintenance') }}
+            {{ $t("home.equipmentMaintenance") }}
           </view>
           <view class="half-subtitle">
-            {{ $t('home.fillMaintenanceWorkOrder') }}
+            {{ $t("home.fillMaintenanceWorkOrder") }}
           </view>
         </uni-col>
         <!-- 巡检工单 -->
-        <uni-col :span="12" class="xunjian flex-col justify-between" @click="navigatorTo('/pages/inspection/index')">
+        <uni-col
+          :span="12"
+          class="xunjian flex-col justify-between"
+          @click="navigatorTo('/pages/inspection/index')">
           <view class="half-title">
-            {{ $t('home.inspectionWorkOrder') }}
+            {{ $t("home.inspectionWorkOrder") }}
           </view>
           <view class="half-subtitle">
-            {{ $t('home.receiveInspectionWorkOrderAndSubmit') }}
+            {{ $t("home.receiveInspectionWorkOrderAndSubmit") }}
           </view>
         </uni-col>
       </uni-row>
       <uni-row class="row-full">
         <!-- 故障上报 -->
-        <uni-col class="guzhang flex-row align-center" @click="navigatorTo('/pages/fault/index')">
+        <uni-col
+          class="guzhang flex-row align-center"
+          @click="navigatorTo('/pages/fault/index')">
           <view class="half-title" style="margin-right: 10px">
-            {{ $t('home.faultReporting') }}
+            {{ $t("home.faultReporting") }}
           </view>
           <view class="half-subtitle">
-            {{ $t('home.fillAndReportFaultWorkOrder') }}
+            {{ $t("home.fillAndReportFaultWorkOrder") }}
           </view>
         </uni-col>
       </uni-row>
@@ -137,10 +157,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiDu') }}
+                {{ $t("home.dailyReportRuiDu") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiDuTip') }}
+                {{ $t("home.dailyReportRuiDuTip") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -154,10 +174,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiHen') }}
+                {{ $t("home.dailyReportRuiHen") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiHenTip') }}
+                {{ $t("home.dailyReportRuiHenTip") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -171,10 +191,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiHen') }}
+                {{ $t("home.dailyReportRuiHen") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiHenApproval') }}
+                {{ $t("home.dailyReportRuiHenApproval") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -188,10 +208,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiYing') }}
+                {{ $t("home.dailyReportRuiYing") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiYingTip') }}
+                {{ $t("home.dailyReportRuiYingTip") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -205,10 +225,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiYing') }}
+                {{ $t("home.dailyReportRuiYing") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiYingApproval') }}
+                {{ $t("home.dailyReportRuiYingApproval") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -222,10 +242,10 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiYingX') }}
+                {{ $t("home.dailyReportRuiYingX") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiYingXTip') }}
+                {{ $t("home.dailyReportRuiYingXTip") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -239,100 +259,114 @@
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.dailyReportRuiYingX') }}
+                {{ $t("home.dailyReportRuiYingX") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.dailyReportRuiYingXApproval') }}
+                {{ $t("home.dailyReportRuiYingXApproval") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 库存查询 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/inventory/index')">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/inventory/index')">
           <image src="/static/home/kucun.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.inventoryQuery') }}
+                {{ $t("home.inventoryQuery") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.clickToQueryInventoryData') }}
+                {{ $t("home.clickToQueryInventoryData") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 设备台账 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/ledger/index')">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/ledger/index')">
           <image src="/static/home/taizhang.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.equipmentLedger') }}
+                {{ $t("home.equipmentLedger") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.viewEquipmentLedger') }}
+                {{ $t("home.viewEquipmentLedger") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 设备状态变更 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/statusChange/index')">
-          <image src="/static/home/zhuangtaibiangeng.svg" mode="aspectFill"></image>
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/statusChange/index')">
+          <image
+            src="/static/home/zhuangtaibiangeng.svg"
+            mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.equipmentStatusChange') }}
+                {{ $t("home.equipmentStatusChange") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.adjustEquipmentStatus') }}
+                {{ $t("home.adjustEquipmentStatus") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 设备责任人 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/deviceUser/index')">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/deviceUser/index')">
           <image src="/static/home/deviceUser.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.deviceUser') }}
+                {{ $t("home.deviceUser") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.deviceUserTip') }}
+                {{ $t("home.deviceUserTip") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 设备实时数据监控 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/realTimeData/index')">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/realTimeData/index')">
           <image src="/static/home/shujujiankong.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.realTimeEquipmentDataMonitoring') }}
+                {{ $t("home.realTimeEquipmentDataMonitoring") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.viewRealTimeEquipmentData') }}
+                {{ $t("home.viewRealTimeEquipmentData") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
         <!-- 统计分析 -->
-        <view class="card-cell flex-row align-center justify-between" @click="navigatorTo('/pages/statistic/index')">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/statistic/index')">
           <image src="/static/home/tongjifenxi.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
-                {{ $t('home.statisticalAnalysis') }}
+                {{ $t("home.statisticalAnalysis") }}
               </view>
               <view class="subtitle">
-                {{ $t('home.equipmentDataStatisticalAnalysis') }}
+                {{ $t("home.equipmentDataStatisticalAnalysis") }}
               </view>
             </view>
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
@@ -346,323 +380,343 @@
 </template>
 
 <script setup>
-  import { onShow } from '@dcloudio/uni-app';
-  import { nextTick, onMounted, ref } from 'vue';
-  import { getUnreadMessageCount } from '@/api/message';
-  import { getOvertimeTaskList } from '@/api/task';
-  import { getLoginUserInfo } from '@/api/login';
-  import { useDataDictStore } from '@/store/modules/dataDict';
-  import { useDeptStore } from '@/store/modules/dept';
-  import { useDeviceStore } from '@/store/modules/device';
-  import { messageNavigate } from '@/utils/navigate';
-  import Upgrade from '@/components/upgrade.vue';
-
-  const navigatorTo = url => {
-    uni.navigateTo({
-      url: url,
-    });
-  };
-
-  const messageCount = ref(0);
-  onMounted(async () => {
-    await Promise.all([
-      useDataDictStore().loadDataDictList(),
-      useDeptStore().loadDeptList(),
-      useDeviceStore().loadDeviceTypeList(),
-    ]);
+import { onShow } from "@dcloudio/uni-app";
+import { nextTick, onMounted, ref } from "vue";
+import { getUnreadMessageCount } from "@/api/message";
+import { getOvertimeTaskList } from "@/api/task";
+import { getLoginUserInfo } from "@/api/login";
+import { useDataDictStore } from "@/store/modules/dataDict";
+import { useDeptStore } from "@/store/modules/dept";
+import { useDeviceStore } from "@/store/modules/device";
+import { messageNavigate } from "@/utils/navigate";
+import Upgrade from "@/components/upgrade.vue";
+
+const navigatorTo = url => {
+  uni.navigateTo({
+    url: url,
   });
-
-  // 是否展示瑞都日报入口
-  const isShowRuiduDaily = ref(false);
-  const rhReportFlag = ref(false);
-  const rhReportApprovalFlag = ref(false);
-  const ryReportFlag = ref(false);
-  const ryReportApprovalFlag = ref(false);
-  const ryXjReportFlag = ref(false);
-  const ryXjReportApprovalFlag = ref(false);
-  const userInfo = ref({});
-  const getLoginUser = async () => {
-    const response = await getLoginUserInfo();
-    if (response.code === 0) {
-      userInfo.value = response.data;
-      isShowRuiduDaily.value = response.data.rdReportFlag;
-      rhReportFlag.value = response.data.rhReportFlag;
-      rhReportApprovalFlag.value = response.data.rhReportApprovalFlag;
-      ryReportFlag.value = response.data.ryReportFlag;
-      ryReportApprovalFlag.value = response.data.ryReportApprovalFlag;
-      ryXjReportFlag.value = response.data.ryXjReportFlag;
-      ryXjReportApprovalFlag.value = response.data.ryXjReportApprovalFlag;
-    }
-  };
-  getLoginUser();
-
-  const overtimeTaskList = ref([]);
-  const isNavigated = ref(false);
-  // 处理缓存的钉钉消息传递的消息
-  onShow(async () => {
-    const response = await Promise.all([getUnreadMessageCount(), getOvertimeTaskList({ pageNo: 1, pageSize: 10 })]);
-    // const response = await getUnreadMessageCount()
-    // messageCount.value = response.data
-    if (response[0].code === 0) {
-      messageCount.value = response[0].data;
-    }
-    if (response[1].code === 0) {
-      const list = response[1].data.list;
-      const mapList = [];
-      for (let i = 0; i < list.length; i += 2) {
-        mapList.push({
-          item1: list[i],
-          item2: list[i + 1],
-        });
-      }
-      overtimeTaskList.value = mapList;
+};
+
+const messageCount = ref(0);
+onMounted(async () => {
+  await Promise.all([
+    useDataDictStore().loadDataDictList(),
+    useDeptStore().loadDeptList(),
+    useDeviceStore().loadDeviceTypeList(),
+  ]);
+});
+
+// 是否展示瑞都日报入口
+const isShowRuiduDaily = ref(false);
+const rhReportFlag = ref(false);
+const rhReportApprovalFlag = ref(false);
+const ryReportFlag = ref(false);
+const ryReportApprovalFlag = ref(false);
+const ryXjReportFlag = ref(false);
+const ryXjReportApprovalFlag = ref(false);
+const userInfo = ref({});
+const getLoginUser = async () => {
+  const response = await getLoginUserInfo();
+  if (response.code === 0) {
+    userInfo.value = response.data;
+    isShowRuiduDaily.value = response.data.rdReportFlag;
+    // isShowRuiduDaily.value = true;
+    rhReportFlag.value = response.data.rhReportFlag;
+    rhReportApprovalFlag.value = response.data.rhReportApprovalFlag;
+    ryReportFlag.value = response.data.ryReportFlag;
+    // ryReportFlag.value = true;
+    ryReportApprovalFlag.value = response.data.ryReportApprovalFlag;
+    // ryReportApprovalFlag.value = true;
+    ryXjReportFlag.value = response.data.ryXjReportFlag;
+    // ryXjReportFlag.value = true;
+    ryXjReportApprovalFlag.value = response.data.ryXjReportApprovalFlag;
+    // ryXjReportApprovalFlag.value = true;
+  }
+};
+getLoginUser();
+
+const overtimeTaskList = ref([]);
+const isNavigated = ref(false);
+// 处理缓存的钉钉消息传递的消息
+onShow(async () => {
+  const response = await Promise.all([
+    getUnreadMessageCount(),
+    getOvertimeTaskList({ pageNo: 1, pageSize: 10 }),
+  ]);
+  // const response = await getUnreadMessageCount()
+  // messageCount.value = response.data
+  if (response[0].code === 0) {
+    messageCount.value = response[0].data;
+  }
+  if (response[1].code === 0) {
+    const list = response[1].data.list;
+    const mapList = [];
+    for (let i = 0; i < list.length; i += 2) {
+      mapList.push({
+        item1: list[i],
+        item2: list[i + 1],
+      });
     }
+    overtimeTaskList.value = mapList;
+  }
 
-    // await getOvertimeTaskList({ pageNo: 1, pageSize: 10 })
-
-    await nextTick(() => {
-      const json = uni.getStorageSync('dingTalkJson');
-      // console.log('home: dingTalkJson -> ' + json + `, isTrue: ${!!json}`)
-      if (json) {
-        const obj = JSON.parse(json);
-        if (obj.type) {
-          messageNavigate(obj);
-          uni.removeStorageSync('dingTalkJson');
-          // console.log('home: dingTalkJson -> ' + uni.getStorageSync('dingTalkJson'))
-        }
-      } else {
-        if (isNavigated.value) return;
-        let args = '';
-
-        // #ifdef APP
-        args = plus.runtime.arguments;
-        // #endif
-
-        // console.log('home: args -> ' + args)
-        const parts = args.match(/^deepoil:\/\/([^/]+)\/([^/]+)$/);
-        if (parts) {
-          const type = parts[1];
-          const id = parts[2];
-          messageNavigate({ type, id });
-          isNavigated.value = true;
-        }
+  // await getOvertimeTaskList({ pageNo: 1, pageSize: 10 })
+
+  await nextTick(() => {
+    const json = uni.getStorageSync("dingTalkJson");
+    // console.log('home: dingTalkJson -> ' + json + `, isTrue: ${!!json}`)
+    if (json) {
+      const obj = JSON.parse(json);
+      if (obj.type) {
+        messageNavigate(obj);
+        uni.removeStorageSync("dingTalkJson");
+        // console.log('home: dingTalkJson -> ' + uni.getStorageSync('dingTalkJson'))
+      }
+    } else {
+      if (isNavigated.value) return;
+      let args = "";
+
+      // #ifdef APP
+      args = plus.runtime.arguments;
+      // #endif
+
+      // console.log('home: args -> ' + args)
+      const parts = args.match(/^deepoil:\/\/([^/]+)\/([^/]+)$/);
+      if (parts) {
+        const type = parts[1];
+        const id = parts[2];
+        messageNavigate({ type, id });
+        isNavigated.value = true;
       }
-    });
+    }
   });
+});
 </script>
 
 <style lang="scss" scoped>
-  .home {
-    width: 100%;
-    height: 100%;
-    position: relative;
+.home {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.row-full {
+  width: 100%;
+  height: 64px;
+  margin-bottom: 10px;
+}
+
+.row-half {
+  width: 100%;
+  height: 68px;
+  margin-bottom: 5px;
+
+  .uni-col {
+    padding: 15px !important;
     box-sizing: border-box;
-    overflow: hidden;
   }
-
-  .row-full {
-    width: 100%;
-    height: 64px;
-    margin-bottom: 10px;
+}
+
+.daiban-tixing {
+  height: 100%;
+  min-height: 128rpx;
+  background-image: url("/static/home/kapian.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  padding: 0 10px !important;
+  padding-left: 15px !important;
+  box-sizing: border-box;
+}
+
+.dt-title {
+  width: 37px;
+  font-size: 20px;
+  line-height: 20px;
+  text-shadow: 0 2px 4px rgba(98, 114, 125, 0.36);
+  text-align: center;
+  font-style: normal;
+
+  .todo {
+    color: #004098;
   }
 
-  .row-half {
-    width: 100%;
-    height: 68px;
-    margin-bottom: 5px;
-
-    .uni-col {
-      padding: 15px !important;
-      box-sizing: border-box;
-    }
+  .remind {
+    color: #333333;
   }
-
-  .daiban-tixing {
-    height: 100%;
-    min-height: 128rpx;
-    background-image: url('/static/home/kapian.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    padding: 0 10px !important;
-    padding-left: 15px !important;
-    box-sizing: border-box;
-  }
-
-  .dt-title {
-    width: 37px;
-    font-size: 20px;
-    line-height: 20px;
-    text-shadow: 0 2px 4px rgba(98, 114, 125, 0.36);
-    text-align: center;
-    font-style: normal;
-
-    .todo {
-      color: #004098;
-    }
-
-    .remind {
-      color: #333333;
-    }
+}
+
+.dt-swiper {
+  width: calc(100%);
+  height: 100%;
+}
+
+.dt-status {
+  width: 60px;
+  height: 18px;
+  background: #ffffff;
+  box-shadow:
+    0 2px 4px 0 rgba(98, 114, 125, 0.36),
+    0 2px 4px 0 rgba(0, 0, 0, 0.13);
+  border-radius: 6px;
+  font-family:
+    PingFangSC,
+    PingFang SC;
+  font-weight: 500;
+  font-size: 10px;
+  line-height: 18px;
+  text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
+  text-align: center;
+
+  &.green {
+    color: #2bbb80;
   }
 
-  .dt-swiper {
-    width: calc(100%);
-    height: 100%;
+  &.blue {
+    color: #3b63c9;
   }
-
-  .dt-status {
-    width: 60px;
-    height: 18px;
-    background: #ffffff;
-    box-shadow: 0 2px 4px 0 rgba(98, 114, 125, 0.36), 0 2px 4px 0 rgba(0, 0, 0, 0.13);
-    border-radius: 6px;
-    font-family: PingFangSC, PingFang SC;
-    font-weight: 500;
-    font-size: 10px;
-    line-height: 18px;
-    text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
-    text-align: center;
-
-    &.green {
-      color: #2bbb80;
-    }
-
-    &.blue {
-      color: #3b63c9;
-    }
+}
+
+.dt-content {
+  flex: 1;
+  width: auto;
+  margin-left: 10px;
+  font-family:
+    PingFangSC,
+    PingFang SC;
+  font-weight: 500;
+  font-size: 10px;
+  color: #595959;
+  line-height: 14px;
+  text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+.dt-time {
+  font-family:
+    PingFangSC,
+    PingFang SC;
+  font-weight: 500;
+  font-size: 10px;
+  color: #646464;
+  line-height: 14px;
+  text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
+}
+
+.half-title {
+  font-family:
+    PingFangSC,
+    PingFang SC;
+  font-weight: 500;
+  font-size: 14px;
+  color: #ffffff;
+}
+
+.half-subtitle {
+  font-family:
+    PingFangSC,
+    PingFang SC;
+  font-weight: 500;
+  font-size: 10px;
+  color: #ffffff;
+}
+
+.yunxingjilu {
+  width: calc(50% - 2.5px);
+  height: 100%;
+  background-image: url("/static/home/yunxingjilu.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  margin-right: 2.5px;
+}
+
+.baoyang {
+  width: calc(50% - 2.5px);
+  height: 100%;
+  background-image: url("/static/home/baoyang.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  margin-left: 2.5px;
+}
+
+.shebeiweixiu {
+  width: calc(50% - 2.5px);
+  height: 100%;
+  background-image: url("/static/home/shebeiweixiu.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  margin-right: 2.5px;
+}
+
+.xunjian {
+  width: calc(50% - 2.5px);
+  height: 100%;
+  background-image: url("/static/home/xunjian.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  margin-left: 2.5px;
+}
+
+.guzhang {
+  // width: 100%;
+  height: 100%;
+  background-image: url("/static/home/guzhang.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+
+  padding: 15px !important;
+  box-sizing: border-box;
+}
+
+.card {
+  width: 100%;
+  background: #ffffff;
+  border-radius: 6px;
+  padding: 20px;
+  box-sizing: border-box;
+}
+
+.card-cell {
+  width: 100%;
+  height: 50px;
+
+  image {
+    width: 32px;
+    height: 32px;
   }
+}
 
-  .dt-content {
-    flex: 1;
-    width: auto;
-    margin-left: 10px;
-    font-family: PingFangSC, PingFang SC;
-    font-weight: 500;
-    font-size: 10px;
-    color: #595959;
-    line-height: 14px;
-    text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    overflow: hidden;
-  }
+.cell-con {
+  margin-left: 10px;
+  margin-right: 10px;
+  width: calc(100% - 32px - 10px - 10px);
+  height: 100%;
+  border-bottom: 0.5px solid #cacccf;
+}
 
-  .dt-time {
-    font-family: PingFangSC, PingFang SC;
-    font-weight: 500;
-    font-size: 10px;
-    color: #646464;
-    line-height: 14px;
-    text-shadow: 0px 2px 4px rgba(98, 114, 125, 0.36);
-  }
+.cell-text {
+  width: 100%;
+  font-weight: 500;
 
-  .half-title {
-    font-family: PingFangSC, PingFang SC;
-    font-weight: 500;
+  .title {
     font-size: 14px;
-    color: #ffffff;
-  }
-
-  .half-subtitle {
-    font-family: PingFangSC, PingFang SC;
-    font-weight: 500;
-    font-size: 10px;
-    color: #ffffff;
-  }
-
-  .yunxingjilu {
-    width: calc(50% - 2.5px);
-    height: 100%;
-    background-image: url('/static/home/yunxingjilu.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    margin-right: 2.5px;
-  }
-
-  .baoyang {
-    width: calc(50% - 2.5px);
-    height: 100%;
-    background-image: url('/static/home/baoyang.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    margin-left: 2.5px;
-  }
-
-  .shebeiweixiu {
-    width: calc(50% - 2.5px);
-    height: 100%;
-    background-image: url('/static/home/shebeiweixiu.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    margin-right: 2.5px;
-  }
-
-  .xunjian {
-    width: calc(50% - 2.5px);
-    height: 100%;
-    background-image: url('/static/home/xunjian.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    margin-left: 2.5px;
-  }
-
-  .guzhang {
-    // width: 100%;
-    height: 100%;
-    background-image: url('/static/home/guzhang.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-
-    padding: 15px !important;
-    box-sizing: border-box;
-  }
-
-  .card {
-    width: 100%;
-    background: #ffffff;
-    border-radius: 6px;
-    padding: 20px;
-    box-sizing: border-box;
-  }
-
-  .card-cell {
-    width: 100%;
-    height: 50px;
-
-    image {
-      width: 32px;
-      height: 32px;
-    }
+    color: #333333;
+    line-height: 20px;
   }
 
-  .cell-con {
+  .subtitle {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
     margin-left: 10px;
-    margin-right: 10px;
-    width: calc(100% - 32px - 10px - 10px);
-    height: 100%;
-    border-bottom: 0.5px solid #cacccf;
   }
 
-  .cell-text {
-    width: 100%;
-    font-weight: 500;
-
-    .title {
-      font-size: 14px;
-      color: #333333;
-      line-height: 20px;
-    }
-
-    .subtitle {
-      font-size: 12px;
-      color: #999999;
-      line-height: 17px;
-      margin-left: 10px;
-    }
-
-    .icon {
-      width: 6px;
-      height: 10px;
-    }
+  .icon {
+    width: 6px;
+    height: 10px;
   }
+}
 </style>

+ 217 - 4
pages/recordFilling/detail.vue

@@ -79,7 +79,9 @@
           class="item-content flex-col align-center justify-between"
           :class="{ 'bottom-bold': item.nonSumList.length > 0 }"
           v-for="nosum in item.nonSumList.filter(
-            nosum => !keys.includes(nosum.description)
+            nosum =>
+              !keys.includes(nosum.description) &&
+              nosum.description !== 'productionStatus'
           )">
           <!-- isCollection为1,提示:以下数值取自PLC,如有不符请修改 -->
           <uni-notice-bar
@@ -154,12 +156,14 @@
           </view>
         </view>
         <!-- {{ nosum.name }} {{ nosum.description }} -->
+
         <uni-forms
+          v-if="item.deviceName === '生产日报'"
           ref="formRef"
           labelWidth="auto"
           validateTrigger="submit"
           err-show-type="toast"
-          :model="{ nonSumList: item.nonSumList }">
+          :model="{ nonSumList: item.nonSumList, reportDetails }">
           <template
             v-for="(nosum, nosumIndex) in item.nonSumList"
             :key="nosumIndex">
@@ -193,6 +197,89 @@
                 :maxlength="1000" />
             </uni-forms-item>
           </template>
+          <template v-if="deptName === 'ry'">
+            <uv-divider text="生产动态" textPosition="left"> </uv-divider>
+            <uni-forms-item v-if="isView">
+              <button
+                type="primary"
+                size="mini"
+                class="detail-btn"
+                @click="addProductionStatusRow()">
+                添加一行
+              </button>
+            </uni-forms-item>
+            <template v-for="(detail, index) in reportDetails" :key="index">
+              <uni-forms-item label="日期" style="margin-top: 32px">
+                <span>{{ params.createTime }}</span>
+              </uni-forms-item>
+              <uni-forms-item :label="`${$t('ruiDu.timeNode')}:`" required>
+                <view
+                  @click="
+                    !isView ? undefined : handleClickTimeRangeItem(index)
+                  ">
+                  <view
+                    class="time-range-item"
+                    v-if="detail.startTime && detail.endTime">
+                    {{ detail.startTime }} 至 {{ detail.endTime }}
+                  </view>
+                  <view class="time-range-item" v-else> 请选择 </view>
+                </view>
+              </uni-forms-item>
+              <uni-forms-item label="时长(H)">
+                <span>{{ detail.duration }}</span>
+              </uni-forms-item>
+              <uni-forms-item
+                label="工况"
+                required
+                :name="['reportDetails', index, 'currentOperation']"
+                :rules="[{ required: true, errorMessage: '请输入工况' }]">
+                <uni-easyinput
+                  type="textarea"
+                  autoHeight
+                  v-bind="defaultProps"
+                  v-model="detail.currentOperation"
+                  :disabled="!isView"
+                  :maxlength="2000" />
+              </uni-forms-item>
+              <uni-forms-item
+                label="结束井深(m)"
+                required
+                v-if="showDepth(item.nonSumList)"
+                :name="['reportDetails', index, 'currentDepth']"
+                :rules="[{ required: true, errorMessage: '请输入结束深度' }]">
+                <uni-easyinput
+                  type="number"
+                  v-bind="defaultProps"
+                  :disabled="!isView"
+                  v-model.number="detail.currentDepth"
+                  @input="
+                    val => inputCurrentDepth(item.nonSumList, val, index)
+                  " />
+              </uni-forms-item>
+              <uni-forms-item
+                label="详情"
+                required
+                :name="['reportDetails', index, 'constructionDetail']"
+                :rules="[{ required: true, errorMessage: '请输入详情' }]">
+                <uni-easyinput
+                  type="textarea"
+                  autoHeight
+                  :disabled="!isView"
+                  v-bind="defaultProps"
+                  v-model="detail.constructionDetail"
+                  :maxlength="2000" />
+              </uni-forms-item>
+              <uni-forms-item v-if="isView" label="操作">
+                <button
+                  type="warn"
+                  size="mini"
+                  class="detail-btn"
+                  @click="removeReportDetailRow(index)">
+                  删除
+                </button>
+              </uni-forms-item>
+            </template>
+          </template>
         </uni-forms>
       </view>
     </view>
@@ -208,10 +295,18 @@
       </button>
     </template>
   </z-paging>
+
+  <tpf-time-range
+    ref="reportDetailsTimeRangeRef"
+    :startTime="startTime"
+    :startDefaultTime="startDefaultTime"
+    :endTime="endTime"
+    :endDefaultTime="endDefaultTime"
+    @timeRange="reportDetailsTimeRange"></tpf-time-range>
 </template>
 
 <script setup>
-import { ref, reactive, getCurrentInstance, watch } from "vue";
+import { ref, reactive, getCurrentInstance, watch, computed } from "vue";
 import { onReady, onLoad } from "@dcloudio/uni-app";
 import dayjs from "dayjs";
 import {
@@ -220,9 +315,13 @@ import {
   recordFillingDetailGetPageAndAttrs,
   recordFillingDetailInsertDataList,
   getDeptName,
+  reportDetailsGet,
 } from "@/api/recordFilling";
 import { reloginByUserId } from "@/utils/auth.js";
 import { useDataDictStore } from "@/store/modules/dataDict";
+import { useDebounceFn } from "@/utils/useDebounceFn.js";
+import tpfTimeRange from "@/components/tpf-time-range/tpf-time-range.vue";
+
 // 引用全局变量$t
 const { appContext } = getCurrentInstance();
 const t = appContext.config.globalProperties.$t;
@@ -230,6 +329,98 @@ const t = appContext.config.globalProperties.$t;
 // 获取字典项
 const { getStrDictOptions, getIntDictOptions } = useDataDictStore();
 
+const defaultProps = computed(() => ({
+  inputBorder: false,
+  clearable: false,
+  placeholder: "请输入",
+  style: {
+    "text-align": "right",
+  },
+  styles: {
+    disableColor: "#fff",
+  },
+}));
+
+const reportDetailsTimeRangeRef = ref(null);
+
+const startTime = ref("00:00");
+const startDefaultTime = ref("08:00");
+const endTime = ref("24:00");
+const endDefaultTime = ref("08:00");
+
+const reportDetailIndex = ref(0);
+
+const handleClickTimeRangeItem = index => {
+  reportDetailIndex.value = index;
+  reportDetailsTimeRangeRef.value.open();
+};
+
+const calculateDuration = row => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0;
+    return;
+  }
+
+  const todayStr = dayjs().format("YYYY-MM-DD");
+  const start = dayjs(`${todayStr} ${row.startTime}`);
+  const end = dayjs(`${todayStr} ${row.endTime}`);
+
+  let diffMinutes = end.diff(start, "minute");
+
+  if (diffMinutes < 0) {
+    diffMinutes += 1440;
+  }
+
+  row.duration = Number((diffMinutes / 60).toFixed(2));
+};
+
+const reportDetailsTimeRange = data => {
+  reportDetails.value[reportDetailIndex.value].startTime = data[0];
+  reportDetails.value[reportDetailIndex.value].endTime = data[1];
+
+  calculateDuration(reportDetails.value[reportDetailIndex.value]);
+};
+
+const reportDetails = ref([]);
+
+const addProductionStatusRow = () => {
+  if (!reportDetails.value) {
+    reportDetails.value = [];
+  }
+  reportDetails.value.push({
+    startTime: "08:00",
+    endTime: "08:00",
+    duration: 0,
+    currentDepth: 0,
+    currentOperation: "",
+    constructionDetail: "",
+  });
+};
+
+const removeReportDetailRow = index => {
+  if (index === 0) {
+    uni.showToast({ title: "至少填写一条生产动态", icon: "none" });
+    return;
+  }
+
+  reportDetails.value.splice(index, 1);
+};
+
+const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(
+  list,
+  val,
+  index
+) {
+  if (reportDetails.value && index === reportDetails.value.length - 1) {
+    const currentDepth = list.find(item => item.description === "currentDepth");
+    if (currentDepth) currentDepth.fillContent = val;
+  }
+}, 300);
+
+const showDepth = computed(() => list => {
+  return list.some(item => item.description === "currentDepth");
+});
+
 const NON_KEYS = [
   "repairTime",
   "selfStopTime",
@@ -635,6 +826,9 @@ function calculateTotalRunTime(
   targetItem.fillContent = toFixed(total);
 }
 
+const formatT = arr =>
+  `${arr[0].toString().padStart(2, "0")}:${arr[1].toString().padStart(2, "0")}`;
+
 // @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
 const queryList = (pageNo, pageSize) => {
   const userId = uni.getStorageSync("userId");
@@ -789,6 +983,21 @@ const queryList = (pageNo, pageSize) => {
       const hasReport = resList.some(item => item.deviceName === "生产日报");
       if (hasReport) {
         applyAccumulatedToReport();
+
+        reportDetailsGet(params.value.orderId).then(res => {
+          reportDetails.value = (res.data ?? []).map(item => ({
+            startTime: formatT(item.startTime),
+            endTime: formatT(item.endTime),
+            duration: item.duration,
+            currentDepth: item.currentDepth,
+            currentOperation: item.currentOperation,
+            constructionDetail: item.constructionDetail,
+          }));
+
+          if (!reportDetails.value.length) {
+            addProductionStatusRow();
+          }
+        });
       }
     })
     .catch(res => {
@@ -1396,8 +1605,12 @@ const submitData = async () => {
     }));
     console.log("提交用的数据:submitList", submitList);
     // 3. 提交所有填写记录
+    const reqData = {
+      createReqVO: submitList,
+      reportDetails: reportDetails.value,
+    };
 
-    const res = await recordFillingDetailInsertDataList(submitList);
+    const res = await recordFillingDetailInsertDataList(reqData);
     console.log("🚀 ~ 提交工单填报内容结果 ~ res:", res);
 
     if (res?.code === 0) {

+ 214 - 14
pages/ruiDu/compontents/report-form.vue

@@ -112,9 +112,9 @@ const unselectedEquipmentNames = ref("");
 
 const timeRangeRef = ref(null);
 const startTime = ref("00:00");
-const startDefaultTime = ref("06:00");
+const startDefaultTime = ref("08:00");
 const endTime = ref("24:00");
-const endDefaultTime = ref("06:00");
+const endDefaultTime = ref("08:00");
 
 const timeRange = data => {
   form.startTime = data[0];
@@ -135,6 +135,7 @@ const form = reactive({
   malfunction: "",
   attachments: [],
   reportFuels: [],
+  reportDetails: [],
 });
 
 const formDataBaseRules = reactive({
@@ -157,21 +158,29 @@ const formDataBaseRules = reactive({
     ],
   },
   // 当日生产动态
-  productionStatus: {
+  // productionStatus: {
+  //   rules: [
+  //     {
+  //       required: true,
+  //       errorMessage: `${t("operation.PleaseFillIn")}${t(
+  //         "ruiDu.dailyProductionDynamic"
+  //       )}`,
+  //     },
+  //   ],
+  // },
+  nextPlan: {
     rules: [
       {
         required: true,
-        errorMessage: `${t("operation.PleaseFillIn")}${t(
-          "ruiDu.dailyProductionDynamic"
-        )}`,
+        errorMessage: `请输入下步工作计划`,
       },
     ],
   },
-  nextPlan: {
+  constructionBrief: {
     rules: [
       {
         required: true,
-        errorMessage: `请输入下步工作计划`,
+        errorMessage: `请输入当日施工简报`,
       },
     ],
   },
@@ -323,6 +332,8 @@ const submitForm = async () => {
         reportId: id,
       })),
       dailyFuel: Number(formDataCopy.dailyFuel),
+      reportDetails: formDataCopy.reportDetails,
+      constructionBrief: formDataCopy.constructionBrief || "",
       repairTime: formDataCopy[id].repairTime || 0,
       selfStopTime: formDataCopy[id].selfStopTime || 0,
       accidentTime: formDataCopy[id].accidentTime || 0,
@@ -385,6 +396,30 @@ const handleTransferChange = selectedIds => {
 
 const steps = ref([]);
 
+const formatT = arr =>
+  `${arr[0].toString().padStart(2, "0")}:${arr[1].toString().padStart(2, "0")}`;
+
+const addReportDetailRow = () => {
+  if (!form.reportDetails) {
+    form.reportDetails = [];
+  }
+  form.reportDetails.push({
+    startTime: "08:00",
+    endTime: "08:00",
+    duration: 0,
+    constructionDetail: "",
+  });
+};
+
+const removeReportDetailRow = index => {
+  if (index === 0) {
+    uni.showToast({ title: "至少填写一条生产动态", icon: "none" });
+    return;
+  }
+
+  form.reportDetails?.splice(index, 1);
+};
+
 const formDataFormat = () => {
   // 处理时间范围
   timeRangeFormat();
@@ -448,6 +483,17 @@ const formDataFormat = () => {
     };
   }
 
+  form.reportDetails = (props.reportData.reportDetails || []).map(item => ({
+    duration: item.duration || 0,
+    constructionDetail: item.constructionDetail || "",
+    startTime: formatT(item.startTime),
+    endTime: formatT(item.endTime),
+  }));
+
+  if (!form.reportDetails.length) {
+    addReportDetailRow();
+  }
+
   // 当日生产动态
   form.productionStatus = props.reportData.productionStatus || ""; //当日生产动态
   // 下步工作计划
@@ -472,7 +518,9 @@ const formDataFormat = () => {
     // 这里保持你原有的数值处理逻辑
     customFuel: Number(
       Number(
-        props.formDisable ? v.customFuel ?? 0 : v.customFuel ?? v.zhbdFuel ?? 0
+        props.formDisable
+          ? (v.customFuel ?? 0)
+          : (v.customFuel ?? v.zhbdFuel ?? 0)
       ).toFixed(2)
     ),
   }));
@@ -532,6 +580,41 @@ const handleClickTimeRange = () => {
   timeRangeRef.value.open();
 };
 
+const reportDetailsTimeRangeRef = ref(null);
+
+const reportDetailIndex = ref(0);
+
+const handleClickTimeRangeItem = index => {
+  reportDetailIndex.value = index;
+  reportDetailsTimeRangeRef.value.open();
+};
+
+const calculateDuration = row => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0;
+    return;
+  }
+
+  const todayStr = dayjs().format("YYYY-MM-DD");
+  const start = dayjs(`${todayStr} ${row.startTime}`);
+  const end = dayjs(`${todayStr} ${row.endTime}`);
+
+  let diffMinutes = end.diff(start, "minute");
+
+  if (diffMinutes < 0) {
+    diffMinutes += 1440;
+  }
+
+  row.duration = Number((diffMinutes / 60).toFixed(2));
+};
+
+const reportDetailsTimeRange = data => {
+  form.reportDetails[reportDetailIndex.value].startTime = data[0];
+  form.reportDetails[reportDetailIndex.value].endTime = data[1];
+
+  calculateDuration(form.reportDetails[reportDetailIndex.value]);
+};
+
 const uploadRef = ref(null);
 
 const attachmentList = ref([]);
@@ -1131,10 +1214,10 @@ watch(
             :styles="{ disableColor: '#fff' }"
             :placeholder="inputPlaceholder"
             :disabled="formDisable"
-            v-model="dailyFuel" />
+            v-model.number="dailyFuel" />
         </uni-forms-item>
 
-        <uni-forms-item
+        <!-- <uni-forms-item
           class="form-item"
           :label="`${$t('ruiDu.dailyProductionDynamic')}:`"
           :required="isRequired"
@@ -1150,7 +1233,7 @@ watch(
             :disabled="formDisable"
             v-model="form.productionStatus"
             :maxlength="1000" />
-        </uni-forms-item>
+        </uni-forms-item> -->
         <!-- 下步工作计划 -->
         <uni-forms-item
           class="form-item"
@@ -1219,6 +1302,23 @@ watch(
             v-model="form.faultDowntime"
             :maxlength="1000" />
         </uni-forms-item>
+        <uni-forms-item
+          v-if="istime === 'true'"
+          class="form-item"
+          label="当日施工简报"
+          required
+          name="constructionBrief">
+          <uni-easyinput
+            class="digit-item"
+            type="textarea"
+            :inputBorder="false"
+            :clearable="true"
+            :styles="{ disableColor: '#fff' }"
+            :placeholder="inputPlaceholder"
+            :disabled="istime !== 'true'"
+            v-model="form.constructionBrief"
+            :maxlength="2000" />
+        </uni-forms-item>
         <uni-forms-item
           class="form-item"
           :label="`${$t('ruiDu.attachment')}:`"
@@ -1266,6 +1366,92 @@ watch(
 
         <!-- 附件 -->
       </div>
+      <div class="content">
+        <div class="content-title">
+          生产动态
+
+          <button
+            v-if="!formDisable && istime !== 'true'"
+            type="primary"
+            size="mini"
+            class="detail-btn"
+            @click="addReportDetailRow()">
+            添加一行
+          </button>
+        </div>
+
+        <template v-for="(item, index) in form.reportDetails" :key="index">
+          <uni-forms-item class="form-item" label="日期">
+            <uni-easyinput
+              class="digit-item"
+              :inputBorder="false"
+              :clearable="false"
+              :styles="{ disableColor: '#fff' }"
+              :placeholder="inputPlaceholder"
+              :disabled="true"
+              :model-value="
+                dayjs(reportData.createTime).format('YYYY-MM-DD')
+              " />
+          </uni-forms-item>
+          <uni-forms-item
+            class="form-item"
+            :label="`${$t('ruiDu.timeNode')}:`"
+            required>
+            <view
+              class="item-content"
+              @click="props.formDisable ? '' : handleClickTimeRangeItem(index)">
+              <view
+                class="time-range-item"
+                v-if="item.startTime && item.endTime">
+                {{ item.startTime }} 至 {{ item.endTime }}
+              </view>
+              <view class="time-range-item" v-else>
+                {{ selectPlaceholder }}
+              </view>
+            </view>
+          </uni-forms-item>
+          <uni-forms-item class="form-item" label="时长(H)">
+            <uni-easyinput
+              class="digit-item"
+              :inputBorder="false"
+              :clearable="false"
+              :styles="{ disableColor: '#fff' }"
+              :placeholder="inputPlaceholder"
+              :disabled="true"
+              :model-value="item.duration" />
+          </uni-forms-item>
+          <uni-forms-item
+            class="form-item"
+            label="施工详情"
+            required
+            :name="['reportDetails', index, 'constructionDetail']"
+            :rules="[{ required: true, errorMessage: '请输入施工详情' }]">
+            <uni-easyinput
+              class="digit-item"
+              type="textarea"
+              autoHeight
+              :inputBorder="false"
+              :clearable="true"
+              :styles="{ disableColor: '#fff' }"
+              :placeholder="inputPlaceholder"
+              :disabled="formDisable"
+              v-model="item.constructionDetail"
+              :maxlength="2000" />
+          </uni-forms-item>
+          <uni-forms-item
+            v-if="!formDisable && istime !== 'true'"
+            class="form-item"
+            label="操作">
+            <button
+              type="warn"
+              size="mini"
+              class="detail-btn"
+              @click="removeReportDetailRow(index)">
+              删除
+            </button>
+          </uni-forms-item>
+        </template>
+      </div>
 
       <div
         v-for="(platformId, index) in form.platformIds"
@@ -1400,7 +1586,7 @@ watch(
             :styles="{ disableColor: '#fff' }"
             :placeholder="inputPlaceholder"
             :disabled="formDisable"
-            v-model="form[platformId].extProperty[index].actualValue" />
+            v-model.number="form[platformId].extProperty[index].actualValue" />
           <uni-easyinput
             v-else
             style="text-align: right"
@@ -1495,7 +1681,7 @@ watch(
             :styles="{ disableColor: '#fff' }"
             :placeholder="inputPlaceholder"
             :disabled="formDisable"
-            v-model="fuel.customFuel"
+            v-model.number="fuel.customFuel"
             @input="handleListChange" />
         </uni-forms-item>
       </div>
@@ -1512,6 +1698,14 @@ watch(
     :endDefaultTime="endDefaultTime"
     @timeRange="timeRange"></tpf-time-range>
 
+  <tpf-time-range
+    ref="reportDetailsTimeRangeRef"
+    :startTime="startTime"
+    :startDefaultTime="startDefaultTime"
+    :endTime="endTime"
+    :endDefaultTime="endDefaultTime"
+    @timeRange="reportDetailsTimeRange"></tpf-time-range>
+
   <device-transfer
     ref="deviceTransferRef"
     :allList="reportData.selectedDevices"
@@ -1671,4 +1865,10 @@ watch(
 .time-range-item {
   margin: 10px;
 }
+
+.detail-btn {
+  margin: 0;
+  margin-left: auto;
+  float: right;
+}
 </style>

+ 9 - 9
pages/ruiDu/index.vue

@@ -38,7 +38,7 @@
           <view
             class="module-status"
             :class="{
-              pending: item.status == 0, //待填写
+              pending: item.status === 0, //待填写
             }">
             {{ fillStatusDict[item.status] }}
           </view>
@@ -89,19 +89,19 @@
                 item.auditStatus === 0
                   ? 'my-tag-info'
                   : item.auditStatus === 10
-                  ? 'my-tag-primary'
-                  : item.auditStatus === 20
-                  ? 'my-tag-success'
-                  : 'my-tag-danger',
+                    ? 'my-tag-primary'
+                    : item.auditStatus === 20
+                      ? 'my-tag-success'
+                      : 'my-tag-danger',
               ]">
               {{
                 item.auditStatus === 0
                   ? "待提交"
                   : item.auditStatus === 10
-                  ? "待审批"
-                  : item.auditStatus === 20
-                  ? "审批通过"
-                  : "审批拒绝"
+                    ? "待审批"
+                    : item.auditStatus === 20
+                      ? "审批通过"
+                      : "审批拒绝"
               }}
             </span>
           </view>

+ 55 - 53
pages/ruiying/approval.vue

@@ -1,46 +1,48 @@
 <script setup>
-  import { ref } from 'vue';
-  import { approvalIotRyDailyReport } from '@/api/ruiying';
-  import Form from './components/form.vue';
+import { ref } from "vue";
+import { approvalIotRyDailyReport } from "@/api/ruiying";
+import Form from "./components/form.vue";
 
-  const formRef = ref(null);
+const formRef = ref(null);
 
-  const formLoading = ref(false);
+const formLoading = ref(false);
 
-  const submitForm = async auditStatus => {
-    if (!formRef.value) return;
+const submitForm = async auditStatus => {
+  if (!formRef.value) return;
 
-    try {
-      // await formRef.value.formRef.validate();
-      const form = formRef.value.form;
+  try {
+    await formRef.value.formRef.validateField("constructionBrief");
+    const form = formRef.value.form;
 
-      // if (form.nonProductionTime && !form.nptReason) {
-      //   uni.showToast({
-      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
-      //     icon: 'none',
-      //   });
-      //   return;
-      // }
+    // if (form.nonProductionTime && !form.nptReason) {
+    //   uni.showToast({
+    //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+    //     icon: 'none',
+    //   });
+    //   return;
+    // }
 
-      formLoading.value = true;
+    formLoading.value = true;
 
-      const { opinion, id } = form;
-      const data = { auditStatus, opinion, id };
+    const { opinion, id, constructionBrief } = form;
+    const data = { auditStatus, opinion, id, constructionBrief };
 
-      await approvalIotRyDailyReport(data);
+    // console.log('data :>> ', data);
 
-      formRef.value.loadDetail(id);
+    await approvalIotRyDailyReport(data);
 
-      uni.showToast({
-        title: auditStatus === 20 ? '通过成功' : '拒绝成功',
-        icon: 'success',
-      });
-    } catch (error) {
-      console.log('error :>> ', error);
-    } finally {
-      formLoading.value = false;
-    }
-  };
+    formRef.value.loadDetail(id);
+
+    uni.showToast({
+      title: auditStatus === 20 ? "通过成功" : "拒绝成功",
+      icon: "success",
+    });
+  } catch (error) {
+    console.log("error :>> ", error);
+  } finally {
+    formLoading.value = false;
+  }
+};
 </script>
 
 <template>
@@ -72,27 +74,27 @@
 </template>
 
 <style lang="scss" scoped>
-  @import '@/style/work-order-segmented.scss';
-  .page {
-    padding-bottom: 0;
+@import "@/style/work-order-segmented.scss";
+.page {
+  padding-bottom: 0;
+}
+
+.footer-btn {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  padding: 0 32px;
+  height: 100%;
+
+  gap: 0 32px;
+
+  & > uni-button {
+    margin: 0;
   }
+}
 
-  .footer-btn {
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
-    padding: 0 32px;
-    height: 100%;
-
-    gap: 0 32px;
-
-    & > uni-button {
-      margin: 0;
-    }
-  }
-
-  :deep(.mini-btn) {
-    height: 38px !important;
-    font-size: 16px !important;
-  }
+:deep(.mini-btn) {
+  height: 38px !important;
+  font-size: 16px !important;
+}
 </style>

+ 597 - 333
pages/ruiying/components/form.vue

@@ -1,243 +1,356 @@
 <script setup>
-  import { ref, computed, watch, nextTick, reactive } from 'vue';
-  import { onLoad } from '@dcloudio/uni-app';
-  import { getRuiYingReportDetail } from '@/api/ruiying';
-  import { useDataDictStore } from '@/store/modules/dataDict';
-
-  const props = defineProps({
-    type: {
-      type: String,
-      default: 'edit',
-    },
-  });
-
-  const NON_PROD_FIELDS = [
-    { key: 'repairTime', label: '设备故障' },
-    { key: 'selfStopTime', label: '设备保养' },
-    { key: 'accidentTime', label: '工程质量' },
-    { key: 'complexityTime', label: '技术受限' },
-    { key: 'rectificationTime', label: '生产组织' },
-    { key: 'waitingStopTime', label: '不可抗力' },
-    { key: 'partyaDesign', label: '甲方设计' },
-    { key: 'partyaPrepare', label: '甲方准备' },
-    { key: 'partyaResource', label: '甲方资源' },
-    { key: 'relocationTime', label: '生产配合' },
-    { key: 'winterBreakTime', label: '待命' },
-    { key: 'otherNptTime', label: '其他非生产时间' },
-  ];
-
-  const FORM_KEYS = [
-    'id',
-    'deptId',
-    'projectId',
-    'taskId',
-    'deptName',
-    'contractName',
-    'taskName',
-    'rigStatus',
-    'designWellDepth',
-    'currentDepth',
-    'dailyPowerUsage',
-    'dailyFuel',
-    'mudDensity',
-    'mudViscosity',
-    'lateralLength',
-    'wellInclination',
-    'azimuth',
-    'designWellStruct',
-    'personnel',
-    'drillingWorkingTime',
-    'otherProductionTime',
-    'lastCurrentDepth',
-
-    'productionStatus',
-    'remark',
-    'createTime',
-
-    'opinion',
-    'repairTime',
-    'selfStopTime',
-    'accidentTime',
-    'complexityTime',
-    'rectificationTime',
-    'waitingStopTime',
-    'partyaDesign',
-    'partyaPrepare',
-    'partyaResource',
-    'relocationTime',
-    'winterBreakTime',
-    'otherNptTime',
-    'otherNptReason',
-
-    'status',
-    'auditStatus',
-  ];
-
-  const formType = ref('edit');
-
-  const initFormData = () => {
-    const base = {
-      drillingWorkingTime: 0,
-      otherProductionTime: 0,
-    };
-    // 初始化所有非生产时间字段为 0
-    NON_PROD_FIELDS.forEach(field => {
-      base[field.key] = 0;
-    });
-    return base;
+import { ref, computed, watch, nextTick, reactive } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { getRuiYingReportDetail } from "@/api/ruiying";
+import { useDataDictStore } from "@/store/modules/dataDict";
+import tpfTimeRange from "@/components/tpf-time-range/tpf-time-range.vue";
+import dayjs from "dayjs";
+import { useDebounceFn } from "@/utils/useDebounceFn.js";
+
+const props = defineProps({
+  type: {
+    type: String,
+    default: "edit",
+  },
+});
+
+const NON_PROD_FIELDS = [
+  { key: "repairTime", label: "设备故障" },
+  { key: "selfStopTime", label: "设备保养" },
+  { key: "accidentTime", label: "工程质量" },
+  { key: "complexityTime", label: "技术受限" },
+  { key: "rectificationTime", label: "生产组织" },
+  { key: "waitingStopTime", label: "不可抗力" },
+  { key: "partyaDesign", label: "甲方设计" },
+  { key: "partyaPrepare", label: "甲方准备" },
+  { key: "partyaResource", label: "甲方资源" },
+  { key: "relocationTime", label: "生产配合" },
+  { key: "winterBreakTime", label: "待命" },
+  { key: "otherNptTime", label: "其他非生产时间" },
+];
+
+const FORM_KEYS = [
+  "id",
+  "deptId",
+  "projectId",
+  "taskId",
+  "deptName",
+  "contractName",
+  "taskName",
+  "rigStatus",
+  "designWellDepth",
+  "currentDepth",
+  "dailyPowerUsage",
+  "dailyFuel",
+  "mudDensity",
+  "mudViscosity",
+  "lateralLength",
+  "wellInclination",
+  "azimuth",
+  "designWellStruct",
+  "personnel",
+  "drillingWorkingTime",
+  "otherProductionTime",
+  "lastCurrentDepth",
+
+  "reportDetails",
+  "constructionBrief",
+  "remark",
+  "createTime",
+
+  "opinion",
+  "repairTime",
+  "selfStopTime",
+  "accidentTime",
+  "complexityTime",
+  "rectificationTime",
+  "waitingStopTime",
+  "partyaDesign",
+  "partyaPrepare",
+  "partyaResource",
+  "relocationTime",
+  "winterBreakTime",
+  "otherNptTime",
+  "otherNptReason",
+
+  "status",
+  "auditStatus",
+];
+
+const formType = ref("edit");
+
+const initFormData = () => {
+  const base = {
+    drillingWorkingTime: 0,
+    otherProductionTime: 0,
+    constructionBrief: "",
+    reportDetails: [],
   };
+  // 初始化所有非生产时间字段为 0
+  NON_PROD_FIELDS.forEach(field => {
+    base[field.key] = 0;
+  });
+  return base;
+};
 
-  const form = ref(initFormData());
+const form = ref(initFormData());
 
-  async function loadDetail(id) {
-    try {
-      const { data } = await getRuiYingReportDetail({ id });
+const formatT = arr =>
+  `${arr[0].toString().padStart(2, "0")}:${arr[1].toString().padStart(2, "0")}`;
 
-      form.value = initFormData();
+async function loadDetail(id) {
+  try {
+    const { data } = await getRuiYingReportDetail({ id });
 
-      FORM_KEYS.forEach(key => {
-        if (Object.prototype.hasOwnProperty.call(data, key) && data[key] !== null && data[key] !== undefined) {
-          form.value[key] = data[key];
-        }
-      });
-      form.value.id = id;
+    form.value = initFormData();
 
-      if (props.type.includes('approval') && data.auditStatus !== 10) {
-        formType.value = 'readonly';
+    FORM_KEYS.forEach(key => {
+      if (
+        Object.prototype.hasOwnProperty.call(data, key) &&
+        data[key] !== null &&
+        data[key] !== undefined
+      ) {
+        form.value[key] = data[key];
       }
+    });
 
-      if (props.type.includes('edit') && data.status !== 0) {
-        formType.value = 'readonly';
-      }
+    form.value.reportDetails = form.value.reportDetails.map(item => ({
+      duration: item.duration || 0,
+      constructionDetail: item.constructionDetail || "",
+      currentDepth: item.currentDepth || 0,
+      currentOperation: item.currentOperation || "",
+      startTime: formatT(item.startTime),
+      endTime: formatT(item.endTime),
+    }));
 
-      if (props.type.includes('detail')) {
-        formType.value = 'readonly';
-      }
-    } finally {
+    if (!form.value.reportDetails.length) {
+      addReportDetailRow();
     }
-  }
 
-  const dictStore = useDataDictStore();
+    form.value.id = id;
 
-  const nptReasonOptions = ref([]);
-  const rigStatusOptions = ref([]);
+    if (props.type.includes("approval") && data.auditStatus !== 10) {
+      formType.value = "readonly";
+    }
 
-  const loadOptions = () => {
-    nptReasonOptions.value = dictStore.getStrDictOptions('nptReason').map(v => ({
-      text: v.label,
-      value: v.value,
-    }));
-    rigStatusOptions.value = dictStore.getStrDictOptions('rigStatus').map(v => ({
-      text: v.label,
-      value: v.value,
-    }));
-  };
+    if (props.type.includes("edit") && data.status !== 0) {
+      formType.value = "readonly";
+    }
 
-  onLoad(options => {
-    if (dictStore.dataDict.length <= 0) {
-      dictStore.loadDataDictList().then(() => {
-        loadOptions();
-      });
-    } else loadOptions();
-    loadDetail(options.id);
+    if (props.type.includes("detail")) {
+      formType.value = "readonly";
+    }
+  } finally {
+  }
+}
+
+const addReportDetailRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = [];
+  }
+  form.value.reportDetails.push({
+    startTime: "08:00",
+    endTime: "08:00",
+    duration: 0,
+    constructionDetail: "",
+    currentDepth: 0,
+    currentOperation: "",
   });
+};
+
+const removeReportDetailRow = index => {
+  if (index === 0) {
+    uni.showToast({ title: "至少填写一条生产动态", icon: "none" });
+    return;
+  }
 
-  const defaultProps = computed(() => ({
-    inputBorder: false,
-    clearable: false,
-    placeholder: '请输入',
-    style: {
-      'text-align': 'right',
-    },
-    styles: {
-      disableColor: '#fff',
-    },
+  form.value.reportDetails?.splice(index, 1);
+};
+
+const dictStore = useDataDictStore();
+
+const nptReasonOptions = ref([]);
+const rigStatusOptions = ref([]);
+
+const loadOptions = () => {
+  nptReasonOptions.value = dictStore.getStrDictOptions("nptReason").map(v => ({
+    text: v.label,
+    value: v.value,
+  }));
+  rigStatusOptions.value = dictStore.getStrDictOptions("rigStatus").map(v => ({
+    text: v.label,
+    value: v.value,
   }));
+};
 
-  const disabled = computed(() => field => {
-    if (field === 'edit')
-      return formType.value === 'readonly' || props.type.includes('approval') || props.type.includes('detail');
-    else return formType.value === 'readonly';
+onLoad(options => {
+  if (dictStore.dataDict.length <= 0) {
+    dictStore.loadDataDictList().then(() => {
+      loadOptions();
+    });
+  } else loadOptions();
+  loadDetail(options.id);
+});
+
+const defaultProps = computed(() => ({
+  inputBorder: false,
+  clearable: false,
+  placeholder: "请输入",
+  style: {
+    "text-align": "right",
+  },
+  styles: {
+    disableColor: "#fff",
+  },
+}));
+
+const disabled = computed(() => field => {
+  if (field === "edit")
+    return (
+      formType.value === "readonly" ||
+      props.type.includes("approval") ||
+      props.type.includes("detail")
+    );
+  else return formType.value === "readonly";
+});
+
+const formRef = ref(null);
+
+// 辅助函数:计算总时间
+const sumNonProdTimes = () => {
+  let sum = 0;
+  NON_PROD_FIELDS.forEach(field => {
+    sum += Number(form.value[field.key] || 0);
   });
+  return sum;
+};
+
+// 校验函数:总时间必须为 24
+const validateTotalTime = (rule, value, data, callback) => {
+  const drillingTime = Number(form.value.drillingWorkingTime || 0);
+  const otherTime = Number(form.value.otherProductionTime || 0);
+
+  const nonProdSum = sumNonProdTimes();
+
+  let total = 0;
+  let msg = "";
 
-  const formRef = ref(null);
+  total = parseFloat((drillingTime + otherTime + nonProdSum).toFixed(2));
+  msg = `进尺(${drillingTime})+其他(${otherTime})+非生产(${nonProdSum})=${total}H,必须为24H`;
 
-  // 辅助函数:计算总时间
-  const sumNonProdTimes = () => {
-    let sum = 0;
-    NON_PROD_FIELDS.forEach(field => {
-      sum += Number(form.value[field.key] || 0);
+  if (Math.abs(total - 24) > 0.01) {
+    callback(msg);
+  }
+  return true;
+};
+
+const validateLastCurrentDepth = (rule, value, data, callback) => {
+  if (value && Number(value) < (form.value.lastCurrentDepth ?? 0)) {
+    callback(`当前深度需大于等于上一次填报深度${form.value.lastCurrentDepth}m`);
+  } else {
+    callback();
+  }
+};
+
+// uni-forms 规则定义
+const rules = reactive({
+  currentDepth: {
+    rules: [
+      { required: true, errorMessage: "请输入当前深度" },
+      { validateFunction: validateLastCurrentDepth },
+    ],
+  },
+
+  drillingWorkingTime: {
+    rules: [
+      { required: true, errorMessage: "请输入进尺工作时间" },
+      { validateFunction: validateTotalTime },
+    ],
+  },
+  constructionBrief: {
+    rules: [{ required: true, errorMessage: `请输入当日施工简报` }],
+  },
+});
+
+const allTimeKeys = [
+  "drillingWorkingTime",
+  "otherProductionTime",
+  ...NON_PROD_FIELDS.map(f => f.key),
+];
+
+watch(
+  () => allTimeKeys.map(key => form.value[key]),
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField("drillingWorkingTime");
     });
-    return sum;
-  };
+  }
+);
 
-  // 校验函数:总时间必须为 24
-  const validateTotalTime = (rule, value, data, callback) => {
-    const drillingTime = Number(form.value.drillingWorkingTime || 0);
-    const otherTime = Number(form.value.otherProductionTime || 0);
+defineExpose({ formRef, form, loadDetail });
 
-    const nonProdSum = sumNonProdTimes();
+const orange = computed(() => {
+  const drillingTime = Number(form.value.drillingWorkingTime || 0);
+  const otherTime = Number(form.value.otherProductionTime || 0);
 
-    let total = 0;
-    let msg = '';
+  const nonProdSum = sumNonProdTimes();
 
-    total = parseFloat((drillingTime + otherTime + nonProdSum).toFixed(2));
-    msg = `进尺(${drillingTime})+其他(${otherTime})+非生产(${nonProdSum})=${total}H,必须为24H`;
+  let total = 0;
 
-    if (Math.abs(total - 24) > 0.01) {
-      callback(msg);
-    }
-    return true;
-  };
+  total = parseFloat((drillingTime + otherTime + nonProdSum).toFixed(2));
 
-  const validateLastCurrentDepth = (rule, value, data, callback) => {
-    if (value && Number(value) < (form.value.lastCurrentDepth ?? 0)) {
-      callback(`当前深度需大于等于上一次填报深度${form.value.lastCurrentDepth}m`);
-    } else {
-      callback();
-    }
-  };
+  if (Math.abs(total - 24) > 0.01) return true;
+  return false;
+});
 
-  // uni-forms 规则定义
-  const rules = reactive({
-    currentDepth: {
-      rules: [{ required: true, errorMessage: '请输入当前深度' }, { validateFunction: validateLastCurrentDepth }],
-    },
-
-    productionStatus: {
-      rules: [{ required: true, errorMessage: '请输入生产动态' }],
-    },
-    drillingWorkingTime: {
-      rules: [{ required: true, errorMessage: '请输入进尺工作时间' }, { validateFunction: validateTotalTime }],
-    },
-  });
+const reportDetailsTimeRangeRef = ref(null);
 
-  const allTimeKeys = ['drillingWorkingTime', 'otherProductionTime', ...NON_PROD_FIELDS.map(f => f.key)];
+const startTime = ref("00:00");
+const startDefaultTime = ref("08:00");
+const endTime = ref("24:00");
+const endDefaultTime = ref("08:00");
 
-  watch(
-    () => allTimeKeys.map(key => form.value[key]),
-    () => {
-      nextTick(() => {
-        formRef.value?.validateField('drillingWorkingTime');
-      });
-    }
-  );
+const reportDetailIndex = ref(0);
 
-  defineExpose({ formRef, form, loadDetail });
+const handleClickTimeRangeItem = index => {
+  reportDetailIndex.value = index;
+  reportDetailsTimeRangeRef.value.open();
+};
 
-  const orange = computed(() => {
-    const drillingTime = Number(form.value.drillingWorkingTime || 0);
-    const otherTime = Number(form.value.otherProductionTime || 0);
+const calculateDuration = row => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0;
+    return;
+  }
 
-    const nonProdSum = sumNonProdTimes();
+  const todayStr = dayjs().format("YYYY-MM-DD");
+  const start = dayjs(`${todayStr} ${row.startTime}`);
+  const end = dayjs(`${todayStr} ${row.endTime}`);
 
-    let total = 0;
+  let diffMinutes = end.diff(start, "minute");
 
-    total = parseFloat((drillingTime + otherTime + nonProdSum).toFixed(2));
+  if (diffMinutes < 0) {
+    diffMinutes += 1440;
+  }
 
-    if (Math.abs(total - 24) > 0.01) return true;
-    return false;
-  });
+  row.duration = Number((diffMinutes / 60).toFixed(2));
+};
+
+const reportDetailsTimeRange = data => {
+  form.value.reportDetails[reportDetailIndex.value].startTime = data[0];
+  form.value.reportDetails[reportDetailIndex.value].endTime = data[1];
+
+  calculateDuration(form.value.reportDetails[reportDetailIndex.value]);
+};
+
+const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val, index) {
+  if (
+    form.value.reportDetails &&
+    index === form.value.reportDetails.length - 1
+  ) {
+    form.value.currentDepth = val;
+  }
+}, 300);
 </script>
 
 <template>
@@ -292,7 +405,11 @@
         <span class="readOnly">{{ form.designWellDepth }}</span>
       </uni-forms-item>
       <uni-forms-item label="当前井深(m)" name="currentDepth" required>
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.currentDepth" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.currentDepth" />
       </uni-forms-item>
       <uni-forms-item label="当日用电量(kWh)" name="dailyPowerUsage">
         <uni-easyinput
@@ -310,13 +427,25 @@
           v-model="form.dailyFuel" />
       </uni-forms-item>
       <uni-forms-item label="泥浆粘度(S)" name="mudViscosity">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.mudViscosity" />
-      </uni-forms-item>
-      <uni-forms-item label="泥浆粘度(S)" name="mudViscosity">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.mudViscosity" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.mudViscosity" />
       </uni-forms-item>
+      <!-- <uni-forms-item label="泥浆粘度(S)" name="mudViscosity">
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.mudViscosity" />
+      </uni-forms-item> -->
       <uni-forms-item label="水平段长度(m)" name="lateralLength">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.lateralLength" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.lateralLength" />
       </uni-forms-item>
       <uni-forms-item label="井斜(°)" name="wellInclination">
         <uni-easyinput
@@ -326,7 +455,11 @@
           v-model="form.wellInclination" />
       </uni-forms-item>
       <uni-forms-item label="方位(°)" name="azimuth">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.azimuth" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.azimuth" />
       </uni-forms-item>
       <uni-forms-item label="设计井身结构" name="designWellStruct">
         <uni-easyinput
@@ -346,7 +479,7 @@
           v-model="form.personnel"
           :maxlength="1000" />
       </uni-forms-item>
-      <uni-forms-item label="生产动态" name="productionStatus" required>
+      <!-- <uni-forms-item label="生产动态" name="productionStatus" required>
         <uni-easyinput
           type="textarea"
           autoHeight
@@ -354,6 +487,19 @@
           v-model="form.productionStatus"
           :disabled="disabled('edit')"
           :maxlength="1000" />
+      </uni-forms-item> -->
+      <uni-forms-item
+        v-if="props.type === 'approval' || props.type === 'approval-detail'"
+        label="当日施工简报"
+        required
+        name="constructionBrief">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('approval')"
+          v-model="form.constructionBrief"
+          :maxlength="2000" />
       </uni-forms-item>
       <uni-forms-item label="备注" name="remark">
         <uni-easyinput
@@ -365,8 +511,92 @@
           :maxlength="1000" />
       </uni-forms-item>
 
+      <uv-divider text="生产动态" textPosition="left"></uv-divider>
+      <uni-forms-item v-if="!disabled('edit')">
+        <button
+          type="primary"
+          size="mini"
+          class="detail-btn"
+          @click="addReportDetailRow()">
+          添加一行
+        </button>
+      </uni-forms-item>
+      <template v-for="(item, index) in form.reportDetails" :key="index">
+        <uni-forms-item label="日期" style="margin-top: 32px">
+          <span class="readOnly">{{
+            dayjs(form.createTime).format("YYYY-MM-DD")
+          }}</span>
+        </uni-forms-item>
+        <uni-forms-item :label="`${$t('ruiDu.timeNode')}:`" required>
+          <view
+            class="item-content"
+            @click="disabled('edit') ? '' : handleClickTimeRangeItem(index)">
+            <view class="time-range-item" v-if="item.startTime && item.endTime">
+              {{ item.startTime }} 至 {{ item.endTime }}
+            </view>
+            <view class="time-range-item" v-else> '请选择' </view>
+          </view>
+        </uni-forms-item>
+        <uni-forms-item label="时长(H)">
+          <span class="readOnly">{{ item.duration }}</span>
+        </uni-forms-item>
+        <uni-forms-item
+          label="工况"
+          required
+          :name="['reportDetails', index, 'currentOperation']"
+          :rules="[{ required: true, errorMessage: '请输入工况' }]">
+          <uni-easyinput
+            type="textarea"
+            autoHeight
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model="item.currentOperation"
+            :maxlength="2000" />
+        </uni-forms-item>
+        <uni-forms-item
+          label="结束井深(m)"
+          required
+          :name="['reportDetails', index, 'currentDepth']"
+          :rules="[
+            { required: true, errorMessage: '请输入结束深度' },
+            { validateFunction: validateLastCurrentDepth },
+          ]">
+          <uni-easyinput
+            type="number"
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model.number="item.currentDepth"
+            @input="val => inputCurrentDepth(val, index)" />
+        </uni-forms-item>
+        <uni-forms-item
+          label="详情"
+          required
+          :name="['reportDetails', index, 'constructionDetail']"
+          :rules="[{ required: true, errorMessage: '请输入详情' }]">
+          <uni-easyinput
+            type="textarea"
+            autoHeight
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model="item.constructionDetail"
+            :maxlength="2000" />
+        </uni-forms-item>
+        <uni-forms-item v-if="!disabled('edit')" label="操作">
+          <button
+            type="warn"
+            size="mini"
+            class="detail-btn"
+            @click="removeReportDetailRow(index)">
+            删除
+          </button>
+        </uni-forms-item>
+      </template>
+
       <uv-divider text="生产时间" textPosition="left"></uv-divider>
-      <uni-forms-item label="进尺工作时间(H)" name="drillingWorkingTime" required>
+      <uni-forms-item
+        label="进尺工作时间(H)"
+        name="drillingWorkingTime"
+        required>
         <uni-easyinput
           type="number"
           v-bind="defaultProps"
@@ -374,7 +604,10 @@
           :disabled="disabled('edit')"
           v-model="form.drillingWorkingTime" />
       </uni-forms-item>
-      <uni-forms-item label="其它生产时间(H)" name="otherProductionTime" required>
+      <uni-forms-item
+        label="其它生产时间(H)"
+        name="otherProductionTime"
+        required>
         <uni-easyinput
           type="number"
           v-bind="defaultProps"
@@ -383,7 +616,11 @@
           v-model="form.otherProductionTime" />
       </uni-forms-item>
       <uv-divider text="非生产时间" textPosition="left"></uv-divider>
-      <uni-forms-item v-for="field in NON_PROD_FIELDS" :key="field.key" :label="field.label + '(H)'" :name="field.key">
+      <uni-forms-item
+        v-for="field in NON_PROD_FIELDS"
+        :key="field.key"
+        :label="field.label + '(H)'"
+        :name="field.key">
         <uni-easyinput
           type="number"
           :class="{ 'orange-text': orange }"
@@ -402,7 +639,10 @@
           :maxlength="1000" />
       </uni-forms-item>
 
-      <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
+      <uni-forms-item
+        v-if="type.includes('approval')"
+        label="审批意见"
+        name="opinion">
         <uni-easyinput
           type="textarea"
           autoHeight
@@ -413,158 +653,182 @@
       </uni-forms-item>
     </uni-forms>
   </view>
+
+  <tpf-time-range
+    ref="reportDetailsTimeRangeRef"
+    :startTime="startTime"
+    :startDefaultTime="startDefaultTime"
+    :endTime="endTime"
+    :endDefaultTime="endDefaultTime"
+    @timeRange="reportDetailsTimeRange"></tpf-time-range>
 </template>
 
 <style lang="scss" scoped>
-  .content {
-    background-color: white;
-    padding: 16px 16px;
-    border-radius: 8px;
-    box-sizing: border-box;
-  }
-
-  .uni-forms {
-    margin-top: 10px;
+.content {
+  background-color: white;
+  padding: 16px 16px;
+  border-radius: 8px;
+  box-sizing: border-box;
+}
+
+.uni-forms {
+  margin-top: 10px;
+  height: 100%;
+
+  .uni-form {
     height: 100%;
+  }
 
-    .uni-form {
-      height: 100%;
-    }
-
-    .uni-forms-item {
-      display: flex;
-      align-items: center;
-      flex: 1;
-      margin-bottom: 6px;
-      border-bottom: 1px dashed #cacccf;
-    }
-
-    :deep(.uni-forms-item__content) {
-      text-align: right;
-      .readOnly {
-        padding-right: 10px;
-      }
-    }
-
-    :deep(.uni-forms-item__label) {
-      height: 44px;
-      font-weight: 500;
-      font-size: 14px;
-      color: #333333 !important;
-      width: max-content !important;
-    }
-
-    :deep(.uni-select) {
-      border: none;
-      text-align: right;
-      padding-right: 0;
-      .uniui-bottom:before {
-        content: '\e6b5' !important;
-        font-size: 16px !important;
-      }
-    }
-
-    :deep(.uni-easyinput__content-textarea) {
-      min-height: inherit;
-      margin: 10px;
-    }
-
-    :deep(.is-disabled) {
-      color: #333333 !important;
-    }
+  .uni-forms-item {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    margin-bottom: 6px;
+    border-bottom: 1px dashed #cacccf;
+  }
 
-    :deep(.red-text > .is-disabled) {
-      color: rgb(220 38 38 / 0.8) !important;
+  :deep(.uni-forms-item__content) {
+    text-align: right;
+    .readOnly {
+      padding-right: 10px;
     }
+  }
 
-    :deep(.orange-text > .is-disabled) {
-      color: rgb(234 88 12 / 0.8) !important;
-    }
+  :deep(.uni-forms-item__label) {
+    height: 44px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #333333 !important;
+    width: max-content !important;
+  }
 
-    :deep(.uni-select--disabled) {
-      background-color: #fff;
+  :deep(.uni-select) {
+    border: none;
+    text-align: right;
+    padding-right: 0;
+    .uniui-bottom:before {
+      content: "\e6b5" !important;
+      font-size: 16px !important;
     }
   }
 
-  .red-text {
-    color: rgb(220 38 38 / 0.8) !important;
+  :deep(.uni-easyinput__content-textarea) {
+    min-height: inherit;
+    margin: 10px;
   }
 
-  .orange-text {
-    color: rgb(234 88 12 / 0.8) !important;
+  :deep(.is-disabled) {
+    color: #333333 !important;
   }
 
-  .red {
-    border: 1px solid rgb(254 226 226);
-    color: rgb(220 38 38 / 0.8);
-    background-color: rgb(254 226 226);
+  :deep(.red-text > .is-disabled) {
+    color: rgb(220 38 38 / 0.8) !important;
   }
 
-  .orange {
-    border: 1px solid rgb(254 215 170);
-    color: rgb(234 88 12 / 0.8);
-    background-color: rgb(254 215 170);
+  :deep(.orange-text > .is-disabled) {
+    color: rgb(234 88 12 / 0.8) !important;
   }
 
-  .tip {
-    border-radius: 8px;
-    border: 1px solid #e5e5e5;
-    background-color: rgba(239, 246, 255, 0.8);
-    box-sizing: border-box;
-    padding: 10px;
-    display: flex;
-    flex-direction: column;
-    gap: 6px;
-
-    .item {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      font-size: 12px;
-
-      .left {
-        color: rgb(75 85 99);
-
-        span {
-          color: rgb(31 41 55);
-          font-weight: 600;
-        }
-      }
-
-      .right {
-        display: inline-flex;
-        align-items: center;
-        border-radius: 4px;
-        padding: 2px 4px;
-        font-weight: 500;
-      }
-    }
+  :deep(.uni-select--disabled) {
+    background-color: #fff;
   }
-
-  .opinion {
-    border-radius: 8px;
-    border: 1px solid rgb(254 240 138);
-    background-color: rgb(254 252 232);
-    box-sizing: border-box;
-    padding: 10px;
+}
+
+.red-text {
+  color: rgb(220 38 38 / 0.8) !important;
+}
+
+.orange-text {
+  color: rgb(234 88 12 / 0.8) !important;
+}
+
+.red {
+  border: 1px solid rgb(254 226 226);
+  color: rgb(220 38 38 / 0.8);
+  background-color: rgb(254 226 226);
+}
+
+.orange {
+  border: 1px solid rgb(254 215 170);
+  color: rgb(234 88 12 / 0.8);
+  background-color: rgb(254 215 170);
+}
+
+.tip {
+  border-radius: 8px;
+  border: 1px solid #e5e5e5;
+  background-color: rgba(239, 246, 255, 0.8);
+  box-sizing: border-box;
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+
+  .item {
     display: flex;
     align-items: center;
     justify-content: space-between;
     font-size: 12px;
-    margin-top: 10px;
 
     .left {
-      font-weight: 600;
-      color: rgb(133 77 14);
+      color: rgb(75 85 99);
+
+      span {
+        color: rgb(31 41 55);
+        font-weight: 600;
+      }
     }
 
     .right {
+      display: inline-flex;
+      align-items: center;
+      border-radius: 4px;
+      padding: 2px 4px;
       font-weight: 500;
-      color: rgb(75 85 99);
     }
   }
+}
+
+.opinion {
+  border-radius: 8px;
+  border: 1px solid rgb(254 240 138);
+  background-color: rgb(254 252 232);
+  box-sizing: border-box;
+  padding: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 12px;
+  margin-top: 10px;
+
+  .left {
+    font-weight: 600;
+    color: rgb(133 77 14);
+  }
 
-  :deep(.uv-divider__text) {
-    color: #333 !important;
+  .right {
+    font-weight: 500;
+    color: rgb(75 85 99);
   }
+}
+
+:deep(.uv-divider__text) {
+  color: #333 !important;
+}
+
+.detail-btn {
+  margin: 0;
+  margin-left: auto;
+  float: right;
+}
+
+.time-range-item {
+  margin: 10px;
+}
+
+.item-content {
+  display: flex;
+  align-items: center;
+  justify-content: end;
+}
 </style>

+ 53 - 53
pages/ruiyingx/approval.vue

@@ -1,46 +1,46 @@
 <script setup>
-  import { ref } from 'vue';
-  import { approvalIotRyDailyReport } from '@/api/ruiying';
-  import Form from './components/form.vue';
+import { ref } from "vue";
+import { approvalIotRyDailyReport } from "@/api/ruiying";
+import Form from "./components/form.vue";
 
-  const formRef = ref(null);
+const formRef = ref(null);
 
-  const formLoading = ref(false);
+const formLoading = ref(false);
 
-  const submitForm = async auditStatus => {
-    if (!formRef.value) return;
+const submitForm = async auditStatus => {
+  if (!formRef.value) return;
 
-    try {
-      // await formRef.value.formRef.validate();
-      const form = formRef.value.form;
+  try {
+    await formRef.value.formRef.validateField("constructionBrief");
+    const form = formRef.value.form;
 
-      // if (form.nonProductionTime && !form.nptReason) {
-      //   uni.showToast({
-      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
-      //     icon: 'none',
-      //   });
-      //   return;
-      // }
+    // if (form.nonProductionTime && !form.nptReason) {
+    //   uni.showToast({
+    //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+    //     icon: 'none',
+    //   });
+    //   return;
+    // }
 
-      formLoading.value = true;
+    formLoading.value = true;
 
-      const { opinion, id } = form;
-      const data = { auditStatus, opinion, id };
+    const { opinion, id, constructionBrief } = form;
+    const data = { auditStatus, opinion, id, constructionBrief };
 
-      await approvalIotRyDailyReport(data);
+    await approvalIotRyDailyReport(data);
 
-      formRef.value.loadDetail(form.id);
+    formRef.value.loadDetail(form.id);
 
-      uni.showToast({
-        title: auditStatus === 20 ? '通过成功' : '拒绝成功',
-        icon: 'success',
-      });
-    } catch (error) {
-      console.log('error :>> ', error);
-    } finally {
-      formLoading.value = false;
-    }
-  };
+    uni.showToast({
+      title: auditStatus === 20 ? "通过成功" : "拒绝成功",
+      icon: "success",
+    });
+  } catch (error) {
+    console.log("error :>> ", error);
+  } finally {
+    formLoading.value = false;
+  }
+};
 </script>
 
 <template>
@@ -72,27 +72,27 @@
 </template>
 
 <style lang="scss" scoped>
-  @import '@/style/work-order-segmented.scss';
-  .page {
-    padding-bottom: 0;
-  }
-
-  .footer-btn {
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
-    padding: 0 32px;
-    height: 100%;
-
-    gap: 0 32px;
-
-    & > uni-button {
-      margin: 0;
-    }
+@import "@/style/work-order-segmented.scss";
+.page {
+  padding-bottom: 0;
+}
+
+.footer-btn {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  padding: 0 32px;
+  height: 100%;
+
+  gap: 0 32px;
+
+  & > uni-button {
+    margin: 0;
   }
+}
 
-  :deep(.mini-btn) {
-    height: 38px !important;
-    font-size: 16px !important;
-  }
+:deep(.mini-btn) {
+  height: 38px !important;
+  font-size: 16px !important;
+}
 </style>

+ 586 - 341
pages/ruiyingx/components/form.vue

@@ -1,253 +1,360 @@
 <script setup>
-  import { ref, computed, watch, nextTick, reactive } from 'vue';
-  import { onLoad } from '@dcloudio/uni-app';
-  import { getRuiYingReportDetail } from '@/api/ruiying';
-  import { useDataDictStore } from '@/store/modules/dataDict';
-
-  const props = defineProps({
-    type: {
-      type: String,
-      default: 'edit',
-    },
-  });
-
-  const NON_PROD_FIELDS = [
-    { key: 'repairTime', label: '设备故障' },
-    { key: 'selfStopTime', label: '设备保养' },
-    { key: 'accidentTime', label: '工程质量' },
-    { key: 'complexityTime', label: '技术受限' },
-    { key: 'rectificationTime', label: '生产组织' },
-    { key: 'waitingStopTime', label: '不可抗力' },
-    { key: 'partyaDesign', label: '甲方设计' },
-    { key: 'partyaPrepare', label: '甲方准备' },
-    { key: 'partyaResource', label: '甲方资源' },
-    { key: 'relocationTime', label: '生产配合' },
-    { key: 'winterBreakTime', label: '待命' },
-    { key: 'otherNptTime', label: '其他非生产时间' },
-  ];
-
-  const FORM_KEYS = [
-    'id',
-    'deptId',
-    'projectId',
-    'taskId',
-    'deptName',
-    'contractName',
-    'taskName',
-    'repairStatus',
-    'technique',
-    'wellCategory',
-    'designWellDepth',
-    'wellControlLevel',
-    'casingPipeSize',
-    'dailyFuel',
-    'currentOperation',
-    'nextPlan',
-    'ratedProductionTime',
-    'productionTime',
-    'totalStaffNum',
-    'onDutyStaffNum',
-    'leaveStaffNum',
-    'productionStatus',
-    'remark',
-    'createTime',
-    'opinion',
-    'repairTime',
-    'selfStopTime',
-    'accidentTime',
-    'complexityTime',
-    'rectificationTime',
-    'waitingStopTime',
-    'partyaDesign',
-    'partyaPrepare',
-    'partyaResource',
-    'relocationTime',
-    'winterBreakTime',
-    'otherNptTime',
-    'otherNptReason',
-    'status',
-    'auditStatus',
-  ];
-
-  const formType = ref('edit');
-
-  const initFormData = () => {
-    const base = {
-      ratedProductionTime: 0,
-      productionTime: 0,
-    };
-    // 初始化所有非生产时间字段为 0
-    NON_PROD_FIELDS.forEach(field => {
-      base[field.key] = 0;
-    });
-    return base;
+import { ref, computed, watch, nextTick, reactive } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { getRuiYingReportDetail } from "@/api/ruiying";
+import { useDataDictStore } from "@/store/modules/dataDict";
+import dayjs from "dayjs";
+
+const props = defineProps({
+  type: {
+    type: String,
+    default: "edit",
+  },
+});
+
+const NON_PROD_FIELDS = [
+  { key: "repairTime", label: "设备故障" },
+  { key: "selfStopTime", label: "设备保养" },
+  { key: "accidentTime", label: "工程质量" },
+  { key: "complexityTime", label: "技术受限" },
+  { key: "rectificationTime", label: "生产组织" },
+  { key: "waitingStopTime", label: "不可抗力" },
+  { key: "partyaDesign", label: "甲方设计" },
+  { key: "partyaPrepare", label: "甲方准备" },
+  { key: "partyaResource", label: "甲方资源" },
+  { key: "relocationTime", label: "生产配合" },
+  { key: "winterBreakTime", label: "待命" },
+  { key: "otherNptTime", label: "其他非生产时间" },
+];
+
+const FORM_KEYS = [
+  "id",
+  "deptId",
+  "projectId",
+  "taskId",
+  "deptName",
+  "contractName",
+  "taskName",
+  "repairStatus",
+  "technique",
+  "wellCategory",
+  "designWellDepth",
+  "wellControlLevel",
+  "casingPipeSize",
+  "dailyFuel",
+  "currentOperation",
+  "nextPlan",
+  "ratedProductionTime",
+  "productionTime",
+  "totalStaffNum",
+  "onDutyStaffNum",
+  "leaveStaffNum",
+  "reportDetails",
+  "constructionBrief",
+  "remark",
+  "createTime",
+  "opinion",
+  "repairTime",
+  "selfStopTime",
+  "accidentTime",
+  "complexityTime",
+  "rectificationTime",
+  "waitingStopTime",
+  "partyaDesign",
+  "partyaPrepare",
+  "partyaResource",
+  "relocationTime",
+  "winterBreakTime",
+  "otherNptTime",
+  "otherNptReason",
+  "status",
+  "auditStatus",
+];
+
+const formType = ref("edit");
+
+const initFormData = () => {
+  const base = {
+    ratedProductionTime: 0,
+    productionTime: 0,
+    constructionBrief: "",
+    reportDetails: [],
   };
+  // 初始化所有非生产时间字段为 0
+  NON_PROD_FIELDS.forEach(field => {
+    base[field.key] = 0;
+  });
+  return base;
+};
 
-  const form = ref(initFormData());
+const form = ref(initFormData());
 
-  async function loadDetail(id) {
-    try {
-      const { data } = await getRuiYingReportDetail({ id });
+const formatT = arr =>
+  `${arr[0].toString().padStart(2, "0")}:${arr[1].toString().padStart(2, "0")}`;
 
-      form.value = initFormData();
+async function loadDetail(id) {
+  try {
+    const { data } = await getRuiYingReportDetail({ id });
 
-      FORM_KEYS.forEach(key => {
-        if (Object.prototype.hasOwnProperty.call(data, key) && data[key] !== null && data[key] !== undefined) {
-          form.value[key] = data[key];
-        }
-      });
-      form.value.id = id;
+    form.value = initFormData();
 
-      if (props.type.includes('approval') && data.auditStatus !== 10) {
-        formType.value = 'readonly';
+    FORM_KEYS.forEach(key => {
+      if (
+        Object.prototype.hasOwnProperty.call(data, key) &&
+        data[key] !== null &&
+        data[key] !== undefined
+      ) {
+        form.value[key] = data[key];
       }
+    });
 
-      if (props.type.includes('edit') && data.status !== 0) {
-        formType.value = 'readonly';
-      }
+    form.value.reportDetails = form.value.reportDetails.map(item => ({
+      duration: item.duration || 0,
+      constructionDetail: item.constructionDetail || "",
+      currentOperation: item.currentOperation || "",
+      startTime: formatT(item.startTime),
+      endTime: formatT(item.endTime),
+    }));
 
-      if (props.type.includes('detail')) {
-        formType.value = 'readonly';
-      }
-    } finally {
+    if (!form.value.reportDetails.length) {
+      addReportDetailRow();
+    }
+
+    form.value.id = id;
+
+    if (props.type.includes("approval") && data.auditStatus !== 10) {
+      formType.value = "readonly";
+    }
+
+    if (props.type.includes("edit") && data.status !== 0) {
+      formType.value = "readonly";
+    }
+
+    if (props.type.includes("detail")) {
+      formType.value = "readonly";
     }
+  } finally {
   }
+}
 
-  const dictStore = useDataDictStore();
+const addReportDetailRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = [];
+  }
+  form.value.reportDetails.push({
+    startTime: "08:00",
+    endTime: "08:00",
+    duration: 0,
+    constructionDetail: "",
+    currentOperation: "",
+  });
+};
+
+const removeReportDetailRow = index => {
+  if (index === 0) {
+    uni.showToast({ title: "至少填写一条生产动态", icon: "none" });
+    return;
+  }
 
-  const nptReasonOptions = ref([]);
-  const rigStatusOptions = ref([]);
-  const techniqueOptions = ref([]);
+  form.value.reportDetails?.splice(index, 1);
+};
 
-  const loadOptions = () => {
-    nptReasonOptions.value = dictStore.getStrDictOptions('ryNptReason').map(v => ({
+const dictStore = useDataDictStore();
+
+const nptReasonOptions = ref([]);
+const rigStatusOptions = ref([]);
+const techniqueOptions = ref([]);
+
+const loadOptions = () => {
+  nptReasonOptions.value = dictStore
+    .getStrDictOptions("ryNptReason")
+    .map(v => ({
       text: v.label,
       value: v.value,
     }));
-    rigStatusOptions.value = dictStore.getStrDictOptions('repairStatus').map(v => ({
+  rigStatusOptions.value = dictStore
+    .getStrDictOptions("repairStatus")
+    .map(v => ({
       text: v.label,
       value: v.value,
     }));
-    techniqueOptions.value = dictStore.getStrDictOptions('rq_iot_project_technology_ry').map(v => ({
+  techniqueOptions.value = dictStore
+    .getStrDictOptions("rq_iot_project_technology_ry")
+    .map(v => ({
       text: v.label,
       value: v.value,
     }));
-  };
+};
 
-  onLoad(options => {
-    if (dictStore.dataDict.length <= 0) {
-      dictStore.loadDataDictList().then(() => {
-        loadOptions();
-      });
-    } else loadOptions();
-    loadDetail(options.id);
+onLoad(options => {
+  if (dictStore.dataDict.length <= 0) {
+    dictStore.loadDataDictList().then(() => {
+      loadOptions();
+    });
+  } else loadOptions();
+  loadDetail(options.id);
+});
+
+const defaultProps = computed(() => ({
+  inputBorder: false,
+  clearable: false,
+  placeholder: "请输入",
+  style: {
+    "text-align": "right",
+  },
+  styles: {
+    disableColor: "#fff",
+  },
+}));
+
+const disabled = computed(() => field => {
+  if (field === "edit")
+    return (
+      formType.value === "readonly" ||
+      props.type.includes("approval") ||
+      props.type.includes("detail")
+    );
+  else return formType.value === "readonly";
+});
+
+const transitTime = computed(() => {
+  const cap = form.value.productionTime ?? 0;
+  const gas = form.value.ratedProductionTime ?? 0;
+
+  if (!gas) return { original: 0, value: "0%" };
+
+  const original = cap / gas;
+  return { original, value: (original * 100).toFixed(2) + "%" };
+});
+
+const formRef = ref(null);
+
+const onDutyStaffNum = computed(() => {
+  return (form.value.totalStaffNum ?? 0) - (form.value.leaveStaffNum ?? 0);
+});
+
+// 辅助函数:计算总时间
+const sumNonProdTimes = () => {
+  let sum = 0;
+  NON_PROD_FIELDS.forEach(field => {
+    sum += Number(form.value[field.key] || 0);
   });
+  return sum;
+};
 
-  const defaultProps = computed(() => ({
-    inputBorder: false,
-    clearable: false,
-    placeholder: '请输入',
-    style: {
-      'text-align': 'right',
-    },
-    styles: {
-      disableColor: '#fff',
-    },
-  }));
-
-  const disabled = computed(() => field => {
-    if (field === 'edit')
-      return formType.value === 'readonly' || props.type.includes('approval') || props.type.includes('detail');
-    else return formType.value === 'readonly';
-  });
+// 校验函数:总时间必须为 24
+const validateTotalTime = (rule, value, data, callback) => {
+  const rateTime = Number(form.value.ratedProductionTime || 0);
+  const time = Number(form.value.productionTime || 0);
 
-  const transitTime = computed(() => {
-    const cap = form.value.productionTime ?? 0;
-    const gas = form.value.ratedProductionTime ?? 0;
+  const nonProdSum = sumNonProdTimes();
 
-    if (!gas) return { original: 0, value: '0%' };
+  let total = 0;
+  let msg = "";
 
-    const original = cap / gas;
-    return { original, value: (original * 100).toFixed(2) + '%' };
-  });
+  total = parseFloat((time + nonProdSum).toFixed(2));
+  msg = `生产(${time})+非生产(${nonProdSum})=${total}H,必须等于额定${rateTime}H`;
 
-  const formRef = ref(null);
+  if (Math.abs(total - rateTime) > 0.01) {
+    callback(msg);
+  }
+  return true;
+};
+
+const rules = reactive({
+  repairStatus: {
+    rules: [{ required: true, errorMessage: "请输入施工状态" }],
+  },
+
+  ratedProductionTime: {
+    rules: [
+      { required: true, errorMessage: "请输入额定生产时间" },
+      { validateFunction: validateTotalTime },
+    ],
+  },
+  productionTime: {
+    rules: [
+      { required: true, errorMessage: "请输入生产时间" },
+      { validateFunction: validateTotalTime },
+    ],
+  },
+  constructionBrief: {
+    rules: [{ required: true, errorMessage: `请输入当日施工简报` }],
+  },
+});
+
+const allTimeKeys = [
+  "ratedProductionTime",
+  "productionTime",
+  ...NON_PROD_FIELDS.map(f => f.key),
+];
+
+watch(
+  () => allTimeKeys.map(key => form.value[key]),
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField(["ratedProductionTime", "productionTime"]);
+    });
+  }
+);
 
-  const onDutyStaffNum = computed(() => {
-    return (form.value.totalStaffNum ?? 0) - (form.value.leaveStaffNum ?? 0);
-  });
+defineExpose({ formRef, form, loadDetail });
 
-  // 辅助函数:计算总时间
-  const sumNonProdTimes = () => {
-    let sum = 0;
-    NON_PROD_FIELDS.forEach(field => {
-      sum += Number(form.value[field.key] || 0);
-    });
-    return sum;
-  };
+const orange = computed(() => {
+  const rateTime = Number(form.value.ratedProductionTime || 0);
+  const time = Number(form.value.productionTime || 0);
 
-  // 校验函数:总时间必须为 24
-  const validateTotalTime = (rule, value, data, callback) => {
-    const rateTime = Number(form.value.ratedProductionTime || 0);
-    const time = Number(form.value.productionTime || 0);
+  const nonProdSum = sumNonProdTimes();
 
-    const nonProdSum = sumNonProdTimes();
+  let total = 0;
+  let msg = "";
 
-    let total = 0;
-    let msg = '';
+  total = parseFloat((time + nonProdSum).toFixed(2));
+  msg = `生产(${time})+非生产(${nonProdSum})=${total}H,必须等于额定${rateTime}H`;
 
-    total = parseFloat((time + nonProdSum).toFixed(2));
-    msg = `生产(${time})+非生产(${nonProdSum})=${total}H,必须等于额定${rateTime}H`;
+  if (Math.abs(total - rateTime) > 0.01) return true;
+  return false;
+});
 
-    if (Math.abs(total - rateTime) > 0.01) {
-      callback(msg);
-    }
-    return true;
-  };
+const reportDetailsTimeRangeRef = ref(null);
 
-  const rules = reactive({
-    repairStatus: {
-      rules: [{ required: true, errorMessage: '请输入施工状态' }],
-    },
-    productionStatus: {
-      rules: [{ required: true, errorMessage: '请输入生产动态' }],
-    },
-    ratedProductionTime: {
-      rules: [{ required: true, errorMessage: '请输入额定生产时间' }, { validateFunction: validateTotalTime }],
-    },
-    productionTime: {
-      rules: [{ required: true, errorMessage: '请输入生产时间' }, { validateFunction: validateTotalTime }],
-    },
-  });
+const startTime = ref("00:00");
+const startDefaultTime = ref("08:00");
+const endTime = ref("24:00");
+const endDefaultTime = ref("08:00");
 
-  const allTimeKeys = ['ratedProductionTime', 'productionTime', ...NON_PROD_FIELDS.map(f => f.key)];
+const reportDetailIndex = ref(0);
 
-  watch(
-    () => allTimeKeys.map(key => form.value[key]),
-    () => {
-      nextTick(() => {
-        formRef.value?.validateField(['ratedProductionTime', 'productionTime']);
-      });
-    }
-  );
+const handleClickTimeRangeItem = index => {
+  reportDetailIndex.value = index;
+  reportDetailsTimeRangeRef.value.open();
+};
 
-  defineExpose({ formRef, form, loadDetail });
+const calculateDuration = row => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0;
+    return;
+  }
 
-  const orange = computed(() => {
-    const rateTime = Number(form.value.ratedProductionTime || 0);
-    const time = Number(form.value.productionTime || 0);
+  const todayStr = dayjs().format("YYYY-MM-DD");
+  const start = dayjs(`${todayStr} ${row.startTime}`);
+  const end = dayjs(`${todayStr} ${row.endTime}`);
 
-    const nonProdSum = sumNonProdTimes();
+  let diffMinutes = end.diff(start, "minute");
 
-    let total = 0;
-    let msg = '';
+  if (diffMinutes < 0) {
+    diffMinutes += 1440;
+  }
 
-    total = parseFloat((time + nonProdSum).toFixed(2));
-    msg = `生产(${time})+非生产(${nonProdSum})=${total}H,必须等于额定${rateTime}H`;
+  row.duration = Number((diffMinutes / 60).toFixed(2));
+};
 
-    if (Math.abs(total - rateTime) > 0.01) return true;
-    return false;
-  });
+const reportDetailsTimeRange = data => {
+  form.value.reportDetails[reportDetailIndex.value].startTime = data[0];
+  form.value.reportDetails[reportDetailIndex.value].endTime = data[1];
+
+  calculateDuration(form.value.reportDetails[reportDetailIndex.value]);
+};
 </script>
 
 <template>
@@ -316,16 +423,28 @@
           v-model="form.technique" />
       </uni-forms-item>
       <uni-forms-item label="井别" name="wellCategory">
-        <uni-easyinput v-bind="defaultProps" v-model="form.wellCategory" :disabled="disabled('edit')" />
+        <uni-easyinput
+          v-bind="defaultProps"
+          v-model="form.wellCategory"
+          :disabled="disabled('edit')" />
       </uni-forms-item>
       <uni-forms-item label="设计井深(m)" name="designWellDepth">
-        <uni-easyinput v-bind="defaultProps" v-model="form.designWellDepth" :disabled="disabled('edit')" />
+        <uni-easyinput
+          v-bind="defaultProps"
+          v-model="form.designWellDepth"
+          :disabled="disabled('edit')" />
       </uni-forms-item>
       <uni-forms-item label="井控级别" name="wellControlLevel">
-        <uni-easyinput v-bind="defaultProps" v-model="form.wellControlLevel" :disabled="disabled('edit')" />
+        <uni-easyinput
+          v-bind="defaultProps"
+          v-model="form.wellControlLevel"
+          :disabled="disabled('edit')" />
       </uni-forms-item>
       <uni-forms-item label="套生段产管尺寸(mm)" name="casingPipeSize">
-        <uni-easyinput v-bind="defaultProps" v-model="form.casingPipeSize" :disabled="disabled('edit')" />
+        <uni-easyinput
+          v-bind="defaultProps"
+          v-model="form.casingPipeSize"
+          :disabled="disabled('edit')" />
       </uni-forms-item>
       <uni-forms-item label="当日油耗(升)" name="dailyFuel">
         <uni-easyinput
@@ -354,18 +473,34 @@
           :maxlength="1000" />
       </uni-forms-item>
       <uni-forms-item label="运行时效" name="transitTime">
-        <span class="readOnly" :class="{ 'red-text': transitTime.original > 1.0 }">{{ transitTime.value }}</span>
+        <span
+          class="readOnly"
+          :class="{ 'red-text': transitTime.original > 1.0 }"
+          >{{ transitTime.value }}</span
+        >
       </uni-forms-item>
       <uni-forms-item label="全员数量" name="totalStaffNum">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.totalStaffNum" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.totalStaffNum" />
       </uni-forms-item>
       <uni-forms-item label="在岗人数" name="onDutyStaffNum">
-        <uni-easyinput type="number" v-bind="defaultProps" disabled v-model="onDutyStaffNum" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          disabled
+          v-model="onDutyStaffNum" />
       </uni-forms-item>
       <uni-forms-item label="休假人员数量" name="leaveStaffNum">
-        <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form.leaveStaffNum" />
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.leaveStaffNum" />
       </uni-forms-item>
-      <uni-forms-item label="生产动态" name="productionStatus" required>
+      <!-- <uni-forms-item label="生产动态" name="productionStatus" required>
         <uni-easyinput
           type="textarea"
           autoHeight
@@ -373,6 +508,19 @@
           v-model="form.productionStatus"
           :disabled="disabled('edit')"
           :maxlength="1000" />
+      </uni-forms-item> -->
+      <uni-forms-item
+        v-if="props.type === 'approval' || props.type === 'approval-detail'"
+        label="当日施工简报"
+        required
+        name="constructionBrief">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('approval')"
+          v-model="form.constructionBrief"
+          :maxlength="2000" />
       </uni-forms-item>
       <uni-forms-item label="备注" name="remark">
         <uni-easyinput
@@ -384,7 +532,10 @@
           :maxlength="1000" />
       </uni-forms-item>
       <uv-divider text="生产时间" textPosition="left"></uv-divider>
-      <uni-forms-item label="额定生产时间(H)" name="ratedProductionTime" required>
+      <uni-forms-item
+        label="额定生产时间(H)"
+        name="ratedProductionTime"
+        required>
         <uni-easyinput
           type="number"
           v-bind="defaultProps"
@@ -401,8 +552,91 @@
           v-model="form.productionTime" />
       </uni-forms-item>
 
+      <uv-divider text="生产动态" textPosition="left"></uv-divider>
+      <uni-forms-item v-if="!disabled('edit')">
+        <button
+          type="primary"
+          size="mini"
+          class="detail-btn"
+          @click="addReportDetailRow()">
+          添加一行
+        </button>
+      </uni-forms-item>
+      <template v-for="(item, index) in form.reportDetails" :key="index">
+        <uni-forms-item label="日期" style="margin-top: 32px">
+          <span class="readOnly">{{
+            dayjs(form.createTime).format("YYYY-MM-DD")
+          }}</span>
+        </uni-forms-item>
+        <uni-forms-item :label="`${$t('ruiDu.timeNode')}:`" required>
+          <view
+            class="item-content"
+            @click="disabled('edit') ? '' : handleClickTimeRangeItem(index)">
+            <view class="time-range-item" v-if="item.startTime && item.endTime">
+              {{ item.startTime }} 至 {{ item.endTime }}
+            </view>
+            <view class="time-range-item" v-else>
+              {{ selectPlaceholder }}
+            </view>
+          </view>
+        </uni-forms-item>
+        <uni-forms-item label="时长(H)">
+          <span class="readOnly">{{ item.duration }}</span>
+        </uni-forms-item>
+        <uni-forms-item
+          label="工况"
+          required
+          :name="['reportDetails', index, 'currentOperation']"
+          :rules="[{ required: true, errorMessage: '请输入工况' }]">
+          <uni-easyinput
+            type="textarea"
+            autoHeight
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model="item.currentOperation"
+            :maxlength="2000" />
+        </uni-forms-item>
+        <!-- <uni-forms-item
+          label="结束井深(m)"
+          required
+          :name="['reportDetails', index, 'currentDepth']"
+          :rules="[{ required: true, errorMessage: '请输入结束深度' }, { validateFunction: validateLastCurrentDepth }]">
+          <uni-easyinput
+            type="number"
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model.number="item.currentDepth" />
+        </uni-forms-item> -->
+        <uni-forms-item
+          label="详情"
+          required
+          :name="['reportDetails', index, 'constructionDetail']"
+          :rules="[{ required: true, errorMessage: '请输入详情' }]">
+          <uni-easyinput
+            type="textarea"
+            autoHeight
+            v-bind="defaultProps"
+            :disabled="disabled('edit')"
+            v-model="item.constructionDetail"
+            :maxlength="2000" />
+        </uni-forms-item>
+        <uni-forms-item v-if="!disabled('edit')" label="操作">
+          <button
+            type="warn"
+            size="mini"
+            class="detail-btn"
+            @click="removeReportDetailRow(index)">
+            删除
+          </button>
+        </uni-forms-item>
+      </template>
+
       <uv-divider text="非生产时间" textPosition="left"></uv-divider>
-      <uni-forms-item v-for="field in NON_PROD_FIELDS" :key="field.key" :label="field.label + '(H)'" :name="field.key">
+      <uni-forms-item
+        v-for="field in NON_PROD_FIELDS"
+        :key="field.key"
+        :label="field.label + '(H)'"
+        :name="field.key">
         <uni-easyinput
           type="number"
           :class="{ 'orange-text': orange }"
@@ -421,7 +655,10 @@
           :maxlength="1000" />
       </uni-forms-item>
 
-      <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
+      <uni-forms-item
+        v-if="type.includes('approval')"
+        label="审批意见"
+        name="opinion">
         <uni-easyinput
           type="textarea"
           autoHeight
@@ -432,164 +669,172 @@
       </uni-forms-item>
     </uni-forms>
   </view>
+
+  <tpf-time-range
+    ref="reportDetailsTimeRangeRef"
+    :startTime="startTime"
+    :startDefaultTime="startDefaultTime"
+    :endTime="endTime"
+    :endDefaultTime="endDefaultTime"
+    @timeRange="reportDetailsTimeRange"></tpf-time-range>
 </template>
 
 <style lang="scss" scoped>
-  .content {
-    background-color: white;
-    padding: 16px 16px;
-    border-radius: 8px;
-    box-sizing: border-box;
-  }
-
-  .uni-forms {
-    margin-top: 10px;
+.content {
+  background-color: white;
+  padding: 16px 16px;
+  border-radius: 8px;
+  box-sizing: border-box;
+}
+
+.uni-forms {
+  margin-top: 10px;
+  height: 100%;
+
+  .uni-form {
     height: 100%;
+  }
 
-    .uni-form {
-      height: 100%;
-    }
-
-    .uni-forms-item {
-      display: flex;
-      align-items: center;
-      flex: 1;
-      margin-bottom: 6px;
-      border-bottom: 1px dashed #cacccf;
-    }
-
-    :deep(.uni-forms-item__content) {
-      text-align: right;
-      .readOnly {
-        padding-right: 10px;
-      }
-    }
-
-    :deep(.uni-forms-item__label) {
-      height: 44px;
-      font-weight: 500;
-      font-size: 14px;
-      color: #333333 !important;
-      width: max-content !important;
-    }
-
-    :deep(.uni-select) {
-      border: none;
-      text-align: right;
-      padding-right: 0;
-      .uniui-bottom:before {
-        content: '\e6b5' !important;
-        font-size: 16px !important;
-      }
-    }
-
-    :deep(.uni-easyinput__content-textarea) {
-      min-height: inherit;
-      margin: 10px;
-    }
-
-    :deep(.is-disabled) {
-      color: #333333 !important;
-    }
-
-    :deep(.red-text > .is-disabled) {
-      color: rgb(220 38 38 / 0.8) !important;
-    }
-
-    :deep(.orange-text > .is-disabled) {
-      color: rgb(234 88 12 / 0.8) !important;
-    }
+  .uni-forms-item {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    margin-bottom: 6px;
+    border-bottom: 1px dashed #cacccf;
+  }
 
-    :deep(.uni-select--disabled) {
-      background-color: #fff;
+  :deep(.uni-forms-item__content) {
+    text-align: right;
+    .readOnly {
+      padding-right: 10px;
     }
   }
 
-  .red-text {
-    color: rgb(220 38 38 / 0.8) !important;
+  :deep(.uni-forms-item__label) {
+    height: 44px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #333333 !important;
+    width: max-content !important;
   }
 
-  .blue-text {
-    color: rgb(59 130 246 / 0.8) !important;
+  :deep(.uni-select) {
+    border: none;
+    text-align: right;
+    padding-right: 0;
+    .uniui-bottom:before {
+      content: "\e6b5" !important;
+      font-size: 16px !important;
+    }
   }
 
-  .orange-text {
-    color: rgb(234 88 12 / 0.8) !important;
+  :deep(.uni-easyinput__content-textarea) {
+    min-height: inherit;
+    margin: 10px;
   }
 
-  .red {
-    border: 1px solid rgb(254 226 226);
-    color: rgb(220 38 38 / 0.8);
-    background-color: rgb(254 226 226);
+  :deep(.is-disabled) {
+    color: #333333 !important;
   }
 
-  .orange {
-    border: 1px solid rgb(254 215 170);
-    color: rgb(234 88 12 / 0.8);
-    background-color: rgb(254 215 170);
+  :deep(.red-text > .is-disabled) {
+    color: rgb(220 38 38 / 0.8) !important;
   }
 
-  .blue {
-    border: 1px solid rgb(219 234 254);
-    color: rgb(59 130 246 / 0.8);
-    background-color: rgb(240 249 255);
+  :deep(.orange-text > .is-disabled) {
+    color: rgb(234 88 12 / 0.8) !important;
   }
 
-  .tip {
-    border-radius: 8px;
-    border: 1px solid #e5e5e5;
-    background-color: rgba(239, 246, 255, 0.8);
-    box-sizing: border-box;
-    padding: 10px;
-    display: flex;
-    flex-direction: column;
-    gap: 6px;
-
-    .item {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      font-size: 12px;
-
-      .left {
-        color: rgb(75 85 99);
-
-        span {
-          color: rgb(31 41 55);
-          font-weight: 600;
-        }
-      }
-
-      .right {
-        display: inline-flex;
-        align-items: center;
-        border-radius: 4px;
-        padding: 2px 4px;
-        font-weight: 500;
-      }
-    }
+  :deep(.uni-select--disabled) {
+    background-color: #fff;
   }
-
-  .opinion {
-    border-radius: 8px;
-    border: 1px solid rgb(254 240 138);
-    background-color: rgb(254 252 232);
-    box-sizing: border-box;
-    padding: 10px;
+}
+
+.red-text {
+  color: rgb(220 38 38 / 0.8) !important;
+}
+
+.blue-text {
+  color: rgb(59 130 246 / 0.8) !important;
+}
+
+.orange-text {
+  color: rgb(234 88 12 / 0.8) !important;
+}
+
+.red {
+  border: 1px solid rgb(254 226 226);
+  color: rgb(220 38 38 / 0.8);
+  background-color: rgb(254 226 226);
+}
+
+.orange {
+  border: 1px solid rgb(254 215 170);
+  color: rgb(234 88 12 / 0.8);
+  background-color: rgb(254 215 170);
+}
+
+.blue {
+  border: 1px solid rgb(219 234 254);
+  color: rgb(59 130 246 / 0.8);
+  background-color: rgb(240 249 255);
+}
+
+.tip {
+  border-radius: 8px;
+  border: 1px solid #e5e5e5;
+  background-color: rgba(239, 246, 255, 0.8);
+  box-sizing: border-box;
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+
+  .item {
     display: flex;
     align-items: center;
     justify-content: space-between;
     font-size: 12px;
-    margin-top: 10px;
 
     .left {
-      font-weight: 600;
-      color: rgb(133 77 14);
+      color: rgb(75 85 99);
+
+      span {
+        color: rgb(31 41 55);
+        font-weight: 600;
+      }
     }
 
     .right {
+      display: inline-flex;
+      align-items: center;
+      border-radius: 4px;
+      padding: 2px 4px;
       font-weight: 500;
-      color: rgb(75 85 99);
     }
   }
+}
+
+.opinion {
+  border-radius: 8px;
+  border: 1px solid rgb(254 240 138);
+  background-color: rgb(254 252 232);
+  box-sizing: border-box;
+  padding: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 12px;
+  margin-top: 10px;
+
+  .left {
+    font-weight: 600;
+    color: rgb(133 77 14);
+  }
+
+  .right {
+    font-weight: 500;
+    color: rgb(75 85 99);
+  }
+}
 </style>

+ 42 - 17
utils/request.js

@@ -1,12 +1,9 @@
 // request.js
 import Request from "luch-request";
 import qs from "qs";
-import { ref } from "vue";
 import config from "@/utils/config";
-import errorCode from "@/utils/errorCode";
 import {
   getTenantId,
-  getUserId,
   getAccessToken,
   getRefreshToken,
   setToken,
@@ -76,7 +73,7 @@ function closeLoading() {
 const http = new Request(defaultOptions);
 // 请求拦截器
 http.interceptors.request.use(
-  (config) => {
+  config => {
     // console.log('config ', config, config.withToken)
     // 在发送请求之前做些什么
     // 自定义处理【loading 加载中】:如果需要显示 loading,则显示 loading
@@ -95,7 +92,7 @@ http.interceptors.request.use(
 
     config.header = {
       ...config.header,
-      Authorization: getAccessToken() ? "Bearer " + getAccessToken() : "",
+      "Authorization": getAccessToken() ? "Bearer " + getAccessToken() : "",
       "tenant-id": getTenantId() ? getTenantId() : "1",
     };
     // 只处理GET请求的数组参数
@@ -128,7 +125,7 @@ http.interceptors.request.use(
     // console.log('config--', config, )
     return config;
   },
-  (error) => {
+  error => {
     // 对请求错误做些什么
     return Promise.reject(error);
   }
@@ -137,7 +134,7 @@ http.interceptors.request.use(
  * @description 响应拦截器
  */
 http.interceptors.response.use(
-  (response) => {
+  response => {
     // console.log('response-', response)
     // 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌
     if (
@@ -189,7 +186,7 @@ http.interceptors.response.use(
     localeFormatObj(response.data);
     return Promise.resolve(response.data);
   },
-  (error) => {
+  error => {
     const userStore = $store("user");
     const isLogin = userStore.isLogin;
     let errorMessage = "网络请求出错";
@@ -264,7 +261,7 @@ http.interceptors.response.use(
 // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
 let requestList = []; // 请求队列
 let isRefreshToken = false; // 是否正在刷新中
-const refreshTokenFun = async (config) => {
+const refreshTokenFun = async config => {
   // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error)
   if (config.url.indexOf("/auth/refresh-token") >= 0) {
     return Promise.reject("error");
@@ -289,7 +286,7 @@ const refreshTokenFun = async (config) => {
       }
       // 2.1 刷新成功,则回放队列的请求 + 当前请求
       config.header.Authorization = "Bearer " + getAccessToken();
-      requestList.forEach((cb) => {
+      requestList.forEach(cb => {
         cb();
       });
       requestList = [];
@@ -297,7 +294,7 @@ const refreshTokenFun = async (config) => {
     } catch (e) {
       // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
       // 2.2 刷新失败,只回放队列的请求
-      requestList.forEach((cb) => {
+      requestList.forEach(cb => {
         cb();
       });
       // 提示是否要登出。即不回放当前请求!不然会形成递归
@@ -308,7 +305,7 @@ const refreshTokenFun = async (config) => {
     }
   } else {
     // 添加到队列,等待刷新获取到新的令牌
-    return new Promise((resolve) => {
+    return new Promise(resolve => {
       requestList.push(() => {
         config.header.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
         resolve(request(config));
@@ -320,28 +317,56 @@ const refreshTokenFun = async (config) => {
 /**
  * 处理 401 未登录的错误
  */
+// 1. 在函数外部定义一个变量,用来标记弹窗是否正在显示
+let isReloginModalShowing = false;
+
 const handleAuthorized = () => {
+  // 2. 如果弹窗已经在显示中,直接返回 Reject,不再执行后续逻辑
+  if (isReloginModalShowing) {
+    return Promise.reject({
+      code: 401,
+      msg: "正在进行登录验证...",
+    });
+  }
+
+  // 3. 标记弹窗为显示状态
+  isReloginModalShowing = true;
+
   const userStore = $store("user");
+
+  // 建议:先获取文案,防止 LogOut 后 isLogin 状态变了导致文案不准确
+  const errorMsg = userStore.isLogin ? "您的登陆已过期" : "请先登录";
+
   userStore.LogOut(true);
+  console.log("弹窗");
+
   uni.showModal({
     title: "提示",
-    content: userStore.isLogin ? "您的登陆已过期" : "请先登录",
+    content: errorMsg,
     showCancel: false,
     confirmText: "确定",
-    success: (res) => {
+    success: res => {
+      // 4. 用户点击确定后,重置标记(允许下次再弹)
+      isReloginModalShowing = false;
+
       if (res.confirm) {
-				uni.reLaunch({ url: '/pages/user/login' })
+        uni.reLaunch({ url: "/pages/user/login" });
       }
     },
+    fail: () => {
+      // 5. 防御性代码:如果弹窗调用失败,也需要重置标记,防止死锁
+      isReloginModalShowing = false;
+    },
   });
+
   // 登录超时
   return Promise.reject({
     code: 401,
-    msg: userStore.isLogin ? "您的登陆已过期" : "请先登录",
+    msg: errorMsg,
   });
 };
 
-export const request = (config) => {
+export const request = config => {
   return http.middleware(config);
 };