Преглед изворни кода

Merge branch 'ruiheng' of shuzhihua/pms-app into master

yanghao пре 1 недеља
родитељ
комит
7a08bfae87

+ 20 - 0
App.vue

@@ -1,5 +1,6 @@
 <script>
 import { initAppDatabase } from '@/utils/appDb';
+import { runWgtHotUpdate } from '@/utils/hot-update';
 
 export default {
 	onLaunch: (options) => {
@@ -46,6 +47,25 @@ export default {
 			}
 		});
 		// #endif
+
+		// #ifdef APP-PLUS
+		/**
+		 * 这里是本次新增的 .wgt 热更新入口。
+		 *
+		 * 为什么放在 App.vue 的 onLaunch:
+		 * 1. 这是整个项目真正的应用启动入口,热更新要尽量早执行,就应该接在这里。
+		 * 2. 现有项目的旧升级逻辑写在页面组件里,只会在登录页 / 首页 mounted 后才触发,
+		 *    对“启动即检查热更新”这个目标来说太晚了。
+		 * 3. 放在这里还能保证热更新和数据库初始化、scheme 监听这类“应用级逻辑”处于同一层级,
+		 *    后续维护时更容易看清启动链路。
+		 *
+		 * 为什么这里不 await:
+		 * 1. 原有 onLaunch 里的数据库初始化本身就是非阻塞调用,项目没有把启动流程串行化。
+		 * 2. 热更新检查失败时我们希望“静默降级,不阻断原有启动”。
+		 * 3. 因此这里直接触发即可,让原有启动逻辑保持原样继续往下走。
+		 */
+		runWgtHotUpdate();
+		// #endif
 	},
 	onExit: () => {
 		// #ifdef APP

+ 11 - 0
api/qhse.js

@@ -0,0 +1,11 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取设备实时数据列表
+ * @param params
+ */
+export const getDetail = (id) =>
+  request({
+    url: `/rq/iot-accident-report/get?id=` + id,
+    method: 'GET',
+  })

+ 22 - 0
api/ruiHengReport.js

@@ -0,0 +1,22 @@
+
+import { request } from "@/utils/request";
+
+
+// 瑞恒日报
+export const getRhRate = (params) => {
+  return request({
+    url: "/rq/stat/rh/device/utilizationRate",
+    method: "get",
+    params,
+  });
+};
+
+// 队伍明细
+export const getRhTeamDetail = (params) => {
+  return request({
+    url: "rq/stat/rh/device/teamUtilizationRate",
+    method: "get",
+    params,
+  });
+};
+

+ 9 - 0
api/ruiYingReport.js

@@ -0,0 +1,9 @@
+import { request } from "@/utils/request";
+
+export const getRyProductionBriefs = (params) => {
+  return request({
+    url: "/pms/iot-ry-daily-report/productionBriefs",
+    method: "get",
+    params,
+  });
+};

+ 1 - 0
api/ruihen.js

@@ -121,3 +121,4 @@ export const selectedDeptsEmployee = (params) => {
     params,
   });
 };
+

+ 87 - 39
components/upgrade.vue

@@ -5,14 +5,12 @@
     type="center"
     :animation="false"
     :mask-click="false"
-    style="z-index: 999"
-  >
+    style="z-index: 999">
     <view class="upgrade-popup">
       <image
         class="header-bg"
         src="../static/common/upgrade_bg.png"
-        mode="widthFix"
-      ></image>
+        mode="widthFix"></image>
       <view class="main">
         <view class="version"
           >{{ t("version.newVersion") }}{{ versionName }}</view
@@ -51,11 +49,15 @@
   </uni-popup>
 </template>
 
-<script>
-import { getAppVersion } from "@/api/app.js";
-import { checkVersion, downloadApp, installApp } from "@/utils/upgrade.js";
-// 引用全局变量$t
-import { getCurrentInstance } from "vue";
+<script>
+import {
+  isHotUpdateRunning,
+  HOT_UPDATE_FALLBACK_APP_CHECK_EVENT,
+} from "@/utils/hot-update.js";
+import { getAppVersion } from "@/api/app.js";
+import { checkVersion, downloadApp, installApp } from "@/utils/upgrade.js";
+// 引用全局变量$t
+import { getCurrentInstance } from "vue";
 
 export default {
   data() {
@@ -66,13 +68,15 @@ export default {
       downloadUrl: "", //APP下载链接
       isDownloadFinish: false, //是否下载完成
       hasProgress: false, //是否能显示进度条
-      currentPercent: 0, //当前下载百分比
-      isStartDownload: false, //是否开始下载
-      fileName: "", //下载后app本地路径名称
-
-      t: null, // 全局翻译函数
-    };
-  },
+      currentPercent: 0, //当前下载百分比
+      isStartDownload: false, //是否开始下载
+      fileName: "", //下载后app本地路径名称
+      hasTriggeredStartupAppCheck: false,
+      pendingHotUpdateFallbackAppCheck: false,
+
+      t: null, // 全局翻译函数
+    };
+  },
   computed: {
     //设置进度条样式,实时更新进度位置
     setProStyle() {
@@ -95,36 +99,80 @@ export default {
     if (options.from == "backbutton") {
       return true;
     }
-  },
-  mounted() {
-    //检测版本
-    // #ifdef APP
-    this.appCheckVersion();
-    // #endif
-    const { appContext } = getCurrentInstance();
-    const t = appContext.config.globalProperties.$t;
-    this.t = t;
-  },
-  created() {
-    uni.$on("upgrade-app", this.bindEmit);
-  },
-  beforeDestroy() {
-    uni.$off("upgrade-app", this.bindEmit);
-  },
-  methods: {
-    async appCheckVersion() {
-      console.log("🚀 ~ appCheckVersion ~ appCheckVersion:");
+  },
+  mounted() {
+    const { appContext } = getCurrentInstance();
+    const t = appContext.config.globalProperties.$t;
+    this.t = t;
+
+    //检测版本
+    // #ifdef APP
+    /**
+     * 这里保留原来的页面级整包更新检查,但增加一个非常轻量的互斥判断。
+     *
+     * 为什么要加这层判断:
+     * 1. 当前项目原本就在登录页和首页自动挂载这个组件。
+     * 2. 本次新增的 .wgt 热更新改到了 App 启动阶段,如果这里还无条件发起旧检查,
+     *    启动时就可能同时出现“热更新弹窗”和“整包升级弹窗”。
+     * 3. 这不是为了废掉原有逻辑,而是为了让原有逻辑在“启动热更新正在执行”时先让路,
+     *    避免两个升级流程互相打架。
+     *
+     * 不加这段代码会怎样:
+     * - App.vue onLaunch 触发热更新检查
+     * - 页面 mounted 又触发旧的整包升级检查
+     * - 用户可能连续看到两个升级提示,甚至两个流程同时下载,体验会非常差
+     */
 
-      const { data: remote } = await getAppVersion();
-      console.log("checkVersion-remote", remote);
-      const up = checkVersion({
+    if (this.pendingHotUpdateFallbackAppCheck) {
+      this.triggerStartupAppCheckOnce();
+    } else if (!isHotUpdateRunning()) {
+      this.triggerStartupAppCheckOnce();
+    } else {
+      console.log(
+        "skip page upgrade check because startup hot update is running"
+      );
+    }
+    // #endif
+  },
+  created() {
+    uni.$on("upgrade-app", this.bindEmit);
+    uni.$on(
+      HOT_UPDATE_FALLBACK_APP_CHECK_EVENT,
+      this.handleHotUpdateFallbackAppCheck
+    );
+  },
+  beforeDestroy() {
+    uni.$off("upgrade-app", this.bindEmit);
+    uni.$off(
+      HOT_UPDATE_FALLBACK_APP_CHECK_EVENT,
+      this.handleHotUpdateFallbackAppCheck
+    );
+  },
+  methods: {
+    triggerStartupAppCheckOnce() {
+      if (this.hasTriggeredStartupAppCheck) return;
+      this.hasTriggeredStartupAppCheck = true;
+      this.appCheckVersion();
+    },
+    handleHotUpdateFallbackAppCheck() {
+      if (!this.t) {
+        this.pendingHotUpdateFallbackAppCheck = true;
+        return;
+      }
+
+      this.pendingHotUpdateFallbackAppCheck = false;
+      this.triggerStartupAppCheckOnce();
+    },
+    async appCheckVersion() {
+      const { data: remote } = await getAppVersion();
+      const up = await checkVersion({
         name: remote.appVersion, //最新版本名称
         code: remote.appVersion, //最新版本号
         content: "", //更新内容
         url: remote.url, //下载链接
         forceUpdate: true, //是否强制升级
       });
-      console.log("🚀 ~ appCheckVersion ~ up:", up);
+
       if (up) {
         this.open();
       }

+ 62 - 59
locale/en.json

@@ -93,17 +93,17 @@
 	"home.receiveMaintenanceWorkOrderAndSubmit": "Receive maintenance work orders and submit",
 	"home.equipmentMaintenance": "Equipment maintenance",
 	"home.fillMaintenanceWorkOrder": "Fill in maintenance work orders",
-	"home.inspectionWorkOrder": "Inspection work order",
-	"home.receiveInspectionWorkOrderAndSubmit": "Receive inspection work orders and submit",
-	"home.faultReporting": "Fault reporting",
-	"home.fillAndReportFaultWorkOrder": "Fill in and report fault work orders",
-	"home.ruiDuReport": "RuiDu report",
-	"home.ruiDuReportTip": "View daily report data",
-	"home.inventoryQuery": "Inventory query",
-	"home.clickToQueryInventoryData": "Click to query inventory data",
-	"home.maintenanceSearch": "Maintenance query",
-	"home.maintenanceSearchTip": "View equipment maintenance distance",
-	"home.equipmentLedger": "Equipment ledger",
+	"home.inspectionWorkOrder": "Inspection work order",
+	"home.receiveInspectionWorkOrderAndSubmit": "Receive inspection work orders and submit",
+	"home.faultReporting": "Fault reporting",
+	"home.fillAndReportFaultWorkOrder": "Fill in and report fault work orders",
+	"home.ruiDuReport": "RuiDu report",
+	"home.ruiDuReportTip": "View daily report data",
+	"home.inventoryQuery": "Inventory query",
+	"home.clickToQueryInventoryData": "Click to query inventory data",
+	"home.maintenanceSearch": "Maintenance query",
+	"home.maintenanceSearchTip": "View equipment maintenance distance",
+	"home.equipmentLedger": "Equipment ledger",
 	"home.viewEquipmentLedger": "View equipment ledger",
 	"home.equipmentStatusChange": "Equipment status change",
 	"home.deviceUser": "Equipment responsible person",
@@ -133,52 +133,54 @@
 	"user.passwordError1": "New password is the same as old password",
 	"user.passwordError2": "New password does not match confirm password",
 	"operationRecordFilling.responsiblePerson": "Responsible person",
-	"operationRecordFilling.workOrderName": "Work order name",
-	"operationRecordFilling.belongToTeam": "Belonging team",
-	"operationRecordFilling.totalRunningTime": "Total running time",
-	"operationRecordFilling.plcNotice": "The following values are from PLC, please modify if inconsistent",
-	"operationRecordFilling.workOrderDevice": "Work order equipment",
-	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Filled content cannot be greater than",
-	"ruiDuReport.selectTitle": "Select RuiDu report",
-	"ruiDuReport.dailyDetail": "Daily detail",
-	"ruiDuReport.dailyTeamStatistic": "Daily single-team statistics",
-	"ruiDuReport.filterAction": "Filter",
-	"ruiDuReport.filterTitle": "Filter conditions",
-	"ruiDuReport.searchKey": "Search key",
-	"ruiDuReport.searchKeyPlaceholder": "Please enter search key",
-	"ruiDuReport.project": "Project",
-	"ruiDuReport.projectPlaceholder": "Please enter project",
-	"ruiDuReport.task": "Task",
-	"ruiDuReport.taskPlaceholder": "Please enter task",
-	"ruiDuReport.dept": "Department",
-	"ruiDuReport.constructionBrief": "Brief",
-	"ruiDuReport.createTime": "Create time",
-	"maintenanceSearch.title": "Maintenance query",
-	"maintenanceSearch.deviceCode": "Equipment code",
-	"maintenanceSearch.deviceCodePlaceholder": "Please enter equipment code",
-	"maintenanceSearch.deviceName": "Equipment name",
-	"maintenanceSearch.deviceNamePlaceholder": "Please enter equipment name",
-	"maintenanceSearch.deviceStatus": "Equipment status",
-	"maintenanceSearch.totalRunTime": "Total runtime H",
-	"maintenanceSearch.totalMileage": "Total mileage KM",
-	"maintenanceSearch.multiAttrs": "Multi-attribute totals",
-	"maintenanceSearch.noMaintenancePlan": "No maintenance plan",
-	"maintenanceSearch.generatedNotExecuted": "Generated, not executed",
-	"maintenanceSearch.notGenerated": "Not generated",
-	"maintenanceSearch.detailTitle": "Maintenance item details",
-	"maintenanceSearch.maintenanceDuration": "Maintenance duration",
-	"maintenanceSearch.maintenanceMileage": "Maintenance mileage",
-	"maintenanceSearch.maintenanceDate": "Maintenance date",
-	"maintenanceSearch.lastMaintenanceOperationTime": "Last maintenance runtime",
-	"maintenanceSearch.runTimeCycle": "Runtime cycle",
-	"maintenanceSearch.lastMaintenanceMileage": "Last maintenance mileage",
-	"maintenanceSearch.mileageCycle": "Mileage cycle",
-	"maintenanceSearch.lastMaintenanceNaturalDate": "Last natural date",
-	"maintenanceSearch.naturalDateCycle": "Natural day cycle",
-	"maintenanceSearch.nextMaintTime": "Time to next maintenance",
-	"maintenanceSearch.nextMaintKil": "Mileage to next maintenance",
-	"maintenanceSearch.nextMaintDate": "Next maintenance date",
-	"workOrder.addDevice": "Add equipment",
+	"operationRecordFilling.workOrderName": "Work order name",
+	"operationRecordFilling.belongToTeam": "Belonging team",
+	"operationRecordFilling.totalRunningTime": "Total running time",
+	"operationRecordFilling.plcNotice": "The following values are from PLC, please modify if inconsistent",
+	"operationRecordFilling.workOrderDevice": "Work order equipment",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Filled content cannot be greater than",
+	"ruiDuReport.selectTitle": "Select RuiDu report",
+	"ruiHengReport.selectTitle": "Select RuiHeng report",
+	"ruiYingReport.selectTitle": "Select RuiYing report",
+	"ruiDuReport.dailyDetail": "Daily detail",
+	"ruiDuReport.dailyTeamStatistic": "Daily single-team statistics",
+	"ruiDuReport.filterAction": "Filter",
+	"ruiDuReport.filterTitle": "Filter conditions",
+	"ruiDuReport.searchKey": "Search key",
+	"ruiDuReport.searchKeyPlaceholder": "Please enter search key",
+	"ruiDuReport.project": "Project",
+	"ruiDuReport.projectPlaceholder": "Please enter project",
+	"ruiDuReport.task": "Task",
+	"ruiDuReport.taskPlaceholder": "Please enter task",
+	"ruiDuReport.dept": "Department",
+	"ruiDuReport.constructionBrief": "Brief",
+	"ruiDuReport.createTime": "Create time",
+	"maintenanceSearch.title": "Maintenance query",
+	"maintenanceSearch.deviceCode": "Equipment code",
+	"maintenanceSearch.deviceCodePlaceholder": "Please enter equipment code",
+	"maintenanceSearch.deviceName": "Equipment name",
+	"maintenanceSearch.deviceNamePlaceholder": "Please enter equipment name",
+	"maintenanceSearch.deviceStatus": "Equipment status",
+	"maintenanceSearch.totalRunTime": "Total runtime H",
+	"maintenanceSearch.totalMileage": "Total mileage KM",
+	"maintenanceSearch.multiAttrs": "Multi-attribute totals",
+	"maintenanceSearch.noMaintenancePlan": "No maintenance plan",
+	"maintenanceSearch.generatedNotExecuted": "Generated, not executed",
+	"maintenanceSearch.notGenerated": "Not generated",
+	"maintenanceSearch.detailTitle": "Maintenance item details",
+	"maintenanceSearch.maintenanceDuration": "Maintenance duration",
+	"maintenanceSearch.maintenanceMileage": "Maintenance mileage",
+	"maintenanceSearch.maintenanceDate": "Maintenance date",
+	"maintenanceSearch.lastMaintenanceOperationTime": "Last maintenance runtime",
+	"maintenanceSearch.runTimeCycle": "Runtime cycle",
+	"maintenanceSearch.lastMaintenanceMileage": "Last maintenance mileage",
+	"maintenanceSearch.mileageCycle": "Mileage cycle",
+	"maintenanceSearch.lastMaintenanceNaturalDate": "Last natural date",
+	"maintenanceSearch.naturalDateCycle": "Natural day cycle",
+	"maintenanceSearch.nextMaintTime": "Time to next maintenance",
+	"maintenanceSearch.nextMaintKil": "Mileage to next maintenance",
+	"maintenanceSearch.nextMaintDate": "Next maintenance date",
+	"workOrder.addDevice": "Add equipment",
 	"workOrder.addMaterial": "Add material",
 	"workOrder.selectMaterial": "Select material",
 	"workOrder.materialDetails": "Material details",
@@ -525,5 +527,6 @@
 	"overtime.type1": "Operation record",
 	"overtime.type2": "Maintenance work order",
 	"overtime.type3": "Upkeep work order",
-	"overtime.type4": "Inspection work order"
-}
+	"overtime.type4": "Inspection work order",
+	"qhse.detail.title": "Incident Event Details"
+}

+ 59 - 56
locale/ja.json

@@ -93,17 +93,17 @@
 	"home.receiveMaintenanceWorkOrderAndSubmit": "保養作業指示書を受け取り、提出",
 	"home.equipmentMaintenance": "機器修理",
 	"home.fillMaintenanceWorkOrder": "修理作業指示書を入力",
-	"home.inspectionWorkOrder": "検査作業指示書",
-	"home.receiveInspectionWorkOrderAndSubmit": "検査作業指示書を受け取り、提出",
-	"home.faultReporting": "故障報告",
-	"home.fillAndReportFaultWorkOrder": "故障作業指示書を入力して報告",
-	"home.ruiDuReport": "瑞都レポート",
-	"home.ruiDuReportTip": "日報データを表示",
-	"home.inventoryQuery": "在庫照会",
-	"home.clickToQueryInventoryData": "クリックして在庫データを照会",
-	"home.maintenanceSearch": "保養照会",
-	"home.maintenanceSearchTip": "機器の保養距離を表示",
-	"home.equipmentLedger": "機器台帳",
+	"home.inspectionWorkOrder": "検査作業指示書",
+	"home.receiveInspectionWorkOrderAndSubmit": "検査作業指示書を受け取り、提出",
+	"home.faultReporting": "故障報告",
+	"home.fillAndReportFaultWorkOrder": "故障作業指示書を入力して報告",
+	"home.ruiDuReport": "瑞都レポート",
+	"home.ruiDuReportTip": "日報データを表示",
+	"home.inventoryQuery": "在庫照会",
+	"home.clickToQueryInventoryData": "クリックして在庫データを照会",
+	"home.maintenanceSearch": "保養照会",
+	"home.maintenanceSearchTip": "機器の保養距離を表示",
+	"home.equipmentLedger": "機器台帳",
 	"home.viewEquipmentLedger": "機器台帳を表示",
 	"home.equipmentStatusChange": "機器状態変更",
 	"home.deviceUser": "機器担当者",
@@ -136,49 +136,51 @@
 	"operationRecordFilling.workOrderName": "作業指示書名",
 	"operationRecordFilling.belongToTeam": "所属チーム",
 	"operationRecordFilling.totalRunningTime": "累計運行時間",
-	"operationRecordFilling.plcNotice": "以下の数値はPLCから取得したものです。不一致がある場合は修正してください",
-	"operationRecordFilling.workOrderDevice": "作業指示書機器",
-	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "入力内容は次の値を超えることができません",
-	"ruiDuReport.selectTitle": "瑞都レポートを選択",
-	"ruiDuReport.dailyDetail": "日報詳細",
-	"ruiDuReport.dailyTeamStatistic": "日報単一チーム統計",
-	"ruiDuReport.filterAction": "絞り込み",
-	"ruiDuReport.filterTitle": "フィルター条件",
-	"ruiDuReport.searchKey": "検索条件",
-	"ruiDuReport.searchKeyPlaceholder": "検索条件を入力してください",
-	"ruiDuReport.project": "プロジェクト",
-	"ruiDuReport.projectPlaceholder": "プロジェクトを入力してください",
-	"ruiDuReport.task": "タスク",
-	"ruiDuReport.taskPlaceholder": "タスクを入力してください",
-	"ruiDuReport.dept": "部門",
-	"ruiDuReport.constructionBrief": "施工概要",
-	"ruiDuReport.createTime": "作成時間",
-	"maintenanceSearch.title": "保養照会",
-	"maintenanceSearch.deviceCode": "機器コード",
-	"maintenanceSearch.deviceCodePlaceholder": "機器コードを入力してください",
-	"maintenanceSearch.deviceName": "機器名",
-	"maintenanceSearch.deviceNamePlaceholder": "機器名を入力してください",
-	"maintenanceSearch.deviceStatus": "機器状態",
-	"maintenanceSearch.totalRunTime": "累計運行時間H",
-	"maintenanceSearch.totalMileage": "累計運行距離KM",
-	"maintenanceSearch.multiAttrs": "複数属性の累計値",
-	"maintenanceSearch.noMaintenancePlan": "保養計画なし",
-	"maintenanceSearch.generatedNotExecuted": "生成済み未実行",
-	"maintenanceSearch.notGenerated": "未生成",
-	"maintenanceSearch.detailTitle": "保養項目詳細",
-	"maintenanceSearch.maintenanceDuration": "保養時間",
-	"maintenanceSearch.maintenanceMileage": "保養距離",
-	"maintenanceSearch.maintenanceDate": "保養日付",
-	"maintenanceSearch.lastMaintenanceOperationTime": "前回保養運行時間",
-	"maintenanceSearch.runTimeCycle": "運行時間周期",
-	"maintenanceSearch.lastMaintenanceMileage": "前回保養距離",
-	"maintenanceSearch.mileageCycle": "運行距離周期",
-	"maintenanceSearch.lastMaintenanceNaturalDate": "前回保養自然日",
-	"maintenanceSearch.naturalDateCycle": "自然日周期",
-	"maintenanceSearch.nextMaintTime": "次回保養までの時間",
-	"maintenanceSearch.nextMaintKil": "次回保養までの距離",
-	"maintenanceSearch.nextMaintDate": "次回保養日",
-	"workOrder.addDevice": "機器を追加",
+	"operationRecordFilling.plcNotice": "以下の数値はPLCから取得したものです。不一致がある場合は修正してください",
+	"operationRecordFilling.workOrderDevice": "作業指示書機器",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "入力内容は次の値を超えることができません",
+	"ruiDuReport.selectTitle": "瑞都レポートを選択",
+	"ruiHengReport.selectTitle": "瑞恒レポートを選択",
+	"ruiYingReport.selectTitle": "瑞鹰レポートを選択",
+	"ruiDuReport.dailyDetail": "日報詳細",
+	"ruiDuReport.dailyTeamStatistic": "日報単一チーム統計",
+	"ruiDuReport.filterAction": "絞り込み",
+	"ruiDuReport.filterTitle": "フィルター条件",
+	"ruiDuReport.searchKey": "検索条件",
+	"ruiDuReport.searchKeyPlaceholder": "検索条件を入力してください",
+	"ruiDuReport.project": "プロジェクト",
+	"ruiDuReport.projectPlaceholder": "プロジェクトを入力してください",
+	"ruiDuReport.task": "タスク",
+	"ruiDuReport.taskPlaceholder": "タスクを入力してください",
+	"ruiDuReport.dept": "部門",
+	"ruiDuReport.constructionBrief": "施工概要",
+	"ruiDuReport.createTime": "作成時間",
+	"maintenanceSearch.title": "保養照会",
+	"maintenanceSearch.deviceCode": "機器コード",
+	"maintenanceSearch.deviceCodePlaceholder": "機器コードを入力してください",
+	"maintenanceSearch.deviceName": "機器名",
+	"maintenanceSearch.deviceNamePlaceholder": "機器名を入力してください",
+	"maintenanceSearch.deviceStatus": "機器状態",
+	"maintenanceSearch.totalRunTime": "累計運行時間H",
+	"maintenanceSearch.totalMileage": "累計運行距離KM",
+	"maintenanceSearch.multiAttrs": "複数属性の累計値",
+	"maintenanceSearch.noMaintenancePlan": "保養計画なし",
+	"maintenanceSearch.generatedNotExecuted": "生成済み未実行",
+	"maintenanceSearch.notGenerated": "未生成",
+	"maintenanceSearch.detailTitle": "保養項目詳細",
+	"maintenanceSearch.maintenanceDuration": "保養時間",
+	"maintenanceSearch.maintenanceMileage": "保養距離",
+	"maintenanceSearch.maintenanceDate": "保養日付",
+	"maintenanceSearch.lastMaintenanceOperationTime": "前回保養運行時間",
+	"maintenanceSearch.runTimeCycle": "運行時間周期",
+	"maintenanceSearch.lastMaintenanceMileage": "前回保養距離",
+	"maintenanceSearch.mileageCycle": "運行距離周期",
+	"maintenanceSearch.lastMaintenanceNaturalDate": "前回保養自然日",
+	"maintenanceSearch.naturalDateCycle": "自然日周期",
+	"maintenanceSearch.nextMaintTime": "次回保養までの時間",
+	"maintenanceSearch.nextMaintKil": "次回保養までの距離",
+	"maintenanceSearch.nextMaintDate": "次回保養日",
+	"workOrder.addDevice": "機器を追加",
 	"workOrder.addMaterial": "資材を追加",
 	"workOrder.selectMaterial": "資材を選択",
 	"workOrder.materialDetails": "資材詳細",
@@ -525,5 +527,6 @@
 	"overtime.type1": "運行記録",
 	"overtime.type2": "修理作業指示書",
 	"overtime.type3": "保養作業指示書",
-	"overtime.type4": "検査作業指示書"
-}
+	"overtime.type4": "検査作業指示書",
+	"qhse.detail.title": "事故イベント詳細"
+}

+ 59 - 56
locale/ru.json

@@ -93,17 +93,17 @@
 	"home.receiveMaintenanceWorkOrderAndSubmit": "Получить рабочий заказ на обслуживание и отправить",
 	"home.equipmentMaintenance": "Ремонт оборудования",
 	"home.fillMaintenanceWorkOrder": "Заполнение рабочего заказа на ремонт",
-	"home.inspectionWorkOrder": "Рабочий заказ на инспекцию",
-	"home.receiveInspectionWorkOrderAndSubmit": "Получить рабочий заказ на инспекцию и отправить",
-	"home.faultReporting": "Сообщение о неисправности",
-	"home.fillAndReportFaultWorkOrder": "Заполнение и отправка рабочего заказа на неисправность",
-	"home.ruiDuReport": "Отчет RuiDu",
-	"home.ruiDuReportTip": "Просмотр данных ежедневного отчета",
-	"home.inventoryQuery": "Запрос инвентаря",
-	"home.clickToQueryInventoryData": "Нажмите, чтобы запросить данные инвентаря",
-	"home.maintenanceSearch": "Запрос обслуживания",
-	"home.maintenanceSearchTip": "Просмотр расстояния до обслуживания",
-	"home.equipmentLedger": "Книга учета оборудования",
+	"home.inspectionWorkOrder": "Рабочий заказ на инспекцию",
+	"home.receiveInspectionWorkOrderAndSubmit": "Получить рабочий заказ на инспекцию и отправить",
+	"home.faultReporting": "Сообщение о неисправности",
+	"home.fillAndReportFaultWorkOrder": "Заполнение и отправка рабочего заказа на неисправность",
+	"home.ruiDuReport": "Отчет RuiDu",
+	"home.ruiDuReportTip": "Просмотр данных ежедневного отчета",
+	"home.inventoryQuery": "Запрос инвентаря",
+	"home.clickToQueryInventoryData": "Нажмите, чтобы запросить данные инвентаря",
+	"home.maintenanceSearch": "Запрос обслуживания",
+	"home.maintenanceSearchTip": "Просмотр расстояния до обслуживания",
+	"home.equipmentLedger": "Книга учета оборудования",
 	"home.viewEquipmentLedger": "Просмотр книги учета оборудования",
 	"home.equipmentStatusChange": "Изменение статуса оборудования",
 	"home.deviceUser": "Ответственный за оборудование",
@@ -136,49 +136,51 @@
 	"operationRecordFilling.workOrderName": "Название рабочего заказа",
 	"operationRecordFilling.belongToTeam": "Принадлежащая команда",
 	"operationRecordFilling.totalRunningTime": "Общее время работы",
-	"operationRecordFilling.plcNotice": "Следующие значения получены из PLC, при несоответствии исправьте",
-	"operationRecordFilling.workOrderDevice": "Оборудование рабочего заказа",
-	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Заполненное содержимое не может превышать",
-	"ruiDuReport.selectTitle": "Выберите отчет RuiDu",
-	"ruiDuReport.dailyDetail": "Детали ежедневного отчета",
-	"ruiDuReport.dailyTeamStatistic": "Статистика ежедневного отчета по бригаде",
-	"ruiDuReport.filterAction": "Фильтр",
-	"ruiDuReport.filterTitle": "Фильтры",
-	"ruiDuReport.searchKey": "Ключ поиска",
-	"ruiDuReport.searchKeyPlaceholder": "Введите ключ поиска",
-	"ruiDuReport.project": "Проект",
-	"ruiDuReport.projectPlaceholder": "Введите проект",
-	"ruiDuReport.task": "Задача",
-	"ruiDuReport.taskPlaceholder": "Введите задачу",
-	"ruiDuReport.dept": "Отдел",
-	"ruiDuReport.constructionBrief": "Сводка",
-	"ruiDuReport.createTime": "Время создания",
-	"maintenanceSearch.title": "Запрос обслуживания",
-	"maintenanceSearch.deviceCode": "Код оборудования",
-	"maintenanceSearch.deviceCodePlaceholder": "Введите код оборудования",
-	"maintenanceSearch.deviceName": "Название оборудования",
-	"maintenanceSearch.deviceNamePlaceholder": "Введите название оборудования",
-	"maintenanceSearch.deviceStatus": "Статус оборудования",
-	"maintenanceSearch.totalRunTime": "Общее время работы H",
-	"maintenanceSearch.totalMileage": "Общий пробег KM",
-	"maintenanceSearch.multiAttrs": "Итоги по нескольким атрибутам",
-	"maintenanceSearch.noMaintenancePlan": "Нет плана обслуживания",
-	"maintenanceSearch.generatedNotExecuted": "Создано, не выполнено",
-	"maintenanceSearch.notGenerated": "Не создано",
-	"maintenanceSearch.detailTitle": "Детали обслуживания",
-	"maintenanceSearch.maintenanceDuration": "Длительность обслуживания",
-	"maintenanceSearch.maintenanceMileage": "Пробег обслуживания",
-	"maintenanceSearch.maintenanceDate": "Дата обслуживания",
-	"maintenanceSearch.lastMaintenanceOperationTime": "Последнее время работы",
-	"maintenanceSearch.runTimeCycle": "Цикл времени работы",
-	"maintenanceSearch.lastMaintenanceMileage": "Последний пробег",
-	"maintenanceSearch.mileageCycle": "Цикл пробега",
-	"maintenanceSearch.lastMaintenanceNaturalDate": "Последняя календарная дата",
-	"maintenanceSearch.naturalDateCycle": "Календарный цикл",
-	"maintenanceSearch.nextMaintTime": "Время до обслуживания",
-	"maintenanceSearch.nextMaintKil": "Пробег до обслуживания",
-	"maintenanceSearch.nextMaintDate": "Дата следующего обслуживания",
-	"workOrder.addDevice": "Добавить оборудование",
+	"operationRecordFilling.plcNotice": "Следующие значения получены из PLC, при несоответствии исправьте",
+	"operationRecordFilling.workOrderDevice": "Оборудование рабочего заказа",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Заполненное содержимое не может превышать",
+	"ruiDuReport.selectTitle": "Выберите отчет RuiDu",
+	"ruiHengReport.selectTitle": "Выберите отчет RuiHeng",
+	"ruiYingReport.selectTitle": "Выберите отчет RuiYing",
+	"ruiDuReport.dailyDetail": "Детали ежедневного отчета",
+	"ruiDuReport.dailyTeamStatistic": "Статистика ежедневного отчета по бригаде",
+	"ruiDuReport.filterAction": "Фильтр",
+	"ruiDuReport.filterTitle": "Фильтры",
+	"ruiDuReport.searchKey": "Ключ поиска",
+	"ruiDuReport.searchKeyPlaceholder": "Введите ключ поиска",
+	"ruiDuReport.project": "Проект",
+	"ruiDuReport.projectPlaceholder": "Введите проект",
+	"ruiDuReport.task": "Задача",
+	"ruiDuReport.taskPlaceholder": "Введите задачу",
+	"ruiDuReport.dept": "Отдел",
+	"ruiDuReport.constructionBrief": "Сводка",
+	"ruiDuReport.createTime": "Время создания",
+	"maintenanceSearch.title": "Запрос обслуживания",
+	"maintenanceSearch.deviceCode": "Код оборудования",
+	"maintenanceSearch.deviceCodePlaceholder": "Введите код оборудования",
+	"maintenanceSearch.deviceName": "Название оборудования",
+	"maintenanceSearch.deviceNamePlaceholder": "Введите название оборудования",
+	"maintenanceSearch.deviceStatus": "Статус оборудования",
+	"maintenanceSearch.totalRunTime": "Общее время работы H",
+	"maintenanceSearch.totalMileage": "Общий пробег KM",
+	"maintenanceSearch.multiAttrs": "Итоги по нескольким атрибутам",
+	"maintenanceSearch.noMaintenancePlan": "Нет плана обслуживания",
+	"maintenanceSearch.generatedNotExecuted": "Создано, не выполнено",
+	"maintenanceSearch.notGenerated": "Не создано",
+	"maintenanceSearch.detailTitle": "Детали обслуживания",
+	"maintenanceSearch.maintenanceDuration": "Длительность обслуживания",
+	"maintenanceSearch.maintenanceMileage": "Пробег обслуживания",
+	"maintenanceSearch.maintenanceDate": "Дата обслуживания",
+	"maintenanceSearch.lastMaintenanceOperationTime": "Последнее время работы",
+	"maintenanceSearch.runTimeCycle": "Цикл времени работы",
+	"maintenanceSearch.lastMaintenanceMileage": "Последний пробег",
+	"maintenanceSearch.mileageCycle": "Цикл пробега",
+	"maintenanceSearch.lastMaintenanceNaturalDate": "Последняя календарная дата",
+	"maintenanceSearch.naturalDateCycle": "Календарный цикл",
+	"maintenanceSearch.nextMaintTime": "Время до обслуживания",
+	"maintenanceSearch.nextMaintKil": "Пробег до обслуживания",
+	"maintenanceSearch.nextMaintDate": "Дата следующего обслуживания",
+	"workOrder.addDevice": "Добавить оборудование",
 	"workOrder.addMaterial": "Добавить материал",
 	"workOrder.selectMaterial": "Выбрать материал",
 	"workOrder.materialDetails": "Детали материала",
@@ -525,5 +527,6 @@
 	"overtime.type1": "Операционная запись",
 	"overtime.type2": "Ремонтный рабочий заказ",
 	"overtime.type3": "Рабочий заказ на обслуживание",
-	"overtime.type4": "Рабочий заказ на инспекцию"
-}
+	"overtime.type4": "Рабочий заказ на инспекцию",
+	"qhse.detail.title": "Детали QHSE"
+}

+ 16 - 11
locale/zh-Hans.json

@@ -128,6 +128,8 @@
   "home.dailyReportRuiDu": "瑞都日报",
   "home.dailyReportRuiDuTip": "填写日报",
   "home.ruiDuReport": "瑞都报表",
+  "home.ruiHengReport": "瑞恒报表",
+  "home.ruiYingReport": "瑞鹰报表",
   "home.ruiDuReportTip": "查看日报报表",
   "home.dailyReportRuiHen": "瑞恒日报",
   "home.dailyReportRuiHenTip": "填写日报",
@@ -162,7 +164,7 @@
   "user.modifyPhoneAndPassword": "修改手机和密码",
   "user.aboutUs": "关于我们",
   "user.currentVersion": "当前版本",
-  "user.logout": "退出",
+  "user.logout": "退出登录",
   "user.userInfo": "用户信息",
   "user.updatePassword": "修改密码",
   "user.oldPassword": "旧密码",
@@ -188,17 +190,19 @@
   "operationRecordFilling.PleaseLoadAllItems": "请加载所有填报项后再提交",
   // --------------------------------------- 瑞都报表 ----------------------------------------
   "ruiDuReport.selectTitle": "选择瑞都报表",
+  "ruiHengReport.selectTitle": "选择瑞恒报表",
+  "ruiYingReport.selectTitle": "选择瑞鹰报表",
   "ruiDuReport.dailyDetail": "日报详情",
   "ruiDuReport.dailyTeamStatistic": "日报单井队统计",
-  "ruiDuReport.filterAction": "筛选",
-  "ruiDuReport.filterTitle": "筛选条件",
-  "ruiDuReport.searchKey": "查询条件",
-  "ruiDuReport.searchKeyPlaceholder": "请输入查询条件",
-  "ruiDuReport.project": "项目",
-  "ruiDuReport.projectPlaceholder": "请输入项目",
-  "ruiDuReport.task": "任务",
-  "ruiDuReport.taskPlaceholder": "请输入任务",
-  "ruiDuReport.dept": "部门",
+  "ruiDuReport.filterAction": "筛选",
+  "ruiDuReport.filterTitle": "筛选条件",
+  "ruiDuReport.searchKey": "查询条件",
+  "ruiDuReport.searchKeyPlaceholder": "请输入查询条件",
+  "ruiDuReport.project": "项目",
+  "ruiDuReport.projectPlaceholder": "请输入项目",
+  "ruiDuReport.task": "任务",
+  "ruiDuReport.taskPlaceholder": "请输入任务",
+  "ruiDuReport.dept": "部门",
   "ruiDuReport.constructionBrief": "施工简报",
   "ruiDuReport.createTime": "创建时间",
   // --------------------------------------- 保养查询 ----------------------------------------
@@ -752,5 +756,6 @@
   "overtime.type1": "运行记录",
   "overtime.type2": "维修工单",
   "overtime.type3": "保养工单",
-  "overtime.type4": "巡检工单"
+  "overtime.type4": "巡检工单",
+  "qhse.detail.title": "事故事件详情"
 }

+ 17 - 11
locale/zh-Hant.json

@@ -84,6 +84,8 @@
   "home.faultReporting": "故障上报",
   "home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
   "home.ruiDuReport": "瑞都报表",
+  "home.ruiHengReport": "瑞恒报表",
+   "home.ruiYingReport": "瑞鹰报表",
   "home.ruiDuReportTip": "查看日报报表",
   "home.inventoryQuery": "库存查询",
   "home.clickToQueryInventoryData": "点击查询库存数据",
@@ -106,7 +108,7 @@
   "user.modifyPhoneAndPassword": "修改手机和密码",
   "user.aboutUs": "关于我们",
   "user.currentVersion": "当前版本",
-  "user.logout": "退出",
+  "user.logout": "退出登录",
   "user.userInfo": "用户信息",
   "user.updatePassword": "修改密码",
   "user.oldPassword": "旧密码",
@@ -124,17 +126,19 @@
   "operationRecordFilling.belongToTeam": "所属队伍",
   "operationRecordFilling.totalRunningTime": "累计运行时间",
   "ruiDuReport.selectTitle": "选择瑞都报表",
+  "ruiHengReport.selectTitle": "选择瑞恒报表",
+  "ruiYingReport.selectTitle": "选择瑞鹰报表",
   "ruiDuReport.dailyDetail": "日报详情",
   "ruiDuReport.dailyTeamStatistic": "日报单井队统计",
-  "ruiDuReport.filterAction": "筛选",
-  "ruiDuReport.filterTitle": "筛选条件",
-  "ruiDuReport.searchKey": "查询条件",
-  "ruiDuReport.searchKeyPlaceholder": "请输入查询条件",
-  "ruiDuReport.project": "项目",
-  "ruiDuReport.projectPlaceholder": "请输入项目",
-  "ruiDuReport.task": "任务",
-  "ruiDuReport.taskPlaceholder": "请输入任务",
-  "ruiDuReport.dept": "部门",
+  "ruiDuReport.filterAction": "筛选",
+  "ruiDuReport.filterTitle": "筛选条件",
+  "ruiDuReport.searchKey": "查询条件",
+  "ruiDuReport.searchKeyPlaceholder": "请输入查询条件",
+  "ruiDuReport.project": "项目",
+  "ruiDuReport.projectPlaceholder": "请输入项目",
+  "ruiDuReport.task": "任务",
+  "ruiDuReport.taskPlaceholder": "请输入任务",
+  "ruiDuReport.dept": "部门",
   "ruiDuReport.constructionBrief": "施工简报",
   "ruiDuReport.createTime": "创建时间",
   "maintenanceSearch.title": "保养查询",
@@ -519,5 +523,7 @@
   "statistic.maintenance.orderType.title": "工单类型统计",
   "statistic.maintenance.orderType.status1": "临时新建",
   "statistic.maintenance.orderType.status2": "计划生成",
-  "statistic.inspection.workOrder.title": "巡检工单状态统计"
+  "statistic.inspection.workOrder.title": "巡检工单状态统计",
+
+  "qhse.detail.title": "事故事件详情"
 }

+ 129 - 129
manifest.json

@@ -1,130 +1,130 @@
 {
-    "name" : "DeepOil",
-    "appid" : "__UNI__6E4BC49",
-    "description" : "",
-    "versionName" : "1.3.2",
-    "versionCode" : 10302,
-    "transformPx" : false,
-    /* 5+App特有相关 */
-    "app-plus" : {
-        "usingComponents" : true,
-        "nvueStyleCompiler" : "uni-app",
-        "compilerVersion" : 3,
-        "splashscreen" : {
-            "alwaysShowBeforeRender" : true,
-            "waiting" : true,
-            "autoclose" : true,
-            "delay" : 0
-        },
-        "webpack" : {
-            "externals" : {
-                "dingtalk-jsapi" : "dingtalk-jsapi" // 告诉 webpack 不打包该依赖
-            }
-        },
-        /* 模块配置 */
-        "modules" : {
-            "VideoPlayer" : {
-                "enabled" : true,
-                "mode" : "default",
-                "orientation" : "auto",
-                "background" : "#000000",
-                "controls" : true,
-                "autoPlay" : false,
-                "loop" : false,
-                "showFullscreenBtn" : true,
-                "showPlayBtn" : true,
-                "showCenterPlayBtn" : true,
-                "showProgress" : true,
-                "objectFit" : "contain"
-            },
-            "SQLite" : {}
-        },
-        /* 应用发布信息 */
-        "distribute" : {
-            /* sdk */
-            "sdkConfigs" : {
-                "oauth" : {
-                    "dingtalk" : {
-                        "appid" : "dingcrhejkptu0mcsw3r",
-                        "universalLinks" : "http://1.94.244.160:70/"
-                    }
-                }
-            },
-            /* android打包配置 */
-            "android" : {
-                "permissions" : [
-                    "<uses-feature android:name=\"android.hardware.camera\"/>",
-                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
-                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
-                    // "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
-                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
-                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
-                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
-                    "<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
-                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
-                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
-                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_SMS\"/>",
-                    "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
-                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
-                    "<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
-                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
-                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
-                    "<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
-                ],
-                "schemes" : "deepoil"
-            },
-            /* ios打包配置 */
-            "ios" : {
-                "dSYMs" : false
-            },
-            /* SDK配置 */
-            "sdkConfigs__UNI__6E4BC49\t" : {},
-            "splashscreen" : {
-                "useOriginalMsgbox" : false
-            }
-        }
-    },
-    /* 快应用特有相关 */
-    "quickapp" : {},
-    /* 小程序特有相关 */
-    "mp-weixin" : {
-        "appid" : "",
-        "setting" : {
-            "urlCheck" : false
-        },
-        "usingComponents" : true
-    },
-    "mp-alipay" : {
-        "usingComponents" : true
-    },
-    "mp-baidu" : {
-        "usingComponents" : true
-    },
-    "mp-toutiao" : {
-        "usingComponents" : true
-    },
-    "uniStatistics" : {
-        "enable" : false
-    },
-    "vueVersion" : "3",
-    "h5" : {
-        "router" : {
-            "base" : "./"
-        }
-    }
-}
+	"name": "DeepOil",
+	"appid": "__UNI__6E4BC49",
+	"description": "",
+	"versionName": "1.3.2",
+	"versionCode": 10302,
+	"transformPx": false,
+	/* 5+App特有相关 */
+	"app-plus": {
+		"usingComponents": true,
+		"nvueStyleCompiler": "uni-app",
+		"compilerVersion": 3,
+		"splashscreen": {
+			"alwaysShowBeforeRender": true,
+			"waiting": true,
+			"autoclose": true,
+			"delay": 0
+		},
+		"webpack": {
+			"externals": {
+				"dingtalk-jsapi": "dingtalk-jsapi" // 告诉 webpack 不打包该依赖
+			}
+		},
+		/* 模块配置 */
+		"modules": {
+			"VideoPlayer": {
+				"enabled": true,
+				"mode": "default",
+				"orientation": "auto",
+				"background": "#000000",
+				"controls": true,
+				"autoPlay": false,
+				"loop": false,
+				"showFullscreenBtn": true,
+				"showPlayBtn": true,
+				"showCenterPlayBtn": true,
+				"showProgress": true,
+				"objectFit": "contain"
+			},
+			"SQLite": {}
+		},
+		/* 应用发布信息 */
+		"distribute": {
+			/* sdk */
+			"sdkConfigs": {
+				"oauth": {
+					"dingtalk": {
+						"appid": "dingcrhejkptu0mcsw3r",
+						"universalLinks": "http://1.94.244.160:70/"
+					}
+				}
+			},
+			/* android打包配置 */
+			"android": {
+				"permissions": [
+					"<uses-feature android:name=\"android.hardware.camera\"/>",
+					"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+					"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+					// "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+					"<uses-permission android:name=\"android.permission.CAMERA\"/>",
+					"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+					"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+					"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
+					"<uses-permission android:name=\"android.permission.INTERNET\"/>",
+					"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+					"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
+					"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
+					"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+					"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
+					"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
+					"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
+					"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
+				],
+				"schemes": "deepoil"
+			},
+			/* ios打包配置 */
+			"ios": {
+				"dSYMs": false
+			},
+			/* SDK配置 */
+			"sdkConfigs__UNI__6E4BC49\t": {},
+			"splashscreen": {
+				"useOriginalMsgbox": false
+			}
+		}
+	},
+	/* 快应用特有相关 */
+	"quickapp": {},
+	/* 小程序特有相关 */
+	"mp-weixin": {
+		"appid": "",
+		"setting": {
+			"urlCheck": false
+		},
+		"usingComponents": true
+	},
+	"mp-alipay": {
+		"usingComponents": true
+	},
+	"mp-baidu": {
+		"usingComponents": true
+	},
+	"mp-toutiao": {
+		"usingComponents": true
+	},
+	"uniStatistics": {
+		"enable": false
+	},
+	"vueVersion": "3",
+	"h5": {
+		"router": {
+			"base": "./"
+		}
+	}
+}

+ 541 - 516
pages.json

@@ -1,520 +1,545 @@
 {
-  "pages": [
-    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
-    {
-      // 登录
-      "path": "pages/user/login",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 首页
-      "path": "pages/home/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 我的
-      "path": "pages/user/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 修改手机和密码
-      "path": "pages/user/change",
-      "style": {
-        "navigationBarTitleText": "%user.modifyPhoneAndPassword%"
-      }
-    },
-    {
-      // 运行记录填报
-      "path": "pages/recordFilling/list",
-      "style": {
-        "navigationBarTitleText": "%home.operationRecordFilling%"
-      }
-    },
+	"pages": [
+		//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			// 登录
+			"path": "pages/user/login",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 入口
+			"path": "pages/entry/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 首页
+			"path": "pages/home/index",
+			"style": {
+				"navigationBarTitleText": "PMS",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#3d80de"
+			}
+		},
+		{
+			// 我的
+			"path": "pages/user/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 修改手机和密码
+			"path": "pages/user/change",
+			"style": {
+				"navigationBarTitleText": "%user.modifyPhoneAndPassword%"
+			}
+		},
+		{
+			// 运行记录填报
+			"path": "pages/recordFilling/list",
+			"style": {
+				"navigationBarTitleText": "%home.operationRecordFilling%"
+			}
+		},
 
-    {
-      // 运行记录填报详情
-      "path": "pages/recordFilling/detail",
-      "style": {
-        "navigationBarTitleText": "%home.operationRecordFilling%"
-      }
-    },
-    {
-      // 选择设备
-      "path": "pages/devide/multiple",
-      "style": {
-        "navigationBarTitleText": "%device.selectDevice%"
-      }
-    },
-    {
-      // 物料详情
-      "path": "pages/material/view",
-      "style": {
-        "navigationBarTitleText": "%workOrder.materialDetails%"
-      }
-    },
-    {
-      // 物料详情 - 维修工单
-      "path": "pages/material/repair-view",
-      "style": {
-        "navigationBarTitleText": "%workOrder.materialDetails%"
-      }
-    },
-    {
-      // 保养工单
-      "path": "pages/maintenance/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 保养工单创建
-      "path": "pages/maintenance/create",
-      "style": {
-        "navigationBarTitleText": "%maintenanceWorkOrder.createMaintenanceWorkOrder%"
-      }
-    },
-    {
-      // 保养工单 - 去保养
-      "path": "pages/maintenance/edit",
-      "style": {
-        "navigationBarTitleText": "%maintenanceWorkOrder.editMaintenanceWorkOrder%"
-      }
-    },
-    {
-      // 保养工单详情
-      "path": "pages/maintenance/detail",
-      "style": {
-        "navigationBarTitleText": "%maintenanceWorkOrder.viewMaintenanceWorkOrder%"
-      }
-    },
-    {
-      // 保养查询
-      "path": "pages/maintenance/search",
-      "style": {
-        "navigationBarTitleText": "%maintenanceSearch.title%"
-      }
-    },
-    {
-      // 设备维修
-      "path": "pages/repair/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 设备维修详情
-      "path": "pages/repair/detail",
-      "style": {
-        "navigationBarTitleText": "%equipmentMaintenance.viewWorkOrder%"
-      }
-    },
-    {
-      // 设备维修创建
-      "path": "pages/repair/create",
-      "style": {
-        "navigationBarTitleText": "%equipmentMaintenance.createWorkOrder%"
-      }
-    },
-    {
-      // 设备维修创建
-      "path": "pages/repair/edit",
-      "style": {
-        "navigationBarTitleText": "%equipmentMaintenance.editWorkOrder%"
-      }
-    },
-    {
-      // 故障上报
-      "path": "pages/fault/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 故障上报详情
-      "path": "pages/fault/detail",
-      "style": {
-        "navigationBarTitleText": "%fault.viewWorkOrder%"
-      }
-    },
-    {
-      // 故障上报创建
-      "path": "pages/fault/create",
-      "style": {
-        "navigationBarTitleText": "%fault.createWorkOrder%"
-      }
-    },
-    {
-      // 故障上报编辑
-      "path": "pages/fault/edit",
-      "style": {
-        "navigationBarTitleText": "%fault.editWorkOrder%"
-      }
-    },
-    {
-      // 巡检工单
-      "path": "pages/inspection/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 巡检工单详情
-      "path": "pages/inspection/detail",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 巡检工单详情
-      "path": "pages/inspection/edit",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/ruihen-task/index",
-      "style": {
-        "navigationBarTitleText": "%ruihen.taskTitle%"
-      }
-    },
-    {
-      "path": "pages/ruihen-task/create",
-      "style": {
-        "navigationBarTitleText": "%ruihen.taskCreateTitle%"
-      }
-    },
-    {
-      "path": "pages/ruihen-task/detail",
-      "style": {
-        "navigationBarTitleText": "%ruihen.taskDetailTitle%"
-      }
-    },
-    {
-      "path": "pages/ruihen-task/edit",
-      "style": {
-        "navigationBarTitleText": "%ruihen.taskEditTitle%"
-      }
-    },
-    {
-      "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",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.indexTitle%"
-      }
-    },
-    {
-      // 瑞都日报-列表
-      "path": "pages/ruiDu/create",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.createTitle%"
-      }
-    },
-    {
-      // 瑞都日报-编辑
-      "path": "pages/ruiDu/approval",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.approvalTitle%"
-      }
-    },
-    {
-      // 瑞都日报-详情
-      "path": "pages/ruiDu/detail",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.detailTitle%"
-      }
-    },
-    {
-      // 瑞都日报-编辑
-      "path": "pages/ruiDu/edit",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.editTitle%"
-      }
-    },
-    {
-      // 瑞都报表-日报详情
-      "path": "pages/ruiDuReport/daily-detail",
-      "style": {
-        "navigationBarTitleText": "%ruiDuReport.dailyDetail%"
-      }
-    },
-    {
-      // 瑞都报表-日报单井队统计
-      "path": "pages/ruiDuReport/daily-team-statistic",
-      "style": {
-        "navigationBarTitleText": "%ruiDuReport.dailyTeamStatistic%"
-      }
-    },
-    {
-      // 库存查询
-      "path": "pages/inventory/index",
-      "style": {
-        "navigationBarTitleText": "%inventory.title%"
-      }
-    },
-    {
-      // 库存查询-筛选
-      "path": "pages/inventory/search/index",
-      "style": {
-        "navigationBarTitleText": "%inventory.search.title%"
-      }
-    },
-    {
-      // 消息管理
-      "path": "pages/message/index",
-      "style": {
-        "navigationBarTitleText": "%message.title%"
-      }
-    },
-    {
-      // 消息管理详情
-      "path": "pages/message/detail/index",
-      "style": {
-        "navigationBarTitleText": "%message.title%"
-      }
-    },
-    {
-      // 状态变更列表
-      "path": "pages/statusChange/index",
-      "style": {
-        "navigationBarTitleText": "%statusChange.title%",
-        "app-plus": {
-          "titleNView": {
-            "buttons": [
-              {
-                "text": "%statusChange.insert%",
-                "color": "#004098",
-                "fontSize": "14px",
-                "width": "80px"
-              }
-            ]
-          }
-        }
-      }
-    },
-    {
-      // 状态变更列调整记录
-      "path": "pages/statusChange/history/index",
-      "style": {
-        "navigationBarTitleText": "%statusChange.history.title%"
-      }
-    },
-    {
-      // 状态变更新增
-      "path": "pages/statusChange/form/index",
-      "style": {
-        "navigationBarTitleText": "%statusChange.form.title%"
-      }
-    },
-    {
-      // 设备责任人列表
-      "path": "pages/deviceUser/index",
-      "style": {
-        "navigationBarTitleText": "%deviceUser.title%",
-        "app-plus": {
-          "titleNView": {
-            "buttons": [
-              {
-                "text": "%statusChange.insert%",
-                "color": "#004098",
-                "fontSize": "14px",
-                "width": "80px"
-              }
-            ]
-          }
-        }
-      }
-    },
-    {
-      // 设备责任人调整记录
-      "path": "pages/deviceUser/history/index",
-      "style": {
-        "navigationBarTitleText": "%deviceUser.history.title%"
-      }
-    },
-    {
-      // 设备责任人新增
-      "path": "pages/deviceUser/form/index",
-      "style": {
-        "navigationBarTitleText": "%deviceUser.form.title%"
-      }
-    },
-    {
-      // 实时数据监控
-      "path": "pages/realTimeData/index",
-      "style": {
-        "navigationBarTitleText": "%realTimeData.title%"
-      }
-    },
-    {
-      // 实时数据监控详情
-      "path": "pages/realTimeData/detail/index",
-      "style": {
-        "navigationBarTitleText": "%realTimeData.detail.title%"
-      }
-    },
-    {
-      // 实时数据监控详情
-      "path": "pages/realTimeData/chart/index",
-      "style": {
-        "navigationBarTitleText": "%realTimeData.detail.title%"
-      }
-    },
-    {
-      // 设备台账
-      "path": "pages/ledger/index",
-      "style": {
-        "navigationBarTitleText": "%ledger.title%"
-      }
-    },
-    {
-      // 新建设备台账
-      "path": "pages/ledger/form/index",
-      "style": {
-        "navigationBarTitleText": "%ledger.form.title%"
-      }
-    },
-    {
-      // 设备台账详情
-      "path": "pages/ledger/detail/index",
-      "style": {
-        "navigationBarTitleText": "%ledger.detail.title%"
-      }
-    },
-    {
-      // 统计分析
-      "path": "pages/statistic/index",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      // 超时工单列表
-      "path": "pages/overtime/index",
-      "style": {
-        "navigationBarTitleText": "%overtime.title%"
-      }
-    }
-  ],
+		{
+			// 运行记录填报详情
+			"path": "pages/recordFilling/detail",
+			"style": {
+				"navigationBarTitleText": "%home.operationRecordFilling%"
+			}
+		},
+		{
+			// 选择设备
+			"path": "pages/devide/multiple",
+			"style": {
+				"navigationBarTitleText": "%device.selectDevice%"
+			}
+		},
+		{
+			// 物料详情
+			"path": "pages/material/view",
+			"style": {
+				"navigationBarTitleText": "%workOrder.materialDetails%"
+			}
+		},
+		{
+			// 物料详情 - 维修工单
+			"path": "pages/material/repair-view",
+			"style": {
+				"navigationBarTitleText": "%workOrder.materialDetails%"
+			}
+		},
+		{
+			// 保养工单
+			"path": "pages/maintenance/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 保养工单创建
+			"path": "pages/maintenance/create",
+			"style": {
+				"navigationBarTitleText": "%maintenanceWorkOrder.createMaintenanceWorkOrder%"
+			}
+		},
+		{
+			// 保养工单 - 去保养
+			"path": "pages/maintenance/edit",
+			"style": {
+				"navigationBarTitleText": "%maintenanceWorkOrder.editMaintenanceWorkOrder%"
+			}
+		},
+		{
+			// 保养工单详情
+			"path": "pages/maintenance/detail",
+			"style": {
+				"navigationBarTitleText": "%maintenanceWorkOrder.viewMaintenanceWorkOrder%"
+			}
+		},
+		{
+			// 保养查询
+			"path": "pages/maintenance/search",
+			"style": {
+				"navigationBarTitleText": "%maintenanceSearch.title%"
+			}
+		},
+		{
+			// 设备维修
+			"path": "pages/repair/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 设备维修详情
+			"path": "pages/repair/detail",
+			"style": {
+				"navigationBarTitleText": "%equipmentMaintenance.viewWorkOrder%"
+			}
+		},
+		{
+			// 设备维修创建
+			"path": "pages/repair/create",
+			"style": {
+				"navigationBarTitleText": "%equipmentMaintenance.createWorkOrder%"
+			}
+		},
+		{
+			// 设备维修创建
+			"path": "pages/repair/edit",
+			"style": {
+				"navigationBarTitleText": "%equipmentMaintenance.editWorkOrder%"
+			}
+		},
+		{
+			// 故障上报
+			"path": "pages/fault/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 故障上报详情
+			"path": "pages/fault/detail",
+			"style": {
+				"navigationBarTitleText": "%fault.viewWorkOrder%"
+			}
+		},
+		{
+			// 故障上报创建
+			"path": "pages/fault/create",
+			"style": {
+				"navigationBarTitleText": "%fault.createWorkOrder%"
+			}
+		},
+		{
+			// 故障上报编辑
+			"path": "pages/fault/edit",
+			"style": {
+				"navigationBarTitleText": "%fault.editWorkOrder%"
+			}
+		},
+		{
+			// 巡检工单
+			"path": "pages/inspection/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 巡检工单详情
+			"path": "pages/inspection/detail",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 巡检工单详情
+			"path": "pages/inspection/edit",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/ruihen-task/index",
+			"style": {
+				"navigationBarTitleText": "%ruihen.taskTitle%"
+			}
+		},
+		{
+			"path": "pages/ruihen-task/create",
+			"style": {
+				"navigationBarTitleText": "%ruihen.taskCreateTitle%"
+			}
+		},
+		{
+			"path": "pages/ruihen-task/detail",
+			"style": {
+				"navigationBarTitleText": "%ruihen.taskDetailTitle%"
+			}
+		},
+		{
+			"path": "pages/ruihen-task/edit",
+			"style": {
+				"navigationBarTitleText": "%ruihen.taskEditTitle%"
+			}
+		},
+		{
+			"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",
+			"style": {
+				"navigationBarTitleText": "%ruiDu.indexTitle%"
+			}
+		},
+		{
+			// 瑞都日报-列表
+			"path": "pages/ruiDu/create",
+			"style": {
+				"navigationBarTitleText": "%ruiDu.createTitle%"
+			}
+		},
+		{
+			// 瑞都日报-编辑
+			"path": "pages/ruiDu/approval",
+			"style": {
+				"navigationBarTitleText": "%ruiDu.approvalTitle%"
+			}
+		},
+		{
+			// 瑞都日报-详情
+			"path": "pages/ruiDu/detail",
+			"style": {
+				"navigationBarTitleText": "%ruiDu.detailTitle%"
+			}
+		},
+		{
+			// 瑞都日报-编辑
+			"path": "pages/ruiDu/edit",
+			"style": {
+				"navigationBarTitleText": "%ruiDu.editTitle%"
+			}
+		},
+		{
+			// 瑞都报表-日报详情
+			"path": "pages/ruiDuReport/daily-detail",
+			"style": {
+				"navigationBarTitleText": "%ruiDuReport.dailyDetail%"
+			}
+		},
+		{
+			// 瑞都报表-日报单井队统计
+			"path": "pages/ruiDuReport/daily-team-statistic",
+			"style": {
+				"navigationBarTitleText": "%ruiDuReport.dailyTeamStatistic%"
+			}
+		},
+		{
+			// 瑞恒报表-日报详情
+			"path": "pages/ruiHengReport/index",
+			"style": {
+				"navigationBarTitleText": "%ruiDuReport.dailyDetail%"
+			}
+		},
+		{
+			// 瑞鹰报表-日报详情
+			"path": "pages/ruiYingReport/index",
+			"style": {
+				"navigationBarTitleText": "%ruiDuReport.dailyDetail%"
+			}
+		},
+		{
+			// 库存查询
+			"path": "pages/inventory/index",
+			"style": {
+				"navigationBarTitleText": "%inventory.title%"
+			}
+		},
+		{
+			// 库存查询-筛选
+			"path": "pages/inventory/search/index",
+			"style": {
+				"navigationBarTitleText": "%inventory.search.title%"
+			}
+		},
+		{
+			// 消息管理
+			"path": "pages/message/index",
+			"style": {
+				"navigationBarTitleText": "%message.title%"
+			}
+		},
+		{
+			// 消息管理详情
+			"path": "pages/message/detail/index",
+			"style": {
+				"navigationBarTitleText": "%message.title%"
+			}
+		},
+		{
+			// 状态变更列表
+			"path": "pages/statusChange/index",
+			"style": {
+				"navigationBarTitleText": "%statusChange.title%",
+				"app-plus": {
+					"titleNView": {
+						"buttons": [{
+							"text": "%statusChange.insert%",
+							"color": "#004098",
+							"fontSize": "14px",
+							"width": "80px"
+						}]
+					}
+				}
+			}
+		},
+		{
+			// 状态变更列调整记录
+			"path": "pages/statusChange/history/index",
+			"style": {
+				"navigationBarTitleText": "%statusChange.history.title%"
+			}
+		},
+		{
+			// 状态变更新增
+			"path": "pages/statusChange/form/index",
+			"style": {
+				"navigationBarTitleText": "%statusChange.form.title%"
+			}
+		},
+		{
+			// 设备责任人列表
+			"path": "pages/deviceUser/index",
+			"style": {
+				"navigationBarTitleText": "%deviceUser.title%",
+				"app-plus": {
+					"titleNView": {
+						"buttons": [{
+							"text": "%statusChange.insert%",
+							"color": "#004098",
+							"fontSize": "14px",
+							"width": "80px"
+						}]
+					}
+				}
+			}
+		},
+		{
+			// 设备责任人调整记录
+			"path": "pages/deviceUser/history/index",
+			"style": {
+				"navigationBarTitleText": "%deviceUser.history.title%"
+			}
+		},
+		{
+			// 设备责任人新增
+			"path": "pages/deviceUser/form/index",
+			"style": {
+				"navigationBarTitleText": "%deviceUser.form.title%"
+			}
+		},
+		{
+			// 实时数据监控
+			"path": "pages/realTimeData/index",
+			"style": {
+				"navigationBarTitleText": "%realTimeData.title%"
+			}
+		},
+		{
+			// 实时数据监控详情
+			"path": "pages/realTimeData/detail/index",
+			"style": {
+				"navigationBarTitleText": "%realTimeData.detail.title%"
+			}
+		},
+		{
+			// 实时数据监控详情
+			"path": "pages/realTimeData/chart/index",
+			"style": {
+				"navigationBarTitleText": "%realTimeData.detail.title%"
+			}
+		},
+		{
+			// 设备台账
+			"path": "pages/ledger/index",
+			"style": {
+				"navigationBarTitleText": "%ledger.title%"
+			}
+		},
+		{
+			// 新建设备台账
+			"path": "pages/ledger/form/index",
+			"style": {
+				"navigationBarTitleText": "%ledger.form.title%"
+			}
+		},
+		{
+			// 设备台账详情
+			"path": "pages/ledger/detail/index",
+			"style": {
+				"navigationBarTitleText": "%ledger.detail.title%"
+			}
+		},
+		{
+			// 统计分析
+			"path": "pages/statistic/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			// 超时工单列表
+			"path": "pages/overtime/index",
+			"style": {
+				"navigationBarTitleText": "%overtime.title%"
+			}
+		},
+		{
+			// qhse详情
+			"path": "pages/qhse/detail",
+			"style": {
+				"navigationBarTitleText": "%qhse.detail.title%"
+			}
+		}
+	],
 
-  "tabBar": {
-    // "height": "50px", //总高45 - 9px顶部padding
-    "height": "45px",
-    "iconWidth": "15px",
-    "fontSize": "14px",
-    "borderStyle": "white",
-    "backgroundColor": "white",
-    "color": "#999999",
-    "selectedColor": "#004098",
-    "list": [
-      {
-        "pagePath": "pages/home/index",
-        "iconPath": "/static/tabbar/shouye-weixuanzhong.png",
-        "selectedIconPath": "/static/tabbar/shouye-xuanzhong.png",
-        "text": "%app.home%"
-      },
-      {
-        "pagePath": "pages/user/index",
-        "iconPath": "/static/tabbar/wode-weixuanzhong.png",
-        "selectedIconPath": "/static/tabbar/wode-xuanzhong.png",
-        "text": "%app.user%"
-      }
-    ]
-  },
-  "globalStyle": {
-    "navigationBarTextStyle": "black",
-    "navigationBarTitleText": "%app.appName%",
-    "navigationBarBackgroundColor": "#F3F5F9",
-    "backgroundColor": "#F3F5F9",
-    "rpxCalcMaxDeviceWidth": 960,
-    "rpxCalcBaseDeviceWidth": 375,
-    "rpxCalcIncludeWidth": 750
-  },
-  "uniIdRouter": {},
-  "easycom": {
-    // 配置easycom组件自动引入
-    "autoscan": true, // 是否开启easycom组件的自动扫描
-    "custom": {
-      // 自定义扫描规则
-      // "^uni-(.*)": "uni_modules/uni-$1/uni-$1.vue"
-      "^global-(.*)": "@/components/global/$1.vue"
-    }
-  }
+	"tabBar": {
+		// "height": "50px", //总高45 - 9px顶部padding
+		"height": "70px",
+		"iconWidth": "15px",
+		"fontSize": "14px",
+		"borderStyle": "white",
+		"backgroundColor": "white",
+		"color": "#999999",
+		"selectedColor": "#004098",
+		"list": [{
+				"pagePath": "pages/entry/index",
+				"iconPath": "/static/tabbar/shouye-weixuanzhong.png",
+				"selectedIconPath": "/static/tabbar/shouye-xuanzhong.png",
+				"text": "%app.home%"
+			},
+			{
+				"pagePath": "pages/user/index",
+				"iconPath": "/static/tabbar/wode-weixuanzhong.png",
+				"selectedIconPath": "/static/tabbar/wode-xuanzhong.png",
+				"text": "%app.user%"
+			}
+		]
+	},
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "%app.appName%",
+		"navigationBarBackgroundColor": "#F3F5F9",
+		"backgroundColor": "#F3F5F9",
+		"rpxCalcMaxDeviceWidth": 960,
+		"rpxCalcBaseDeviceWidth": 375,
+		"rpxCalcIncludeWidth": 750
+	},
+	"uniIdRouter": {},
+	"easycom": {
+		// 配置easycom组件自动引入
+		"autoscan": true, // 是否开启easycom组件的自动扫描
+		"custom": {
+			// 自定义扫描规则
+			// "^uni-(.*)": "uni_modules/uni-$1/uni-$1.vue"
+			"^global-(.*)": "@/components/global/$1.vue"
+		}
+	}
 }

+ 837 - 0
pages/entry/index.vue

@@ -0,0 +1,837 @@
+<template>
+  <scroll-view class="entry-page" scroll-y>
+    <view class="hero">
+      <image class="hero-bg" src="/static/entry/bg.png" mode="widthFix" />
+      <view class="hero-mask"></view>
+      <view class="hero-content">
+        <view class="status-row"></view>
+
+        <view class="profile-row">
+          <view class="avatar-wrap">
+            <image
+              style="
+                width: 50px;
+                height: 50px;
+                background-color: transparent;
+                border-radius: 50%;
+              "
+              :src="
+                userInfo?.avatar ? userInfo?.avatar : '/static/user/avatar.png'
+              "
+            ></image>
+          </view>
+          <view class="profile-main">
+            <view class="greeting">{{ greetingText }},{{ displayName }}</view>
+          </view>
+          <view
+            class="notice-wrap"
+            @click="navigatorTo('/pages/message/index')"
+          >
+            <uni-badge :text="messageCount" absolute="rightTop" size="small">
+              <view class="notice-button">
+                <image
+                  src="~@/static/home/message.png"
+                  style="
+                    width: 15px;
+                    height: 15px;
+                    background-color: transparent;
+                  "
+                  @click="navigatorTo('/pages/message/index')"
+                />
+              </view>
+            </uni-badge>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="content-shell">
+      <view class="hero-title">工作台</view>
+      <view class="section-card">
+        <view class="section-title">核心功能</view>
+        <scroll-view class="core-scroll" scroll-x show-scrollbar="false">
+          <view class="core-list">
+            <view
+              v-for="item in coreFeatures"
+              :key="item.title"
+              class="core-item"
+              :class="{ active: item.active }"
+              @click="navigatorTo(item.path)"
+            >
+              <view class="core-icon">
+                <image
+                  :src="item.iconText"
+                  mode="aspectFit"
+                  style="
+                    width: 40px;
+                    height: 40px;
+                    background-color: transparent;
+                  "
+                />
+              </view>
+              <view class="core-name">{{ item.title }}</view>
+              <view class="core-desc">{{ item.desc }}</view>
+              <view class="core-indicator" v-if="item.active"></view>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="section-card">
+        <view class="section-title">PMS常用</view>
+        <view class="tool-grid">
+          <view
+            v-for="item in commonTools"
+            :key="item.title"
+            class="tool-item"
+            @click="navigatorTo(item.path)"
+          >
+            <image class="tool-icon" :src="item.icon" mode="aspectFit" />
+            <view class="tool-title">{{ item.title }}</view>
+          </view>
+        </view>
+      </view>
+
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-title">日报入口</view>
+          <view class="section-tip">
+            <uni-icons type="personadd" size="16" color="#246BFF" />
+            <text>按角色显示</text>
+          </view>
+        </view>
+        <view class="daily-list">
+          <view
+            v-if="!!userInfo.rhReportFlag"
+            class="daily-item"
+            @click="navigatorTo('/pages/ruihen/index?type=edit')"
+          >
+            <image
+              class="daily-icon"
+              src="/static/entry/ruiheng.png"
+              mode="aspectFit"
+            />
+            <view class="daily-content">
+              <view class="daily-title">瑞恒日报</view>
+
+              <view class="daily-subtitle-wrap">
+                <view class="daily-subtitle">进入瑞恒日报</view>
+                <uni-icons type="right" size="12" color="#8090a8" />
+              </view>
+            </view>
+          </view>
+
+          <view
+            class="daily-item"
+            v-if="!!userInfo.rdReportFlag"
+            @click="navigatorTo('/pages/ruiDu/index')"
+          >
+            <image
+              class="daily-icon"
+              src="/static/entry/ruidu.png"
+              mode="aspectFit"
+            />
+            <view class="daily-content">
+              <view class="daily-title">瑞都日报</view>
+
+              <view class="daily-subtitle-wrap">
+                <view class="daily-subtitle">进入瑞都日报</view>
+                <uni-icons type="right" size="12" color="#8090a8" />
+              </view>
+            </view>
+          </view>
+
+          <view
+            class="daily-item"
+            v-if="!!userInfo.ryReportFlag"
+            @click="navigatorTo('/pages/ruiying/index?type=edit')"
+          >
+            <image
+              class="daily-icon"
+              src="/static/entry/ruiying.png"
+              mode="aspectFit"
+            />
+            <view class="daily-content">
+              <view class="daily-title">瑞鹰日报</view>
+
+              <view class="daily-subtitle-wrap">
+                <view class="daily-subtitle">进入瑞鹰日报</view>
+                <uni-icons type="right" size="12" color="#8090a8" />
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </scroll-view>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import dayjs from "dayjs";
+import { getLoginUserInfo } from "@/api/login";
+import { getUnreadMessageCount } from "@/api/message";
+import { getOvertimeTaskList } from "@/api/task";
+import { getUserInfo } from "@/utils/auth";
+
+const userInfo = ref({});
+const messageCount = ref(0);
+const todoStats = ref({
+  maintenance: 0,
+  fault: 0,
+});
+
+const coreFeatures = [
+  {
+    title: "PMS",
+    desc: "设备生命周期管理",
+    iconText: "/static/entry/pms.png",
+    iconBg: "linear-gradient(135deg, #eaf2ff 0%, #d8e7ff 100%)",
+    iconColor: "#246BFF",
+    active: true,
+    path: "/pages/home/index",
+  },
+  {
+    title: "QHSE",
+    desc: "质量健康安全环保",
+    iconText: "/static/entry/qhse.png",
+    iconBg: "linear-gradient(135deg, #ecfff7 0%, #d8f8ea 100%)",
+    iconColor: "#1eb980",
+    path: "/pages/qhse/index",
+  },
+  {
+    title: "组态管理",
+    desc: "流程与仪表组态",
+    iconText: "/static/entry/zutai.png",
+    iconBg: "linear-gradient(135deg, #f1edff 0%, #e0d9ff 100%)",
+    iconColor: "#6d5efc",
+    path: "/pages/zutai/index",
+  },
+  {
+    title: "智慧连油",
+    desc: "润滑管理平台",
+    iconText: "/static/entry/lianyou.png",
+    iconBg: "linear-gradient(135deg, #e7fbff 0%, #d4f7fb 100%)",
+    iconColor: "#10a8c6",
+    path: "/pages/lianyou/index",
+  },
+
+  {
+    title: "生产运营双周会",
+    desc: "双周会管理与跟踪",
+    iconText: "/static/entry/yunying.png",
+    iconBg: "linear-gradient(135deg, #fff3e8 0%, #ffe3cc 100%)",
+    iconColor: "#ff8a1f",
+    path: "/pages/swh/index",
+  },
+];
+
+const commonTools = [
+  {
+    title: "运行记录",
+    icon: "/static/entry/record.png",
+    path: "/pages/recordFilling/list",
+  },
+  {
+    title: "保养工单",
+    icon: "/static/entry/baoyang.png",
+    path: "/pages/maintenance/index",
+  },
+  {
+    title: "设备维修",
+    icon: "/static/entry/weixiu.png",
+    path: "/pages/repair/index",
+  },
+  {
+    title: "巡检工单",
+    icon: "/static/entry/xunjian.png",
+    path: "/pages/inspection/index",
+  },
+  {
+    title: "故障上报",
+    icon: "/static/entry/guzhang.png",
+    path: "/pages/fault/index",
+  },
+  {
+    title: "库存管理",
+    icon: "/static/entry/kucun.png",
+    path: "/pages/inventory/index",
+  },
+  {
+    title: "保养查询",
+    icon: "/static/entry/byquery.png",
+    path: "/pages/maintenance/search",
+  },
+  {
+    title: "设备台账",
+    icon: "/static/entry/taizhang.png",
+    path: "/pages/ledger/index",
+  },
+  {
+    title: "设备状态变更",
+    icon: "/static/entry/status.png",
+    path: "/pages/statusChange/index",
+  },
+  {
+    title: "设备责任人",
+    icon: "/static/entry/zeren.png",
+    path: "/pages/deviceUser/index",
+  },
+];
+
+const dailyEntries = computed(() => [
+  {
+    title: "瑞恒日报",
+    subtitle: "进入瑞恒日报",
+    path: "/pages/ruihen/index?type=edit",
+    icon: "/static/entry/ruiheng.png",
+    visible: !!userInfo.value.rhReportFlag,
+  },
+  {
+    title: "瑞都日报",
+    subtitle: "进入瑞都日报",
+    path: "/pages/ruiDu/index",
+    icon: "/static/entry/ruidu.png",
+    visible: !!userInfo.value.rdReportFlag,
+  },
+  {
+    title: "瑞鹰日报",
+    subtitle: "进入瑞鹰日报",
+    path: "/pages/ruiying/index?type=edit",
+    icon: "/static/entry/ruiying.png",
+    visible: !!userInfo.value.ryReportFlag,
+  },
+]);
+
+const visibleDailyEntries = computed(() => {
+  const list = dailyEntries.value.filter((item) => item.visible);
+  if (list.length) return list.slice(0, 3);
+  return dailyEntries.value
+    .map((item) => ({ ...item, visible: true }))
+    .slice(0, 3);
+});
+
+const todoCards = computed(() => [
+  {
+    title: "保养工单",
+    icon: "/static/home/baoyang.png",
+    count: todoStats.value.maintenance,
+    color: "#1eb980",
+  },
+  {
+    title: "故障上报",
+    icon: "/static/home/guzhang.png",
+    count: todoStats.value.fault,
+    color: "#ff4d4f",
+  },
+]);
+
+const currentTimeText = computed(() => dayjs().format("H:mm"));
+
+const greetingText = computed(() => {
+  const hour = dayjs().hour();
+  if (hour < 12) return "上午好";
+  if (hour < 18) return "下午好";
+  return "晚上好";
+});
+
+const displayName = computed(
+  () => userInfo.value.nickname || userInfo.value.username || "张工",
+);
+const avatarText = computed(() =>
+  String(displayName.value || "张").slice(0, 1),
+);
+
+const projectName = computed(
+  () =>
+    userInfo.value.deptName ||
+    userInfo.value.postGroup ||
+    userInfo.value.stationName ||
+    "智慧工厂示例项目",
+);
+
+const parseCachedUserInfo = () => {
+  const cache = getUserInfo();
+  if (!cache) return {};
+  try {
+    const parsed = typeof cache === "string" ? JSON.parse(cache) : cache;
+    if (typeof parsed === "string") {
+      return JSON.parse(parsed)?.user || {};
+    }
+    return parsed?.user || parsed || {};
+  } catch (error) {
+    return {};
+  }
+};
+
+const buildTodoStats = (list) => {
+  const stats = {
+    maintenance: 0,
+    fault: 0,
+  };
+
+  list.forEach((item) => {
+    const typeText = String(item?.type || "");
+    if (typeText.includes("保养")) {
+      stats.maintenance += 1;
+    }
+    if (typeText.includes("故障")) {
+      stats.fault += 1;
+    }
+  });
+
+  return stats;
+};
+
+const loadPageData = async () => {
+  userInfo.value = parseCachedUserInfo();
+
+  try {
+    const [userRes, messageRes, todoRes] = await Promise.all([
+      getLoginUserInfo(),
+      getUnreadMessageCount(),
+      getOvertimeTaskList({ pageNo: 1, pageSize: 50 }),
+    ]);
+
+    if (userRes?.code === 0 && userRes.data) {
+      userInfo.value = userRes.data;
+    }
+    if (messageRes?.code === 0) {
+      messageCount.value = messageRes.data || 0;
+    }
+    if (todoRes?.code === 0) {
+      const todoList = todoRes.data?.list || [];
+      todoStats.value = buildTodoStats(todoList);
+    }
+  } catch (error) {
+    messageCount.value = messageCount.value || 0;
+  }
+};
+
+const navigatorTo = (url) => {
+  if (!url) return;
+  uni.navigateTo({ url });
+};
+
+onShow(() => {
+  loadPageData();
+});
+</script>
+
+<style lang="scss" scoped>
+.entry-page {
+  height: 100vh;
+  background: linear-gradient(180deg, #edf4ff 0%, #f7f9fc 28%, #f6f8fb 100%);
+  padding-bottom: 30rpx;
+}
+
+.hero {
+  position: relative;
+  height: 640rpx;
+  overflow: hidden;
+  padding-bottom: 20rpx;
+}
+
+.hero-bg {
+  position: absolute;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.hero-mask {
+  position: absolute;
+  inset: 0;
+  height: 55%;
+  background: linear-gradient(
+    to bottom,
+    rgba(255, 255, 255, 0) 0%,
+    rgb(45, 114, 212, 0.4) 88%,
+    rgb(45, 114, 212, 0.4) 89%,
+    rgb(45, 114, 212, 0.7) 91%,
+    rgb(41, 113, 214, 0.2) 95%,
+    #f9fbfd 100%
+  );
+
+  pointer-events: none;
+}
+
+.hero-content {
+  position: relative;
+  z-index: 2;
+  padding: calc(var(--status-bar-height) + 24rpx) 28rpx 0;
+}
+
+.hero-title {
+  position: absolute;
+  left: 0%;
+  top: -8%;
+  // bottom: -110%;
+  // background: #f7f9fc;
+  background: linear-gradient(
+    to right,
+    #f7f9fc 0%,
+    #f7f9fc 50%,
+    rgba(253, 253, 254, 0.3) 85%,
+    rgba(253, 253, 254, 0.2) 90%,
+    rgba(253, 253, 254, 0) 100%
+  );
+  border-radius: 16rpx 0rpx 0rpx 0rpx;
+  padding-top: 20rpx;
+  padding-bottom: 50rpx;
+  padding-left: 28rpx;
+  width: 50%;
+  height: 80rpx;
+  font-size: 40rpx;
+  font-weight: 700;
+  z-index: -3;
+}
+
+.status-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 42rpx;
+}
+
+.profile-row {
+  display: flex;
+  align-items: center;
+}
+
+.avatar-wrap {
+  width: 82rpx;
+  height: 82rpx;
+  border-radius: 50%;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  backdrop-filter: blur(8px);
+}
+
+.avatar-text {
+  color: #ffffff;
+  font-size: 34rpx;
+  font-weight: 700;
+}
+
+.profile-main {
+  flex: 1;
+  min-width: 0;
+  margin-left: 22rpx;
+}
+
+.greeting {
+  color: #ffffff;
+  font-size: 30rpx;
+  font-weight: 700;
+  line-height: 1.2;
+  text-shadow: 0 6rpx 18rpx rgba(0, 31, 90, 0.18);
+}
+
+.project-pill {
+  display: inline-flex;
+  align-items: center;
+  gap: 12rpx;
+  max-width: 100%;
+  margin-top: 18rpx;
+  padding: 8rpx 18rpx;
+  border-radius: 15rpx;
+  background: rgba(255, 255, 255, 0.18);
+  color: #ffffff;
+  backdrop-filter: blur(8px);
+}
+
+.project-name {
+  max-width: 360rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 20rpx;
+}
+
+.notice-wrap {
+  margin-left: 12rpx;
+  padding-right: 28rpx;
+}
+
+.notice-button {
+  width: 30rpx;
+  height: 30rpx;
+  display: flex;
+
+  align-items: center;
+  justify-content: center;
+}
+
+.notice-glyph {
+  font-size: 32rpx;
+  line-height: 1;
+}
+
+.content-shell {
+  position: relative;
+  z-index: 2;
+  margin-top: -325rpx;
+  padding: 0 24rpx calc(env(safe-area-inset-bottom) + 34rpx);
+}
+
+.page-title {
+  margin: 0 6rpx 22rpx;
+  color: #071426;
+  font-size: 72rpx;
+  font-weight: 800;
+  letter-spacing: 1rpx;
+}
+
+.section-card {
+  margin-bottom: 22rpx;
+  padding: 26rpx 22rpx 24rpx;
+  border-radius: 20rpx;
+  background: rgba(255, 255, 255, 0.94);
+  box-shadow: 0 14rpx 24rpx rgba(13, 43, 91, 0.02);
+  border: 1rpx solid #eef3fc;
+}
+
+.section-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16rpx;
+}
+
+.section-title {
+  color: #101c2e;
+  font-size: 36rpx;
+  font-weight: 700;
+}
+
+.section-tip {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+  color: #5572a3;
+  font-size: 24rpx;
+}
+
+.core-scroll {
+  margin-top: 22rpx;
+  white-space: nowrap;
+}
+
+.core-list {
+  display: inline-flex;
+  gap: 18rpx;
+  padding-bottom: 4rpx;
+}
+
+.core-item {
+  position: relative;
+  width: 188rpx;
+  min-height: 238rpx;
+  padding: 24rpx 18rpx 20rpx;
+  border-radius: 22rpx;
+  background: #f9fbfd;
+  border: 1rpx solid #e5edf8;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.core-item.active {
+  border-color: #246bff;
+  background: linear-gradient(180deg, #ffffff 0%, #dfebfd 100%);
+  box-shadow: inset 0 0 0 2rpx rgba(36, 107, 255, 0.08);
+  .core-name {
+    color: #0b57f6;
+  }
+}
+
+.core-icon {
+  width: 84rpx;
+  height: 84rpx;
+  border-radius: 24rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  text {
+    font-size: 42rpx;
+    font-weight: 800;
+  }
+}
+
+.core-name {
+  margin-top: 22rpx;
+  color: #111d2f;
+  font-size: 20rpx;
+  font-weight: 700;
+  white-space: normal;
+}
+
+.core-desc {
+  margin-top: 10rpx;
+  color: #6f7f96;
+  font-size: 18rpx;
+  line-height: 1.5;
+  white-space: normal;
+}
+
+.core-indicator {
+  position: absolute;
+  left: 50%;
+  bottom: 16rpx;
+  width: 34rpx;
+  height: 8rpx;
+  margin-left: -17rpx;
+  border-radius: 999rpx;
+  background: #246bff;
+}
+
+.tool-grid {
+  display: grid;
+  grid-template-columns: repeat(5, minmax(0, 1fr));
+  gap: 18rpx;
+  margin-top: 22rpx;
+}
+
+.tool-item {
+  min-height: 140rpx;
+  padding: 22rpx 10rpx 18rpx;
+  border-radius: 20rpx;
+  background: linear-gradient(180deg, #ffffff 0%, #fbfcff 100%);
+  border: 1rpx solid #e5edf8;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+}
+
+.tool-icon {
+  width: 54rpx;
+  height: 54rpx;
+}
+
+.tool-title {
+  margin-top: 16rpx;
+  color: #111d2f;
+  font-size: 22rpx;
+  line-height: 1.3;
+  text-align: center;
+}
+
+.daily-list {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 5rpx;
+  margin-top: 22rpx;
+}
+
+.daily-item {
+  padding: 30rpx 0rpx;
+  border-radius: 16rpx;
+  background: linear-gradient(180deg, #ffffff 0%, #fbfcff 100%);
+  border: 1rpx solid #e5edf8;
+  display: flex;
+  // flex-direction: column;
+  // align-items: center;
+  gap: 10rpx;
+  // min-width: 0;
+}
+
+.daily-subtitle-wrap {
+  display: flex;
+  align-items: center;
+  margin-top: 8rpx;
+  gap: 8rpx;
+}
+
+.daily-icon {
+  width: 64rpx;
+  height: 64rpx;
+  flex-shrink: 0;
+}
+
+.daily-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.daily-title {
+  color: #111d2f;
+  font-size: 22rpx;
+  font-weight: 700;
+}
+
+.daily-subtitle {
+  // margin-top: 8rpx;
+  color: #6f7f96;
+  font-size: 16rpx;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.todo-list {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 18rpx;
+  margin-top: 22rpx;
+}
+
+.todo-item {
+  min-width: 0;
+  padding: 22rpx 20rpx;
+  border-radius: 22rpx;
+  background: linear-gradient(180deg, #ffffff 0%, #fbfcff 100%);
+  border: 1rpx solid #e5edf8;
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+
+.todo-icon {
+  width: 58rpx;
+  height: 58rpx;
+  flex-shrink: 0;
+}
+
+.todo-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.todo-title {
+  color: #111d2f;
+  font-size: 26rpx;
+  font-weight: 700;
+}
+
+.todo-subtitle {
+  margin-top: 8rpx;
+  color: #6f7f96;
+  font-size: 22rpx;
+}
+
+.todo-right {
+  display: flex;
+  align-items: flex-end;
+  gap: 6rpx;
+}
+
+.todo-count {
+  font-size: 48rpx;
+  font-weight: 800;
+  line-height: 1;
+}
+
+.todo-unit {
+  margin-bottom: 6rpx;
+  color: #6f7f96;
+  font-size: 22rpx;
+}
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 617 - 492
pages/home/index.vue


+ 326 - 0
pages/qhse/detail.vue

@@ -0,0 +1,326 @@
+<template>
+  <view class="page">
+    <view class="hero-card">
+      <!-- <view class="hero-badge">QHSE</view> -->
+      <view class="hero-title">事故事件详情</view>
+      <view class="hero-subtitle">
+        查看事故时间、等级、现场信息及附件资料
+      </view>
+    </view>
+    <scroll-view scroll-y class="detail-scroll">
+      <view class="">
+        <view class="detail-item">
+          <view class="detail-label">事故时间</view>
+          <view class="detail-value">
+            <text>{{
+              new Date(form.actualTime).toLocaleDateString().replace(/\//g, "-")
+            }}</text>
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">事故级别</view>
+          <view class="detail-value text-value">{{
+            form.accidentGrade || "-"
+          }}</view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">事故类型</view>
+          <view class="detail-value">
+            <uni-easyinput
+              :modelValue="form.accidentType || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">事故地点</view>
+          <view class="detail-value">
+            <uni-easyinput
+              :modelValue="form.accidentAddress || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">现场负责人</view>
+          <view class="detail-value">
+            <uni-easyinput
+              :modelValue="form.dutyPerson || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">损失情况</view>
+          <view class="detail-value">
+            <uni-easyinput
+              :modelValue="form.lossSituation || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">现场采取措施</view>
+          <view class="detail-value">
+            <uni-easyinput
+              type="textarea"
+              autoHeight
+              :modelValue="form.emergencyMeasure || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">事故简要经过</view>
+          <view class="detail-value">
+            <uni-easyinput
+              type="textarea"
+              autoHeight
+              :modelValue="form.description || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">备注</view>
+          <view class="detail-value">
+            <uni-easyinput
+              type="textarea"
+              autoHeight
+              :modelValue="form.remark || ''"
+              :disabled="true"
+              :inputBorder="false"
+              :clearable="false"
+              placeholder="-"
+            />
+          </view>
+        </view>
+
+        <view class="detail-item">
+          <view class="detail-label">附件图片</view>
+          <view class="detail-value">
+            <view v-if="imageList.length" class="image-list">
+              <view
+                v-for="(item, index) in imageList"
+                :key="index"
+                class="image-item"
+                @click="previewImage(item)"
+              >
+                <image :src="item" mode="aspectFill" />
+              </view>
+            </view>
+            <view v-else class="empty-text">暂无图片</view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { getDetail } from "@/api/qhse";
+
+const id = ref(null);
+const form = ref({});
+
+const imageList = computed(() => {
+  const pic = form.value?.pic;
+  if (!pic) return [];
+  return Array.isArray(pic) ? pic.filter(Boolean) : [pic];
+});
+
+onLoad(async (options) => {
+  id.value = Number(options?.id) || null;
+
+  if (!id.value) {
+    return;
+  }
+
+  try {
+    const data = await getDetail(id.value);
+    form.value = data.data || {};
+  } catch (error) {
+    console.error("获取事故详情失败", error);
+    uni.showToast({
+      title: "获取详情失败",
+      icon: "none",
+    });
+  }
+});
+
+const previewImage = (current) => {
+  if (!current || !imageList.value.length) return;
+
+  uni.previewImage({
+    urls: imageList.value,
+    current,
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.hero-card {
+  position: relative;
+  overflow: hidden;
+  margin-bottom: 24rpx;
+  padding: 32rpx 30rpx;
+  border-radius: 28rpx;
+  background: linear-gradient(135deg, #0f4c81 0%, #1b76c9 100%);
+  box-shadow: 0 16rpx 40rpx rgba(15, 76, 129, 0.18);
+
+  &::after {
+    content: "";
+    position: absolute;
+    top: -40rpx;
+    right: -20rpx;
+    width: 180rpx;
+    height: 180rpx;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.12);
+  }
+}
+
+.hero-badge {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 18rpx;
+  padding: 8rpx 18rpx;
+  border-radius: 999rpx;
+  background: rgba(255, 255, 255, 0.14);
+  color: #e9f4ff;
+  font-size: 22rpx;
+  letter-spacing: 2rpx;
+}
+
+.hero-title {
+  position: relative;
+  z-index: 1;
+  margin-bottom: 12rpx;
+  color: #ffffff;
+  font-size: 38rpx;
+  font-weight: 600;
+}
+
+.hero-subtitle {
+  position: relative;
+  z-index: 1;
+  color: rgba(255, 255, 255, 0.82);
+  font-size: 24rpx;
+  line-height: 1.6;
+}
+
+.page {
+  height: 190%;
+  background: #f4f7fb;
+}
+
+.detail-scroll {
+  height: 100%;
+}
+
+.detail-list {
+  padding: 24rpx;
+  box-sizing: border-box;
+}
+
+.detail-item {
+  margin-bottom: 20rpx;
+  padding: 24rpx;
+  border-radius: 20rpx;
+  background: #ffffff;
+  box-shadow: 0 8rpx 24rpx rgba(31, 45, 61, 0.06);
+  border-left: solid 8rpx #009eff;
+}
+
+.detail-label {
+  margin-bottom: 14rpx;
+  color: #6b7a8c;
+  font-size: 24rpx;
+  line-height: 1.4;
+}
+
+.detail-value {
+  color: #1f2d3d;
+  font-size: 28rpx;
+  line-height: 1.7;
+}
+
+.text-value {
+  min-height: 44rpx;
+}
+
+.image-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 18rpx;
+}
+
+.image-item {
+  width: calc(50% - 9rpx);
+  height: 220rpx;
+  overflow: hidden;
+  border-radius: 16rpx;
+  background: #eef3f8;
+
+  image {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.empty-text {
+  color: #97a6b5;
+  font-size: 26rpx;
+}
+
+::v-deep .uni-easyinput__content,
+::v-deep .uni-easyinput__content-textarea,
+::v-deep .uni-date-editor,
+::v-deep .uni-date-editor--x {
+  padding: 0 !important;
+  border: none !important;
+  background: transparent !important;
+}
+
+::v-deep .uni-easyinput__content-input,
+::v-deep .uni-easyinput__content-textarea,
+::v-deep .uni-date__x-input {
+  padding: 0 !important;
+  color: #1f2d3d !important;
+  font-size: 28rpx !important;
+  line-height: 1.7 !important;
+}
+
+::v-deep .is-disabled {
+  opacity: 1 !important;
+}
+</style>

+ 659 - 0
pages/ruiHengReport/index.vue

@@ -0,0 +1,659 @@
+<template>
+  <view class="report-page">
+    <scroll-view class="report-scroll" scroll-y>
+      <view v-if="loading" class="state-card">
+        <text class="state-text">数据加载中...</text>
+      </view>
+
+      <view v-else-if="!dataList.length" class="state-card empty">
+        <text class="state-text">当前日期暂无数据</text>
+      </view>
+
+      <view v-else class="report-list">
+        <view
+          v-for="item in sortedList"
+          :key="item.projectDeptId"
+          class="report-card"
+        >
+          <view class="card-header">
+            <view>
+              <view class="dept-name">{{ item.projectDeptName || "--" }}</view>
+            </view>
+            <view class="rate-badge" @click="openTeamDetail(item)">
+              查看队伍明细
+            </view>
+          </view>
+
+          <view class="metrics-grid">
+            <!-- <view class="metric-item">
+              <text class="metric-label">队伍总数</text>
+              <text class="metric-value">{{
+                formatNumber(item.teamCount)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">队伍总数</text>
+              <text class="summary-value">
+                {{ formatNumber(item.teamCount) }}
+              </text>
+            </view>
+
+            <!-- <view class="metric-item">
+              <text class="metric-label">施工队伍</text>
+              <text class="metric-value">{{
+                formatNumber(item.sgTeamCount)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">施工队伍</text>
+              <text class="summary-value">
+                {{ formatNumber(item.sgTeamCount) }}
+              </text>
+            </view>
+
+            <!-- <view class="metric-item">
+              <text class="metric-label">施工准备</text>
+              <text class="metric-value">{{
+                formatNumber(item.zbTeamCount)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">施工准备</text>
+              <text class="summary-value">
+                {{ formatNumber(item.zbTeamCount) }}
+              </text>
+            </view>
+
+            <!-- <view class="metric-item">
+              <text class="metric-label">待命</text>
+              <text class="metric-value">{{
+                formatNumber(item.dmTeamCount)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">待命</text>
+              <text class="summary-value">
+                {{ formatNumber(item.dmTeamCount) }}
+              </text>
+            </view>
+
+            <!-- <view class="metric-item">
+              <text class="metric-label">当日运行时效</text>
+              <text class="metric-value">{{
+                formatPercent(item.hourUtilizationRate)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">当日运行时效</text>
+              <text class="summary-value">
+                {{ formatPercent(item.hourUtilizationRate) }}
+              </text>
+            </view>
+
+            <!-- <view class="metric-item">
+              <text class="metric-label">平均运行时效</text>
+              <text class="metric-value">{{
+                formatPercent(item.yearHourUtilizationRate)
+              }}</text>
+            </view> -->
+
+            <view class="summary-row">
+              <text class="summary-label">平均运行时效</text>
+              <text class="summary-value">
+                {{ formatPercent(item.yearHourUtilizationRate) }}
+              </text>
+            </view>
+          </view>
+
+          <view class="summary-panel">
+            <view class="metric-item">
+              <text class="metric-label">日注气量(万方)</text>
+              <text class="metric-value">{{
+                formatDecimal(item.gasInjection)
+              }}</text>
+            </view>
+
+            <view class="metric-item">
+              <text class="metric-label">累计注气量(万方)</text>
+              <text class="metric-value">{{
+                formatDecimal(item.yearGasInjection)
+              }}</text>
+            </view>
+
+            <view class="metric-item">
+              <text class="metric-label">日注水量(万方)</text>
+              <text class="metric-value">{{
+                formatDecimal(item.waterInjection)
+              }}</text>
+            </view>
+
+            <view class="metric-item">
+              <text class="metric-label">累计注水量(万方)</text>
+              <text class="metric-value">{{
+                formatDecimal(item.yearWaterInjection)
+              }}</text>
+            </view>
+          </view>
+
+          <view class="desc-panel">
+            <view class="desc-title">生产动态概述</view>
+            <text class="desc-text">
+              {{ item.productionSummary || "暂无生产动态概述" }}
+            </text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <view class="filter-bar">
+      <view class="filter-main">
+        <text class="filter-label">创建时间</text>
+
+        <uni-datetime-picker
+          v-model="selectedDate"
+          class="date-picker"
+          type="date"
+          :teleport="true"
+          return-type="string"
+          :clear-icon="false"
+          :border="false"
+          @change="handleDateChange"
+        />
+      </view>
+      <button
+        class="filter-button"
+        type="primary"
+        size="mini"
+        @click="loadList"
+      >
+        查询
+      </button>
+    </view>
+
+    <uni-popup
+      ref="teamDetailPopup"
+      type="right"
+      background-color="#fff"
+      :is-mask-click="true"
+    >
+      <view class="team-drawer">
+        <view class="team-drawer__header">
+          <text class="team-drawer__title">
+            {{ currentDetailTitle || "队伍明细" }}
+          </text>
+          <text class="team-drawer__close" @click="closeTeamDetail">关闭</text>
+        </view>
+
+        <view class="team-drawer__sub">
+          统计日期:{{ selectedDate || "--" }}
+        </view>
+
+        <scroll-view class="team-drawer__scroll" scroll-y>
+          <view v-if="teamDetailLoading" class="team-state-card">
+            <text class="team-state-text">数据加载中...</text>
+          </view>
+
+          <view
+            v-else-if="!teamDetailList.length"
+            class="team-state-card empty"
+          >
+            <text class="team-state-text">暂无队伍明细</text>
+          </view>
+
+          <view v-else class="team-detail-list">
+            <view
+              v-for="(team, index) in teamDetailList"
+              :key="team.teamName || index"
+              class="team-detail-card"
+            >
+              <view class="team-detail-card__title">
+                {{ team.teamName || "--" }}
+              </view>
+
+              <view class="team-detail-grid">
+                <view class="team-detail-item">
+                  <text class="team-detail-label">累计天数</text>
+                  <text class="team-detail-value">{{
+                    formatNumber(team.cumulativeDays)
+                  }}</text>
+                </view>
+                <view class="team-detail-item">
+                  <text class="team-detail-label">施工天数</text>
+                  <text class="team-detail-value">{{
+                    formatNumber(team.constructionDays)
+                  }}</text>
+                </view>
+                <view class="team-detail-item team-detail-item--full">
+                  <text class="team-detail-label">设备利用率</text>
+                  <text class="team-detail-value">{{
+                    formatPercent(team.utilizationRate)
+                  }}</text>
+                </view>
+              </view>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { onMounted } from "vue";
+import dayjs from "dayjs";
+import { getRhRate, getRhTeamDetail } from "@/api/ruiHengReport";
+
+const selectedDate = ref(dayjs().subtract(1, "day").format("YYYY-MM-DD"));
+const dataList = ref([]);
+const loading = ref(false);
+const teamDetailPopup = ref(null);
+const teamDetailLoading = ref(false);
+const teamDetailList = ref([]);
+const currentDetailTitle = ref("");
+
+const buildQueryParams = () => {
+  const baseDate = selectedDate.value
+    ? dayjs(selectedDate.value)
+    : dayjs().subtract(1, "day");
+
+  return {
+    createTime: [
+      baseDate.startOf("day").format("YYYY-MM-DD HH:mm:ss"),
+      baseDate.endOf("day").format("YYYY-MM-DD HH:mm:ss"),
+    ],
+  };
+};
+
+const sortedList = computed(() =>
+  [...dataList.value].sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999)),
+);
+
+const loadList = async () => {
+  loading.value = true;
+  try {
+    const response = await getRhRate(buildQueryParams());
+    dataList.value = Array.isArray(response?.data) ? response.data : [];
+  } catch (error) {
+    dataList.value = [];
+    uni.showToast({
+      title: "数据加载失败",
+      icon: "none",
+    });
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleDateChange = () => {
+  loadList();
+};
+
+const openTeamDetail = async (item) => {
+  currentDetailTitle.value = `${item.projectDeptName || "--"} 队伍明细`;
+  teamDetailList.value = [];
+  teamDetailLoading.value = true;
+  teamDetailPopup.value?.open();
+
+  try {
+    const response = await getRhTeamDetail({
+      deptId: item.projectDeptId,
+      createTime: buildQueryParams().createTime,
+    });
+    teamDetailList.value = Array.isArray(response?.data) ? response.data : [];
+  } catch (error) {
+    teamDetailList.value = [];
+    uni.showToast({
+      title: "队伍明细加载失败",
+      icon: "none",
+    });
+  } finally {
+    teamDetailLoading.value = false;
+  }
+};
+
+const closeTeamDetail = () => {
+  teamDetailPopup.value?.close();
+};
+
+const formatNumber = (value) => {
+  if (value === null || value === undefined || value === "") return "--";
+  return value;
+};
+
+const formatDecimal = (value) => {
+  if (value === null || value === undefined || value === "") return "--";
+  return Number(value).toFixed(2);
+};
+
+const formatPercent = (value) => {
+  if (value === null || value === undefined || value === "") return "--";
+  return `${(Number(value) * 100).toFixed(2)}%`;
+};
+
+onMounted(() => {
+  loadList();
+});
+</script>
+
+<style lang="scss" scoped>
+.report-page {
+  max-height: 100vh;
+  padding: 12px;
+  box-sizing: border-box;
+}
+
+.filter-bar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 14px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.94);
+  box-shadow: 0 14px 32px rgba(25, 56, 104, 0.08);
+  backdrop-filter: blur(10px);
+}
+
+.filter-main {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10rpx;
+}
+
+.filter-label {
+  display: block;
+  margin-bottom: 8px;
+  color: #5f6f87;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.date-picker {
+  padding: 0 12px;
+  border: 1px solid rgba(0, 64, 152, 0.1);
+  border-radius: 12px;
+  background: #f8fbff;
+}
+
+.filter-button {
+  width: 76px;
+  height: 38px;
+  line-height: 40px;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #004098 0%, #1f68d8 100%);
+  font-size: 14px;
+}
+
+.filter-tip {
+  margin: 10px 4px 0;
+  color: #6d7c91;
+  font-size: 12px;
+}
+
+.report-scroll {
+  height: calc(100vh - 140px);
+  padding-top: 12px;
+  box-sizing: border-box;
+}
+
+.report-list {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding-bottom: 18px;
+}
+
+.report-card {
+  padding: 16px;
+  border: 1px solid rgba(0, 64, 152, 0.08);
+  border-radius: 20px;
+  background: rgba(255, 255, 255, 0.96);
+  box-shadow: 0 18px 38px rgba(31, 57, 90, 0.08);
+}
+
+.card-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 14px;
+}
+
+.dept-name {
+  color: #122033;
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 26px;
+}
+
+.dept-meta {
+  margin-top: 4px;
+  color: #7f8ca0;
+  font-size: 12px;
+}
+
+.rate-badge {
+  flex-shrink: 0;
+  min-width: 84px;
+  padding: 8px 12px;
+  border-radius: 999px;
+  background: linear-gradient(135deg, #e6f0ff 0%, #d8e7ff 100%);
+  color: #004098;
+  font-size: 14px;
+  font-weight: 700;
+  text-align: center;
+}
+
+.metrics-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 10px;
+  margin-bottom: 14px;
+  margin-top: 14px;
+}
+
+.metric-item {
+  padding: 12px;
+  border-radius: 14px;
+  background: #f6f9fd;
+}
+
+.metric-label {
+  display: block;
+  color: #6e7e95;
+  font-size: 12px;
+}
+
+.metric-value {
+  display: block;
+  margin-top: 6px;
+  color: #18273b;
+  font-size: 20px;
+  font-weight: 700;
+}
+
+.summary-panel {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 8px 14px;
+  padding: 14px 0;
+  // border-top: 1px solid #edf2f7;
+  border-bottom: 1px solid #edf2f7;
+}
+
+.summary-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10px;
+}
+
+.summary-label {
+  color: #6d7c91;
+  font-size: 12px;
+}
+
+.summary-value {
+  color: #17253a;
+  font-size: 13px;
+  font-weight: 600;
+}
+
+.desc-panel {
+  padding-top: 14px;
+}
+
+.desc-title {
+  margin-bottom: 8px;
+  color: #1f2f46;
+  font-size: 14px;
+  font-weight: 700;
+}
+
+.desc-text {
+  color: #5f6f87;
+  font-size: 13px;
+  line-height: 22px;
+  white-space: pre-wrap;
+}
+
+.state-card {
+  margin-top: 18px;
+  padding: 40px 20px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.92);
+  text-align: center;
+  box-shadow: 0 12px 28px rgba(30, 54, 88, 0.06);
+}
+
+.state-card.empty {
+  border: 1px dashed rgba(0, 64, 152, 0.16);
+}
+
+.state-text {
+  color: #73839a;
+  font-size: 14px;
+}
+
+.team-drawer {
+  width: 82vw;
+  max-width: 620rpx;
+  height: 100vh;
+  padding: 18px 14px calc(18px + env(safe-area-inset-bottom));
+  box-sizing: border-box;
+  background: linear-gradient(180deg, #f8fbff 0%, #eef4fb 100%);
+  display: flex;
+  flex-direction: column;
+}
+
+.team-drawer__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.team-drawer__title {
+  min-width: 0;
+  color: #122033;
+  font-size: 18px;
+  font-weight: 700;
+}
+
+.team-drawer__close {
+  flex-shrink: 0;
+  color: #004098;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.team-drawer__sub {
+  margin-top: 8px;
+  margin-bottom: 14px;
+  color: #70819a;
+  font-size: 12px;
+}
+
+.team-drawer__scroll {
+  flex: 1;
+  min-height: 0;
+  padding-bottom: 50rpx;
+}
+
+.team-detail-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding-bottom: 10px;
+}
+
+.team-detail-card {
+  padding: 14px;
+  border-radius: 16px;
+  background: rgba(255, 255, 255, 0.96);
+  box-shadow: 0 10px 24px rgba(31, 57, 90, 0.08);
+}
+
+.team-detail-card__title {
+  margin-bottom: 12px;
+  color: #18273b;
+  font-size: 16px;
+  font-weight: 700;
+}
+
+.team-detail-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 10px;
+}
+
+.team-detail-item {
+  padding: 10px 12px;
+  border-radius: 12px;
+  background: #f6f9fd;
+}
+
+.team-detail-item--full {
+  grid-column: 1 / -1;
+}
+
+.team-detail-label {
+  display: block;
+  color: #6e7e95;
+  font-size: 12px;
+}
+
+.team-detail-value {
+  display: block;
+  margin-top: 6px;
+  color: #18273b;
+  font-size: 18px;
+  font-weight: 700;
+}
+
+.team-state-card {
+  margin-top: 8px;
+  padding: 36px 20px;
+  border-radius: 16px;
+  background: rgba(255, 255, 255, 0.92);
+  text-align: center;
+  box-shadow: 0 12px 28px rgba(30, 54, 88, 0.06);
+}
+
+.team-state-card.empty {
+  border: 1px dashed rgba(0, 64, 152, 0.16);
+}
+
+.team-state-text {
+  color: #73839a;
+  font-size: 14px;
+}
+</style>

+ 443 - 0
pages/ruiYingReport/index.vue

@@ -0,0 +1,443 @@
+<template>
+  <view class="report-page">
+    <scroll-view class="report-scroll" scroll-y>
+      <view v-if="loading" class="state-card">
+        <text class="state-text">数据加载中...</text>
+      </view>
+
+      <view v-else-if="!dataList.length" class="state-card empty">
+        <text class="state-text">当前日期暂无数据</text>
+      </view>
+
+      <view v-else class="report-list">
+        <view
+          v-for="(item, index) in dataList"
+          :key="index"
+          class="report-card"
+        >
+          <!-- 基本信息 -->
+          <view class="card-header">
+            <view class="header-left">
+              <text class="company-name">{{
+                item.projectClassification || "--"
+              }}</text>
+              <text class="project-name">{{ item.projectName || "--" }}</text>
+            </view>
+            <view
+              class="status-badge"
+              :class="getStatusClass(item.constructionStatusName)"
+            >
+              {{ item.constructionStatusName || "--" }}
+            </view>
+          </view>
+
+          <!-- 队伍和任务 -->
+          <view class="info-row">
+            <view class="info-item">
+              <text class="info-label">队伍</text>
+              <text class="info-value">{{ item.deptName || "--" }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">生产任务</text>
+              <text class="info-value">{{ item.taskName || "--" }}</text>
+            </view>
+          </view>
+
+          <!-- 核心指标 -->
+          <view class="metrics-grid">
+            <view class="metric-item">
+              <text class="metric-label">当日进尺/井次</text>
+              <text class="metric-value">{{ formatFootageOrWell(item) }}</text>
+            </view>
+            <view class="metric-item">
+              <text class="metric-label">当日电耗</text>
+              <text class="metric-value"
+                >{{ formatNumber(item.dailyPowerUsage) }}kWh</text
+              >
+            </view>
+            <view class="metric-item">
+              <text class="metric-label">当日油耗</text>
+              <text class="metric-value"
+                >{{ formatNumber(item.dailyFuel) }}L</text
+              >
+            </view>
+            <view class="metric-item">
+              <text class="metric-label">非生产时间</text>
+              <text class="metric-value"
+                >{{ formatNumber(item.nonProductionTime) }}h</text
+              >
+            </view>
+          </view>
+
+          <!-- 下步任务 -->
+          <view class="desc-panel" v-if="item.nextPlan">
+            <view class="desc-title">下步任务</view>
+            <text class="desc-text">{{ item.nextPlan }}</text>
+          </view>
+
+          <!-- 当日生产简况 -->
+          <view class="desc-panel" v-if="item.constructionBrief">
+            <view class="desc-title">当日生产简况</view>
+            <text class="desc-text">{{ item.constructionBrief }}</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 筛选栏 -->
+    <view class="filter-bar">
+      <view class="filter-main">
+        <text class="filter-label">日期</text>
+        <uni-datetime-picker
+          v-model="selectedDate"
+          class="date-picker"
+          type="date"
+          :teleport="true"
+          return-type="string"
+          :clear-icon="false"
+          :border="false"
+          @change="handleDateChange"
+        />
+      </view>
+      <button
+        class="filter-button"
+        type="primary"
+        size="mini"
+        @click="loadList"
+      >
+        查询
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import dayjs from "dayjs";
+// 假设接口地址为 getRyProductionBriefs,请根据实际接口调整
+import { getRyProductionBriefs } from "@/api/ruiYingReport";
+
+// 状态
+const selectedDate = ref(dayjs().subtract(1, "day").format("YYYY-MM-DD"));
+const dataList = ref([]);
+const loading = ref(false);
+
+// 构建查询参数
+const buildQueryParams = () => {
+  const baseDate = selectedDate.value
+    ? dayjs(selectedDate.value)
+    : dayjs().subtract(1, "day");
+
+  return {
+    createTime: [
+      baseDate.startOf("day").format("YYYY-MM-DD HH:mm:ss"),
+      baseDate.endOf("day").format("YYYY-MM-DD HH:mm:ss"),
+    ],
+  };
+};
+
+// 格式化数字
+const formatNumber = (value, fractionDigits = 1) => {
+  if (value === null || value === undefined || value === "") return "--";
+  const numberValue = Number(value);
+  if (isNaN(numberValue)) return "--";
+  if (Number.isInteger(numberValue)) {
+    return `${numberValue}`;
+  }
+  return numberValue.toFixed(fractionDigits);
+};
+
+// 格式化进尺/井次
+const formatFootageOrWell = (row) => {
+  if (!row) return "--";
+  if (row.projectClassification === "钻井") {
+    const footage = formatNumber(row.dailyFootage);
+    return footage === "--" ? "--" : `${footage}m`;
+  }
+  return formatNumber(row.completedWells, 0);
+};
+
+// 获取状态样式
+const getStatusClass = (status) => {
+  if (!status) return "";
+  const statusMap = {
+    施工中: "status-construction",
+    待命: "status-standby",
+    停工: "status-stopped",
+    完工: "status-completed",
+    准备: "status-ready",
+  };
+  return statusMap[status] || "";
+};
+
+// 加载数据
+const loadList = async () => {
+  loading.value = true;
+  try {
+    const response = await getRyProductionBriefs(buildQueryParams());
+    dataList.value = Array.isArray(response?.data) ? response.data : [];
+  } catch (error) {
+    dataList.value = [];
+    uni.showToast({
+      title: "数据加载失败",
+      icon: "none",
+    });
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 日期变化处理
+const handleDateChange = () => {
+  loadList();
+};
+
+// 生命周期
+onMounted(() => {
+  loadList();
+});
+</script>
+
+<style lang="scss" scoped>
+.report-page {
+  max-height: 100vh;
+  padding: 12px;
+  box-sizing: border-box;
+}
+
+// 筛选栏
+.filter-bar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 14px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.94);
+  box-shadow: 0 14px 32px rgba(25, 56, 104, 0.08);
+  backdrop-filter: blur(10px);
+}
+
+.filter-main {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10rpx;
+}
+
+.filter-label {
+  display: block;
+  margin-bottom: 8px;
+  color: #5f6f87;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.date-picker {
+  padding: 0 12px;
+  border: 1px solid rgba(0, 64, 152, 0.1);
+  border-radius: 12px;
+  background: #f8fbff;
+}
+
+.filter-button {
+  width: 76px;
+  height: 38px;
+  line-height: 40px;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #004098 0%, #1f68d8 100%);
+  font-size: 14px;
+}
+
+// 滚动区域
+.report-scroll {
+  height: calc(100vh - 140px);
+  padding-top: 12px;
+  box-sizing: border-box;
+}
+
+// 列表
+.report-list {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding-bottom: 18px;
+}
+
+// 卡片
+.report-card {
+  padding: 16px;
+  border: 1px solid rgba(0, 64, 152, 0.08);
+  border-radius: 20px;
+  background: rgba(255, 255, 255, 0.96);
+  box-shadow: 0 18px 38px rgba(31, 57, 90, 0.08);
+}
+
+// 头部
+.card-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+
+.header-left {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  flex: 1;
+  min-width: 0;
+}
+
+.company-name {
+  color: #122033;
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 26px;
+}
+
+.project-name {
+  color: #5f6f87;
+  font-size: 14px;
+  line-height: 20px;
+}
+
+// 状态标签
+.status-badge {
+  flex-shrink: 0;
+  padding: 4px 14px;
+  border-radius: 999px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  background: linear-gradient(135deg, #e6f0ff 0%, #d8e7ff 100%);
+  color: #004098;
+}
+
+.status-construction {
+  background: #e6f7e6;
+  color: #52c41a;
+}
+
+.status-standby {
+  background: #fff7e6;
+  color: #faad14;
+}
+
+.status-stopped {
+  background: #fde6e6;
+  color: #ff4d4f;
+}
+
+.status-completed {
+  background: #e6f0ff;
+  color: #1890ff;
+}
+
+.status-ready {
+  background: #f0f0f0;
+  color: #8c8c8c;
+}
+
+// 信息行
+.info-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 8px 16px;
+  padding: 12px 0;
+  border-top: 1px solid #f0f2f5;
+  border-bottom: 1px solid #f0f2f5;
+}
+
+.info-item {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.info-label {
+  color: #6d7c91;
+  font-size: 12px;
+}
+
+.info-value {
+  color: #17253a;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+// 指标网格
+.metrics-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 10px;
+  padding: 12px 0;
+  border-bottom: 1px solid #f0f2f5;
+}
+
+.metric-item {
+  padding: 12px;
+  border-radius: 14px;
+  background: #f6f9fd;
+}
+
+.metric-label {
+  display: block;
+  color: #6e7e95;
+  font-size: 12px;
+}
+
+.metric-value {
+  display: block;
+  margin-top: 6px;
+  color: #18273b;
+  font-size: 18px;
+  font-weight: 700;
+}
+
+// 描述面板
+.desc-panel {
+  padding-top: 14px;
+}
+
+.desc-panel + .desc-panel {
+  border-top: 1px solid #f0f2f5;
+  padding-top: 14px;
+  margin-top: 0;
+}
+
+.desc-title {
+  margin-bottom: 6px;
+  color: #1f2f46;
+  font-size: 14px;
+  font-weight: 700;
+}
+
+.desc-text {
+  color: #5f6f87;
+  font-size: 13px;
+  line-height: 22px;
+  white-space: pre-wrap;
+}
+
+// 空状态
+.state-card {
+  margin-top: 18px;
+  padding: 40px 20px;
+  border-radius: 18px;
+  background: rgba(255, 255, 255, 0.92);
+  text-align: center;
+  box-shadow: 0 12px 28px rgba(30, 54, 88, 0.06);
+}
+
+.state-card.empty {
+  border: 1px dashed rgba(0, 64, 152, 0.16);
+}
+
+.state-text {
+  color: #73839a;
+  font-size: 14px;
+}
+</style>

+ 432 - 270
pages/user/index.vue

@@ -1,277 +1,439 @@
 <template>
-	<view class=" page  mine-container" :style="{height: `${windowHeight}px`}">
-		<view class="page-back"></view>
-		<view class="navgator justify-center align-center">
-			<view class="nav-title">
-				{{ $t('app.appName') }}
-			</view>
-		</view>
-		<view class="content-section">
-			<!--顶部个人信息栏-->
-			<view class="user-info flex-row justify-start align-center">
-				<image class="avatar" :src="userInfo?.avatar ? userInfo?.avatar : '/static/common/avata.gif'"></image>
-				<view class="info flex-col align-center justify-start">
-					<view class="text flex-row"> <span
-							class="span">{{$t('user.username')}}:</span>{{ userInfo?.nickname }} </view>
-					<view class="text flex-row"> <span class="span">{{$t('user.phone')}}:</span>{{ userInfo?.mobile }}
-					</view>
-
-				</view>
-			</view>
-			<!-- 菜单 -->
-			<view class="menu-list">
-				<view class="card-cell flex-row align-center justify-between" @click="navigateToChange">
-					<image src="/static/user/anquanzhongxin.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('user.securityCenter') }}
-							</view>
-							<view class="subtitle">
-								{{ $t('user.modifyPhoneAndPassword') }}
-							</view>
-						</view>
-						<uni-icons type="right" :color="'#CACCCF'" size="15" />
-					</view>
-				</view>
-
-				<view class="card-cell flex-row align-center justify-between">
-					<image src="/static/user/guanyuwomen.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('user.aboutUs') }}
-							</view>
-							<view class="subtitle">
-								{{ $t('user.currentVersion') + ' ' + version }}
-							</view>
-						</view>
-						<uni-icons type="right" :color="'#CACCCF'" size="15" />
-					</view>
-				</view>
-
-				<view class="card-cell flex-row align-center justify-between" @click="logout">
-					<image src="/static/user/tuichu.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('user.logout') }}
-							</view>
-						</view>
-						<uni-icons type="right" :color="'#CACCCF'" size="15" />
-					</view>
-				</view>
-			</view>
-
-
-		</view>
-	</view>
+  <view class="page mine-container" :style="{ height: `${windowHeight}px` }">
+    <view class="page-back"></view>
+    <view class="navgator justify-center align-center">
+      <view class="nav-title">
+        {{ $t("app.user") }}
+      </view>
+
+      <view class="nav-actions">
+        <view class="notice-wrap" @click="navigateToMessage">
+          <uni-badge :text="messageCount" absolute="rightTop" size="small">
+            <view class="notice-button">
+              <image
+                src="~@/static/home/message.png"
+                style="width: 15px; height: 15px; background-color: transparent"
+              />
+            </view>
+          </uni-badge>
+        </view>
+      </view>
+    </view>
+    <view class="content-section">
+      <!--顶部个人信息栏-->
+      <view class="user-info flex-col">
+        <view class="flex-row" style="padding-bottom: 10px">
+          <image
+            class="avatar"
+            :src="
+              userInfo?.avatar ? userInfo?.avatar : '/static/user/avatar.png'
+            "
+          ></image>
+          <view class="info flex-col align-center justify-start">
+            <view class="text flex-row" style="align-items: center; gap: 10px">
+              <text class="nickname">{{ userInfo?.nickname }}</text>
+              <text
+                style="
+                  background-color: #e6f2fd;
+                  padding: 2px 10px;
+                  color: #1755dc;
+                  border-radius: 20px;
+                "
+                >{{ userInfo.username }}</text
+              >
+            </view>
+            <view
+              class="text flex-row"
+              style="
+                margin-top: 10px;
+                display: flex;
+                align-items: center;
+                gap: 5px;
+              "
+            >
+              <uni-icons type="phone-filled" size="16" color="#656f85" />
+              <text>{{ userInfo?.mobile }}</text>
+            </view>
+            <!-- 项目部 -->
+            <view
+              class="text flex-row"
+              style="
+                margin-top: 10px;
+                display: flex;
+                align-items: center;
+                gap: 5px;
+              "
+            >
+              <uni-icons type="staff" size="16" color="#656f85" />
+              <text>{{ userInfo?.dept.name }}</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- <view class="info-list flex-row w-full justify-between items-center">
+          <view class="flex-col justify-center items-center list-item">
+            <text class="info-text">6</text>
+            <text class="info-tip">待处理</text>
+          </view>
+          <view
+            class="flex-col justify-center items-center list-item mark-item"
+          >
+            <text class="info-text">6</text>
+            <text class="info-tip">关注设备</text>
+          </view>
+          <view class="flex-col justify-center items-center list-item">
+            <text class="info-text">6</text>
+            <text class="info-tip">导出报表</text>
+          </view>
+        </view> -->
+      </view>
+      <!-- 菜单 -->
+      <view class="menu-list">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigateToChange"
+        >
+          <image
+            src="/static/user/anquanzhongxin.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("user.securityCenter") }}
+              </view>
+              <view class="subtitle">
+                {{ $t("user.modifyPhoneAndPassword") }}
+              </view>
+            </view>
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+
+        <view class="card-cell flex-row align-center justify-between">
+          <image src="/static/user/guanyuwomen.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("user.aboutUs") }}
+              </view>
+              <view class="subtitle">
+                {{ $t("user.currentVersion") + " " + version }}
+              </view>
+            </view>
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+
+        <!-- <view
+          class="card-cell flex-row align-center justify-between"
+          @click="logout"
+        >
+          <image src="/static/user/tuichu.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("user.logout") }}
+              </view>
+            </view>
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view> -->
+      </view>
+      <button
+        type="warn"
+        plain="true"
+        @click="logout"
+        style="
+          background-color: #fff;
+          border: 0.5px solid #f2f4f7;
+          margin-top: 10px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        "
+      >
+        <image
+          style="width: 22px; height: 22px; padding-right: 8px"
+          src="/static/user/logout.png"
+        ></image>
+        <text>{{ $t("user.logout") }}</text>
+      </button>
+    </view>
+  </view>
 </template>
 
 <script setup>
-	import {
-		onMounted,
-		ref
-	} from 'vue';
-	import {
-		useI18n
-	} from 'vue-i18n'
-	import {
-		getLoginUserInfo
-	} from "@/api/login";
-	const {
-		t
-	} = useI18n({
-		useScope: 'global'
-	})
-
-	import $store from '@/store';
-	// const userStore = $store('user');
-	//
-	// const userInfo = ref(JSON.parse(getUserInfo()))
-	// userInfo.value = JSON.parse(userInfo.value).user
-	// console.log('useer---', userInfo.value)
-	// const avatar = userInfo.value.avatar
-	// const name = userInfo.value.nickname
-	// const phone = userInfo.value.phone || ''
-	const windowHeight = ref(0)
-	uni.getSystemInfo({
-		success: function(res) {
-			windowHeight.value = res.windowHeight
-		}
-	})
-
-	const navigateToChange = () => {
-		uni.navigateTo({
-			url: '/pages/user/change?info=' + JSON.stringify(userInfo.value)
-		})
-	}
-
-	const userStore = $store('user')
-	const logout = () => {
-		uni.showModal({
-			title: t('api.message'),
-			content: `${t('login.logoutConfirm')}?`,
-			success: async function(res) {
-				if (res.confirm) {
-					await userStore.LogOut();
-					await uni.reLaunch({
-						url: '/pages/user/login'
-					})
-				} else if (res.cancel) {
-					console.log('用户点击取消');
-				}
-			}
-		})
-	}
-
-	const userInfo = ref({})
-	const version = ref('')
-	onMounted(async () => {
-		version.value = uni.getAppBaseInfo().appVersion
-		userInfo.value = (await getLoginUserInfo()).data
-
-		uni.$once('updateUserInfo', async () => {
-			userInfo.value = (await getLoginUserInfo()).data
-		})
-	})
+import { getCurrentWgtInfo } from "@/utils/hot-update";
+import { onMounted, ref } from "vue";
+import { useI18n } from "vue-i18n";
+import { getLoginUserInfo } from "@/api/login";
+import { getUnreadMessageCount } from "@/api/message";
+import { onShow } from "@dcloudio/uni-app";
+const { t } = useI18n({
+  useScope: "global",
+});
+
+import $store from "@/store";
+// const userStore = $store('user');
+//
+// const userInfo = ref(JSON.parse(getUserInfo()))
+// userInfo.value = JSON.parse(userInfo.value).user
+// console.log('useer---', userInfo.value)
+// const avatar = userInfo.value.avatar
+// const name = userInfo.value.nickname
+// const phone = userInfo.value.phone || ''
+const windowHeight = ref(0);
+uni.getSystemInfo({
+  success: function (res) {
+    windowHeight.value = res.windowHeight;
+  },
+});
+
+const navigateToChange = () => {
+  uni.navigateTo({
+    url: "/pages/user/change?info=" + JSON.stringify(userInfo.value),
+  });
+};
+
+const navigateToMessage = () => {
+  uni.navigateTo({
+    url: "/pages/message/index",
+  });
+};
+
+const userStore = $store("user");
+const logout = () => {
+  uni.showModal({
+    title: t("api.message"),
+    content: `${t("login.logoutConfirm")}?`,
+    success: async function (res) {
+      if (res.confirm) {
+        await userStore.LogOut();
+        await uni.reLaunch({
+          url: "/pages/user/login",
+        });
+      } else if (res.cancel) {
+        console.log("用户点击取消");
+      }
+    },
+  });
+};
+
+const userInfo = ref({});
+const version = ref("");
+const messageCount = ref(0);
+
+const loadUserPageData = async () => {
+  const [userRes, messageRes] = await Promise.all([
+    getLoginUserInfo(),
+    getUnreadMessageCount(),
+  ]);
+  userInfo.value = userRes?.data || {};
+  messageCount.value = messageRes?.code === 0 ? messageRes.data || 0 : 0;
+};
+
+onMounted(async () => {
+  await loadUserPageData();
+  const wgtInfo = await getCurrentWgtInfo();
+  version.value = wgtInfo?.version || "";
+
+  uni.$once("updateUserInfo", async () => {
+    userInfo.value = (await getLoginUserInfo()).data;
+  });
+});
+
+onShow(() => {
+  loadUserPageData();
+});
 </script>
 
 <style lang="scss" scoped>
-	.page {
-		padding: 10px !important;
-	}
-
-	.mine-container {
-		width: 100%;
-		height: 100%;
-		position: relative;
-		box-sizing: border-box;
-		overflow: hidden;
-	}
-
-	.page-back {
-		background-image: url("/static/common/1.png");
-		background-repeat: no-repeat;
-		background-size: 100% 100%;
-		position: fixed;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 350px;
-		z-index: 0;
-	}
-
-	.navgator {
-		position: fixed;
-		top: 1.5625rem;
-		left: 0;
-		width: 100%;
-		height: 55px;
-		padding: 15px 0;
-
-		z-index: 2;
-		// background-image: url('/static/common/nav-back.png');
-	}
-
-	.nav-title {
-		font-family: PingFang-SC, PingFang-SC;
-		font-weight: bold;
-		font-size: 16px;
-		color: #FFFFFF;
-		line-height: 22px;
-		text-align: right;
-		font-style: normal;
-	}
-
-	.content-section {
-		position: relative;
-		margin-top: 70px;
-		width: 100%;
-		height: calc(100% - 55px - 20px);
-		overflow-y: auto;
-	}
-
-	.user-info {
-		width: 100%;
-		height: 80px;
-		padding: 14px 20px;
-		box-sizing: border-box;
-		background: #FFFFFF;
-		border-radius: 6px;
-		font-size: 14px;
-		color: #333333;
-
-		.avatar {
-			width: 52px;
-			height: 52px;
-			border-radius: 50%;
-			margin-right: 18px;
-		}
-
-		.info {
-			width: calc(100% - 52px - 18px);
-		}
-
-	}
-
-	.text {
-		height: 19px;
-		width: 100%;
-
-		.span {
-			display: block;
-			min-width: 70px;
-		}
-	}
-
-	.menu-list {
-		height: 274px;
-		background: #FFFFFF;
-		border-radius: 6px;
-		margin-top: 10px;
-		padding: 20px;
-		box-sizing: border-box;
-	}
-
-	.card-cell {
-		width: 100%;
-		height: 50px;
-
-		image {
-			width: 32px;
-			height: 32px;
-		}
-	}
-
-	.cell-con {
-		margin-left: 20px;
-		margin-right: 10px;
-		width: calc(100% - 32px - 20px - 10px);
-		height: 100%;
-		border-bottom: 0.5px solid #CACCCF;
-	}
-
-	.cell-text {
-		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>
+.page {
+  padding: 10px !important;
+}
+
+.mine-container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.page-back {
+  background-image: url("/static/entry/bg.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 200px;
+  z-index: 0;
+}
+
+.navgator {
+  position: fixed;
+  top: 1.5625rem;
+  left: 0;
+  width: 100%;
+  height: 55px;
+  padding: 15px 0;
+
+  z-index: 2;
+  // background-image: url('/static/common/nav-back.png');
+}
+
+.nav-actions {
+  position: absolute;
+  right: 30px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.nav-title {
+  font-family: PingFang-SC, PingFang-SC;
+  padding-bottom: 10rpx;
+  font-size: 18px;
+  color: #ffffff;
+  line-height: 22px;
+  text-align: right;
+  // font-style: normal;
+}
+
+.content-section {
+  position: relative;
+  margin-top: 100px;
+  width: 100%;
+  height: calc(100% - 55px - 20px);
+  overflow-y: auto;
+}
+
+.notice-wrap {
+  display: flex;
+  align-items: center;
+}
+
+.notice-button {
+  width: 30rpx;
+  height: 30rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.user-info {
+  width: 100%;
+  height: 120px;
+  padding: 14px 20px;
+  padding-top: 20px;
+  box-sizing: border-box;
+  background: #ffffff;
+  border-radius: 6px;
+  font-size: 14px;
+  color: #333333;
+
+  .avatar {
+    width: 72px;
+    height: 72px;
+    border-radius: 50%;
+    margin-right: 18px;
+  }
+
+  .info {
+    width: calc(100% - 52px - 18px);
+  }
+}
+
+.info-list {
+  // margin-top: 50rpx;
+  border-top: 1px solid #e2e6ee;
+  padding-top: 10px;
+  padding-right: 20px;
+  padding-left: 20px;
+  .list-item {
+    text-align: center;
+  }
+  .mark-item {
+    border-left: 1px solid #e2e6ee;
+    border-right: 1px solid #e2e6ee;
+    padding-left: 30px;
+    padding-right: 30px;
+  }
+  .info-text {
+    font-size: 48rpx;
+    font-weight: 500;
+    color: #0355ed;
+  }
+
+  .info-tip {
+    color: #657085;
+  }
+}
+
+.text {
+  height: 19px;
+  width: 100%;
+
+  .span {
+    display: block;
+    min-width: 70px;
+  }
+}
+
+.nickname {
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.menu-list {
+  height: 274px;
+  background: #ffffff;
+  border-radius: 6px;
+  margin-top: 10px;
+  padding: 20px;
+  box-sizing: border-box;
+}
+
+.card-cell {
+  width: 100%;
+  height: 50px;
+
+  image {
+    width: 32px;
+    height: 32px;
+  }
+}
+
+.cell-con {
+  margin-left: 20px;
+  margin-right: 10px;
+  width: calc(100% - 32px - 20px - 10px);
+  height: 100%;
+  border-bottom: 0.5px solid #cacccf;
+}
+
+.cell-text {
+  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>

+ 313 - 287
pages/user/login.vue

@@ -1,40 +1,57 @@
 <template>
-	<view class="login">
-		<view class="login-top">
-			<image class="back-img" src="../../static/login/login-back.png"></image>
-			<view class="login-text">
-				<view class="text">
-					{{ $t('login.welcome') }}
-				</view>
-				<view class="text">
-					{{ $t('app.appName') }}
-				</view>
-			</view>
-		</view>
-		<view class="login-form-wrap">
-			<uni-forms class="login-form" ref="formRef" :modelValue="loginData" :rules="loginRules">
-				<uni-forms-item name="username" class="margin-bt">
-					<!-- type="number" -->
-					<uni-easyinput class="login-input" v-model="loginData.username" :placeholder="$t('login.enterUsername')" :placeholderStyle="placeholderStyle" :styles="inputStyles" />
-				</uni-forms-item>
-				<uni-forms-item name="password" class="margin-bt">
-					<uni-easyinput type="password" v-model="loginData.password" :placeholder="$t('login.enterPassword')" :placeholderStyle="placeholderStyle" :styles="inputStyles" />
-				</uni-forms-item>
-			</uni-forms>
-			<button type="primary" @click="formSubmit(formRef)">
-				{{ $t('login.login') }}
-			</button>
-			<view class="flex-row align-center justify-between">
-				<view class="btn-text" @click="loginWithDingTalk">
-					{{ $t('login.loginWithDingTalk') }}
-				</view>
-				<view class="btn-text" @click="openLanguagePopup">
-					{{ $t('login.languageChange') }}
-				</view>
-			</view>
-		</view>
-
-		<!-- <view class="uni-padding-wrap">
+  <view class="login">
+    <view class="login-top">
+      <image class="back-img" src="../../static/login/login-back.png"></image>
+      <view class="login-text">
+        <view class="text">
+          {{ $t("login.welcome") }}
+        </view>
+        <view class="text">
+          {{ $t("app.appName") }}
+        </view>
+      </view>
+    </view>
+    <view class="login-form-wrap">
+      <uni-forms
+        class="login-form"
+        ref="formRef"
+        :modelValue="loginData"
+        :rules="loginRules"
+      >
+        <uni-forms-item name="username" class="margin-bt">
+          <!-- type="number" -->
+          <uni-easyinput
+            class="login-input"
+            v-model="loginData.username"
+            :placeholder="$t('login.enterUsername')"
+            :placeholderStyle="placeholderStyle"
+            :styles="inputStyles"
+          />
+        </uni-forms-item>
+        <uni-forms-item name="password" class="margin-bt">
+          <uni-easyinput
+            type="password"
+            v-model="loginData.password"
+            :placeholder="$t('login.enterPassword')"
+            :placeholderStyle="placeholderStyle"
+            :styles="inputStyles"
+          />
+        </uni-forms-item>
+      </uni-forms>
+      <button type="primary" @click="formSubmit(formRef)">
+        {{ $t("login.login") }}
+      </button>
+      <view class="flex-row align-center justify-between">
+        <view class="btn-text" @click="loginWithDingTalk">
+          {{ $t("login.loginWithDingTalk") }}
+        </view>
+        <view class="btn-text" @click="openLanguagePopup">
+          {{ $t("login.languageChange") }}
+        </view>
+      </view>
+    </view>
+
+    <!-- <view class="uni-padding-wrap">
 			<view>
 				<checkbox-group @change="handleChange">
 					<label>
@@ -68,33 +85,37 @@
 			</uni-popup>
 		</view> -->
 
-		<!-- 引用语言选择组件 -->
-		<language-popup ref="languagePopupRef" />
-		<upgrade ref="upgradeRef" />
-	</view>
+    <!-- 引用语言选择组件 -->
+    <language-popup ref="languagePopupRef" />
+    <upgrade ref="upgradeRef" />
+  </view>
 </template>
 
 <script setup>
-import { reactive, ref, onMounted, nextTick, getCurrentInstance } from 'vue';
-import { onLoad } from '@dcloudio/uni-app';
+import { reactive, ref, onMounted, nextTick, getCurrentInstance } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
 // 引入接口api
-import { appLogin, dingTalkLogin, dingTalkLoginH5, getInfo, getTokenByUserId } from '@/api/login.js';
+import {
+  appLogin,
+  dingTalkLogin,
+  dingTalkLoginH5,
+  getInfo,
+  getTokenByUserId,
+} from "@/api/login.js";
 // 引入配置文件
-import config from '@/utils/config';
+import config from "@/utils/config";
 // 引入数据库操作
-import { saveUser } from '@/utils/appDb';
+import { saveUser } from "@/utils/appDb";
 // 引入本地存储操作
-import { setUserId, setToken, setDeptId, setUserInfo } from '@/utils/auth.js';
+import { setUserId, setToken, setDeptId, setUserInfo } from "@/utils/auth.js";
 // 引入组件
-import Upgrade from '@/components/upgrade.vue';
-import LanguagePopup from '@/components/language-popup.vue';
-import Privacy from './privacy.vue';
-import Agg from './agreement.vue';
+import Upgrade from "@/components/upgrade.vue";
+import LanguagePopup from "@/components/language-popup.vue";
 
 // 引入钉钉JSAPI -- 仅在H5环境下使用
 let dd = null;
 // #ifdef H5
-import * as dingTalkJsApi from 'dingtalk-jsapi';
+import * as dingTalkJsApi from "dingtalk-jsapi";
 dd = dingTalkJsApi;
 // #endif
 
@@ -103,14 +124,14 @@ const t = appContext.config.globalProperties.$t;
 const languagePopupRef = ref(null);
 
 const openLanguagePopup = () => {
-	languagePopupRef.value.open();
+  languagePopupRef.value.open();
 };
 
 let isChecked = ref(false);
 let my_value = ref(false);
 
 const handleChange = (val) => {
-	my_value.value = val.detail.value[0];
+  my_value.value = val.detail.value[0];
 };
 
 // let privacyRef = ref(null);
@@ -126,303 +147,308 @@ const handleChange = (val) => {
 
 // 判断当前环境是否在钉钉环境
 const isDingTalk = () => {
-	const ua = window.navigator.userAgent.toLowerCase();
-	console.log('🚀 ~ 当前环境 ~ ua:', ua);
-	return ua.includes('dingtalk') || ua.includes('dingtalkwork');
+  const ua = window.navigator.userAgent.toLowerCase();
+  console.log("🚀 ~ 当前环境 ~ ua:", ua);
+  return ua.includes("dingtalk") || ua.includes("dingtalkwork");
 };
 
 const dingTalkAutoLogin = async () => {
-	// 判断是否在钉钉环境
-	if (!isDingTalk()) {
-		console.log('当前环境不是钉钉环境,无法自动登录');
-		return;
-	}
-	// 执行钉钉微应用免登逻辑
-	loginWithDingTalkH5();
+  // 判断是否在钉钉环境
+  if (!isDingTalk()) {
+    console.log("当前环境不是钉钉环境,无法自动登录");
+    return;
+  }
+  // 执行钉钉微应用免登逻辑
+  loginWithDingTalkH5();
 };
 
 // 钉钉登录
 const loginWithDingTalk = async () => {
-	// #ifdef APP
-	const plugin = uni.requireNativePlugin('DingTalk');
-	// 钉钉登录,这里无法使用async,否则java端会报参数错误
-	plugin.login((res) => {
-		console.log(res);
-		if (res.success === 1) {
-			dingTalkLogin({
-				type: 20,
-				code: res.code,
-				state: res.state
-			}).then((res) => {
-				console.log(res);
-				handleLoginSuccess(res);
-			});
-		} else if (res.success === 2) {
-			uni.showToast({ title: t('login.dingTalkError'), icon: 'none' });
-			console.error('APP端钉钉登录失败:', res);
-		}
-	});
-	// #endif
-
-	// #ifdef H5
-	if (isDingTalk()) {
-		if (!dd) {
-			uni.showToast({ title: t('login.dingTalkJsapiMissing'), icon: 'none' });
-			return;
-		}
-		loginWithDingTalkH5();
-	} else {
-		console.log('当前是普通 H5 环境,无法使用钉钉登录');
-		uni.showToast({ title: t('login.h5DingTalk'), icon: 'none' });
-	}
-	// #endif
+  // #ifdef APP
+  const plugin = uni.requireNativePlugin("DingTalk");
+  // 钉钉登录,这里无法使用async,否则java端会报参数错误
+  plugin.login((res) => {
+    console.log(res);
+    if (res.success === 1) {
+      dingTalkLogin({
+        type: 20,
+        code: res.code,
+        state: res.state,
+      }).then((res) => {
+        console.log(res);
+        handleLoginSuccess(res);
+      });
+    } else if (res.success === 2) {
+      uni.showToast({ title: t("login.dingTalkError"), icon: "none" });
+      console.error("APP端钉钉登录失败:", res);
+    }
+  });
+  // #endif
+
+  // #ifdef H5
+  if (isDingTalk()) {
+    if (!dd) {
+      uni.showToast({ title: t("login.dingTalkJsapiMissing"), icon: "none" });
+      return;
+    }
+    loginWithDingTalkH5();
+  } else {
+    console.log("当前是普通 H5 环境,无法使用钉钉登录");
+    uni.showToast({ title: t("login.h5DingTalk"), icon: "none" });
+  }
+  // #endif
 };
 
 const loginWithDingTalkH5 = async () => {
-	const corpId = config.default.corpId;
-	console.log('🚀 ~ loginWithDingTalkH5 ~ corpId:', corpId);
-	const clientId = config.default.clientId;
-	console.log('🚀 ~ loginWithDingTalkH5 ~ clientId:', clientId);
-
-	if (!corpId || !clientId) {
-		console.error('缺少必要参数');
-		return;
-	}
-	dd.requestAuthCode({
-		corpId,
-		clientId,
-		success: async (result) => {
-			console.log('🚀 ~ loginWithDingTalkH5 ~ result:', result);
-			const { code } = result;
-			dingTalkLoginH5({
-				type: 10,
-				state: new Date().getTime(),
-				code: code
-			})
-				.then((res) => {
-					console.log('🚀 ~ loginWithDingTalkH5 ~ res:', res);
-					handleLoginSuccess(res);
-				})
-				.catch((err) => {
-					console.log('🚀 ~ loginWithDingTalkH5 ~ err:', err);
-				});
-		},
-		fail: (err) => {
-			console.log('🚀 ~ loginWithDingTalkH5 ~ err:', err);
-			uni.showToast({
-				title: '获取code失败:' + JSON.stringify(err),
-				icon: 'none'
-			});
-		}
-	});
+  const corpId = config.default.corpId;
+  console.log("🚀 ~ loginWithDingTalkH5 ~ corpId:", corpId);
+  const clientId = config.default.clientId;
+  console.log("🚀 ~ loginWithDingTalkH5 ~ clientId:", clientId);
+
+  if (!corpId || !clientId) {
+    console.error("缺少必要参数");
+    return;
+  }
+  dd.requestAuthCode({
+    corpId,
+    clientId,
+    success: async (result) => {
+      console.log("🚀 ~ loginWithDingTalkH5 ~ result:", result);
+      const { code } = result;
+      dingTalkLoginH5({
+        type: 10,
+        state: new Date().getTime(),
+        code: code,
+      })
+        .then((res) => {
+          console.log("🚀 ~ loginWithDingTalkH5 ~ res:", res);
+          handleLoginSuccess(res);
+        })
+        .catch((err) => {
+          console.log("🚀 ~ loginWithDingTalkH5 ~ err:", err);
+        });
+    },
+    fail: (err) => {
+      console.log("🚀 ~ loginWithDingTalkH5 ~ err:", err);
+      uni.showToast({
+        title: "获取code失败:" + JSON.stringify(err),
+        icon: "none",
+      });
+    },
+  });
 };
 
 onLoad(async (options) => {
-	console.log('onLoad Login', uni.getLocale(), 11, uni.getStorageSync('language'));
-
-	console.log(options);
-
-	// 保存钉钉消息传递的参数
-	if (options.userId) {
-		uni.setStorageSync('dingTalkJson', JSON.stringify(options));
-		const isLoggedIn = uni.getStorageSync('userId');
-		if (!isLoggedIn) {
-			const result = await getTokenByUserId(options.userId);
-			await handleLoginSuccess(result);
-		}
-	}
-	// #ifdef H5
-	// 当前环境为H5时,判断是否是通过钉钉微应用打开的链接
-	// 获取当前Url地址
-	const url = window.location.href;
-	console.log('当前环境为H5时 当前Url地址:', url);
-	// 判断是否是通过钉钉微应用打开的链接
-	if (url.includes('/deepoil')) {
-		dingTalkAutoLogin();
-	}
-	// #endif
+  console.log(
+    "onLoad Login",
+    uni.getLocale(),
+    11,
+    uni.getStorageSync("language"),
+  );
+
+  console.log(options);
+
+  // 保存钉钉消息传递的参数
+  if (options.userId) {
+    uni.setStorageSync("dingTalkJson", JSON.stringify(options));
+    const isLoggedIn = uni.getStorageSync("userId");
+    if (!isLoggedIn) {
+      const result = await getTokenByUserId(options.userId);
+      await handleLoginSuccess(result);
+    }
+  }
+  // #ifdef H5
+  // 当前环境为H5时,判断是否是通过钉钉微应用打开的链接
+  // 获取当前Url地址
+  const url = window.location.href;
+  console.log("当前环境为H5时 当前Url地址:", url);
+  // 判断是否是通过钉钉微应用打开的链接
+  if (url.includes("/deepoil")) {
+    dingTalkAutoLogin();
+  }
+  // #endif
 });
 
 onMounted(() => {
-	// console.log("onMounted");
-	// 检查是否需要显示语言选择弹窗
-	if (!uni.getStorageSync('language')) {
-		nextTick(() => {
-			openLanguagePopup();
-		});
-	}
-	// 检查是否已登录
-	const isLoggedIn = uni.getStorageSync('userId');
-	// console.log("isLoggedIn", isLoggedIn);
-	if (isLoggedIn) {
-		uni.switchTab({
-			url: '/pages/home/index'
-		});
-	}
+  // console.log("onMounted");
+  // 检查是否需要显示语言选择弹窗
+  if (!uni.getStorageSync("language")) {
+    nextTick(() => {
+      openLanguagePopup();
+    });
+  }
+  // 检查是否已登录
+  const isLoggedIn = uni.getStorageSync("userId");
+  // console.log("isLoggedIn", isLoggedIn);
+  if (isLoggedIn) {
+    uni.switchTab({
+      url: "/pages/entry/index",
+    });
+  }
 });
 
-const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px');
+const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
 const inputStyles = reactive({
-	backgroundColor: '#F0F3FB',
-	color: '#797979'
+  backgroundColor: "#F0F3FB",
+  color: "#797979",
 });
 const loginData = reactive({
-	username: '',
-	password: ''
+  username: "",
+  password: "",
 });
 const loginRules = ref({
-	username: {
-		rules: [
-			{
-				required: true,
-				errorMessage: t('login.enterUsername')
-			}
-		]
-	},
-	password: {
-		rules: [
-			{
-				required: true,
-				errorMessage: t('login.enterPassword')
-			}
-		]
-	}
+  username: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t("login.enterUsername"),
+      },
+    ],
+  },
+  password: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t("login.enterPassword"),
+      },
+    ],
+  },
 });
 
 const formRef = ref();
 const formSubmit = async (formEl) => {
-	// if (!my_value.value) {
-	// 	uni.showToast({
-	// 		title: '请阅读并同意隐私政策和用户服务协议',
-	// 		icon: 'none', // 可选 success/error/loading/none
-	// 		duration: 2000, // 持续时间,单位ms
-	// 		mask: true // 是否显示透明蒙层,防止触摸穿透
-	// 	});
-
-	// 	return;
-	// }
-	if (!formEl) return;
-	await formEl
-		.validate()
-		.then((res) => {
-			appLogin({
-				...loginData
-				// rememberMe: ,
-				// tenantName: ""
-			})
-				.then(async (result) => {
-					console.log('result,', result.data);
-					if (result) {
-						await saveUser({
-							name: loginData.username,
-							pwd: loginData.password
-						});
-						await handleLoginSuccess(result);
-					}
-				})
-				.finally(() => {});
-		})
-		.catch((err) => {
-			console.log('err', err);
-		});
+  // if (!my_value.value) {
+  // 	uni.showToast({
+  // 		title: '请阅读并同意隐私政策和用户服务协议',
+  // 		icon: 'none', // 可选 success/error/loading/none
+  // 		duration: 2000, // 持续时间,单位ms
+  // 		mask: true // 是否显示透明蒙层,防止触摸穿透
+  // 	});
+
+  // 	return;
+  // }
+  if (!formEl) return;
+  await formEl
+    .validate()
+    .then((res) => {
+      appLogin({
+        ...loginData,
+        // rememberMe: ,
+        // tenantName: ""
+      })
+        .then(async (result) => {
+          console.log("result,", result.data);
+          if (result) {
+            await saveUser({
+              name: loginData.username,
+              pwd: loginData.password,
+            });
+            await handleLoginSuccess(result);
+          }
+        })
+        .finally(() => {});
+    })
+    .catch((err) => {
+      console.log("err", err);
+    });
 };
 
 const handleLoginSuccess = async (result) => {
-	if (result) {
-		await setUserId(result.data.userId);
-		await setToken(result.data);
-		await getInfo().then(async (res) => {
-			// console.log('useres', res)
-			const data = JSON.stringify({
-				user: res.data.user,
-				roles: res.data.roles
-			});
-			// console.log('data', data)
-			await setUserInfo(data);
-			await setDeptId(res.data.user.deptId);
-
-			await uni.switchTab({
-				url: '/pages/home/index'
-			});
-		});
-	}
+  if (result) {
+    await setUserId(result.data.userId);
+    await setToken(result.data);
+    await getInfo().then(async (res) => {
+      // console.log('useres', res)
+      const data = JSON.stringify({
+        user: res.data.user,
+        roles: res.data.roles,
+      });
+      // console.log('data', data)
+      await setUserInfo(data);
+      await setDeptId(res.data.user.deptId);
+
+      await uni.switchTab({
+        url: "/pages/entry/index",
+      });
+    });
+  }
 };
 </script>
 
 <style lang="scss" scoped>
 .privacy {
-	height: 60vh;
-	width: 85vw;
-	overflow: hidden;
+  height: 60vh;
+  width: 85vw;
+  overflow: hidden;
 }
 
 .uni-padding-wrap {
-	display: flex;
-	z-index: 999;
-	justify-content: center;
-	align-items: center;
-	padding: 0 10rpx;
+  display: flex;
+  z-index: 999;
+  justify-content: center;
+  align-items: center;
+  padding: 0 10rpx;
 }
 .uni-title {
-	font-size: 12px;
+  font-size: 12px;
 }
 .login-top {
-	position: relative;
-	width: 100%;
-	height: 422rpx;
+  position: relative;
+  width: 100%;
+  height: 422rpx;
 }
 
 .back-img {
-	width: 100%;
-	height: 100%;
+  width: 100%;
+  height: 100%;
 }
 
 .login-text {
-	width: 100%;
-	height: 100%;
-	box-sizing: border-box;
-	position: absolute;
-	top: 0;
-	left: 0;
-	color: #ffffff;
-	font-size: 40rpx;
-	font-family: 'Negreta,PingFang SC';
-	font-weight: 600;
-	padding: 0 56rpx;
-	display: flex;
-	justify-content: center;
-	flex-direction: column;
-
-	.text {
-		width: 100%;
-		margin-bottom: 6rpx;
-	}
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  position: absolute;
+  top: 0;
+  left: 0;
+  color: #ffffff;
+  font-size: 40rpx;
+  font-family: "Negreta,PingFang SC";
+  font-weight: 600;
+  padding: 0 56rpx;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+
+  .text {
+    width: 100%;
+    margin-bottom: 6rpx;
+  }
 }
 
 .margin-bt {
-	margin-bottom: 25px;
+  margin-bottom: 25px;
 }
 
 .login-form-wrap {
-	padding: 60rpx;
+  padding: 60rpx;
 }
 
 :deep(.uni-easyinput__content-input) {
-	height: 45px;
+  height: 45px;
 }
 
 :deep(.uni-input-input) {
-	color: #999999 !important;
+  color: #999999 !important;
 }
 
-uni-button[type='primary'] {
-	background: #004098;
+uni-button[type="primary"] {
+  background: #004098;
 }
 
 .btn-text {
-	color: #004098;
-	margin-top: 20px;
-	font-size: 14px;
-	font-weight: 500;
+  color: #004098;
+  margin-top: 20px;
+  font-size: 14px;
+  font-weight: 500;
 }
-</style>
+</style>

BIN
static/entry/baoyang.png


BIN
static/entry/bg.png


BIN
static/entry/byquery.png


BIN
static/entry/guzhang.png


BIN
static/entry/kucun.png


BIN
static/entry/lianyou.png


BIN
static/entry/pms.png


BIN
static/entry/qhse.png


BIN
static/entry/record.png


BIN
static/entry/ruidu.png


BIN
static/entry/ruiheng.png


BIN
static/entry/ruiying.png


BIN
static/entry/status.png


BIN
static/entry/taizhang.png


BIN
static/entry/weixiu.png


BIN
static/entry/xunjian.png


BIN
static/entry/yunying.png


BIN
static/entry/zeren.png


BIN
static/entry/zutai.png


BIN
static/home/rh.png


BIN
static/home/ruiying.png


BIN
static/user/avatar.png


BIN
static/user/bg.png


BIN
static/user/logout.png


+ 675 - 0
utils/hot-update.js

@@ -0,0 +1,675 @@
+import config from "@/utils/config";
+
+/**
+ * 这里用本地存储做一个非常轻量的“启动期热更新锁”。
+ *
+ * 为什么不用纯内存变量:
+ * 1. App.vue 的 onLaunch 和页面组件 mounted 不在同一个文件里,直接共享内存状态不直观。
+ * 2. 现有项目已经在登录页、首页挂了旧的整包升级弹窗组件,我们需要一个跨文件、最小改动的互斥标记。
+ * 3. 用 storage 的好处是组件侧只要读一个 key 就能知道“启动阶段是否已经有热更新流程在跑”。
+ *
+ * 注意:
+ * 这个锁只是为了解决“启动期并发弹窗”的问题,不是分布式锁,也不是强一致锁。
+ * 对当前这个 uni-app 项目来说,这样的复杂度已经足够,而且侵入性最低。
+ */
+export const HOT_UPDATE_LOCK_KEY = "__APP_WGT_HOT_UPDATE_RUNNING__";
+export const HOT_UPDATE_FALLBACK_APP_CHECK_EVENT =
+  "__APP_WGT_HOT_UPDATE_FALLBACK_APP_CHECK__";
+
+/**
+ * 当用户点击“稍后再说”时,我们把当前版本记下来。
+ *
+ * 这样做的原因:
+ * 1. 热更新检查放在 onLaunch,意味着每次冷启动都会检查一次。
+ * 2. 如果用户已经明确拒绝了某个版本,下一次冷启动继续弹同一个版本,体验会比较差。
+ * 3. 只记录“被跳过的目标版本”,新版本来了仍然会重新提示。
+ */
+const HOT_UPDATE_SKIP_VERSION_KEY = "__APP_WGT_HOT_UPDATE_SKIP_VERSION__";
+
+/**
+ * 这里单独声明接口地址,而不是直接写死在 uni.request 里。
+ *
+ * 这样做的原因:
+ * 1. 现有项目已经有 config 环境配置,说明项目本身就是按环境切换接口域名的。
+ * 2. 热更新检查应该沿用现有配置习惯,避免你上线后再去改很多地方。
+ * 3. 这个路径是本次方案“约定”的服务端接口,你可以直接让后端按这个接口实现,
+ *    也可以只改这里一处,把路径替换成你们实际的地址。
+ */
+const HOT_UPDATE_API_URL =
+  config.default.apiUrl + config.default.apiUrlSuffix + "/rq/iot-app/newWgt";
+
+/**
+ * 读取启动期热更新锁。
+ *
+ * 对页面组件的意义:
+ * 旧的整包升级逻辑挂在页面里,页面 mounted 时如果发现启动热更新还在跑,
+ * 就应该先让路,避免同时弹两个升级框。
+ */
+export const isHotUpdateRunning = () => {
+  return uni.getStorageSync(HOT_UPDATE_LOCK_KEY) === "1";
+};
+
+/**
+ * 设置/清理热更新锁。
+ *
+ * 输入:
+ * - running: Boolean,true 表示流程开始,false 表示流程结束。
+ *
+ * 输出:
+ * - 无返回值,副作用是修改 storage 中的互斥标记。
+ */
+const setHotUpdateRunning = (running) => {
+  if (running) {
+    uni.setStorageSync(HOT_UPDATE_LOCK_KEY, "1");
+  } else {
+    uni.removeStorageSync(HOT_UPDATE_LOCK_KEY);
+  }
+};
+
+/**
+ * 等待 plus 对象可用。
+ *
+ * 为什么即便在 APP-PLUS 里也还要做这一步:
+ * 1. 你要求热更新接在启动阶段,启动阶段最容易踩到“plus 尚未完全就绪”的时序问题。
+ * 2. 现有 App.vue 的 onLaunch 里已经直接用了 plus.globalEvent,这说明项目默认运行在 App-Plus 环境。
+ * 3. 但热更新比普通日志更敏感,因为它要马上调用 plus.runtime.getProperty / install / restart,
+ *    所以这里显式兜底一次,会比“直接假设 plus 一定存在”更稳。
+ *
+ * 成功分支:
+ * - plus 已就绪,resolve,继续后续热更新流程。
+ *
+ * 失败分支:
+ * - 理论上这里没有主动 reject,超时场景也继续等待;
+ *   如果 plus 始终不可用,后续逻辑不会开始,也就不会影响原有启动逻辑。
+ */
+const waitForPlusReady = () => {
+  return new Promise((resolve) => {
+    if (typeof plus !== "undefined") {
+      resolve();
+      return;
+    }
+
+    document.addEventListener(
+      "plusready",
+      () => {
+        resolve();
+      },
+      { once: true }
+    );
+  });
+};
+
+/**
+ * 使用 plus.runtime.getProperty 读取当前资源包信息。
+ *
+ * 这是这次方案里最关键的一步,也是你特别强调不能替换掉的地方。
+ *
+ * 为什么必须用它:
+ * 1. 你要做的是 .wgt 资源热更新,比较的是“当前资源包版本”和“服务端资源包版本”。
+ * 2. plus.runtime.getProperty(plus.runtime.appid) 返回的是当前应用资源信息,
+ *    其中 wgtinfo.version 才是最贴近“资源版本”的值。
+ * 3. plus.runtime.version 更偏运行时/客户端信息,uni.getSystemInfo 也不是这个场景最合适的来源,
+ *    所以这里严格按你的要求使用 getProperty。
+ *
+ * 输出:
+ * - resolve(wgtInfo)
+ *   其中最常用的字段包括:
+ *   - appid: 当前应用的 DCloud appid
+ *   - version: 当前资源版本
+ *   - name: 当前应用名称
+ */
+export const getCurrentWgtInfo = () => {
+  return new Promise((resolve, reject) => {
+    try {
+      plus.runtime.getProperty(plus.runtime.appid, (wgtInfo) => {
+        if (!wgtInfo || !wgtInfo.version) {
+          reject(new Error("未能获取当前应用资源版本信息"));
+          return;
+        }
+
+        resolve(wgtInfo);
+      });
+    } catch (error) {
+      reject(error);
+    }
+  });
+};
+
+/**
+ * 一个简单的版本比较函数。
+ *
+ * 为什么这里仍然保留本地比较,而不是完全信任服务端的 update 字段:
+ * 1. 服务端当然应该负责判断版本,但客户端再做一次兜底,可以避免后端配置错误导致重复更新。
+ * 2. 这对“启动期自动更新”尤其重要,因为一旦后端误配,用户会在每次启动都被弹框打扰。
+ *
+ * 比较规则:
+ * - a > b 返回 1
+ * - a = b 返回 0
+ * - a < b 返回 -1
+ */
+const compareVersion = (a = "", b = "") => {
+  const aList = String(a)
+    .split(".")
+    .map((item) => Number(item || 0));
+  const bList = String(b)
+    .split(".")
+    .map((item) => Number(item || 0));
+  const length = Math.max(aList.length, bList.length);
+
+  for (let i = 0; i < length; i += 1) {
+    const aNum = aList[i] || 0;
+    const bNum = bList[i] || 0;
+
+    if (aNum > bNum) return 1;
+    if (aNum < bNum) return -1;
+  }
+
+  return 0;
+};
+
+/**
+ * 判断服务端返回的地址是否真的是 .wgt 文件。
+ *
+ * 这里额外去掉 query/hash,是为了兼容这类地址:
+ * - https://example.com/app/update.wgt?sign=xxx
+ * - https://example.com/app/update.wgt#download
+ */
+const isWgtPackageUrl = (url = "") => {
+  const normalizedUrl = String(url)
+    .trim()
+    .split("#")[0]
+    .split("?")[0]
+    .toLowerCase();
+
+  return normalizedUrl.endsWith(".wgt");
+};
+
+/**
+ * 请求服务端检查是否存在新的 .wgt 资源包。
+ *
+ * 为什么这里直接使用 uni.request,而不是复用现有 utils/request.js:
+ * 1. 现有 request 封装默认会带 token、tenant-id、loading、401 刷新令牌等业务逻辑。
+ * 2. 热更新检查发生在 App 启动最早阶段,不应该依赖登录态,也不应该因为 token 过期影响更新检查。
+ * 3. 启动期代码越“去业务化”,越不容易被后续鉴权改造牵连。
+ *
+ * 请求参数说明:
+ * - appid: 用来确保服务端下发的是当前应用对应的更新包。
+ * - version: 当前资源版本,服务端据此判断是否需要更新。
+ * - platform: 当前平台,便于服务端做 Android / iOS 区分。
+ * - name: 可选,便于后端日志排查。
+ *
+ * 服务端返回格式约定:
+ * {
+ *   code: 0,
+ *   data: {
+ *     update: true,
+ *     packageType: "wgt",
+ *     version: "1.3.6",
+ *     wgtUrl: "https://example.com/app/1.3.6/update.wgt",
+ *     note: "1. 修复巡检表单保存异常\\n2. 优化首页加载速度",
+ *     force: false
+ *   },
+ *   message: "success"
+ * }
+ *
+ * 字段用途:
+ * - code: 业务状态码,0 表示接口业务成功。
+ * - data.update: 服务端是否认为当前客户端需要更新。
+ * - data.packageType: 更新类型。这里约定 wgt 表示资源热更新,native 表示整包更新。
+ * - data.version: 服务端最新资源版本。
+ * - data.wgtUrl: .wgt 下载地址。
+ * - data.note: 更新说明,弹窗里给用户看。
+ * - data.force: 是否强制更新。true 时不展示取消按钮。
+ * - message: 服务端通用提示。
+ */
+const requestHotUpdateInfo = () => {
+  console.log("11", 11);
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: HOT_UPDATE_API_URL,
+      method: "GET",
+      header: {
+        "Content-Type": "application/json",
+        "tenant-id": "1",
+      },
+      timeout: 10000,
+      success: (response) => {
+        if (response.statusCode !== 200) {
+          reject(
+            new Error(
+              `热更新检查接口请求失败,HTTP 状态码:${response.statusCode}`
+            )
+          );
+          return;
+        }
+
+        resolve(response.data || {});
+      },
+      fail: (error) => {
+        reject(
+          new Error(
+            error?.errMsg || "热更新检查接口请求失败,请检查网络或服务端状态"
+          )
+        );
+      },
+      complete: () => {
+        /**
+         * 这里故意不做 UI 处理,只保留 complete 生命周期位置。
+         *
+         * 原因:
+         * 启动期的检查更新是“后台检查”,不是用户手动点按钮触发的请求。
+         * 如果这里也弹 loading,会和现有登录页/首页首屏体验打架。
+         */
+      },
+    });
+  });
+};
+
+/**
+ * 规范化服务端返回,统一后续分支判断。
+ *
+ * 为什么要单独做这一层:
+ * 1. 启动流程最怕到处写 if/else,后面维护时很难看出到底在哪个条件提前返回。
+ * 2. 统一成“shouldUpdate + reason + payload”的结构,后面 orchestration 会清晰很多。
+ *
+ * 成功输出示例:
+ * {
+ *   shouldUpdate: true,
+ *   reason: "has-update",
+ *   payload: { ...服务端 data... }
+ * }
+ */
+const normalizeUpdateResult = (response, currentVersion) => {
+  console.log("response", response);
+
+  const code = response?.code;
+  const message = response?.message || response?.msg || "";
+  const data = response?.data || {};
+
+  if (code !== 0) {
+    return {
+      shouldUpdate: false,
+      reason: "server-error",
+      message: message || "热更新接口返回了业务错误",
+    };
+  }
+
+  // if (!data.update) {
+  //   return {
+  //     shouldUpdate: false,
+  //     reason: "no-update",
+  //     message: "服务端确认当前版本无需更新",
+  //   };
+  // }
+
+  // if (data.packageType && data.packageType !== "wgt") {
+  //   return {
+  //     shouldUpdate: false,
+  //     reason: "not-wgt",
+  //     message: `服务端返回的是 ${data.packageType} 更新,不属于本次 .wgt 热更新流程`,
+  //     payload: data,
+  //   };
+  // }
+
+  if (data.wgtUrl && !isWgtPackageUrl(data.wgtUrl)) {
+    return {
+      shouldUpdate: false,
+      reason: "fallback-app-check-version",
+      message:
+        "服务端返回的 wgtUrl 不是 .wgt 文件地址,回退到原有整包升级检查流程",
+      payload: data,
+    };
+  }
+
+  if (!data.version || !data.wgtUrl) {
+    return {
+      shouldUpdate: false,
+      reason: "invalid-data",
+      message: "服务端返回缺少 version 或 wgtUrl,无法继续热更新",
+    };
+  }
+
+  if (compareVersion(data.version, currentVersion) <= 0) {
+    return {
+      shouldUpdate: false,
+      reason: "not-newer",
+      message: "服务端返回的版本不高于当前版本,跳过本次热更新",
+      payload: data,
+    };
+  }
+
+  const skippedVersion = uni.getStorageSync(HOT_UPDATE_SKIP_VERSION_KEY);
+  if (!data.force && skippedVersion === data.version) {
+    return {
+      shouldUpdate: false,
+      reason: "skipped-same-version",
+      message: "用户之前已经跳过该版本,本次启动不再重复提示",
+      payload: data,
+    };
+  }
+
+  return {
+    shouldUpdate: true,
+    reason: "has-update",
+    payload: data,
+  };
+};
+
+/**
+ * 组装展示给用户的更新文案。
+ *
+ * 这里没有引入 i18n,也没有复用页面组件里的 UI。
+ *
+ * 原因:
+ * 1. 热更新发生在 App 启动阶段,应该尽量减少对页面层、全局实例、组件状态的依赖。
+ * 2. 现有项目的旧升级逻辑是在组件里拿 $t,这条链路不适合直接照搬到启动期。
+ * 3. 启动期用系统级的 uni.showModal 更稳,也更符合“最小侵入式改造”。
+ */
+const buildUpdateContent = (payload) => {
+  const note = payload.note
+    ? String(payload.note)
+    : "修复已知问题,优化使用体验";
+  const content = [`发现新的资源版本:${payload.version}`, "", note];
+
+  if (payload.force) {
+    content.push("", "该更新为强制更新,请完成安装后继续使用。");
+  }
+
+  return content.join("\n");
+};
+
+/**
+ * 提示用户是否开始热更新。
+ *
+ * 成功分支:
+ * - 用户点击确认,resolve(true),继续下载。
+ *
+ * 失败/取消分支:
+ * - 非强更时,用户点击取消,记录本次跳过的版本,resolve(false)。
+ * - 强更时,没有取消按钮,只能确认。
+ */
+const confirmHotUpdate = (payload) => {
+  return new Promise((resolve) => {
+    uni.showModal({
+      title: payload.force ? "发现重要更新" : "发现新版本",
+      content: buildUpdateContent(payload),
+      showCancel: !payload.force,
+      confirmText: "立即更新",
+      cancelText: "稍后再说",
+      success: (result) => {
+        if (result.confirm) {
+          uni.removeStorageSync(HOT_UPDATE_SKIP_VERSION_KEY);
+          resolve(true);
+          return;
+        }
+
+        if (!payload.force && result.cancel) {
+          uni.setStorageSync(HOT_UPDATE_SKIP_VERSION_KEY, payload.version);
+        }
+        resolve(false);
+      },
+      fail: () => {
+        resolve(false);
+      },
+    });
+  });
+};
+
+/**
+ * 下载 .wgt 文件。
+ *
+ * 为什么这里要单独拆出来:
+ * 1. 下载是一个明显独立的异步阶段,最需要看清楚成功和失败分支。
+ * 2. 下载是启动期流程里耗时较长的一步,单独封装后更方便控制提示方式。
+ *
+ * 用户可见表现:
+ * - 下载中:顶部展示固定 loading 提示,不展示实时百分比。
+ * - 下载成功:loading 自动关闭,进入安装阶段。
+ * - 下载失败:loading 关闭,并在上层统一提示“热更新失败”。
+ *
+ * 输出:
+ * - resolve(tempFilePath) 返回下载到本地后的临时文件路径。
+ */
+const downloadWgtPackage = (wgtUrl) => {
+  return new Promise((resolve, reject) => {
+    uni.showLoading({
+      title: "下载更新中",
+      mask: true,
+    });
+
+    uni.downloadFile({
+      url: wgtUrl,
+      timeout: 600000,
+      success: (response) => {
+        if (response.statusCode === 200 && response.tempFilePath) {
+          resolve(response.tempFilePath);
+          return;
+        }
+
+        reject(
+          new Error(
+            `热更新包下载失败,HTTP 状态码:${response.statusCode || "unknown"}`
+          )
+        );
+      },
+      fail: (error) => {
+        reject(new Error(error?.errMsg || "热更新包下载失败"));
+      },
+      complete: () => {
+        uni.hideLoading();
+      },
+    });
+  });
+};
+
+/**
+ * 安装下载完成后的 .wgt 包。
+ *
+ * 为什么安装时要带 appid:
+ * 1. 官方文档说明可以传入 appid 做校验。
+ * 2. 这可以避免服务端配置错误时,把不属于当前应用的资源包安装进来。
+ *
+ * 为什么 force 设为 false:
+ * 1. force=true 会跳过版本校验,风险更高。
+ * 2. 现在我们已经在服务端和客户端各做了一层版本判断,没有必要强制覆盖旧包。
+ */
+const installWgtPackage = (filePath, appid) => {
+  return new Promise((resolve, reject) => {
+    try {
+      plus.runtime.install(
+        filePath,
+        {
+          appid,
+          force: false,
+        },
+        () => {
+          resolve();
+        },
+        (error) => {
+          reject(
+            new Error(
+              error?.message || `热更新安装失败,错误码:${error?.code || ""}`
+            )
+          );
+        }
+      );
+    } catch (error) {
+      reject(error);
+    }
+  });
+};
+
+/**
+ * 安装完成后提示用户并重启应用。
+ *
+ * 为什么这里显式调用 plus.runtime.restart:
+ * 1. .wgt 安装完成只是把新资源放到了运行环境,想让用户真正跑到新代码,还需要重启应用。
+ * 2. 这一步和旧的页面级整包升级不同,旧逻辑 Android 是直接安装 apk。
+ * 3. 热更新的目标是“尽快让新前端资源生效”,因此安装完成后立即重启是最直接的方案。
+ *
+ * 注意这里在 restart 前先清掉锁:
+ * - 因为 storage 会跨重启保留。
+ * - 如果不先清,应用重启后旧组件会误以为“热更新一直在运行中”,造成后续检查失效。
+ */
+const restartAppAfterInstall = (targetVersion) => {
+  return new Promise((resolve) => {
+    uni.showModal({
+      title: "更新完成",
+      content: `资源版本 ${targetVersion} 已安装完成,点击确定后将立即重启应用使更新生效。`,
+      showCancel: false,
+      success: () => {
+        setHotUpdateRunning(false);
+        plus.runtime.restart();
+        resolve();
+      },
+      fail: () => {
+        /**
+         * 即便弹窗失败,我们也仍然执行重启。
+         *
+         * 原因:
+         * - 到这里说明安装已经完成。
+         * - 如果不重启,用户还会继续运行旧资源,和“安装成功”的状态不一致。
+         */
+        setHotUpdateRunning(false);
+        plus.runtime.restart();
+        resolve();
+      },
+    });
+  });
+};
+
+/**
+ * 统一展示“热更新失败”提示。
+ *
+ * 为什么不把所有失败都提示给用户:
+ * 1. 启动阶段的“检查接口失败 / 无网络”很常见,如果每次启动都弹错,会严重影响体验。
+ * 2. 所以我们只对“用户已经明确点击开始更新之后”的失败给出弹窗提示。
+ * 3. 纯检查阶段失败,记录日志后继续进入原有流程即可。
+ */
+const showHotUpdateError = (error, title = "更新失败") => {
+  uni.showModal({
+    title,
+    content: error?.message || "热更新执行失败,请稍后重试",
+    showCancel: false,
+  });
+};
+
+/**
+ * 对外暴露的启动期热更新入口。
+ *
+ * 这是你后续在 App.vue onLaunch 里真正调用的函数。
+ *
+ * 整体执行顺序:
+ * 1. 等待 plus 就绪
+ * 2. 获取当前资源版本
+ * 3. 请求服务端检查更新
+ * 4. 判断是否存在新的 .wgt 包
+ * 5. 提示用户确认
+ * 6. 下载 .wgt
+ * 7. 安装 .wgt
+ * 8. 重启应用
+ *
+ * 返回值:
+ * - Promise<{ status: string, ... }>
+ *   主要用于日志或后续扩展,目前 App.vue 不需要依赖它的返回值。
+ */
+export const runWgtHotUpdate = async () => {
+  // #ifdef APP-PLUS
+  if (isHotUpdateRunning()) {
+    return {
+      status: "locked",
+      message: "已有热更新流程在执行,跳过重复检查",
+    };
+  }
+
+  let currentStage = "prepare";
+  let finalResult = null;
+  setHotUpdateRunning(true);
+
+  try {
+    currentStage = "plus-ready";
+    await waitForPlusReady();
+
+    currentStage = "read-local-version";
+    const wgtInfo = await getCurrentWgtInfo();
+
+    currentStage = "request-server";
+    const response = await requestHotUpdateInfo();
+    const updateResult = normalizeUpdateResult(response, wgtInfo.version);
+
+    if (!updateResult.shouldUpdate) {
+      finalResult = {
+        status: updateResult.reason,
+        message: updateResult.message,
+        data: updateResult.payload || null,
+      };
+      return finalResult;
+    }
+
+    currentStage = "confirm";
+    const confirmed = await confirmHotUpdate(updateResult.payload);
+    if (!confirmed) {
+      finalResult = {
+        status: "cancelled",
+        message: "用户取消本次热更新",
+      };
+      return finalResult;
+    }
+
+    currentStage = "download";
+    const filePath = await downloadWgtPackage(updateResult.payload.wgtUrl);
+
+    currentStage = "install";
+    await installWgtPackage(filePath, wgtInfo.appid);
+
+    currentStage = "restart";
+    await restartAppAfterInstall(updateResult.payload.version);
+
+    finalResult = {
+      status: "restarted",
+      message: "热更新安装完成,应用已触发重启",
+    };
+    return finalResult;
+  } catch (error) {
+    console.error("[hot-update] 执行失败:", currentStage, error);
+
+    if (currentStage === "download") {
+      showHotUpdateError(error, "下载失败");
+    } else if (currentStage === "install" || currentStage === "restart") {
+      showHotUpdateError(error, "安装失败");
+    } else {
+      /**
+       * 检查阶段失败只打日志,不主动打扰用户。
+       *
+       * 为什么这么处理:
+       * - 当前项目 onLaunch 里还有数据库初始化、url scheme 参数接收等逻辑。
+       * - 热更新检查只是“加分项”,不应该因为检查失败阻断原有启动流程。
+       * - 这样最符合“最小侵入式改造”的目标。
+       */
+    }
+
+    finalResult = {
+      status: "error",
+      stage: currentStage,
+      error,
+    };
+    return finalResult;
+  } finally {
+    /**
+     * 正常情况下:
+     * - 无更新 / 用户取消 / 下载失败 / 安装失败,都会走到这里清锁。
+     * - 安装成功后会在 restart 之前先清锁,所以这里再次清理也没有副作用。
+     */
+    setHotUpdateRunning(false);
+
+    if (finalResult?.status === "fallback-app-check-version") {
+      uni.$emit(HOT_UPDATE_FALLBACK_APP_CHECK_EVENT, finalResult);
+    }
+  }
+  // #endif
+
+  return {
+    status: "skip-non-app-plus",
+    message: "当前不是 APP-PLUS 环境,跳过 .wgt 热更新检查",
+  };
+};

+ 21 - 17
utils/upgrade.js

@@ -1,3 +1,5 @@
+import { getCurrentWgtInfo } from "./hot-update";
+
 /**
  * @description 检查app版本是否需要升级
  * @param name:最新版本名称
@@ -6,23 +8,18 @@
  * @param url:下载链接
  * @param forceUpdate:是否强制升级
  */
-export const checkVersion = ({
+export const checkVersion = async ({
   name, //最新版本名称
   code, //最新版本号
   content, //更新内容
   url, //下载链接
   forceUpdate, //是否强制升级
 }) => {
-  const selfVersionCode = uni.getAppBaseInfo().appVersion; // uni.getSystemInfoSync().appVersion; //当前App版本号
-  // console.log('selfVersionCode-getAppBaseInfo', uni.getAppBaseInfo())
-  console.log("selfVersionCode1", selfVersionCode);
-  //线上版本号高于当前,进行在线升级
-  if (code > selfVersionCode) {
+  // const selfVersionCode = uni.getAppBaseInfo().appVersion;
+  const info = await getCurrentWgtInfo();
+  if (code > info.version) {
     let platform = uni.getSystemInfoSync().platform; //手机平台
-    console.log("platform", platform);
-    //安卓手机弹窗升级
     if (platform === "android") {
-      //当前页面不是升级页面跳转防止多次打开
       uni.$emit("upgrade-app", {
         name,
         content,
@@ -30,9 +27,7 @@ export const checkVersion = ({
         forceUpdate,
       });
       return true;
-    }
-    //IOS无法在线升级提示到商店下载
-    else {
+    } else {
       uni.showModal({
         title: "发现新版本 V" + name,
         content: "请到App store进行升级",
@@ -40,6 +35,8 @@ export const checkVersion = ({
       });
     }
   }
+
+  return false;
 };
 
 //获取当前页面url
@@ -64,7 +61,7 @@ export const downloadApp = (downloadUrl, progressCallBack = () => {}) => {
   // 进度更新节流控制变量
   let lastProgressTime = 0;
   const PROGRESS_INTERVAL = 300; // 限制300ms内最多更新一次进度
-  
+
   return new Promise((resolve, reject) => {
     // 创建下载任务
     const downloadTask = plus.downloader.createDownload(
@@ -100,15 +97,22 @@ export const downloadApp = (downloadUrl, progressCallBack = () => {}) => {
           const hasProgress = task.totalSize && task.totalSize > 0;
           if (hasProgress) {
             mockProgress = 0;
-            const current = parseInt((100 * task.downloadedSize) / task.totalSize);
+            const current = parseInt(
+              (100 * task.downloadedSize) / task.totalSize
+            );
             progressCallBack(current, task.downloadedSize, task.totalSize);
           } else {
             // 模拟进度:添加节流控制和平滑增长
-            if (mockProgress < 95 && (now - lastProgressTime > PROGRESS_INTERVAL)) {
+            if (
+              mockProgress < 95 &&
+              now - lastProgressTime > PROGRESS_INTERVAL
+            ) {
               // 根据时间间隔动态调整增长速度,避免停滞感
               const baseIncrement = 1;
-              const maxIncrement = mockProgress < 30 ? 3 : mockProgress < 60 ? 2 : 1;
-              const increment = Math.floor(Math.random() * maxIncrement) + baseIncrement;
+              const maxIncrement =
+                mockProgress < 30 ? 3 : mockProgress < 60 ? 2 : 1;
+              const increment =
+                Math.floor(Math.random() * maxIncrement) + baseIncrement;
               mockProgress = Math.min(mockProgress + increment, 95);
               lastProgressTime = now; // 更新最后更新时间
               progressCallBack(mockProgress, task.downloadedSize, 0);

Неке датотеке нису приказане због велике количине промена