Przeglądaj źródła

✨ feat(日报): 瑞恒瑞鹰日报填报审批

Zimo 1 tydzień temu
rodzic
commit
84c07f0c48

+ 25 - 0
api/ruihen.js

@@ -0,0 +1,25 @@
+import { request } from '@/utils/request';
+
+export function getRuiHenReportDetail(params) {
+  return request({
+    url: '/pms/iot-rh-daily-report/get',
+    method: 'get',
+    params,
+  });
+}
+
+export function createIotRhDailyReport(data) {
+  return request({ url: `/pms/iot-rh-daily-report/create`, data, method: 'post' });
+}
+
+export function approvalIotRhDailyReport(data) {
+  return request({ url: `/pms/iot-rh-daily-report/approval`, data, method: 'put' });
+}
+
+export function getRuiHenReportPage(params) {
+  return request({
+    url: '/pms/iot-rh-daily-report/page',
+    method: 'get',
+    params,
+  });
+}

+ 25 - 0
api/ruiying.js

@@ -0,0 +1,25 @@
+import { request } from '@/utils/request';
+
+export function getRuiYingReportDetail(params) {
+  return request({
+    url: '/pms/iot-ry-daily-report/get',
+    method: 'get',
+    params,
+  });
+}
+
+export function createIotRyDailyReport(data) {
+  return request({ url: `/pms/iot-ry-daily-report/create`, data, method: 'post' });
+}
+
+export function approvalIotRyDailyReport(data) {
+  return request({ url: `/pms/iot-ry-daily-report/approval`, data, method: 'put' });
+}
+
+export function getRuiYingReportPage(params) {
+  return request({
+    url: '/pms/iot-ry-daily-report/page',
+    method: 'get',
+    params,
+  });
+}

+ 11 - 3
locale/zh-Hans.json

@@ -40,6 +40,7 @@
   "operation.view": "查看",
   "operation.edit": "编辑",
   "operation.fill": "填写",
+  "operation.approve": "审批",
   "operation.submit": "提交",
   "operation.save": "保存",
   "operation.add": "新增",
@@ -125,6 +126,15 @@
   "home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
   "home.dailyReportRuiDu": "瑞都日报",
   "home.dailyReportRuiDuTip": "填写日报",
+  "home.dailyReportRuiHen": "瑞恒日报",
+  "home.dailyReportRuiHenTip": "填写日报",
+  "home.dailyReportRuiHenApproval": "审批日报",
+  "home.dailyReportRuiYing": "瑞鹰钻井日报",
+  "home.dailyReportRuiYingTip": "填写日报",
+  "home.dailyReportRuiYingApproval": "审批日报",
+  "home.dailyReportRuiYingX": "瑞鹰修井日报",
+  "home.dailyReportRuiYingXTip": "填写日报",
+  "home.dailyReportRuiYingXApproval": "审批日报",
   "home.inventoryQuery": "库存查询",
   "home.clickToQueryInventoryData": "点击查询库存数据",
   "home.equipmentLedger": "设备台账",
@@ -398,6 +408,7 @@
   "ruiDu.indexTitle": "日报",
   "ruiDu.detailTitle": "日报详情",
   "ruiDu.editTitle": "日报填报",
+  "ruiDu.approvalTitle": "日报填报",
   "ruiDu.shiftLeader": "带班干部",
   "ruiDu.reportName": "日报名称",
   "ruiDu.project": "项目",
@@ -434,9 +445,6 @@
   "ruiDu.cumulativeConstructionLayer": "累计施工-层",
   "ruiDu.dailyPumpTruckTrips": "当日泵车台次",
   "ruiDu.dailyInstrumentMixingSandTrips": "当日仪表/混砂",
-  
-  
-
 
   // --------------------------------------- 库存查询 ----------------------------------------
   "inventory.title": "库存查询",

+ 73 - 1
pages.json

@@ -170,6 +170,78 @@
         "navigationStyle": "custom"
       }
     },
+    {
+      "path": "pages/ruihen/index",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.indexTitle%"
+      }
+    },
+    {
+      "path": "pages/ruihen/detail",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.detailTitle%"
+      }
+    },
+    {
+      "path": "pages/ruihen/edit",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.editTitle%"
+      }
+    },
+    {
+      "path": "pages/ruihen/approval",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.approvalTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiying/index",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.indexTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiying/detail",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.detailTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiying/edit",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.editTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiying/approval",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.approvalTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiyingx/index",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.indexTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiyingx/detail",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.detailTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiyingx/edit",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.editTitle%"
+      }
+    },
+    {
+      "path": "pages/ruiyingx/approval",
+      "style": {
+        "navigationBarTitleText": "%ruiDu.approvalTitle%"
+      }
+    },
     {
       // 瑞都日报-列表
       "path": "pages/ruiDu/index",
@@ -181,7 +253,7 @@
       // 瑞都日报-编辑
       "path": "pages/ruiDu/approval",
       "style": {
-        "navigationBarTitleText": "%ruiDu.editTitle%"
+        "navigationBarTitleText": "%ruiDu.approvalTitle%"
       }
     },
     {

+ 438 - 381
pages/home/index.vue

@@ -5,20 +5,14 @@
       <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"
               style="width: 20px; height: 20px; background-color: transparent"
-              @click="navigatorTo('/pages/message/index')"
-            />
+              @click="navigatorTo('/pages/message/index')" />
           </uni-badge>
         </uni-col>
       </uni-row>
@@ -41,42 +35,33 @@
             :autoplay="true"
             :vertical="true"
             :interval="2000"
-            :duration="500"
-          >
+            :duration="500">
             <swiper-item
               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"
-              >
+              @click="navigatorTo('/pages/overtime/index')">
+              <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;-->
@@ -93,68 +78,52 @@
         <uni-col
           :span="12"
           class="yunxingjilu flex-col justify-between"
-          @click="navigatorTo('/pages/recordFilling/list')"
-        >
+          @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>
@@ -163,127 +132,207 @@
         <view
           class="card-cell flex-row align-center justify-between"
           @click="navigatorTo('/pages/ruiDu/index')"
-          v-if="isShowRuiduDaily"
-        >
+          v-if="isShowRuiduDaily">
           <image src="/static/home/ribao.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.dailyReportRuiDu") }}
+                {{ $t('home.dailyReportRuiDu') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.dailyReportRuiDuTip") }}
+                {{ $t('home.dailyReportRuiDuTip') }}
               </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')"
-        >
-          <image src="/static/home/kucun.svg" mode="aspectFill"></image>
+          @click="navigatorTo('/pages/ruihen/index?type=edit')"
+          v-if="rhReportFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiHen') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.clickToQueryInventoryData") }}
+                {{ $t('home.dailyReportRuiHenTip') }}
               </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')"
-        >
-          <image src="/static/home/taizhang.svg" mode="aspectFill"></image>
+          @click="navigatorTo('/pages/ruihen/index?type=approval')"
+          v-if="rhReportApprovalFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiHen') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.viewEquipmentLedger") }}
+                {{ $t('home.dailyReportRuiHenApproval') }}
               </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>
+          @click="navigatorTo('/pages/ruiying/index?type=edit')"
+          v-if="ryReportFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiYing') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.adjustEquipmentStatus") }}
+                {{ $t('home.dailyReportRuiYingTip') }}
               </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')"
-        >
-          <image src="/static/home/deviceUser.svg" mode="aspectFill"></image>
+          @click="navigatorTo('/pages/ruiying/index?type=approval')"
+          v-if="ryReportApprovalFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiYing') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.deviceUserTip") }}
+                {{ $t('home.dailyReportRuiYingApproval') }}
               </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')"
-        >
-          <image src="/static/home/shujujiankong.svg" mode="aspectFill"></image>
+          @click="navigatorTo('/pages/ruiyingx/index?type=edit')"
+          v-if="ryXjReportFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiYingX') }}
               </view>
               <view class="subtitle">
-                {{ $t("home.viewRealTimeEquipmentData") }}
+                {{ $t('home.dailyReportRuiYingXTip') }}
               </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')"
-        >
+          @click="navigatorTo('/pages/ruiyingx/index?type=approval')"
+          v-if="ryXjReportApprovalFlag">
+          <image src="/static/home/ribao.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.dailyReportRuiYingX') }}
+              </view>
+              <view class="subtitle">
+                {{ $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')">
+          <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') }}
+              </view>
+              <view class="subtitle">
+                {{ $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')">
+          <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') }}
+              </view>
+              <view class="subtitle">
+                {{ $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="cell-con flex-row align-center justify-between">
+            <view class="cell-text flex-row align-center justify-start">
+              <view class="title">
+                {{ $t('home.equipmentStatusChange') }}
+              </view>
+              <view class="subtitle">
+                {{ $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')">
+          <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') }}
+              </view>
+              <view class="subtitle">
+                {{ $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')">
+          <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') }}
+              </view>
+              <view class="subtitle">
+                {{ $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')">
           <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" />
@@ -297,315 +346,323 @@
 </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,
+  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(),
+    ]);
   });
-};
-
-const messageCount = ref(0);
-onMounted(async () => {
-  await Promise.all([
-    useDataDictStore().loadDataDictList(),
-    useDeptStore().loadDeptList(),
-    useDeviceStore().loadDeviceTypeList(),
-  ]);
-});
-
-// 是否展示瑞都日报入口
-const isShowRuiduDaily = 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;
-  }
-};
-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;
+  // 是否展示瑞都日报入口
+  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;
     }
+
+    // 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;
-  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;
+  .home {
+    width: 100%;
+    height: 100%;
+    position: relative;
     box-sizing: border-box;
+    overflow: hidden;
   }
-}
-
-.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-full {
+    width: 100%;
+    height: 64px;
+    margin-bottom: 10px;
   }
 
-  .remind {
-    color: #333333;
+  .row-half {
+    width: 100%;
+    height: 68px;
+    margin-bottom: 5px;
+
+    .uni-col {
+      padding: 15px !important;
+      box-sizing: border-box;
+    }
+  }
+
+  .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-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-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;
+    }
   }
 
-  &.blue {
-    color: #3b63c9;
+  .dt-swiper {
+    width: calc(100%);
+    height: 100%;
   }
-}
-
-.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-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;
+    }
   }
-}
 
-.cell-con {
-  margin-left: 10px;
-  margin-right: 10px;
-  width: calc(100% - 32px - 10px - 10px);
-  height: 100%;
-  border-bottom: 0.5px solid #cacccf;
-}
+  .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-text {
-  width: 100%;
-  font-weight: 500;
+  .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);
+  }
 
-  .title {
+  .half-title {
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 500;
     font-size: 14px;
-    color: #333333;
-    line-height: 20px;
+    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;
   }
 
-  .subtitle {
-    font-size: 12px;
-    color: #999999;
-    line-height: 17px;
+  .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;
+    }
+  }
+
+  .cell-con {
     margin-left: 10px;
+    margin-right: 10px;
+    width: calc(100% - 32px - 10px - 10px);
+    height: 100%;
+    border-bottom: 0.5px solid #cacccf;
   }
 
-  .icon {
-    width: 6px;
-    height: 10px;
+  .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;
+    }
   }
-}
 </style>

+ 98 - 0
pages/ruihen/approval.vue

@@ -0,0 +1,98 @@
+<script setup>
+  import { ref } from 'vue';
+  import { approvalIotRhDailyReport } from '@/api/ruihen';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async auditStatus => {
+    if (!formRef.value) return;
+
+    try {
+      // await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      // if (form.nonProductionTime && !form.nptReason) {
+      //   uni.showToast({
+      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+      //     icon: 'none',
+      //   });
+      //   return;
+      // }
+
+      formLoading.value = true;
+
+      const { opinion, id } = form;
+      const data = { auditStatus, opinion, id };
+
+      await approvalIotRhDailyReport(data);
+
+      formRef.value.loadDetail(id);
+
+      uni.showToast({
+        title: auditStatus === 20 ? '通过成功' : '拒绝成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="approval"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm(20)">
+          审批通过
+        </button>
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="warn"
+          @click="submitForm(30)">
+          审批驳回
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 477 - 0
pages/ruihen/components/form.vue

@@ -0,0 +1,477 @@
+<script setup>
+  import { ref, computed, watch, nextTick, reactive } from 'vue';
+  import { onLoad } from '@dcloudio/uni-app';
+  import { getRuiHenReportDetail } from '@/api/ruihen';
+  import { useDataDictStore } from '@/store/modules/dataDict';
+
+  const props = defineProps({
+    type: {
+      type: String,
+      default: 'edit',
+    },
+  });
+
+  const FORM_KEYS = [
+    'id',
+    'deptId',
+    'projectId',
+    'taskId',
+    'deptName',
+    'contractName',
+    'taskName',
+    'dailyGasInjection',
+    'dailyWaterInjection',
+    'dailyInjectGasTime',
+    'dailyInjectWaterTime',
+    'nonProductionTime',
+    'nptReason',
+    'productionStatus',
+    'remark',
+    'relocationDays',
+    'capacity',
+    'createTime',
+    'opinion',
+    'status',
+    'auditStatus',
+  ];
+
+  const formType = ref('edit');
+
+  const initFormData = () => ({
+    dailyGasInjection: 0,
+    dailyWaterInjection: 0,
+    dailyInjectGasTime: 0,
+    dailyInjectWaterTime: 0,
+    nonProductionTime: 0,
+    relocationDays: 0,
+    capacity: 0,
+  });
+
+  const form = ref(initFormData());
+
+  async function loadDetail(id) {
+    try {
+      const { data } = await getRuiHenReportDetail({ id });
+
+      FORM_KEYS.forEach(key => {
+        form.value[key] = data[key] ?? form.value[key];
+      });
+      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';
+      }
+
+      if (!form.value.capacity) {
+        uni.showToast({ title: '请维护增压机产能', icon: 'error' });
+      }
+    } finally {
+    }
+  }
+
+  const dictStore = useDataDictStore();
+
+  const nptReasonOptions = ref([]);
+
+  const loadOptions = () => {
+    nptReasonOptions.value = dictStore.getStrDictOptions('nptReason').map(v => ({
+      text: v.label,
+      value: v.value,
+    }));
+  };
+
+  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.capacity;
+    const gas = form.value.dailyGasInjection ?? 0;
+
+    if (!cap) return { original: 0, value: '0%' };
+
+    const original = gas / cap;
+    return { original, value: (original * 100).toFixed(2) + '%' };
+  });
+
+  const formRef = ref(null);
+
+  // 辅助函数:计算总时间
+  const sumTimes = () => {
+    // 注意:uni-app input 出来可能是字符串,需转数字
+    const gas = Number(form.value.dailyInjectGasTime) || 0;
+    const water = Number(form.value.dailyInjectWaterTime) || 0;
+    const npt = Number(form.value.nonProductionTime) || 0;
+
+    return parseFloat((gas + water + npt).toFixed(2));
+  };
+
+  // 校验函数:总时间必须为 24
+  const validateTotalTime = (rule, value, data, callback) => {
+    console.log('value :>> ', value);
+    const total = sumTimes();
+    if (total !== 24) {
+      callback(`当前合计 ${total} 小时,三项之和须为 24`);
+    }
+    return true; // uni-forms 如果没调用 callback error,需返回 true 代表通过
+  };
+
+  // // 校验函数:非生产时间原因
+  // const validateNptReason = (rule, value, data, callback) => {
+  //   const npt = Number(form.value.nonProductionTime) || 0;
+  //   console.log('npt :>> ', npt);
+  //   console.log('value :>> ', value);
+  //   if (npt > 0 && !value) {
+  //     callback('非生产时间大于 0 时,必须选择原因');
+  //   }
+  //   return true;
+  // };
+
+  // 复用的时间规则
+  const timeRuleItem = {
+    rules: [
+      { required: true, errorMessage: '请输入时间' },
+      { validateFunction: validateTotalTime }, // 关联自定义校验
+    ],
+  };
+
+  // uni-forms 规则定义
+  const rules = reactive({
+    dailyGasInjection: {
+      rules: [{ required: true, errorMessage: '请输入当日注气量' }],
+    },
+    dailyWaterInjection: {
+      rules: [{ required: true, errorMessage: '请输入当日注水量' }],
+    },
+    productionStatus: {
+      rules: [{ required: true, errorMessage: '请输入生产动态' }],
+    },
+
+    // 时间字段应用复用规则
+    dailyInjectGasTime: timeRuleItem,
+    dailyInjectWaterTime: timeRuleItem,
+    nonProductionTime: timeRuleItem,
+
+    // nptReason: {
+    //   rules: [{ validateFunction: validateNptReason }],
+    // },
+  });
+
+  watch(
+    [() => form.value.dailyInjectGasTime, () => form.value.dailyInjectWaterTime, () => form.value.nonProductionTime],
+    () => {
+      nextTick(() => {
+        formRef.value?.validateField(['nptReason']).catch(() => {});
+        if (sumTimes() === 24) {
+          formRef.value?.clearValidate(['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']);
+        }
+      });
+    }
+  );
+
+  defineExpose({ formRef, form, loadDetail });
+</script>
+
+<template>
+  <view class="content">
+    <view class="tip">
+      <view class="item">
+        <view class="left">
+          <span>运行时效:</span>
+          当日注气量 / 产能
+        </view>
+        <span class="right red"> >120% 红色预警 </span>
+      </view>
+      <view class="item">
+        <view class="left">
+          <span>时间平衡:</span>
+          注气 + 注水 + 非生产 = 24H
+        </view>
+        <span class="right orange"> ≠24H 橙色预警 </span>
+      </view>
+    </view>
+    <view v-if="!type.includes('approval') && form.opinion" class="opinion">
+      <span class="left">审批意见:</span>
+      <span class="right"> {{ form.opinion }} </span>
+    </view>
+    <uni-forms
+      ref="formRef"
+      labelWidth="auto"
+      :model="form"
+      :rules="rules"
+      validateTrigger="blur"
+      err-show-type="toast">
+      <uni-forms-item label="施工队伍" name="deptName">
+        <span class="readOnly">{{ form.deptName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="项目" name="contractName">
+        <span class="readOnly">{{ form.contractName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="任务" name="taskName">
+        <span class="readOnly">{{ form.taskName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="搬迁安装天数(D)" name="relocationDays">
+        <span class="readOnly">{{ form.relocationDays }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="运行时效" name="transitTime">
+        <span class="readOnly" :class="{ 'red-text': transitTime.original > 1.2 }">{{ transitTime.value }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="当日注气量(方)" name="dailyGasInjection" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.dailyGasInjection" />
+      </uni-forms-item>
+      <uni-forms-item label="当日注水量(方)" name="dailyWaterInjection" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.dailyWaterInjection" />
+      </uni-forms-item>
+      <uni-forms-item label="当日注气时间(H)" name="dailyInjectGasTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.dailyInjectGasTime" />
+      </uni-forms-item>
+      <uni-forms-item label="当日注水时间(H)" name="dailyInjectWaterTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.dailyInjectWaterTime" />
+      </uni-forms-item>
+      <uni-forms-item label="非生产时间(H)" name="nonProductionTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.nonProductionTime" />
+      </uni-forms-item>
+      <uni-forms-item label="非生产时间原因" name="nptReason">
+        <uni-data-select
+          :clear="true"
+          align="right"
+          placeholder="请选择"
+          :localdata="nptReasonOptions"
+          placement="top"
+          :disabled="disabled('edit')"
+          v-model="form.nptReason" />
+      </uni-forms-item>
+      <uni-forms-item label="生产动态" name="productionStatus" required>
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          v-model="form.productionStatus"
+          :disabled="disabled('edit')"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="备注" name="remark">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.remark"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('approval')"
+          v-model="form.opinion"
+          :maxlength="1000" />
+      </uni-forms-item>
+    </uni-forms>
+  </view>
+</template>
+
+<style scoped>
+  .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-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;
+    }
+
+    :deep(.uni-select--disabled) {
+      background-color: #fff;
+    }
+  }
+
+  .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;
+
+      .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;
+      }
+    }
+  }
+
+  .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>

+ 45 - 0
pages/ruihen/detail.vue

@@ -0,0 +1,45 @@
+<script setup>
+  import { ref } from 'vue';
+  import Form from './components/form.vue';
+  import { onLoad } from '@dcloudio/uni-app';
+
+  const type = ref();
+
+  onLoad(options => {
+    type.value = options.type;
+  });
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" style="height: 100%">
+      <Form ref="formRef" :type="type"></Form>
+    </scroll-view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 90 - 0
pages/ruihen/edit.vue

@@ -0,0 +1,90 @@
+<script setup>
+  import { ref } from 'vue';
+  import { createIotRhDailyReport } from '@/api/ruihen';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async () => {
+    if (!formRef.value) return;
+
+    try {
+      await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      if (form.nonProductionTime && !form.nptReason) {
+        uni.showToast({
+          title: '非生产时间大于 0 时,必须选择非生产时间原因',
+          icon: 'none',
+        });
+        return;
+      }
+
+      formLoading.value = true;
+
+      const { createTime, ...other } = form;
+      const data = { ...other, fillOrderCreateTime: createTime };
+
+      await createIotRhDailyReport(data);
+
+      formRef.value.loadDetail(form.id);
+
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="edit"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.status !== 0"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm">
+          确定
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 291 - 0
pages/ruihen/index.vue

@@ -0,0 +1,291 @@
+<script setup>
+  import { onShow, onLoad } from '@dcloudio/uni-app';
+  import { ref, reactive, nextTick } from 'vue';
+  import dayjs from 'dayjs';
+  import { getRuiHenReportPage } from '@/api/ruihen';
+  import { useDataDictStore } from '@/store/modules/dataDict';
+
+  const dictStore = useDataDictStore();
+
+  const fillStatusDict = reactive({});
+  const approvalStatusDict = reactive({
+    0: '待提交',
+    10: '待审批',
+    20: '审批通过',
+    30: '审批拒绝',
+  });
+  const constructionStatusDict = reactive({});
+
+  const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px');
+  const inputStyles = reactive({
+    backgroundColor: '#FFFFFF',
+    color: '#797979',
+  });
+
+  const orderName = ref('');
+
+  const paging = ref(null);
+  const dataList = ref([]);
+
+  const queryList = (pageNo, pageSize) => {
+    getRuiHenReportPage({
+      pageNo,
+      pageSize,
+      taskName: orderName.value,
+      contractName: orderName.value,
+    })
+      .then(res => {
+        paging.value.complete(res.data.list);
+      })
+      .catch(() => {
+        paging.value.complete(false);
+      });
+  };
+
+  const searchList = () => {
+    paging.value.reload();
+  };
+
+  const navigatorDetail = item => {
+    uni.navigateTo({
+      url: '/pages/ruihen/detail?id=' + item.id + `&type=${type.value}-detail`,
+    });
+  };
+  const navigatorEdit = item => {
+    uni.navigateTo({
+      url: type.value === 'edit' ? '/pages/ruihen/edit?id=' + item.id : '/pages/ruihen/approval?id=' + item.id,
+    });
+  };
+
+  const formatDate = time => {
+    return dayjs(time).format('YYYY-MM-DD');
+  };
+  const formatTime = time => {
+    return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
+  };
+
+  const type = ref('edit');
+
+  onLoad(option => {
+    type.value = option.type || 'edit';
+  });
+
+  onShow(() => {
+    if (dictStore.dataDict.length <= 0) {
+      dictStore.loadDataDictList().then(() => {
+        dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+          fillStatusDict[item.value] = item.label;
+        });
+        dictStore.getStrDictOptions('constructionStatus').map(item => {
+          constructionStatusDict[item.value] = item.label;
+        });
+      });
+    } else {
+      dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+        fillStatusDict[item.value] = item.label;
+      });
+      dictStore.getStrDictOptions('constructionStatus').map(item => {
+        constructionStatusDict[item.value] = item.label;
+      });
+    }
+    nextTick(() => {
+      searchList();
+    });
+  });
+</script>
+
+<template>
+  <z-paging class="page" ref="paging" v-model="dataList" @query="queryList">
+    <template #top>
+      <view class="top">
+        <uni-easyinput
+          v-model="orderName"
+          :styles="inputStyles"
+          :placeholderStyle="placeholderStyle"
+          :placeholder="$t('operation.searchText')">
+        </uni-easyinput>
+        <button class="mini-btn" type="primary" size="mini" @click="searchList">
+          {{ $t('operation.search') }}
+        </button>
+      </view>
+    </template>
+    <view class="list">
+      <view class="item" v-for="(item, index) in dataList" :key="index">
+        <view class="header">
+          <span class="create-time">{{ item.createTime ? formatDate(item.createTime) : '' }}</span>
+          <span class="fill-status" :class="`status-${item.status}`">{{ fillStatusDict[item.status] }}</span>
+        </view>
+        <view class="content">
+          <view class="content-item">
+            <span class="label">日期:</span>
+            <span>{{ item.createTime ? formatTime(item.createTime) : '' }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">项目:</span>
+            <span>{{ item.contractName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">任务:</span>
+            <span>{{ item.taskName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工队伍:</span>
+            <span>{{ item.deptName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工状态:</span>
+            <span :class="{ constructionStatus: item.constructionStatus }">{{
+              constructionStatusDict[item.constructionStatus]
+            }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">审批状态:</span>
+            <span class="auditStatus" :class="`status-${item.auditStatus}`">{{
+              approvalStatusDict[item.auditStatus]
+            }}</span>
+          </view>
+        </view>
+        <view class="footer">
+          <button class="button" size="mini" type="primary" plain="true" @click="navigatorDetail(item)">
+            {{ $t('operation.view') }}
+          </button>
+          <!-- 填写 -->
+          <button class="button" size="mini" type="primary" @click="navigatorEdit(item)">
+            {{ type === 'edit' ? $t('operation.fill') : $t('operation.approve') }}
+          </button>
+        </view>
+      </view>
+    </view>
+  </z-paging>
+</template>
+
+<style scoped>
+  .page {
+    padding: 10px;
+  }
+
+  .top {
+    height: 40px;
+    background: #f3f5f9;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 20px;
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+
+  .list {
+    margin-top: 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .item {
+      background-color: #fff;
+      padding: 10px;
+      border-radius: 8px;
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      .header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 16px;
+        font-weight: 500;
+
+        .fill-status {
+          font-size: 14px;
+          font-weight: 500;
+          display: inline-flex;
+          padding: 5px;
+          border-radius: 4px;
+
+          &.status-0 {
+            color: #ff4d4f;
+            background: rgba(255, 77, 79, 0.2);
+          }
+          &.status-2 {
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          &.status-1 {
+            color: #00c250;
+            background: rgba(0, 194, 80, 0.2);
+          }
+        }
+      }
+
+      .content {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+
+        .content-item {
+          font-size: 14px;
+          font-weight: 400;
+
+          .label {
+            display: inline-block;
+            font-weight: 500;
+            width: 70px;
+          }
+
+          .constructionStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          .auditStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+
+            &.status-0 {
+              color: #595959;
+              background: rgba(89, 89, 89, 0.2);
+            }
+
+            &.status-30 {
+              color: #ff4d4f;
+              background: rgba(255, 77, 79, 0.2);
+            }
+            &.status-10 {
+              color: #4096ff;
+              background: rgba(64, 150, 255, 0.2);
+            }
+
+            &.status-20 {
+              color: #00c250;
+              background: rgba(0, 194, 80, 0.2);
+            }
+          }
+        }
+      }
+
+      .footer {
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+        gap: 0 12px;
+        height: 32px;
+
+        .button {
+          margin: 0;
+        }
+      }
+    }
+  }
+</style>

+ 98 - 0
pages/ruiying/approval.vue

@@ -0,0 +1,98 @@
+<script setup>
+  import { ref } from 'vue';
+  import { approvalIotRyDailyReport } from '@/api/ruiying';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async auditStatus => {
+    if (!formRef.value) return;
+
+    try {
+      // await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      // if (form.nonProductionTime && !form.nptReason) {
+      //   uni.showToast({
+      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+      //     icon: 'none',
+      //   });
+      //   return;
+      // }
+
+      formLoading.value = true;
+
+      const { opinion, id } = form;
+      const data = { auditStatus, opinion, id };
+
+      await approvalIotRyDailyReport(data);
+
+      formRef.value.loadDetail(id);
+
+      uni.showToast({
+        title: auditStatus === 20 ? '通过成功' : '拒绝成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="approval"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm(20)">
+          审批通过
+        </button>
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="warn"
+          @click="submitForm(30)">
+          审批驳回
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 635 - 0
pages/ruiying/components/form.vue

@@ -0,0 +1,635 @@
+<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 FORM_KEYS = [
+    'id',
+    'deptName',
+    'contractName',
+    'taskName',
+    'rigStatus',
+    'designWellDepth',
+    'currentDepth',
+    'dailyPowerUsage',
+    'dailyFuel',
+    'mudDensity',
+    'mudViscosity',
+    'lateralLength',
+    'wellInclination',
+    'azimuth',
+    'designWellStruct',
+    'productionStatus',
+    'remark',
+    'createTime',
+    'deptId',
+    'projectId',
+    'taskId',
+    'opinion',
+    'personnel',
+    'accidentTime',
+    'repairTime',
+    'selfStopTime',
+    'complexityTime',
+    'relocationTime',
+    'rectificationTime',
+    'waitingStopTime',
+    'winterBreakTime',
+    'drillingWorkingTime',
+    'otherProductionTime',
+    'lastCurrentDepth',
+    'opinion',
+    'status',
+    'auditStatus',
+  ];
+
+  const formType = ref('edit');
+
+  const initFormData = () => ({});
+
+  const form = ref(initFormData());
+
+  async function loadDetail(id) {
+    try {
+      const { data } = await getRuiYingReportDetail({ id });
+
+      FORM_KEYS.forEach(key => {
+        form.value[key] = data[key] ?? form.value[key];
+      });
+      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 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,
+    }));
+  };
+
+  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.capacity;
+  //   const gas = form.value.dailyGasInjection ?? 0;
+
+  //   if (!cap) return { original: 0, value: '0%' };
+
+  //   const original = gas / cap;
+  //   return { original, value: (original * 100).toFixed(2) + '%' };
+  // });
+
+  const formRef = ref(null);
+
+  // 辅助函数:计算总时间
+  const sumTimes = () => {
+    const {
+      drillingWorkingTime = 0,
+      otherProductionTime = 0,
+      accidentTime = 0,
+      repairTime = 0,
+      selfStopTime = 0,
+      complexityTime = 0,
+      relocationTime = 0,
+      rectificationTime = 0,
+      waitingStopTime = 0,
+      winterBreakTime = 0,
+    } = form.value;
+    return parseFloat(
+      (
+        Number(drillingWorkingTime) +
+        Number(otherProductionTime) +
+        Number(accidentTime) +
+        Number(repairTime) +
+        Number(selfStopTime) +
+        Number(complexityTime) +
+        Number(relocationTime) +
+        Number(rectificationTime) +
+        Number(waitingStopTime) +
+        Number(winterBreakTime)
+      ).toFixed(2)
+    );
+  };
+
+  // 校验函数:总时间必须为 24
+  const validateTotalTime = (rule, value, data, callback) => {
+    const total = sumTimes();
+    if (total !== 24) {
+      callback(`当前合计 ${total} 小时,时间之和必须等于 24`);
+    } else {
+      callback();
+    }
+  };
+
+  const validateLastCurrentDepth = (rule, value, data, callback) => {
+    if (value && Number(value) < (form.value.lastCurrentDepth ?? 0)) {
+      callback('当前深度需大于等于上一次填报深度');
+    } else {
+      callback();
+    }
+  };
+
+  // // 校验函数:非生产时间原因
+  // const validateNptReason = (rule, value, data, callback) => {
+  //   const npt = Number(form.value.nonProductionTime) || 0;
+  //   console.log('npt :>> ', npt);
+  //   console.log('value :>> ', value);
+  //   if (npt > 0 && !value) {
+  //     callback('非生产时间大于 0 时,必须选择原因');
+  //   }
+  //   return true;
+  // };
+
+  // 复用的时间规则
+  const timeRuleItem = {
+    rules: [
+      { required: true, errorMessage: '请输入时间' },
+      { validateFunction: validateTotalTime }, // 关联自定义校验
+    ],
+  };
+
+  // uni-forms 规则定义
+  const rules = reactive({
+    currentDepth: {
+      rules: [{ required: true, errorMessage: '请输入当前深度' }, { validateFunction: validateLastCurrentDepth }],
+    },
+
+    productionStatus: {
+      rules: [{ required: true, errorMessage: '请输入生产动态' }],
+    },
+
+    drillingWorkingTime: timeRuleItem,
+    otherProductionTime: timeRuleItem,
+    accidentTime: timeRuleItem,
+    repairTime: timeRuleItem,
+    selfStopTime: timeRuleItem,
+    complexityTime: timeRuleItem,
+    relocationTime: timeRuleItem,
+    rectificationTime: timeRuleItem,
+    waitingStopTime: timeRuleItem,
+    winterBreakTime: timeRuleItem,
+  });
+
+  watch(
+    [
+      () => form.value.drillingWorkingTime,
+      () => form.value.otherProductionTime,
+      () => form.value.accidentTime,
+      () => form.value.repairTime,
+      () => form.value.selfStopTime,
+      () => form.value.complexityTime,
+      () => form.value.relocationTime,
+      () => form.value.rectificationTime,
+      () => form.value.waitingStopTime,
+      () => form.value.winterBreakTime,
+    ],
+    () => {
+      nextTick(() => {
+        if (sumTimes() === 24) {
+          formRef.value?.clearValidate([
+            'drillingWorkingTime',
+            'otherProductionTime',
+            'accidentTime',
+            'repairTime',
+            'selfStopTime',
+            'complexityTime',
+            'relocationTime',
+            'rectificationTime',
+            'waitingStopTime',
+            'winterBreakTime',
+          ]);
+        }
+      });
+    }
+  );
+
+  defineExpose({ formRef, form, loadDetail });
+</script>
+
+<template>
+  <view class="content">
+    <view class="tip">
+      <view class="item">
+        <view class="left">
+          <span>油量消耗:</span>
+          当日油耗
+        </view>
+        <span class="right red"> >15吨 红色预警 </span>
+      </view>
+      <view class="item">
+        <view class="left">
+          <span>时间平衡:</span>
+          进尺 + 其它生产 + 非生产 = 24H
+        </view>
+        <span class="right orange"> ≠24H 橙色预警 </span>
+      </view>
+    </view>
+    <view v-if="!type.includes('approval') && form.opinion" class="opinion">
+      <span class="left">审批意见:</span>
+      <span class="right"> {{ form.opinion }} </span>
+    </view>
+    <uni-forms
+      ref="formRef"
+      labelWidth="auto"
+      :model="form"
+      :rules="rules"
+      validateTrigger="blur"
+      err-show-type="toast">
+      <uni-forms-item label="施工队伍" name="deptName">
+        <span class="readOnly">{{ form.deptName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="项目" name="contractName">
+        <span class="readOnly">{{ form.contractName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="任务" name="taskName">
+        <span class="readOnly">{{ form.taskName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="施工状态" name="rigStatus">
+        <uni-data-select
+          :clear="true"
+          align="right"
+          placeholder="请选择"
+          :localdata="rigStatusOptions"
+          placement="top"
+          :disabled="disabled('edit')"
+          v-model="form.rigStatus" />
+      </uni-forms-item>
+      <uni-forms-item label="设计井深(m)" name="designWellDepth">
+        <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-forms-item>
+      <uni-forms-item label="当日用电量(kWh)" name="dailyPowerUsage">
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.dailyPowerUsage" />
+      </uni-forms-item>
+      <uni-forms-item label="当日油耗(吨)" name="dailyFuel">
+        <uni-easyinput
+          type="number"
+          :class="{ 'red-text': Number(form.dailyFuel) > 15 }"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          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-forms-item>
+      <uni-forms-item label="水平段长度(m)" name="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
+          type="number"
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          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-forms-item>
+      <uni-forms-item label="设计井身结构" name="designWellStruct">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.designWellStruct"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="人员情况" name="personnel">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.personnel"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uv-divider text="生产时间" textPosition="left"></uv-divider>
+      <uni-forms-item label="进尺工作时间(H)" name="drillingWorkingTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.drillingWorkingTime" />
+      </uni-forms-item>
+      <uni-forms-item label="其它生产时间(H)" name="otherProductionTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.otherProductionTime" />
+      </uni-forms-item>
+      <uv-divider text="非生产时间" textPosition="left"></uv-divider>
+      <uni-forms-item label="事故(H)" name="accidentTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.accidentTime" />
+      </uni-forms-item>
+      <uni-forms-item label="修理(H)" name="repairTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.repairTime" />
+      </uni-forms-item>
+      <uni-forms-item label="自停(H)" name="selfStopTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.selfStopTime" />
+      </uni-forms-item>
+      <uni-forms-item label="复杂(H)" name="complexityTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.complexityTime" />
+      </uni-forms-item>
+      <uni-forms-item label="搬迁(H)" name="relocationTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.relocationTime" />
+      </uni-forms-item>
+      <uni-forms-item label="整改(H)" name="rectificationTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.rectificationTime" />
+      </uni-forms-item>
+      <uni-forms-item label="等停(H)" name="waitingStopTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.waitingStopTime" />
+      </uni-forms-item>
+      <uni-forms-item label="冬休(H)" name="winterBreakTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== 24 }"
+          :disabled="disabled('edit')"
+          v-model="form.waitingStopTime" />
+      </uni-forms-item>
+      <uni-forms-item label="生产动态" name="productionStatus" required>
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          v-model="form.productionStatus"
+          :disabled="disabled('edit')"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="备注" name="remark">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.remark"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('approval')"
+          v-model="form.opinion"
+          :maxlength="1000" />
+      </uni-forms-item>
+    </uni-forms>
+  </view>
+</template>
+
+<style scoped>
+  .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-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;
+    }
+
+    :deep(.uni-select--disabled) {
+      background-color: #fff;
+    }
+  }
+
+  .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;
+
+      .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;
+      }
+    }
+  }
+
+  .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);
+    }
+  }
+
+  :deep(.uv-divider__text) {
+    color: #333 !important;
+  }
+</style>

+ 45 - 0
pages/ruiying/detail.vue

@@ -0,0 +1,45 @@
+<script setup>
+  import { ref } from 'vue';
+  import Form from './components/form.vue';
+  import { onLoad } from '@dcloudio/uni-app';
+
+  const type = ref();
+
+  onLoad(options => {
+    type.value = options.type;
+  });
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" style="height: 100%">
+      <Form ref="formRef" :type="type"></Form>
+    </scroll-view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 90 - 0
pages/ruiying/edit.vue

@@ -0,0 +1,90 @@
+<script setup>
+  import { ref } from 'vue';
+  import { createIotRyDailyReport } from '@/api/ruiying';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async () => {
+    if (!formRef.value) return;
+
+    try {
+      await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      // if (form.nonProductionTime && !form.nptReason) {
+      //   uni.showToast({
+      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+      //     icon: 'none',
+      //   });
+      //   return;
+      // }
+
+      formLoading.value = true;
+
+      const { createTime, ...other } = form;
+      const data = { ...other, fillOrderCreateTime: createTime, projectClassification: '1' };
+
+      await createIotRyDailyReport(data);
+
+      formRef.value.loadDetail(form.id);
+
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="edit"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.status !== 0"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm">
+          确定
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 290 - 0
pages/ruiying/index.vue

@@ -0,0 +1,290 @@
+<script setup>
+  import { onShow, onLoad } from '@dcloudio/uni-app';
+  import { ref, reactive, nextTick } from 'vue';
+  import dayjs from 'dayjs';
+  import { getRuiYingReportPage } from '@/api/ruiying';
+  import { useDataDictStore } from '@/store/modules/dataDict';
+
+  const dictStore = useDataDictStore();
+
+  const fillStatusDict = reactive({});
+  const approvalStatusDict = reactive({
+    0: '待提交',
+    10: '待审批',
+    20: '审批通过',
+    30: '审批拒绝',
+  });
+  const constructionStatusDict = reactive({});
+
+  const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px');
+  const inputStyles = reactive({
+    backgroundColor: '#FFFFFF',
+    color: '#797979',
+  });
+
+  const orderName = ref('');
+
+  const paging = ref(null);
+  const dataList = ref([]);
+
+  const queryList = (pageNo, pageSize) => {
+    getRuiYingReportPage({
+      pageNo,
+      pageSize,
+      projectClassification: '1',
+      taskName: orderName.value,
+      contractName: orderName.value,
+    })
+      .then(res => {
+        paging.value.complete(res.data.list);
+      })
+      .catch(() => {
+        paging.value.complete(false);
+      });
+  };
+
+  const searchList = () => {
+    paging.value.reload();
+  };
+
+  const navigatorDetail = item => {
+    uni.navigateTo({
+      url: '/pages/ruiying/detail?id=' + item.id + `&type=${type.value}-detail`,
+    });
+  };
+  const navigatorEdit = item => {
+    uni.navigateTo({
+      url: type.value === 'edit' ? '/pages/ruiying/edit?id=' + item.id : '/pages/ruiying/approval?id=' + item.id,
+    });
+  };
+
+  const formatDate = time => {
+    return dayjs(time).format('YYYY-MM-DD');
+  };
+  const formatTime = time => {
+    return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
+  };
+
+  const type = ref('edit');
+
+  onLoad(option => {
+    type.value = option.type || 'edit';
+  });
+
+  onShow(() => {
+    if (dictStore.dataDict.length <= 0) {
+      dictStore.loadDataDictList().then(() => {
+        dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+          fillStatusDict[item.value] = item.label;
+        });
+        dictStore.getStrDictOptions('rigStatus').map(item => {
+          constructionStatusDict[item.value] = item.label;
+        });
+      });
+    } else {
+      dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+        fillStatusDict[item.value] = item.label;
+      });
+      dictStore.getStrDictOptions('rigStatus').map(item => {
+        constructionStatusDict[item.value] = item.label;
+      });
+    }
+    nextTick(() => {
+      searchList();
+    });
+  });
+</script>
+
+<template>
+  <z-paging class="page" ref="paging" v-model="dataList" @query="queryList">
+    <template #top>
+      <view class="top">
+        <uni-easyinput
+          v-model="orderName"
+          :styles="inputStyles"
+          :placeholderStyle="placeholderStyle"
+          :placeholder="$t('operation.searchText')">
+        </uni-easyinput>
+        <button class="mini-btn" type="primary" size="mini" @click="searchList">
+          {{ $t('operation.search') }}
+        </button>
+      </view>
+    </template>
+    <view class="list">
+      <view class="item" v-for="(item, index) in dataList" :key="index">
+        <view class="header">
+          <span class="create-time">{{ item.createTime ? formatDate(item.createTime) : '' }}</span>
+          <span class="fill-status" :class="`status-${item.status}`">{{ fillStatusDict[item.status] }}</span>
+        </view>
+        <view class="content">
+          <view class="content-item">
+            <span class="label">日期:</span>
+            <span>{{ item.createTime ? formatTime(item.createTime) : '' }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">项目:</span>
+            <span>{{ item.contractName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">任务:</span>
+            <span>{{ item.taskName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工队伍:</span>
+            <span>{{ item.deptName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工状态:</span>
+            <span :class="{ constructionStatus: item.rigStatus }">{{ constructionStatusDict[item.rigStatus] }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">审批状态:</span>
+            <span class="auditStatus" :class="`status-${item.auditStatus}`">{{
+              approvalStatusDict[item.auditStatus]
+            }}</span>
+          </view>
+        </view>
+        <view class="footer">
+          <button class="button" size="mini" type="primary" plain="true" @click="navigatorDetail(item)">
+            {{ $t('operation.view') }}
+          </button>
+          <!-- 填写 -->
+          <button class="button" size="mini" type="primary" @click="navigatorEdit(item)">
+            {{ type === 'edit' ? $t('operation.fill') : $t('operation.approve') }}
+          </button>
+        </view>
+      </view>
+    </view>
+  </z-paging>
+</template>
+
+<style scoped>
+  .page {
+    padding: 10px;
+  }
+
+  .top {
+    height: 40px;
+    background: #f3f5f9;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 20px;
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+
+  .list {
+    margin-top: 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .item {
+      background-color: #fff;
+      padding: 10px;
+      border-radius: 8px;
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      .header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 16px;
+        font-weight: 500;
+
+        .fill-status {
+          font-size: 14px;
+          font-weight: 500;
+          display: inline-flex;
+          padding: 5px;
+          border-radius: 4px;
+
+          &.status-0 {
+            color: #ff4d4f;
+            background: rgba(255, 77, 79, 0.2);
+          }
+          &.status-2 {
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          &.status-1 {
+            color: #00c250;
+            background: rgba(0, 194, 80, 0.2);
+          }
+        }
+      }
+
+      .content {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+
+        .content-item {
+          font-size: 14px;
+          font-weight: 400;
+
+          .label {
+            display: inline-block;
+            font-weight: 500;
+            width: 70px;
+          }
+
+          .constructionStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          .auditStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+
+            &.status-0 {
+              color: #595959;
+              background: rgba(89, 89, 89, 0.2);
+            }
+
+            &.status-30 {
+              color: #ff4d4f;
+              background: rgba(255, 77, 79, 0.2);
+            }
+            &.status-10 {
+              color: #4096ff;
+              background: rgba(64, 150, 255, 0.2);
+            }
+
+            &.status-20 {
+              color: #00c250;
+              background: rgba(0, 194, 80, 0.2);
+            }
+          }
+        }
+      }
+
+      .footer {
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+        gap: 0 12px;
+        height: 32px;
+
+        .button {
+          margin: 0;
+        }
+      }
+    }
+  }
+</style>

+ 98 - 0
pages/ruiyingx/approval.vue

@@ -0,0 +1,98 @@
+<script setup>
+  import { ref } from 'vue';
+  import { approvalIotRyDailyReport } from '@/api/ruiying';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async auditStatus => {
+    if (!formRef.value) return;
+
+    try {
+      // await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      // if (form.nonProductionTime && !form.nptReason) {
+      //   uni.showToast({
+      //     title: '非生产时间大于 0 时,必须选择非生产时间原因',
+      //     icon: 'none',
+      //   });
+      //   return;
+      // }
+
+      formLoading.value = true;
+
+      const { opinion, id } = form;
+      const data = { auditStatus, opinion, id };
+
+      await approvalIotRyDailyReport(data);
+
+      formRef.value.loadDetail(form.id);
+
+      uni.showToast({
+        title: auditStatus === 20 ? '通过成功' : '拒绝成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="approval"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm(20)">
+          审批通过
+        </button>
+        <button
+          :disabled="formLoading || formRef?.form.auditStatus !== 10"
+          :loading="formLoading"
+          class="mini-btn"
+          type="warn"
+          @click="submitForm(30)">
+          审批驳回
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 522 - 0
pages/ruiyingx/components/form.vue

@@ -0,0 +1,522 @@
+<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 FORM_KEYS = [
+    'id',
+    'deptId',
+    'projectId',
+    'taskId',
+    'deptName',
+    'contractName',
+    'taskName',
+    'repairStatus',
+    'technique',
+    'wellCategory',
+    'designWellDepth',
+    'casingPipeSize',
+    'wellControlLevel',
+    'currentOperation',
+    'nextPlan',
+    'transitTime',
+    'ratedProductionTime',
+    'productionTime',
+    'nonProductionTime',
+    'ryNptReason',
+    'productionStatus',
+    'totalStaffNum',
+    'onDutyStaffNum',
+    'leaveStaffNum',
+    'remark',
+    'createTime',
+    'opinion',
+    'status',
+    'auditStatus',
+  ];
+
+  const formType = ref('edit');
+
+  const initFormData = () => ({});
+
+  const form = ref(initFormData());
+
+  async function loadDetail(id) {
+    try {
+      const { data } = await getRuiYingReportDetail({ id });
+
+      FORM_KEYS.forEach(key => {
+        form.value[key] = data[key] ?? form.value[key];
+      });
+      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 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 => ({
+      text: v.label,
+      value: v.value,
+    }));
+    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);
+  });
+
+  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 sumTimes = () => {
+    const { productionTime = 0, nonProductionTime = 0 } = form.value;
+    return Number(productionTime) + Number(nonProductionTime);
+  };
+
+  // 校验函数:总时间必须为 24
+  const validateTotalTime = (rule, value, data, callback) => {
+    const total = sumTimes();
+    if (total !== Number(form.value.ratedProductionTime)) {
+      callback(`生产时间和非生产时间之和必须等于额定生产时间`);
+    } else {
+      callback();
+    }
+  };
+
+  // // 校验函数:非生产时间原因
+  // const validateNptReason = (rule, value, data, callback) => {
+  //   const npt = Number(form.value.nonProductionTime) || 0;
+  //   console.log('npt :>> ', npt);
+  //   console.log('value :>> ', value);
+  //   if (npt > 0 && !value) {
+  //     callback('非生产时间大于 0 时,必须选择原因');
+  //   }
+  //   return true;
+  // };
+
+  // 复用的时间规则
+  // const timeRuleItem = {
+  //   rules: [
+  //     { required: true, errorMessage: '请输入时间' },
+  //     { validateFunction: validateTotalTime }, // 关联自定义校验
+  //   ],
+  // };
+
+  // uni-forms 规则定义
+  const rules = reactive({
+    repairStatus: {
+      rules: [{ required: true, errorMessage: '请输入施工状态' }],
+    },
+    productionStatus: {
+      rules: [{ required: true, errorMessage: '请输入生产动态' }],
+    },
+
+    // 时间字段应用复用规则
+    // productionTime: timeRuleItem,
+    // nonProductionTime: timeRuleItem,
+    // ratedProductionTime: timeRuleItem,
+
+    // nptReason: {
+    //   rules: [{ validateFunction: validateNptReason }],
+    // },
+  });
+
+  watch(
+    [() => form.value.dailyInjectGasTime, () => form.value.dailyInjectWaterTime, () => form.value.nonProductionTime],
+    () => {
+      nextTick(() => {
+        formRef.value?.validateField(['ryNptReason']).catch(() => {});
+        if (sumTimes() === 24) {
+          formRef.value?.clearValidate(['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']);
+        }
+      });
+    }
+  );
+
+  defineExpose({ formRef, form, loadDetail });
+</script>
+
+<template>
+  <view class="content">
+    <view class="tip">
+      <view class="item">
+        <view class="left">
+          <span>运行时效:</span>
+          生产时间/额定生产时间
+        </view>
+        <span class="right red"> >100% 红色预警 </span>
+      </view>
+      <view class="item">
+        <view class="left">
+          <span>时间平衡:</span>
+          生产 + 非生产 = 额定生产
+        </view>
+        <span class="right orange"> ≠额定生产 橙色预警 </span>
+      </view>
+    </view>
+    <view v-if="!type.includes('approval') && form.opinion" class="opinion">
+      <span class="left">审批意见:</span>
+      <span class="right"> {{ form.opinion }} </span>
+    </view>
+    <uni-forms
+      ref="formRef"
+      labelWidth="auto"
+      :model="form"
+      :rules="rules"
+      validateTrigger="blur"
+      err-show-type="toast">
+      <uni-forms-item label="施工队伍" name="deptName">
+        <span class="readOnly">{{ form.deptName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="项目" name="contractName">
+        <span class="readOnly">{{ form.contractName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="任务" name="taskName">
+        <span class="readOnly">{{ form.taskName }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="施工状态" name="repairStatus" required>
+        <uni-data-select
+          :clear="true"
+          align="right"
+          placeholder="请选择"
+          :localdata="rigStatusOptions"
+          placement="top"
+          :disabled="disabled('edit')"
+          v-model="form.repairStatus" />
+      </uni-forms-item>
+      <uni-forms-item label="施工工艺" name="technique">
+        <uni-data-select
+          :clear="true"
+          align="right"
+          placeholder="请选择"
+          :localdata="techniqueOptions"
+          placement="top"
+          :disabled="disabled('edit')"
+          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-forms-item>
+      <uni-forms-item label="设计井深(m)" name="designWellDepth">
+        <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-forms-item>
+      <uni-forms-item label="套生段产管尺寸(mm)" name="casingPipeSize">
+        <uni-easyinput v-bind="defaultProps" v-model="form.casingPipeSize" :disabled="disabled('edit')" />
+      </uni-forms-item>
+      <uni-forms-item label="目前工序" name="currentOperation">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          v-model="form.currentOperation"
+          :disabled="disabled('edit')"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="下步工序" name="nextPlan">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          v-model="form.nextPlan"
+          :disabled="disabled('edit')"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="运行时效" name="transitTime">
+        <span class="readOnly" :class="{ 'red-text': transitTime.original > 1.0 }">{{ transitTime.value }}</span>
+      </uni-forms-item>
+      <uni-forms-item label="额定生产时间(H)" name="ratedProductionTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== Number(form.ratedProductionTime) }"
+          :disabled="disabled('edit')"
+          v-model="form.ratedProductionTime" />
+      </uni-forms-item>
+      <uni-forms-item label="生产时间(H)" name="productionTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== Number(form.ratedProductionTime) }"
+          :disabled="disabled('edit')"
+          v-model="form.productionTime" />
+      </uni-forms-item>
+      <uni-forms-item label="非生产时间(H)" name="nonProductionTime" required>
+        <uni-easyinput
+          type="number"
+          v-bind="defaultProps"
+          :class="{ 'orange-text': sumTimes() !== Number(form.ratedProductionTime) }"
+          :disabled="disabled('edit')"
+          v-model="form.nonProductionTime" />
+      </uni-forms-item>
+      <uni-forms-item label="非生产时间原因" name="ryNptReason">
+        <uni-data-select
+          :clear="true"
+          align="right"
+          placeholder="请选择"
+          :localdata="nptReasonOptions"
+          placement="top"
+          :disabled="disabled('edit')"
+          v-model="form.ryNptReason" />
+      </uni-forms-item>
+      <uni-forms-item label="全员数量" name="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-forms-item>
+      <uni-forms-item label="休假人员数量" name="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-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          v-model="form.productionStatus"
+          :disabled="disabled('edit')"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item label="备注" name="remark">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('edit')"
+          v-model="form.remark"
+          :maxlength="1000" />
+      </uni-forms-item>
+      <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
+        <uni-easyinput
+          type="textarea"
+          autoHeight
+          v-bind="defaultProps"
+          :disabled="disabled('approval')"
+          v-model="form.opinion"
+          :maxlength="1000" />
+      </uni-forms-item>
+    </uni-forms>
+  </view>
+</template>
+
+<style scoped>
+  .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-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;
+    }
+
+    :deep(.uni-select--disabled) {
+      background-color: #fff;
+    }
+  }
+
+  .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;
+
+      .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;
+      }
+    }
+  }
+
+  .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>

+ 45 - 0
pages/ruiyingx/detail.vue

@@ -0,0 +1,45 @@
+<script setup>
+  import { ref } from 'vue';
+  import Form from './components/form.vue';
+  import { onLoad } from '@dcloudio/uni-app';
+
+  const type = ref();
+
+  onLoad(options => {
+    type.value = options.type;
+  });
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" style="height: 100%">
+      <Form ref="formRef" :type="type"></Form>
+    </scroll-view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 97 - 0
pages/ruiyingx/edit.vue

@@ -0,0 +1,97 @@
+<script setup>
+  import { ref } from 'vue';
+  import { createIotRyDailyReport } from '@/api/ruiying';
+  import Form from './components/form.vue';
+
+  const formRef = ref(null);
+
+  const formLoading = ref(false);
+
+  const submitForm = async () => {
+    if (!formRef.value) return;
+
+    try {
+      await formRef.value.formRef.validate();
+      const form = formRef.value.form;
+
+      if (form.nonProductionTime && !form.ryNptReason) {
+        uni.showToast({
+          title: '非生产时间大于 0 时,必须选择非生产时间原因',
+          icon: 'none',
+        });
+        return;
+      }
+
+      if (Number(form.productionTime) + Number(form.nonProductionTime) !== Number(form.ratedProductionTime)) {
+        uni.showToast({
+          title: '生产时间和非生产时间之和必须等于额定生产时间',
+          icon: 'none',
+        });
+      }
+
+      formLoading.value = true;
+
+      const { createTime, ...other } = form;
+      const data = { ...other, fillOrderCreateTime: createTime, projectClassification: '2' };
+
+      await createIotRyDailyReport(data);
+
+      formRef.value.loadDetail(form.id);
+
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success',
+      });
+    } catch (error) {
+      console.log('error :>> ', error);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+</script>
+
+<template>
+  <view class="page">
+    <scroll-view scroll-y="true" class="segmented-content">
+      <Form ref="formRef" type="edit"></Form>
+    </scroll-view>
+    <view class="segmented-footer">
+      <view class="footer-btn">
+        <button
+          :disabled="formLoading || formRef?.form.status !== 0"
+          :loading="formLoading"
+          class="mini-btn"
+          type="primary"
+          @click="submitForm">
+          确定
+        </button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style 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;
+    }
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+</style>

+ 292 - 0
pages/ruiyingx/index.vue

@@ -0,0 +1,292 @@
+<script setup>
+  import { onShow, onLoad } from '@dcloudio/uni-app';
+  import { ref, reactive, nextTick } from 'vue';
+  import dayjs from 'dayjs';
+  import { getRuiYingReportPage } from '@/api/ruiying';
+  import { useDataDictStore } from '@/store/modules/dataDict';
+
+  const dictStore = useDataDictStore();
+
+  const fillStatusDict = reactive({});
+  const approvalStatusDict = reactive({
+    0: '待提交',
+    10: '待审批',
+    20: '审批通过',
+    30: '审批拒绝',
+  });
+  const constructionStatusDict = reactive({});
+
+  const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px');
+  const inputStyles = reactive({
+    backgroundColor: '#FFFFFF',
+    color: '#797979',
+  });
+
+  const orderName = ref('');
+
+  const paging = ref(null);
+  const dataList = ref([]);
+
+  const queryList = (pageNo, pageSize) => {
+    getRuiYingReportPage({
+      pageNo,
+      pageSize,
+      taskName: orderName.value,
+      contractName: orderName.value,
+      projectClassification: '2',
+    })
+      .then(res => {
+        paging.value.complete(res.data.list);
+      })
+      .catch(() => {
+        paging.value.complete(false);
+      });
+  };
+
+  const searchList = () => {
+    paging.value.reload();
+  };
+
+  const navigatorDetail = item => {
+    uni.navigateTo({
+      url: '/pages/ruiyingx/detail?id=' + item.id + `&type=${type.value}-detail`,
+    });
+  };
+  const navigatorEdit = item => {
+    uni.navigateTo({
+      url: type.value === 'edit' ? '/pages/ruiyingx/edit?id=' + item.id : '/pages/ruiyingx/approval?id=' + item.id,
+    });
+  };
+
+  const formatDate = time => {
+    return dayjs(time).format('YYYY-MM-DD');
+  };
+  const formatTime = time => {
+    return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
+  };
+
+  const type = ref('edit');
+
+  onLoad(option => {
+    type.value = option.type || 'edit';
+  });
+
+  onShow(() => {
+    if (dictStore.dataDict.length <= 0) {
+      dictStore.loadDataDictList().then(() => {
+        dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+          fillStatusDict[item.value] = item.label;
+        });
+        dictStore.getStrDictOptions('repairStatus').map(item => {
+          constructionStatusDict[item.value] = item.label;
+        });
+      });
+    } else {
+      dictStore.getStrDictOptions('operation_fill_order_status').map(item => {
+        fillStatusDict[item.value] = item.label;
+      });
+      dictStore.getStrDictOptions('repairStatus').map(item => {
+        constructionStatusDict[item.value] = item.label;
+      });
+    }
+    nextTick(() => {
+      searchList();
+    });
+  });
+</script>
+
+<template>
+  <z-paging class="page" ref="paging" v-model="dataList" @query="queryList">
+    <template #top>
+      <view class="top">
+        <uni-easyinput
+          v-model="orderName"
+          :styles="inputStyles"
+          :placeholderStyle="placeholderStyle"
+          :placeholder="$t('operation.searchText')">
+        </uni-easyinput>
+        <button class="mini-btn" type="primary" size="mini" @click="searchList">
+          {{ $t('operation.search') }}
+        </button>
+      </view>
+    </template>
+    <view class="list">
+      <view class="item" v-for="(item, index) in dataList" :key="index">
+        <view class="header">
+          <span class="create-time">{{ item.createTime ? formatDate(item.createTime) : '' }}</span>
+          <span class="fill-status" :class="`status-${item.status}`">{{ fillStatusDict[item.status] }}</span>
+        </view>
+        <view class="content">
+          <view class="content-item">
+            <span class="label">日期:</span>
+            <span>{{ item.createTime ? formatTime(item.createTime) : '' }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">项目:</span>
+            <span>{{ item.contractName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">任务:</span>
+            <span>{{ item.taskName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工队伍:</span>
+            <span>{{ item.deptName }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">施工状态:</span>
+            <span :class="{ constructionStatus: item.repairStatus }">{{
+              constructionStatusDict[item.repairStatus]
+            }}</span>
+          </view>
+          <view class="content-item">
+            <span class="label">审批状态:</span>
+            <span class="auditStatus" :class="`status-${item.auditStatus}`">{{
+              approvalStatusDict[item.auditStatus]
+            }}</span>
+          </view>
+        </view>
+        <view class="footer">
+          <button class="button" size="mini" type="primary" plain="true" @click="navigatorDetail(item)">
+            {{ $t('operation.view') }}
+          </button>
+          <!-- 填写 -->
+          <button class="button" size="mini" type="primary" @click="navigatorEdit(item)">
+            {{ type === 'edit' ? $t('operation.fill') : $t('operation.approve') }}
+          </button>
+        </view>
+      </view>
+    </view>
+  </z-paging>
+</template>
+
+<style scoped>
+  .page {
+    padding: 10px;
+  }
+
+  .top {
+    height: 40px;
+    background: #f3f5f9;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 20px;
+  }
+
+  :deep(.mini-btn) {
+    height: 38px !important;
+    font-size: 16px !important;
+  }
+
+  .list {
+    margin-top: 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .item {
+      background-color: #fff;
+      padding: 10px;
+      border-radius: 8px;
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      .header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 16px;
+        font-weight: 500;
+
+        .fill-status {
+          font-size: 14px;
+          font-weight: 500;
+          display: inline-flex;
+          padding: 5px;
+          border-radius: 4px;
+
+          &.status-0 {
+            color: #ff4d4f;
+            background: rgba(255, 77, 79, 0.2);
+          }
+          &.status-2 {
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          &.status-1 {
+            color: #00c250;
+            background: rgba(0, 194, 80, 0.2);
+          }
+        }
+      }
+
+      .content {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+
+        .content-item {
+          font-size: 14px;
+          font-weight: 400;
+
+          .label {
+            display: inline-block;
+            font-weight: 500;
+            width: 70px;
+          }
+
+          .constructionStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+            color: #4096ff;
+            background: rgba(64, 150, 255, 0.2);
+          }
+
+          .auditStatus {
+            font-size: 14px;
+            font-weight: 500;
+            display: inline-flex;
+            padding: 5px;
+            border-radius: 4px;
+
+            &.status-0 {
+              color: #595959;
+              background: rgba(89, 89, 89, 0.2);
+            }
+
+            &.status-30 {
+              color: #ff4d4f;
+              background: rgba(255, 77, 79, 0.2);
+            }
+            &.status-10 {
+              color: #4096ff;
+              background: rgba(64, 150, 255, 0.2);
+            }
+
+            &.status-20 {
+              color: #00c250;
+              background: rgba(0, 194, 80, 0.2);
+            }
+          }
+        }
+      }
+
+      .footer {
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+        gap: 0 12px;
+        height: 32px;
+
+        .button {
+          margin: 0;
+        }
+      }
+    }
+  }
+</style>

+ 0 - 1
store/modules/dataDict.js

@@ -12,7 +12,6 @@ export const useDataDictStore = defineStore('dataDict', () => {
   const loadDataDictList = async () => {
     dataDict.value = (await getList()).data;
   };
-
   /**
    * 获取type对应字典列表
    * @param type

+ 11 - 0
uni_modules/uv-divider/changelog.md

@@ -0,0 +1,11 @@
+## 1.0.4(2023-12-06)
+1. 优化
+## 1.0.3(2023-12-06)
+1. 阻止事件冒泡问题
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-divider 分割线

+ 45 - 0
uni_modules/uv-divider/components/uv-divider/props.js

@@ -0,0 +1,45 @@
+export default {
+	props: {
+		// 是否虚线
+		dashed: {
+			type: Boolean,
+			default: false
+		},
+		// 是否细线
+		hairline: {
+			type: Boolean,
+			default: true
+		},
+		// 是否以点替代文字,优先于text字段起作用
+		dot: {
+			type: Boolean,
+			default: false
+		},
+		// 内容文本的位置,left-左边,center-中间,right-右边
+		textPosition: {
+			type: String,
+			default: 'center'
+		},
+		// 文本内容
+		text: {
+			type: [String, Number],
+			default: ''
+		},
+		// 文本大小
+		textSize: {
+			type: [String, Number],
+			default: 14
+		},
+		// 文本颜色
+		textColor: {
+			type: String,
+			default: '#909399'
+		},
+		// 线条颜色
+		lineColor: {
+			type: String,
+			default: '#dcdfe6'
+		},
+		...uni.$uv?.props?.divider
+	}
+}

+ 113 - 0
uni_modules/uv-divider/components/uv-divider/uv-divider.vue

@@ -0,0 +1,113 @@
+<template>
+	<view 
+		class="uv-divider"
+		:style="[$uv.addStyle(customStyle)]"
+		@tap="click"
+		>
+		<uv-line 
+			:color="lineColor"
+			:customStyle="leftLineStyle"
+			:hairline="hairline"
+			:dashed="dashed"></uv-line>
+		<text 
+			v-if="dot"
+			class="uv-divider__dot"
+		>●</text>
+		<text 
+			v-else-if="text"
+			class="uv-divider__text"
+			:style="[textStyle]"
+		>{{text}}</text>
+		<uv-line 
+			:color="lineColor"
+			:customStyle="rightLineStyle"
+			:hairline="hairline"
+			:dashed="dashed"
+		></uv-line>
+	</view>
+</template>
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * divider 分割线
+	 * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
+	 * @tutorial https://www.uvui.cn/components/divider.html
+	 * @property {Boolean}			dashed			是否虚线 (默认 false )
+	 * @property {Boolean}			hairline		是否细线 (默认  true )
+	 * @property {Boolean}			dot				是否以点替代文字,优先于text字段起作用 (默认 false )
+	 * @property {String}			textPosition	内容文本的位置,left-左边,center-中间,right-右边 (默认 'center' )
+	 * @property {String | Number}	text			文本内容
+	 * @property {String | Number}	textSize		文本大小 (默认 14)
+	 * @property {String}			textColor		文本颜色 (默认 '#909399' )
+	 * @property {String}			lineColor		线条颜色 (默认 '#dcdfe6' )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 *
+	 * @event {Function}	click	divider组件被点击时触发
+	 * @example <uv-divider :color="color">锦瑟无端五十弦</uv-divider>
+	 */
+	export default {
+		name: 'uv-divider',
+		mixins: [mpMixin, mixin, props],
+		emits: ['click'],
+		computed: {
+			textStyle() {
+				const style = {}
+				style.fontSize = this.$uv.addUnit(this.textSize)
+				style.color = this.textColor
+				return style
+			},
+			// 左边线条的的样式
+			leftLineStyle() {
+				const style = {}
+				// 如果是在左边,设置左边的宽度为固定值
+				if (this.textPosition === 'left') {
+					style.width = '80rpx'
+				} else {
+					style.flex = 1
+				}
+				return style
+			},
+			// 右边线条的的样式
+			rightLineStyle() {
+				const style = {}
+				// 如果是在右边,设置右边的宽度为固定值
+				if (this.textPosition === 'right') {
+					style.width = '80rpx'
+				} else {
+					style.flex = 1
+				}
+				return style
+			}
+		},
+		methods: {
+			// divider组件被点击时触发
+			click() {
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	$uv-divider-margin: 15px 0 !default;
+	$uv-divider-text-margin: 0 15px !default;
+	$uv-divider-dot-font-size: 12px !default;
+	$uv-divider-dot-margin: 0 12px !default;
+	$uv-divider-dot-color: #c0c4cc !default;
+	.uv-divider {
+		@include flex;
+		flex-direction: row;
+		align-items: center;
+		margin: $uv-divider-margin;
+		&__text {
+			margin: $uv-divider-text-margin;
+		}
+		&__dot {
+			font-size: $uv-divider-dot-font-size;
+			margin: $uv-divider-dot-margin;
+			color: $uv-divider-dot-color;
+		}
+	}
+</style>

+ 88 - 0
uni_modules/uv-divider/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uv-divider",
+  "displayName": "uv-divider 分割线 全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.4",
+  "description": "区隔内容的分割线,一般用于页面底部没有更多的提示。",
+  "keywords": [
+    "divider",
+    "uvui",
+    "uv-ui",
+    "分割线",
+    "没有更多"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-line"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-divider/readme.md

@@ -0,0 +1,11 @@
+## Divider 分割线
+
+> **组件名:uv-divider**
+
+区隔内容的分割线,一般用于页面底部"没有更多"的提示。
+
+### <a href="https://www.uvui.cn/components/divider.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 5 - 0
uni_modules/uv-line/changelog.md

@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增线条组件

+ 34 - 0
uni_modules/uv-line/components/uv-line/props.js

@@ -0,0 +1,34 @@
+export default {
+	props: {
+		color: {
+			type: String,
+			default: '#d6d7d9'
+		},
+		// 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等
+		length: {
+			type: [String, Number],
+			default: '100%'
+		},
+		// 线条方向,col-竖向,row-横向
+		direction: {
+			type: String,
+			default: 'row'
+		},
+		// 是否显示细边框
+		hairline: {
+			type: Boolean,
+			default: true
+		},
+		// 线条与上下左右元素的间距,字符串形式,如"30px"、"20px 30px"
+		margin: {
+			type: [String, Number],
+			default: 0
+		},
+		// 是否虚线,true-虚线,false-实线
+		dashed: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.line
+	}
+}

+ 60 - 0
uni_modules/uv-line/components/uv-line/uv-line.vue

@@ -0,0 +1,60 @@
+<template>
+	<view
+	    class="uv-line"
+	    :style="[lineStyle]"
+	>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * line 线条
+	 * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单
+	 * @tutorial https://www.uvui.cn/components/line.html
+	 * @property {String}			color		线条的颜色 ( 默认 '#d6d7d9' )
+	 * @property {String | Number}	length		长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 ( 默认 '100%' )
+	 * @property {String}			direction	线条的方向,row-横向,col-竖向 (默认 'row' )
+	 * @property {Boolean}			hairline	是否显示细线条 (默认 true )
+	 * @property {String | Number}	margin		线条与上下左右元素的间距,字符串形式,如"30px"  (默认 0 )
+	 * @property {Boolean}			dashed		是否虚线,true-虚线,false-实线 (默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <uv-line color="red"></uv-line>
+	 */
+	export default {
+		name: 'uv-line',
+		mixins: [mpMixin, mixin, props],
+		computed: {
+			lineStyle() {
+				const style = {}
+				style.margin = this.margin
+				// 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了
+				if (this.direction === 'row') {
+					// 此处采用兼容分开写,兼容nvue的写法
+					style.borderBottomWidth = '1px'
+					style.borderBottomStyle = this.dashed ? 'dashed' : 'solid'
+					style.width = this.$uv.addUnit(this.length)
+					if (this.hairline) style.transform = 'scaleY(0.5)'
+				} else {
+					// 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了
+					style.borderLeftWidth = '1px'
+					style.borderLeftStyle = this.dashed ? 'dashed' : 'solid'
+					style.height = this.$uv.addUnit(this.length)
+					if (this.hairline) style.transform = 'scaleX(0.5)'
+				}
+				style.borderColor = this.color
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uv-line {
+		/* #ifndef APP-NVUE */
+		vertical-align: middle;
+		/* #endif */
+	}
+</style>

+ 87 - 0
uni_modules/uv-line/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-line",
+  "displayName": "uv-line 线条  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.1",
+  "description": "uv-line 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单。",
+  "keywords": [
+    "uv-line",
+    "uvui",
+    "uv-ui",
+    "line",
+    "线条"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-line/readme.md

@@ -0,0 +1,11 @@
+## Line 线条
+
+> **组件名:uv-line**
+
+此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单。
+
+### <a href="https://www.uvui.cn/components/line.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 9 - 0
utils/navigate.js

@@ -6,6 +6,7 @@ import { getRepairDetail } from '@/api/repair';
  * @param data
  */
 export const messageNavigate = async data => {
+  console.log('data :>> ', data);
   if (!data.userId) {
     data.userId = '';
   }
@@ -69,5 +70,13 @@ export const messageNavigate = async data => {
     uni.navigateTo({
       url: `/pages/ruiDu/approval?id=${data.businessId}`,
     });
+  } else if (data.type === 'rhDailyReport') {
+    uni.navigateTo({
+      url: `/pages/ruihen/edit?id=${data.businessId}`,
+    });
+  } else if (data.type === 'rhReportApproval') {
+    uni.navigateTo({
+      url: `/pages/ruihen/approval?id=${data.businessId}`,
+    });
   }
 };