yanghao hace 1 mes
padre
commit
6e531b926b

+ 2 - 2
android/simpleDemo/build.gradle

@@ -10,8 +10,8 @@ android {
         minSdkVersion 21
         // targetSdkVersion 33
         targetSdk 33
-        versionCode 10304
-        versionName "1.3.4"
+        versionCode 10305
+        versionName "1.3.5"
         multiDexEnabled true
         compileOptions {
             sourceCompatibility JavaVersion.VERSION_1_8

+ 27 - 9
api/maintenance.js

@@ -11,14 +11,32 @@ export function getMaintenanceCount(params) {
 }
 
 // 获取保养工单列表
-export function getMaintenanceList(params) {
-  return request({
-    url: '/pms/iot-main-work-order/sortedMainWorkOrderPage',
-    // url: '/pms/iot-main-work-order/page',
-    method: 'get',
-    params
-  })
-}
+export function getMaintenanceList(params) {
+  return request({
+    url: '/pms/iot-main-work-order/sortedMainWorkOrderPage',
+    // url: '/pms/iot-main-work-order/page',
+    method: 'get',
+    params
+  })
+}
+
+// 以设备为维度查询最近保养距离
+export function getDeviceMainDistances(params) {
+  return request({
+    url: '/pms/iot-main-work-order/deviceMainDistances',
+    method: 'get',
+    params
+  })
+}
+
+// 根据保养计划查询保养项
+export function getMainPlanBOMs(params) {
+  return request({
+    url: '/pms/iot-maintenance-bom/getMainPlanBOMs',
+    method: 'get',
+    params
+  })
+}
 // 根据设备id获取设备保养项
 export function getDeviceAssociateBomList(params) {
   return request({
@@ -68,4 +86,4 @@ export function getBomMaterialsByWorkOrderId(params) {
     method: 'get',
     params
   })
-}
+}

+ 33 - 0
api/ruiduReport.js

@@ -0,0 +1,33 @@
+import { request } from "@/utils/request";
+
+export function getRuiduReportPage(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/page",
+    method: "get",
+    params,
+  });
+}
+
+export function getRuiduReportTeamPage(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/teamReports",
+    method: "get",
+    params,
+  });
+}
+
+export function getRuiduReportWellPage(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/wellReports",
+    method: "get",
+    params,
+  });
+}
+
+export function getTaskWellNames(params) {
+  return request({
+    url: "/rq/iot-project-task/taskTreeWellNames",
+    method: "get",
+    params,
+  });
+}

+ 12 - 12
config/env.dev.js

@@ -1,14 +1,14 @@
 // 开发环境配置
 export default {
-	// apiUrl: "http://192.168.188.198:48080",
-	apiUrl: "https://iot.deepoil.cc",
-	// apiUrl: 'https://aims.deepoil.cc', //正式
-	apiUrlSuffix: "/admin-api",
-	// 其他开发环境配置...
-	corpId: "dingbe7f9a7e8cffa2bd35c2f4657eb6378f", //钉钉微应用 企业的CorpID - 正式环境
-	clientId: "dingmr9ez0ecgbmscfeb", //钉钉微应用的Client ID - 正式环境(原企业内部应用的 AppKey。原第三方企业应用的 SuiteKey。)
-	AgentId: "3687646006", //钉钉微应用的AgentID - 正式环境
-	MiniAppId: "5000000006298501", //钉钉微应用的MiniAppID - 正式环境
-	AppKey: "dingmr9ez0ecgbmscfeb", //钉钉微应用的AppKey - 正式环境
-	AppSecret: "VhG_zMdTvIBwA_0Ef8FJ0foH3VYYo5T-kw0ukX_PBA8Ah1xl7AjDw5RVYCU0DTpe", //钉钉微应用的AppSecret - 正式环境
-};
+  // apiUrl: "http://192.168.188.198:48080",
+  apiUrl: "https://iot.deepoli.css:5443",
+  // apiUrl: 'https://aims.deepoil.cc', //正式
+  apiUrlSuffix: "/admin-api",
+  // 其他开发环境配置...
+  corpId: "dingbe7f9a7e8cffa2bd35c2f4657eb6378f", //钉钉微应用 企业的CorpID - 正式环境
+  clientId: "dingmr9ez0ecgbmscfeb", //钉钉微应用的Client ID - 正式环境(原企业内部应用的 AppKey。原第三方企业应用的 SuiteKey。)
+  AgentId: "3687646006", //钉钉微应用的AgentID - 正式环境
+  MiniAppId: "5000000006298501", //钉钉微应用的MiniAppID - 正式环境
+  AppKey: "dingmr9ez0ecgbmscfeb", //钉钉微应用的AppKey - 正式环境
+  AppSecret: "VhG_zMdTvIBwA_0Ef8FJ0foH3VYYo5T-kw0ukX_PBA8Ah1xl7AjDw5RVYCU0DTpe", //钉钉微应用的AppSecret - 正式环境
+};

+ 44 - 1
locale/en.json

@@ -97,8 +97,12 @@
 	"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",
@@ -135,6 +139,45 @@
 	"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",
 	"workOrder.addMaterial": "Add material",
 	"workOrder.selectMaterial": "Select material",
@@ -483,4 +526,4 @@
 	"overtime.type3": "Upkeep work order",
 	"overtime.type4": "Inspection work order",
 	"qhse.detail.title": "Incident Event Details"
-}
+}

+ 44 - 1
locale/ja.json

@@ -97,8 +97,12 @@
 	"home.receiveInspectionWorkOrderAndSubmit": "検査作業指示書を受け取り、提出",
 	"home.faultReporting": "故障報告",
 	"home.fillAndReportFaultWorkOrder": "故障作業指示書を入力して報告",
+	"home.ruiDuReport": "瑞都レポート",
+	"home.ruiDuReportTip": "日報データを表示",
 	"home.inventoryQuery": "在庫照会",
 	"home.clickToQueryInventoryData": "クリックして在庫データを照会",
+	"home.maintenanceSearch": "保養照会",
+	"home.maintenanceSearchTip": "機器の保養距離を表示",
 	"home.equipmentLedger": "機器台帳",
 	"home.viewEquipmentLedger": "機器台帳を表示",
 	"home.equipmentStatusChange": "機器状態変更",
@@ -135,6 +139,45 @@
 	"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": "機器を追加",
 	"workOrder.addMaterial": "資材を追加",
 	"workOrder.selectMaterial": "資材を選択",
@@ -483,4 +526,4 @@
 	"overtime.type3": "保養作業指示書",
 	"overtime.type4": "検査作業指示書",
 	"qhse.detail.title": "事故イベント詳細"
-}
+}

+ 44 - 1
locale/ru.json

@@ -97,8 +97,12 @@
 	"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": "Изменение статуса оборудования",
@@ -135,6 +139,45 @@
 	"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": "Добавить оборудование",
 	"workOrder.addMaterial": "Добавить материал",
 	"workOrder.selectMaterial": "Выбрать материал",
@@ -483,4 +526,4 @@
 	"overtime.type3": "Рабочий заказ на обслуживание",
 	"overtime.type4": "Рабочий заказ на инспекцию",
 	"qhse.detail.title": "Детали QHSE"
-}
+}

+ 45 - 0
locale/zh-Hans.json

@@ -127,6 +127,8 @@
   "home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
   "home.dailyReportRuiDu": "瑞都日报",
   "home.dailyReportRuiDuTip": "填写日报",
+  "home.ruiDuReport": "瑞都报表",
+  "home.ruiDuReportTip": "查看日报报表",
   "home.dailyReportRuiHen": "瑞恒日报",
   "home.dailyReportRuiHenTip": "填写日报",
   "home.dailyReportRuiHenTaskTip": "分配任务",
@@ -139,6 +141,8 @@
   "home.dailyReportRuiYingXApproval": "审批日报",
   "home.inventoryQuery": "库存查询",
   "home.clickToQueryInventoryData": "点击查询库存数据",
+  "home.maintenanceSearch": "保养查询",
+  "home.maintenanceSearchTip": "查看设备保养距离",
   "home.equipmentLedger": "设备台账",
   "home.viewEquipmentLedger": "查看设备台账",
   "home.equipmentStatusChange": "设备状态变更",
@@ -182,6 +186,47 @@
   "operationRecordFilling.filledDeviceCount": "已填设备数",
   "operationRecordFilling.unfilledDeviceCount": "未填设备数",
   "operationRecordFilling.PleaseLoadAllItems": "请加载所有填报项后再提交",
+  // --------------------------------------- 瑞都报表 ----------------------------------------
+  "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": "上次保养时长(H)",
+  "maintenanceSearch.runTimeCycle": "运行时长周期(H)",
+  "maintenanceSearch.lastMaintenanceMileage": "上次保养里程(KM)",
+  "maintenanceSearch.mileageCycle": "运行里程周期(KM)",
+  "maintenanceSearch.lastMaintenanceNaturalDate": "上次保养日期",
+  "maintenanceSearch.naturalDateCycle": "自然日周期(D)",
+  "maintenanceSearch.nextMaintTime": "距下次保养时长(H)",
+  "maintenanceSearch.nextMaintKil": "距下次保养里程(KM)",
+  "maintenanceSearch.nextMaintDate": "下次保养日期",
   // --------------------------------------- 状态相关 ----------------------------------------
   "status.enable": "启用",
   "status.disable": "停用",

+ 43 - 0
locale/zh-Hant.json

@@ -83,8 +83,12 @@
   "home.receiveInspectionWorkOrderAndSubmit": "接收巡检工单并提报",
   "home.faultReporting": "故障上报",
   "home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
+  "home.ruiDuReport": "瑞都报表",
+  "home.ruiDuReportTip": "查看日报报表",
   "home.inventoryQuery": "库存查询",
   "home.clickToQueryInventoryData": "点击查询库存数据",
+  "home.maintenanceSearch": "保养查询",
+  "home.maintenanceSearchTip": "查看设备保养距离",
   "home.equipmentLedger": "设备台账",
   "home.viewEquipmentLedger": "查看设备台账",
   "home.equipmentStatusChange": "设备状态变更",
@@ -119,6 +123,45 @@
   "operationRecordFilling.workOrderName": "工单名称",
   "operationRecordFilling.belongToTeam": "所属队伍",
   "operationRecordFilling.totalRunningTime": "累计运行时间",
+  "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": "保养时长(H)",
+  "maintenanceSearch.maintenanceMileage": "保养里程(KM)",
+  "maintenanceSearch.maintenanceDate": "保养日期(D)",
+  "maintenanceSearch.lastMaintenanceOperationTime": "上次保养运行时长(H)",
+  "maintenanceSearch.runTimeCycle": "运行时长周期(H)",
+  "maintenanceSearch.lastMaintenanceMileage": "上次保养里程(KM)",
+  "maintenanceSearch.mileageCycle": "运行里程周期(KM)",
+  "maintenanceSearch.lastMaintenanceNaturalDate": "上次保养自然日期(D)",
+  "maintenanceSearch.naturalDateCycle": "自然日周期(D)",
+  "maintenanceSearch.nextMaintTime": "距下次保养时长(H)",
+  "maintenanceSearch.nextMaintKil": "距下次保养里程(KM)",
+  "maintenanceSearch.nextMaintDate": "下次保养日期(D)",
   // ----------------------------------------------------
   "workOrder.addDevice": "新增设备",
   "workOrder.addMaterial": "新增物料",

+ 59 - 17
package-lock.json

@@ -20,6 +20,7 @@
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
 			"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+			"peer": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -28,6 +29,7 @@
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
 			"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+			"peer": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -36,6 +38,7 @@
 			"version": "7.27.2",
 			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
 			"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+			"peer": true,
 			"dependencies": {
 				"@babel/types": "^7.27.1"
 			},
@@ -50,6 +53,7 @@
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
 			"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+			"peer": true,
 			"dependencies": {
 				"@babel/helper-string-parser": "^7.27.1",
 				"@babel/helper-validator-identifier": "^7.27.1"
@@ -66,12 +70,14 @@
 		"node_modules/@jridgewell/sourcemap-codec": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
-			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+			"peer": true
 		},
 		"node_modules/@vue/compiler-core": {
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
 			"integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+			"peer": true,
 			"dependencies": {
 				"@babel/parser": "^7.27.2",
 				"@vue/shared": "3.5.14",
@@ -84,6 +90,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
 			"integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+			"peer": true,
 			"dependencies": {
 				"@vue/compiler-core": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -93,6 +100,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
 			"integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+			"peer": true,
 			"dependencies": {
 				"@babel/parser": "^7.27.2",
 				"@vue/compiler-core": "3.5.14",
@@ -109,6 +117,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
 			"integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+			"peer": true,
 			"dependencies": {
 				"@vue/compiler-dom": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -123,6 +132,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.14.tgz",
 			"integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+			"peer": true,
 			"dependencies": {
 				"@vue/shared": "3.5.14"
 			}
@@ -131,6 +141,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
 			"integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+			"peer": true,
 			"dependencies": {
 				"@vue/reactivity": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -140,6 +151,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
 			"integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+			"peer": true,
 			"dependencies": {
 				"@vue/reactivity": "3.5.14",
 				"@vue/runtime-core": "3.5.14",
@@ -151,6 +163,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
 			"integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+			"peer": true,
 			"dependencies": {
 				"@vue/compiler-ssr": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -162,7 +175,8 @@
 		"node_modules/@vue/shared": {
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.14.tgz",
-			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ=="
+			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==",
+			"peer": true
 		},
 		"node_modules/call-bind-apply-helpers": {
 			"version": "1.0.2",
@@ -200,7 +214,8 @@
 		"node_modules/csstype": {
 			"version": "3.1.3",
 			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
-			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+			"peer": true
 		},
 		"node_modules/dayjs": {
 			"version": "1.11.13",
@@ -232,6 +247,7 @@
 			"version": "4.5.0",
 			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"peer": true,
 			"engines": {
 				"node": ">=0.12"
 			},
@@ -269,7 +285,8 @@
 		"node_modules/estree-walker": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+			"peer": true
 		},
 		"node_modules/function-bind": {
 			"version": "1.1.2",
@@ -369,6 +386,7 @@
 			"version": "0.30.17",
 			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
 			"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+			"peer": true,
 			"dependencies": {
 				"@jridgewell/sourcemap-codec": "^1.5.0"
 			}
@@ -391,6 +409,7 @@
 					"url": "https://github.com/sponsors/ai"
 				}
 			],
+			"peer": true,
 			"bin": {
 				"nanoid": "bin/nanoid.cjs"
 			},
@@ -412,13 +431,13 @@
 		"node_modules/picocolors": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
-			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+			"peer": true
 		},
 		"node_modules/pinia": {
 			"version": "2.3.1",
 			"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
 			"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
-			"peer": true,
 			"dependencies": {
 				"@vue/devtools-api": "^6.6.3",
 				"vue-demi": "^0.14.10"
@@ -497,6 +516,7 @@
 					"url": "https://github.com/sponsors/ai"
 				}
 			],
+			"peer": true,
 			"dependencies": {
 				"nanoid": "^3.3.8",
 				"picocolors": "^1.1.1",
@@ -597,6 +617,7 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+			"peer": true,
 			"engines": {
 				"node": ">=0.10.0"
 			}
@@ -652,17 +673,20 @@
 		"@babel/helper-string-parser": {
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
-			"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
+			"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+			"peer": true
 		},
 		"@babel/helper-validator-identifier": {
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
-			"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
+			"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+			"peer": true
 		},
 		"@babel/parser": {
 			"version": "7.27.2",
 			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
 			"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+			"peer": true,
 			"requires": {
 				"@babel/types": "^7.27.1"
 			}
@@ -671,6 +695,7 @@
 			"version": "7.27.1",
 			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
 			"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+			"peer": true,
 			"requires": {
 				"@babel/helper-string-parser": "^7.27.1",
 				"@babel/helper-validator-identifier": "^7.27.1"
@@ -684,12 +709,14 @@
 		"@jridgewell/sourcemap-codec": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
-			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+			"peer": true
 		},
 		"@vue/compiler-core": {
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
 			"integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+			"peer": true,
 			"requires": {
 				"@babel/parser": "^7.27.2",
 				"@vue/shared": "3.5.14",
@@ -702,6 +729,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
 			"integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+			"peer": true,
 			"requires": {
 				"@vue/compiler-core": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -711,6 +739,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
 			"integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+			"peer": true,
 			"requires": {
 				"@babel/parser": "^7.27.2",
 				"@vue/compiler-core": "3.5.14",
@@ -727,6 +756,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
 			"integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+			"peer": true,
 			"requires": {
 				"@vue/compiler-dom": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -741,6 +771,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.14.tgz",
 			"integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+			"peer": true,
 			"requires": {
 				"@vue/shared": "3.5.14"
 			}
@@ -749,6 +780,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
 			"integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+			"peer": true,
 			"requires": {
 				"@vue/reactivity": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -758,6 +790,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
 			"integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+			"peer": true,
 			"requires": {
 				"@vue/reactivity": "3.5.14",
 				"@vue/runtime-core": "3.5.14",
@@ -769,6 +802,7 @@
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
 			"integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+			"peer": true,
 			"requires": {
 				"@vue/compiler-ssr": "3.5.14",
 				"@vue/shared": "3.5.14"
@@ -777,7 +811,8 @@
 		"@vue/shared": {
 			"version": "3.5.14",
 			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.14.tgz",
-			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ=="
+			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==",
+			"peer": true
 		},
 		"call-bind-apply-helpers": {
 			"version": "1.0.2",
@@ -805,7 +840,8 @@
 		"csstype": {
 			"version": "3.1.3",
 			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
-			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+			"peer": true
 		},
 		"dayjs": {
 			"version": "1.11.13",
@@ -833,7 +869,8 @@
 		"entities": {
 			"version": "4.5.0",
 			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"peer": true
 		},
 		"es-define-property": {
 			"version": "1.0.1",
@@ -856,7 +893,8 @@
 		"estree-walker": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+			"peer": true
 		},
 		"function-bind": {
 			"version": "1.1.2",
@@ -929,6 +967,7 @@
 			"version": "0.30.17",
 			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
 			"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+			"peer": true,
 			"requires": {
 				"@jridgewell/sourcemap-codec": "^1.5.0"
 			}
@@ -941,7 +980,8 @@
 		"nanoid": {
 			"version": "3.3.11",
 			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
-			"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
+			"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+			"peer": true
 		},
 		"object-inspect": {
 			"version": "1.13.4",
@@ -951,13 +991,13 @@
 		"picocolors": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
-			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+			"peer": true
 		},
 		"pinia": {
 			"version": "2.3.1",
 			"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
 			"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
-			"peer": true,
 			"requires": {
 				"@vue/devtools-api": "^6.6.3",
 				"vue-demi": "^0.14.10"
@@ -983,6 +1023,7 @@
 			"version": "8.5.3",
 			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
 			"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+			"peer": true,
 			"requires": {
 				"nanoid": "^3.3.8",
 				"picocolors": "^1.1.1",
@@ -1049,7 +1090,8 @@
 		"source-map-js": {
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
-			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+			"peer": true
 		},
 		"vue": {
 			"version": "3.5.14",

+ 44 - 23
pages.json

@@ -86,18 +86,25 @@
         "navigationBarTitleText": "%maintenanceWorkOrder.editMaintenanceWorkOrder%"
       }
     },
-    {
-      // 保养工单详情
-      "path": "pages/maintenance/detail",
-      "style": {
-        "navigationBarTitleText": "%maintenanceWorkOrder.viewMaintenanceWorkOrder%"
-      }
-    },
-    {
-      // 设备维修
-      "path": "pages/repair/index",
-      "style": {
-        "navigationStyle": "custom"
+    {
+      // 保养工单详情
+      "path": "pages/maintenance/detail",
+      "style": {
+        "navigationBarTitleText": "%maintenanceWorkOrder.viewMaintenanceWorkOrder%"
+      }
+    },
+    {
+      // 保养查询
+      "path": "pages/maintenance/search",
+      "style": {
+        "navigationBarTitleText": "%maintenanceSearch.title%"
+      }
+    },
+    {
+      // 设备维修
+      "path": "pages/repair/index",
+      "style": {
+        "navigationStyle": "custom"
       }
     },
     {
@@ -294,17 +301,31 @@
         "navigationBarTitleText": "%ruiDu.detailTitle%"
       }
     },
-    {
-      // 瑞都日报-编辑
-      "path": "pages/ruiDu/edit",
-      "style": {
-        "navigationBarTitleText": "%ruiDu.editTitle%"
-      }
-    },
-    {
-      // 库存查询
-      "path": "pages/inventory/index",
-      "style": {
+    {
+      // 瑞都日报-编辑
+      "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%"
       }
     },

+ 137 - 40
pages/home/index.vue

@@ -149,10 +149,10 @@
       </uni-row>
       <view class="card">
         <!-- 瑞都日报 -->
-        <view
-          class="card-cell flex-row align-center justify-between"
-          @click="navigatorTo('/pages/ruiDu/index')"
-          v-if="isShowRuiduDaily">
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/ruiDu/index')"
+          v-if="isShowRuiduDaily">
           <image src="/static/home/ribao.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
@@ -163,13 +163,31 @@
                 {{ $t("home.dailyReportRuiDuTip") }}
               </view>
             </view>
-            <uni-icons type="right" :color="'#CACCCF'" size="15" />
-          </view>
-        </view>
-        <view
-          class="card-cell flex-row align-center justify-between"
-          @click="navigatorTo('/pages/ruihen-task/index')"
-          v-if="rhTaskFlag">
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+        <!-- 瑞都报表 -->
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="openRuiDuReportPopup"
+          v-if="rdReportStatisticsFlag">
+          <image src="/static/home/ribao.svg" mode="aspectFill"></image>
+          <view class="cell-con flex-row align-center justify-between">
+            <view class="cell-text flex-row align-center justify-start">
+              <view class="title">
+                {{ $t("home.ruiDuReport") }}
+              </view>
+              <view class="subtitle">
+                {{ $t("home.ruiDuReportTip") }}
+              </view>
+            </view>
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/ruihen-task/index')"
+          v-if="rhTaskFlag">
           <image src="/static/home/ribao.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
@@ -285,11 +303,11 @@
             <uni-icons type="right" :color="'#CACCCF'" size="15" />
           </view>
         </view>
-        <!-- 库存查询 -->
-        <view
-          class="card-cell flex-row align-center justify-between"
-          @click="navigatorTo('/pages/inventory/index')">
-          <image src="/static/home/kucun.svg" mode="aspectFill"></image>
+        <!-- 库存查询 -->
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/inventory/index')">
+          <image src="/static/home/kucun.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
               <view class="title">
@@ -299,13 +317,30 @@
                 {{ $t("home.clickToQueryInventoryData") }}
               </view>
             </view>
-            <uni-icons type="right" :color="'#CACCCF'" size="15" />
-          </view>
-        </view>
-        <!-- 设备台账 -->
-        <view
-          class="card-cell flex-row align-center justify-between"
-          @click="navigatorTo('/pages/ledger/index')">
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+        <!-- 保养查询 -->
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/maintenance/search')">
+          <image src="/static/home/taizhang.svg" mode="aspectFill"></image>
+          <view class="cell-con flex-row align-center justify-between">
+            <view class="cell-text flex-row align-center justify-start">
+              <view class="title">
+                {{ $t("home.maintenanceSearch") }}
+              </view>
+              <view class="subtitle">
+                {{ $t("home.maintenanceSearchTip") }}
+              </view>
+            </view>
+            <uni-icons type="right" :color="'#CACCCF'" size="15" />
+          </view>
+        </view>
+        <!-- 设备台账 -->
+        <view
+          class="card-cell flex-row align-center justify-between"
+          @click="navigatorTo('/pages/ledger/index')">
           <image src="/static/home/taizhang.svg" mode="aspectFill"></image>
           <view class="cell-con flex-row align-center justify-between">
             <view class="cell-text flex-row align-center justify-start">
@@ -390,11 +425,27 @@
           </view>
         </view>
       </view>
-    </view>
-    <!-- 升级提示 -->
-    <upgrade />
-  </view>
-</template>
+    </view>
+    <!-- 升级提示 -->
+    <upgrade />
+    <uni-popup ref="ruiDuReportPopup" type="bottom" background-color="#fff" border-radius="10px 10px 0 0">
+      <view class="report-popup">
+        <view class="report-popup-header">
+          {{ $t("ruiDuReport.selectTitle") }}
+        </view>
+        <view class="report-popup-item" @click="navigateToRuiDuReport('/pages/ruiDuReport/daily-detail')">
+          {{ $t("ruiDuReport.dailyDetail") }}
+        </view>
+        <view class="report-popup-item" @click="navigateToRuiDuReport('/pages/ruiDuReport/daily-team-statistic')">
+          {{ $t("ruiDuReport.dailyTeamStatistic") }}
+        </view>
+        <view class="report-popup-cancel" @click="closeRuiDuReportPopup">
+          {{ $t("operation.cancel") }}
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
 
 <script setup>
 import { onShow } from "@dcloudio/uni-app";
@@ -408,11 +459,23 @@ import { useDeviceStore } from "@/store/modules/device";
 import { messageNavigate } from "@/utils/navigate";
 import Upgrade from "@/components/upgrade.vue";
 
-const navigatorTo = (url) => {
-  uni.navigateTo({
-    url: url,
-  });
-};
+const navigatorTo = (url) => {
+  uni.navigateTo({
+    url: url,
+  });
+};
+
+const ruiDuReportPopup = ref(null);
+const openRuiDuReportPopup = () => {
+  ruiDuReportPopup.value?.open();
+};
+const closeRuiDuReportPopup = () => {
+  ruiDuReportPopup.value?.close();
+};
+const navigateToRuiDuReport = (url) => {
+  closeRuiDuReportPopup();
+  uni.navigateTo({ url });
+};
 
 const messageCount = ref(0);
 onMounted(async () => {
@@ -424,8 +487,9 @@ onMounted(async () => {
 });
 
 // 是否展示瑞都日报入口
-const isShowRuiduDaily = ref(false);
-const rhReportFlag = ref(false);
+const isShowRuiduDaily = ref(false);
+const rdReportStatisticsFlag = ref(false);
+const rhReportFlag = ref(false);
 const rhTaskFlag = ref(false);
 const rhReportApprovalFlag = ref(false);
 const ryReportFlag = ref(false);
@@ -436,9 +500,10 @@ const userInfo = ref({});
 const getLoginUser = async () => {
   const response = await getLoginUserInfo();
   if (response.code === 0) {
-    userInfo.value = response.data;
-    isShowRuiduDaily.value = response.data.rdReportFlag;
-    // isShowRuiduDaily.value = true;
+    userInfo.value = response.data;
+    isShowRuiduDaily.value = response.data.rdReportFlag;
+    rdReportStatisticsFlag.value = response.data.rdReportStatisticsFlag;
+    // isShowRuiduDaily.value = true;
     rhReportFlag.value = response.data.rhReportFlag;
     rhTaskFlag.value = response.data.rhReportFlag;
     // rhTaskFlag.value = true;
@@ -514,13 +579,45 @@ onShow(async () => {
 </script>
 
 <style lang="scss" scoped>
-.home {
+.home {
   width: 100%;
   height: 100%;
   position: relative;
   box-sizing: border-box;
   overflow: hidden;
-}
+}
+
+.report-popup {
+  padding: 8px 16px calc(61px + env(safe-area-inset-bottom));
+  background: #fff;
+  box-sizing: border-box;
+}
+
+.report-popup-header {
+  height: 44px;
+  line-height: 44px;
+  text-align: center;
+  font-weight: 600;
+  font-size: 16px;
+  color: #333;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.report-popup-item,
+.report-popup-cancel {
+  height: 48px;
+  line-height: 48px;
+  text-align: center;
+  font-size: 15px;
+  color: #333;
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.report-popup-cancel {
+  margin-top: 8px;
+  color: #666;
+  border-bottom: none;
+}
 
 .row-full {
   width: 100%;

+ 56 - 27
pages/inventory/index.vue

@@ -15,21 +15,25 @@
 					</button>
 				</uni-col>
 			</uni-row>
-		</template>
-		<view>
-			<view class="item" v-for="(item,index) in dataList" :key="index">
-				<uni-row>
-					<uni-col :span="6">{{ $t('inventory.materialName') }}</uni-col>
-					<uni-col :span="18">{{ item.materialName }}</uni-col>
-					<uni-col :span="6" style="margin-top: 8px">{{ $t('inventory.costCenter') }}</uni-col>
-					<uni-col :span="18" style="margin-top: 8px">{{ item.costCenter }}</uni-col>
-					<uni-col :span="6" style="margin-top: 8px">{{ $t('inventory.quantity') }}</uni-col>
-					<uni-col :span="18" style="margin-top: 8px">{{ item.quantity }}</uni-col>
-				</uni-row>
-			</view>
-		</view>
-	</z-paging>
-</template>
+		</template>
+		<view>
+			<view class="item" v-for="(item,index) in dataList" :key="index">
+				<view class="field-row">
+					<text class="field-label">{{ $t('inventory.materialName') }}</text>
+					<text class="field-value">{{ item.materialName }}</text>
+				</view>
+				<view class="field-row">
+					<text class="field-label">{{ $t('inventory.costCenter') }}</text>
+					<text class="field-value">{{ item.costCenter }}</text>
+				</view>
+				<view class="field-row">
+					<text class="field-label">{{ $t('inventory.quantity') }}</text>
+					<text class="field-value">{{ item.quantity }}</text>
+				</view>
+			</view>
+		</view>
+	</z-paging>
+</template>
 
 <script setup>
 	import {
@@ -119,17 +123,42 @@
 		font-size: 12px;
 	}
 
-	.item {
-		width: 100%;
-		background: #FFFFFF;
-		border-radius: 6px;
-		margin-top: 10px;
-		padding: 14px 20px;
-		font-size: 14px;
-		box-sizing: border-box;
-	}
-
-	.item-module {
+	.item {
+		width: 100%;
+		background: #FFFFFF;
+		border-radius: 6px;
+		margin-top: 10px;
+		padding: 14px 20px;
+		font-size: 14px;
+		line-height: 20px;
+		box-sizing: border-box;
+	}
+
+	.field-row {
+		display: flex;
+		align-items: flex-start;
+		width: 100%;
+
+		& + .field-row {
+			margin-top: 8px;
+		}
+	}
+
+	.field-label {
+		flex: 0 0 72px;
+		color: #000000;
+		white-space: nowrap;
+	}
+
+	.field-value {
+		flex: 1;
+		min-width: 0;
+		color: #000000;
+		word-break: break-all;
+		overflow-wrap: anywhere;
+	}
+
+	.item-module {
 		width: 100%;
 		height: 42px;
 		box-sizing: border-box;
@@ -176,4 +205,4 @@
 	.item-title-width {
 		min-width: 70px;
 	}
-</style>
+</style>

+ 965 - 0
pages/maintenance/search.vue

@@ -0,0 +1,965 @@
+<template>
+  <view class="page report-page maintenance-search-page">
+    <z-paging
+      ref="paging"
+      v-model="dataList"
+      class="report-paging"
+      :default-page-size="10"
+      @query="queryList">
+      <view class="report-list">
+        <view class="report-card" v-for="item in dataList" :key="item.id">
+          <view class="card-header">
+            <view class="device-title">
+              {{ item.deviceName || "--" }}
+            </view>
+            <view
+              class="distance-tag"
+              :class="getDistanceClass(item.mainDistance)">
+              {{ formatDistance(item.mainDistance) }}
+            </view>
+          </view>
+
+          <view class="card-body">
+            <view class="field-row">
+              <text class="field-label">{{ $t("device.deviceCode") }}</text>
+              <text class="field-value">{{ item.deviceCode || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">{{ $t("ruiDuReport.dept") }}</text>
+              <text class="field-value">{{ item.deptName || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">{{
+                $t("workOrder.responsiblePerson")
+              }}</text>
+              <text class="field-value">{{ item.responsibleNames || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">{{
+                $t("maintenanceSearch.deviceStatus")
+              }}</text>
+              <text class="field-value">{{
+                getDeviceStatusName(item.deviceStatus)
+              }}</text>
+            </view>
+            <view v-if="item.totalRunTime" class="field-row">
+              <text class="field-label">{{
+                $t("maintenanceSearch.totalRunTime")
+              }}</text>
+              <text class="field-value">{{ item.totalRunTime }}</text>
+            </view>
+            <view v-if="item.totalMileage" class="field-row">
+              <text class="field-label">{{
+                $t("maintenanceSearch.totalMileage")
+              }}</text>
+              <text class="field-value">{{ item.totalMileage }}</text>
+            </view>
+            <view v-if="formatMultiAttrs(item)" class="field-row brief-row">
+              <text class="field-label">{{
+                $t("maintenanceSearch.multiAttrs")
+              }}</text>
+              <UniTooltip :content="formatMultiAttrs(item)" placement="top">
+                <text class="field-value brief-text">{{
+                  formatMultiAttrs(item)
+                }}</text>
+              </UniTooltip>
+            </view>
+          </view>
+
+          <view class="card-footer">
+            <view class="status-text">{{ getWorkOrderStatus(item) }}</view>
+            <button
+              v-if="hasDetailSource(item)"
+              class="detail-btn"
+              type="primary"
+              plain
+              @click="openDetail(item)">
+              {{ $t("operation.view") }}
+            </button>
+          </view>
+        </view>
+      </view>
+    </z-paging>
+
+    <UniFab
+      :pattern="fabPattern"
+      horizontal="right"
+      vertical="bottom"
+      direction="horizontal"
+      :popMenu="false"
+      @fabClick="openFilterPopup" />
+
+    <uni-popup
+      ref="filterPopup"
+      type="bottom"
+      background-color="#fff"
+      border-radius="10px 10px 0 0">
+      <view class="filter-popup">
+        <view class="filter-header">
+          <text class="filter-action" @click="closeFilterPopup">
+            {{ $t("operation.cancel") }}
+          </text>
+          <text class="filter-title">{{ $t("ruiDuReport.filterTitle") }}</text>
+          <text class="filter-action primary" @click="applyFilter">
+            {{ $t("operation.confirm") }}
+          </text>
+        </view>
+
+        <view class="filter-body">
+          <view class="filter-item">
+            <view class="filter-label">{{
+              $t("maintenanceSearch.deviceCode")
+            }}</view>
+            <uni-easyinput
+              v-model="filterForm.deviceCode"
+              :inputBorder="false"
+              :styles="inputStyles"
+              :placeholder="$t('maintenanceSearch.deviceCodePlaceholder')" />
+          </view>
+
+          <view class="filter-item">
+            <view class="filter-label">{{
+              $t("maintenanceSearch.deviceName")
+            }}</view>
+            <uni-easyinput
+              v-model="filterForm.deviceName"
+              :inputBorder="false"
+              :styles="inputStyles"
+              :placeholder="$t('maintenanceSearch.deviceNamePlaceholder')" />
+          </view>
+
+          <view class="filter-item dept-item">
+            <view class="filter-label">{{ $t("ruiDuReport.dept") }}</view>
+            <view class="dept-selected">
+              {{ selectedDeptName || $t("operation.PleaseSelect") }}
+            </view>
+            <view class="tree">
+              <DaTree
+                :data="treeData"
+                labelField="name"
+                valueField="id"
+                disabledField="disabled"
+                defaultExpandAll
+                checkedDisabled
+                :defaultCheckedKeys="filterForm.deptId"
+                @change="handleTreeChange" />
+            </view>
+          </view>
+        </view>
+
+        <view class="filter-footer">
+          <button class="filter-button reset" @click="resetFilter">
+            {{ $t("inventory.search.reset") }}
+          </button>
+          <button class="filter-button" type="primary" @click="applyFilter">
+            {{ $t("operation.search") }}
+          </button>
+        </view>
+      </view>
+    </uni-popup>
+
+    <uni-popup
+      ref="detailPopup"
+      type="bottom"
+      background-color="#fff"
+      border-radius="10px 10px 0 0">
+      <view class="detail-popup">
+        <view class="filter-header">
+          <text class="filter-action" @click="closeDetailPopup">
+            {{ $t("operation.cancel") }}
+          </text>
+          <text class="filter-title">{{
+            $t("maintenanceSearch.detailTitle")
+          }}</text>
+          <text class="filter-action primary" @click="closeDetailPopup">
+            {{ $t("operation.confirm") }}
+          </text>
+        </view>
+
+        <view class="detail-device">
+          <view class="detail-device-name">{{ detailDevice.deviceName }}</view>
+          <view class="detail-device-code">{{ detailDevice.deviceCode }}</view>
+        </view>
+
+        <scroll-view scroll-y class="detail-list">
+          <view
+            class="detail-card"
+            v-for="detail in detailList"
+            :key="detail.id">
+            <view class="detail-name">{{ detail.name || "--" }}</view>
+            <view class="detail-grid">
+              <view
+                v-if="detail.runningTimeRule === 0"
+                class="detail-item">
+                <text class="detail-label">{{
+                  $t("maintenanceSearch.totalRunTime")
+                }}</text>
+                <text>{{ detail.totalRunTime ?? detail.tempTotalRunTime ?? "--" }}</text>
+              </view>
+              <view
+                v-if="detail.mileageRule === 0"
+                class="detail-item">
+                <text class="detail-label">{{
+                  $t("maintenanceSearch.totalMileage")
+                }}</text>
+                <text>{{ detail.totalMileage ?? detail.tempTotalMileage ?? "--" }}</text>
+              </view>
+            </view>
+
+            <view
+              v-if="showTimeColumns && detail.runningTimeRule === 0"
+              class="detail-section">
+              <view class="detail-section-title">
+                {{ $t("maintenanceSearch.maintenanceDuration") }}
+              </view>
+              <view class="detail-section-grid">
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.lastMaintenanceOperationTime")
+                  }}</text>
+                  <text>{{ detail.lastRunningTime ?? "--" }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.runTimeCycle")
+                  }}</text>
+                  <text>{{ detail.nextRunningTime ?? "--" }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.nextMaintTime")
+                  }}</text>
+                  <text :class="{ danger: isNegative(calculateTimePeriod(detail)) }">
+                    {{ calculateTimePeriod(detail) }}
+                  </text>
+                </view>
+              </view>
+            </view>
+
+            <view
+              v-if="showMileageColumns && detail.mileageRule === 0"
+              class="detail-section">
+              <view class="detail-section-title">
+                {{ $t("maintenanceSearch.maintenanceMileage") }}
+              </view>
+              <view class="detail-section-grid">
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.lastMaintenanceMileage")
+                  }}</text>
+                  <text>{{ detail.lastRunningKilometers ?? "--" }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.mileageCycle")
+                  }}</text>
+                  <text>{{ detail.nextRunningKilometers ?? "--" }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.nextMaintKil")
+                  }}</text>
+                  <text :class="{ danger: isNegative(calculateKiloPeriod(detail)) }">
+                    {{ calculateKiloPeriod(detail) }}
+                  </text>
+                </view>
+              </view>
+            </view>
+
+            <view
+              v-if="showNaturalDateColumns && detail.naturalDateRule === 0"
+              class="detail-section">
+              <view class="detail-section-title">
+                {{ $t("maintenanceSearch.maintenanceDate") }}
+              </view>
+              <view class="detail-section-grid">
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.lastMaintenanceNaturalDate")
+                  }}</text>
+                  <text>{{ formatDateValue(detail.lastNaturalDate) }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.naturalDateCycle")
+                  }}</text>
+                  <text>{{ detail.nextNaturalDate ?? "--" }}</text>
+                </view>
+                <view class="detail-item">
+                  <text class="detail-label">{{
+                    $t("maintenanceSearch.nextMaintDate")
+                  }}</text>
+                  <text>{{ calculateNextNaturalDate(detail) }}</text>
+                </view>
+              </view>
+            </view>
+          </view>
+
+          <view v-if="!detailLoading && detailList.length === 0" class="empty">
+            {{ $t("common.noData") }}
+          </view>
+        </scroll-view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, onMounted, reactive, ref } from "vue";
+import { useI18n } from "vue-i18n";
+import dayjs from "dayjs";
+import DaTree from "@/components/da-tree/index.vue";
+import UniFab from "@/uni_modules/uni-fab/components/uni-fab/uni-fab.vue";
+import UniTooltip from "@/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue";
+import {
+  getDeviceMainDistances,
+  getMainPlanBOMs,
+  getWorkOrderBOMs,
+} from "@/api/maintenance";
+import { specifiedSimpleDepts } from "@/api";
+import { getDeptId } from "@/utils/auth";
+import { useDataDictStore } from "@/store/modules/dataDict";
+
+const paging = ref(null);
+const { t } = useI18n({ useScope: "global" });
+const filterPopup = ref(null);
+const detailPopup = ref(null);
+const dataList = ref([]);
+const deptOptions = ref([]);
+const treeData = ref([]);
+const detailList = ref([]);
+const detailLoading = ref(false);
+const detailDevice = reactive({
+  deviceCode: "",
+  deviceName: "",
+});
+const deviceStatusDict = reactive({});
+const dictStore = useDataDictStore();
+
+const inputStyles = reactive({
+  backgroundColor: "#f7f8fa",
+  color: "#333",
+});
+
+const fabPattern = reactive({
+  color: "#fff",
+  backgroundColor: "#fff",
+  selectedColor: "#fff",
+  buttonColor: "#004098",
+  iconColor: "#fff",
+  icon: "search",
+});
+
+const filterForm = reactive({
+  deptId: "",
+  deviceCode: "",
+  deviceName: "",
+});
+
+const selectedDeptName = computed(() => {
+  const current = deptOptions.value.find(
+    (item) => String(item.value) === String(filterForm.deptId)
+  );
+  return current?.text || "";
+});
+
+const showTimeColumns = computed(() => {
+  return detailList.value.some((item) => item.runningTimeRule === 0);
+});
+
+const showMileageColumns = computed(() => {
+  return detailList.value.some((item) => item.mileageRule === 0);
+});
+
+const showNaturalDateColumns = computed(() => {
+  return detailList.value.some((item) => item.naturalDateRule === 0);
+});
+
+const handleTree = (
+  data,
+  id = "id",
+  parentId = "parentId",
+  children = "children"
+) => {
+  if (!Array.isArray(data)) return [];
+
+  const childrenListMap = {};
+  const nodeIds = {};
+  const tree = [];
+
+  for (const item of data) {
+    const itemParentId = item[parentId];
+    if (childrenListMap[itemParentId] == null) {
+      childrenListMap[itemParentId] = [];
+    }
+    nodeIds[item[id]] = item;
+    childrenListMap[itemParentId].push(item);
+  }
+
+  for (const item of data) {
+    if (nodeIds[item[parentId]] == null) {
+      tree.push(item);
+    }
+  }
+
+  const adaptToChildrenList = (node) => {
+    if (childrenListMap[node[id]] != null) {
+      node[children] = childrenListMap[node[id]];
+    }
+    if (node[children]) {
+      node[children].forEach(adaptToChildrenList);
+    }
+  };
+
+  tree.forEach(adaptToChildrenList);
+  return tree;
+};
+
+const sortDeptTree = (nodes) => {
+  if (!Array.isArray(nodes)) return [];
+
+  return [...nodes]
+    .sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999))
+    .map((node) => ({
+      ...node,
+      children: sortDeptTree(node.children),
+    }));
+};
+
+const loadDeptOptions = async () => {
+  try {
+    const response = await specifiedSimpleDepts(getDeptId());
+    const list = response?.data || [];
+    deptOptions.value = list.map((item) => ({
+      text: item.name,
+      value: item.id,
+    }));
+    treeData.value = sortDeptTree(handleTree(list));
+  } catch (error) {
+    treeData.value = [];
+    deptOptions.value = [];
+  }
+};
+
+const loadDeviceStatusDict = async () => {
+  if (dictStore.dataDict.length <= 0) {
+    await dictStore.loadDataDictList();
+  }
+  dictStore.getDataDictList("pms_device_status").forEach((item) => {
+    deviceStatusDict[item.value] = item.label;
+  });
+};
+
+const queryList = (pageNo, pageSize) => {
+  getDeviceMainDistances({
+    pageNo,
+    pageSize,
+    deptId: filterForm.deptId || undefined,
+    deviceCode: filterForm.deviceCode || undefined,
+    deviceName: filterForm.deviceName || undefined,
+    setFlag: "",
+  })
+    .then((res) => {
+      paging.value?.complete(res.data?.list || []);
+    })
+    .catch(() => {
+      paging.value?.complete(false);
+    });
+};
+
+const parseDistanceNumber = (distance) => {
+  if (distance === null || distance === undefined || distance === "") {
+    return undefined;
+  }
+  if (typeof distance === "number") return distance;
+  const numericPart = String(distance).match(
+    /[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/
+  )?.[0];
+  return numericPart ? Number(numericPart) : undefined;
+};
+
+const hasMaintenancePlan = (mainDistance) => {
+  return mainDistance !== null && mainDistance !== undefined && mainDistance !== "";
+};
+
+const hasDetailSource = (item) => {
+  return hasMaintenancePlan(item.mainDistance) && (item.workOrderId || item.planId);
+};
+
+const formatDistance = (mainDistance) => {
+  return hasMaintenancePlan(mainDistance)
+    ? mainDistance
+    : $tSafe("maintenanceSearch.noMaintenancePlan");
+};
+
+const getDistanceClass = (distance) => {
+  const value = parseDistanceNumber(distance);
+  if (value === undefined || value === 0) return "";
+  return value < 0 ? "negative" : "positive";
+};
+
+const formatMultiAttrs = (item) => {
+  const runtime = Object.entries(item.multiAttrsTotalRuntime || {});
+  const mileage = Object.entries(item.multiAttrsTotalMileage || {});
+  return runtime
+    .concat(mileage)
+    .filter(([key, value]) => key && value)
+    .map(([key, value]) => `${key}: ${value}`)
+    .join("  ");
+};
+
+const getDeviceStatusName = (code) => {
+  return deviceStatusDict[code] || code || "--";
+};
+
+const getWorkOrderStatus = (item) => {
+  if (item.shouldWorkOrder && item.runningWorkOrder) {
+    return $tSafe("maintenanceSearch.generatedNotExecuted");
+  }
+  if (item.shouldWorkOrder && !item.runningWorkOrder) {
+    return $tSafe("maintenanceSearch.notGenerated");
+  }
+  return "-";
+};
+
+const openFilterPopup = () => {
+  filterPopup.value?.open();
+};
+
+const closeFilterPopup = () => {
+  filterPopup.value?.close();
+};
+
+const handleTreeChange = (value) => {
+  filterForm.deptId = value;
+};
+
+const applyFilter = () => {
+  closeFilterPopup();
+  paging.value?.reload();
+};
+
+const resetFilter = () => {
+  filterForm.deptId = "";
+  filterForm.deviceCode = "";
+  filterForm.deviceName = "";
+};
+
+const openDetail = async (row) => {
+  detailDevice.deviceCode = row.deviceCode || "";
+  detailDevice.deviceName = row.deviceName || "";
+  detailList.value = [];
+  detailPopup.value?.open();
+  detailLoading.value = true;
+
+  try {
+    const params = {
+      pageNo: 1,
+      pageSize: 100,
+      deviceId: row.id,
+    };
+    const response = row.workOrderId
+      ? await getWorkOrderBOMs({ ...params, workOrderId: row.workOrderId })
+      : await getMainPlanBOMs({ ...params, planId: row.planId });
+    detailList.value = Array.isArray(response?.data) ? response.data : [];
+  } catch (error) {
+    detailList.value = [];
+  } finally {
+    detailLoading.value = false;
+  }
+};
+
+const closeDetailPopup = () => {
+  detailPopup.value?.close();
+};
+
+const calculateTimePeriod = (item) => {
+  if (item.runningTimeRule === 0) {
+    const totalRunVal = item.totalRunTime ?? item.tempTotalRunTime;
+    const next = Number(item.nextRunningTime) || 0;
+    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
+    const lastRun = Number(item.lastRunningTime) || 0;
+    return Number((next - (totalRun - lastRun)).toFixed(2));
+  }
+  return typeof item.timePeriod === "number"
+    ? Number(item.timePeriod.toFixed(2))
+    : item.timePeriod || "--";
+};
+
+const calculateKiloPeriod = (item) => {
+  if (item.mileageRule === 0) {
+    const totalRunVal = item.totalMileage ?? item.tempTotalMileage;
+    const next = Number(item.nextRunningKilometers) || 0;
+    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
+    const lastRun = Number(item.lastRunningKilometers) || 0;
+    return Number((next - (totalRun - lastRun)).toFixed(2));
+  }
+  return typeof item.kilometerCycle === "number"
+    ? Number(item.kilometerCycle.toFixed(2))
+    : item.kilometerCycle || "--";
+};
+
+const calculateNextNaturalDate = (item) => {
+  if (item.naturalDateRule !== 0 || !item.lastNaturalDate || !item.nextNaturalDate) {
+    return "--";
+  }
+  return dayjs(item.lastNaturalDate)
+    .add(item.nextNaturalDate, "day")
+    .format("YYYY-MM-DD");
+};
+
+const formatDateValue = (value) => {
+  return value ? dayjs(value).format("YYYY-MM-DD") : "--";
+};
+
+const isNegative = (value) => {
+  if (value === null || value === undefined || value === "") return false;
+  const num = Number(value);
+  return !Number.isNaN(num) && num < 0;
+};
+
+const $tSafe = (key) => {
+  return t(key);
+};
+
+onMounted(() => {
+  loadDeptOptions();
+  loadDeviceStatusDict();
+});
+</script>
+
+<style lang="scss" scoped>
+.report-page {
+  padding: 10px !important;
+}
+
+.report-paging {
+  height: 100%;
+}
+
+.report-list {
+  padding: 8px 6px;
+  box-sizing: border-box;
+}
+
+.report-card {
+  position: relative;
+  margin-bottom: 14px;
+  overflow: hidden;
+  background: #ffffff;
+  border: 1px solid rgba(0, 64, 152, 0.08);
+  border-radius: 8px;
+  box-shadow: 0 6px 18px rgba(35, 54, 79, 0.08);
+}
+
+.card-header {
+  min-height: 52px;
+  padding: 14px 16px 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+  border-bottom: 1px solid #edf1f7;
+}
+
+.device-title {
+  min-width: 0;
+  flex: 1;
+  color: #000000;
+  font-weight: 700;
+  font-size: 18px;
+  line-height: 24px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.distance-tag {
+  max-width: 128px;
+  height: 24px;
+  line-height: 24px;
+  padding: 0 9px;
+  margin-left: 10px;
+  border-radius: 12px;
+  background: #f1f3f8;
+  color: #606266;
+  font-size: 12px;
+  font-weight: 600;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  box-sizing: border-box;
+
+  &.positive {
+    color: #2bbb80;
+    background: rgba(43, 187, 128, 0.12);
+  }
+
+  &.negative {
+    color: #f56c6c;
+    background: rgba(245, 108, 108, 0.12);
+  }
+}
+
+.card-body {
+  padding: 12px 16px 4px;
+  box-sizing: border-box;
+}
+
+.field-row {
+  display: flex;
+  align-items: flex-start;
+  min-height: 34px;
+  font-size: 14px;
+}
+
+.field-label {
+  width: 92px;
+  flex-shrink: 0;
+  color: #000000;
+  font-weight: 600;
+  font-size: 13px;
+  line-height: 22px;
+}
+
+.field-value {
+  min-width: 0;
+  flex: 1;
+  color: #233044;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 22px;
+}
+
+.brief-row {
+  padding-top: 4px;
+}
+
+.brief-row :deep(.uni-tooltip) {
+  min-width: 0;
+  flex: 1;
+  display: block;
+}
+
+.brief-text {
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-footer {
+  padding: 2px 16px 14px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.status-text {
+  min-width: 0;
+  flex: 1;
+  color: #666;
+  font-size: 13px;
+}
+
+.detail-btn {
+  min-width: 72px;
+  height: 30px;
+  line-height: 28px;
+  margin: 0;
+  padding: 0 14px;
+  border-radius: 15px;
+  font-size: 13px;
+}
+
+.filter-popup,
+.detail-popup {
+  height: 86vh;
+  max-height: 86vh;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.filter-header {
+  height: 48px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-title {
+  font-weight: 600;
+  color: #333;
+  font-size: 16px;
+}
+
+.filter-action {
+  min-width: 48px;
+  color: #666;
+  font-size: 14px;
+
+  &.primary {
+    color: #004098;
+    text-align: right;
+  }
+}
+
+.filter-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 12px 16px;
+  box-sizing: border-box;
+}
+
+.filter-item {
+  margin-bottom: 14px;
+}
+
+.filter-label {
+  margin-bottom: 8px;
+  color: #333;
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.dept-selected {
+  min-height: 36px;
+  line-height: 36px;
+  padding: 0 10px;
+  margin-bottom: 8px;
+  background: #f7f8fa;
+  color: #666;
+  border-radius: 4px;
+  box-sizing: border-box;
+}
+
+.tree {
+  height: 420px;
+  overflow: hidden;
+  border: 1px solid #f0f0f0;
+  border-radius: 4px;
+}
+
+.filter-footer {
+  display: flex;
+  gap: 10px;
+  padding: 10px 16px 14px;
+  border-top: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-button {
+  flex: 1;
+  height: 38px;
+  line-height: 38px;
+  font-size: 14px;
+  margin: 0;
+
+  &.reset {
+    color: #004098;
+    background: #fff;
+    border: 1px solid #004098;
+  }
+}
+
+.detail-device {
+  padding: 12px 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.detail-device-name {
+  color: #000;
+  font-weight: 700;
+  font-size: 16px;
+  line-height: 24px;
+}
+
+.detail-device-code {
+  color: #666;
+  font-size: 13px;
+  line-height: 20px;
+}
+
+.detail-list {
+  flex: 1;
+  height: 0;
+  padding: 12px 16px;
+  box-sizing: border-box;
+}
+
+.detail-card {
+  margin-bottom: 12px;
+  padding: 12px;
+  background: #f8fafc;
+  border: 1px solid #edf1f7;
+  border-radius: 8px;
+}
+
+.detail-name {
+  margin-bottom: 10px;
+  color: #000;
+  font-weight: 700;
+  font-size: 15px;
+}
+
+.detail-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 8px 10px;
+}
+
+.detail-section {
+  margin-top: 12px;
+  padding-top: 10px;
+  border-top: 1px solid #edf1f7;
+}
+
+.detail-section-title {
+  margin-bottom: 8px;
+  color: #004098;
+  font-weight: 700;
+  font-size: 14px;
+  line-height: 20px;
+}
+
+.detail-section-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 8px 10px;
+}
+
+.detail-item {
+  min-width: 0;
+  color: #233044;
+  font-size: 13px;
+  line-height: 20px;
+}
+
+.detail-label {
+  display: block;
+  color: #666;
+  font-size: 12px;
+}
+
+@media screen and (max-width: 360px) {
+  .detail-section-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+.danger {
+  color: #f56c6c;
+}
+
+.empty {
+  padding: 32px 0;
+  text-align: center;
+  color: #999;
+  font-size: 14px;
+}
+</style>

+ 16 - 2
pages/recordFilling/detail.vue

@@ -1461,10 +1461,24 @@ const onSubmit = async () => {
         taskId.value &&
         !noGasTasks.value.includes(taskId.value)
       ) {
+        // if (dailyInjectGasTime.fillContent > 0) {
+        //   if (nonSumItem.fillContent <= 0) {
+        //     uni.showToast({
+        //       title: "当日运转时间大于0,注气量也需要大于0",
+        //       icon: "none",
+        //     });
+        //     return;
+        //   }
+        // }
         if (dailyInjectGasTime.fillContent > 0) {
-          if (nonSumItem.fillContent <= 0) {
+          const hasDailyGasInjection =
+            nonSumItem.fillContent !== undefined &&
+            nonSumItem.fillContent !== null &&
+            nonSumItem.fillContent !== "";
+
+          if (!hasDailyGasInjection) {
             uni.showToast({
-              title: "当日运转时间大于0,注气量也需要大于0",
+              title: "当日运转时间大于0,需要填写当日注气量",
               icon: "none",
             });
             return;

+ 176 - 111
pages/ruiDu/compontents/report-form.vue

@@ -22,7 +22,7 @@ import { getTenantId, getAccessToken } from "@/utils/auth.js";
 
 const istime = ref("false");
 
-onLoad(options => {
+onLoad((options) => {
   istime.value = options.istime;
 });
 
@@ -51,7 +51,7 @@ const rdStatusRange = ref([]);
 const techniqueRange = ref([]);
 
 const handleInitSelect = () => {
-  rdStatusRange.value = getStrDictOptions("rdStatus").map(item => {
+  rdStatusRange.value = getStrDictOptions("rdStatus").map((item) => {
     return {
       ...item,
       text: item.label,
@@ -59,7 +59,7 @@ const handleInitSelect = () => {
   });
   // 施工工艺
   techniqueRange.value = getIntDictOptions("rq_iot_project_technology_rd").map(
-    item => {
+    (item) => {
       return {
         ...item,
         text: item.label,
@@ -120,7 +120,7 @@ const startDefaultTime = ref("08:00");
 const endTime = ref("24:00");
 const endDefaultTime = ref("08:00");
 
-const timeRange = data => {
+const timeRange = (data) => {
   form.startTime = data[0];
   form.endTime = data[1];
 };
@@ -206,8 +206,8 @@ const validate = async () => {
   if (istime.value === "true") {
     return await reportFormRef.value.validateField([
       "constructionBrief",
-      ...form.platformIds.flatMap(pid => [
-        ...NON_PROD_FIELDS.map(item => `${pid}.${item.key}`),
+      ...form.platformIds.flatMap((pid) => [
+        ...NON_PROD_FIELDS.map((item) => `${pid}.${item.key}`),
         `${pid}.otherNptReason`,
       ]),
     ]);
@@ -216,16 +216,16 @@ const validate = async () => {
 
 const submitForm = async () => {
   const deleteId = wellOptions.value.filter(
-    o => !form.platformIds.includes(o.value)
+    (o) => !form.platformIds.includes(o.value)
   );
-  deleteId.forEach(o => {
+  deleteId.forEach((o) => {
     delete form[o.value];
   });
   await validate();
 
   let error = false;
 
-  form.platformIds.forEach(id => {
+  form.platformIds.forEach((id) => {
     const pair = form[id];
     // 计算所有时间字段的总和
     const totalTime =
@@ -242,7 +242,7 @@ const submitForm = async () => {
       (pair.winterBreakTime || 0) +
       (pair.otherNptTime || 0);
     const wellName =
-      wellOptions.value.find(item => item.value === id)?.text ??
+      wellOptions.value.find((item) => item.value === id)?.text ??
       props.reportData.wellName ??
       "";
     // 检查总和是否超过24小时
@@ -283,8 +283,8 @@ const submitForm = async () => {
 
   const responseData = [];
   // // 处理施工工艺
-  form.platformIds.forEach(id => {
-    formDataCopy[id].extProperty.forEach(attr => {
+  form.platformIds.forEach((id) => {
+    formDataCopy[id].extProperty.forEach((attr) => {
       if (attr.dataType === "double") {
         attr.actualValue = Number(attr.actualValue);
       }
@@ -292,7 +292,7 @@ const submitForm = async () => {
 
     const attachments = JSON.parse(
       JSON.stringify(formDataCopy.attachments)
-    ).map(item => {
+    ).map((item) => {
       item.bizId = id;
       return item;
     });
@@ -323,14 +323,15 @@ const submitForm = async () => {
       nextPlan: formDataCopy.nextPlan,
       ...(props.reportData.platformWell === 1
         ? {
-            platformId: props.reportData.platforms.find(v => v.reportId === id)
-              .id,
+            platformId: props.reportData.platforms.find(
+              (v) => v.reportId === id
+            ).id,
           }
         : {}),
       productionStatus: formDataCopy.productionStatus,
       rdStatus: formDataCopy[id].rdStatus,
-      techniqueIds: formDataCopy[id].techniqueIds.map(v => v.toString()),
-      reportFuels: formDataCopy.reportFuels.map(item => ({
+      techniqueIds: formDataCopy[id].techniqueIds.map((v) => v.toString()),
+      reportFuels: formDataCopy.reportFuels.map((item) => ({
         ...item,
         customFuel: Number(item.customFuel),
         reportId: id,
@@ -358,7 +359,7 @@ const submitForm = async () => {
   });
 
   // 提交表单
-  updateRuiDuReportBatch(responseData).then(res => {
+  updateRuiDuReportBatch(responseData).then((res) => {
     // 提交成功
     if (res.code === 0) {
       uni.showToast({ title: t("operation.success"), icon: "none" });
@@ -374,48 +375,92 @@ const handleClickSelectDevice = () => {
   deviceTransferRef.value.open();
 };
 
-const handleEquipmentNames = deviceIds => {
+const handleEquipmentNames = (deviceIds) => {
   form.deviceIds = deviceIds || []; //施工设备
   const { selectedDevices = [] } = props.reportData || {};
   const deviceIdSet = new Set(deviceIds);
   //   已选择的设备(名称)
   selectedEquipmentNames.value = selectedDevices
-    .filter(item => deviceIdSet.has(item.id))
-    .map(item => item.deviceName)
+    .filter((item) => deviceIdSet.has(item.id))
+    .map((item) => item.deviceName)
     .join(",");
 
   //   未选择的设备(名称)
   unselectedEquipmentNames.value =
     selectedDevices
-      .filter(item => !deviceIdSet.has(item.id))
-      .map(item => item.deviceName)
+      .filter((item) => !deviceIdSet.has(item.id))
+      .map((item) => item.deviceName)
       .join(",") || t("ruiDu.allEquipmentConstructed");
 };
 
 // 设备选择器回调
-const handleTransferChange = selectedIds => {
+const handleTransferChange = (selectedIds) => {
   // 更新已选择的设备及名称
   handleEquipmentNames(selectedIds);
 };
 
 const steps = ref([]);
 
-const formatT = arr =>
-  `${arr[0].toString().padStart(2, "0")}:${arr[1].toString().padStart(2, "0")}`;
-
-const addReportDetailRow = () => {
-  if (!form.reportDetails) {
-    form.reportDetails = [];
+const formatT = (val) => {
+  if (typeof val === "string") {
+    return val;
   }
-  form.reportDetails.push({
-    startTime: "08:00",
-    endTime: "08:00",
-    duration: 0,
-    constructionDetail: "",
-  });
+  if (Array.isArray(val)) {
+    return `${val[0].toString().padStart(2, "0")}:${val[1]
+      .toString()
+      .padStart(2, "0")}`;
+  }
+  return "08:00";
 };
 
-const removeReportDetailRow = index => {
+const formatDateTimestamp = (val) => {
+  const date = dayjs(val || props.reportData.createTime || new Date());
+  return date.isValid() ? date.startOf("day").valueOf() : "";
+};
+
+const getDefaultReportDate = () =>
+  formatDateTimestamp(props.reportData.createTime);
+
+const getDefaultEndDateTime = (reportDate) =>
+  dayjs(reportDate).add(1, "day").startOf("day").valueOf();
+
+function getReportDetailDuration(row) {
+  if (!row.reportDate || !row.endDateTime || !row.startTime || !row.endTime) {
+    return 0;
+  }
+
+  const reportDate = dayjs(row.reportDate).format("YYYY-MM-DD");
+  const endDateTime = dayjs(row.endDateTime).format("YYYY-MM-DD");
+  const start = dayjs(`${reportDate} ${row.startTime}`);
+  const end = dayjs(`${endDateTime} ${row.endTime}`);
+
+  let diffMinutes = end.diff(start, "minute");
+
+  if (diffMinutes < 0) {
+    diffMinutes += 1440;
+  }
+
+  return Number((diffMinutes / 60).toFixed(2));
+}
+
+const addReportDetailRow = () => {
+  if (!form.reportDetails) {
+    form.reportDetails = [];
+  }
+  const reportDate = getDefaultReportDate();
+  const row = {
+    reportDate,
+    endDateTime: getDefaultEndDateTime(reportDate),
+    startTime: "08:00",
+    endTime: "08:00",
+    duration: 0,
+    constructionDetail: "",
+  };
+  row.duration = getReportDetailDuration(row);
+  form.reportDetails.push(row);
+};
+
+const removeReportDetailRow = (index) => {
   if (index === 0) {
     uni.showToast({ title: "至少填写一条生产动态", icon: "none" });
     return;
@@ -434,7 +479,7 @@ const formDataFormat = () => {
   }
 
   if (props.reportData.platformWell === 1) {
-    form.platformIds = props.reportData.platforms?.map(v => v.reportId) ?? [];
+    form.platformIds = props.reportData.platforms?.map((v) => v.reportId) ?? [];
   } else {
     form.platformIds = [props.reportData.id];
   }
@@ -446,7 +491,7 @@ const formDataFormat = () => {
   // }
 
   if (props.reportData.platformWell === 1) {
-    props.reportData.platforms.forEach(p => {
+    props.reportData.platforms.forEach((p) => {
       form[p.reportId] = {
         rdStatus: p.rdStatus,
         techniqueIds: p.techniqueIds,
@@ -489,12 +534,21 @@ const formDataFormat = () => {
 
   form.constructionBrief = props.reportData.constructionBrief || ""; // 施工简介
 
-  form.reportDetails = (props.reportData.reportDetails || []).map(item => ({
-    duration: item.duration || 0,
-    constructionDetail: item.constructionDetail || "",
-    startTime: formatT(item.startTime),
-    endTime: formatT(item.endTime),
-  }));
+  form.reportDetails = (props.reportData.reportDetails || []).map((item) => {
+    const reportDate = formatDateTimestamp(item.reportDate);
+    const row = {
+      duration: 0,
+      constructionDetail: item.constructionDetail || "",
+      reportDate,
+      endDateTime: item.endDateTime
+        ? formatDateTimestamp(item.endDateTime)
+        : getDefaultEndDateTime(reportDate),
+      startTime: formatT(item.startTime) || "08:00",
+      endTime: formatT(item.endTime) || "08:00",
+    };
+    row.duration = getReportDetailDuration(row);
+    return row;
+  });
 
   if (!form.reportDetails.length) {
     addReportDetailRow();
@@ -519,7 +573,7 @@ const formDataFormat = () => {
 
   const validList = list1?.length > 0 ? list1 : list2?.length > 0 ? list2 : [];
 
-  form.reportFuels = validList.map(v => ({
+  form.reportFuels = validList.map((v) => ({
     ...v,
     // 这里保持你原有的数值处理逻辑
     customFuel: Number(
@@ -531,7 +585,7 @@ const formDataFormat = () => {
     ),
   }));
 
-  steps.value = (props.reportData.taskProgresses ?? []).map(v => ({
+  steps.value = (props.reportData.taskProgresses ?? []).map((v) => ({
     title: v.rdStatusLabel,
     desc: v.createTime,
   }));
@@ -564,7 +618,7 @@ const timeRangeFormat = () => {
 
 watch(
   () => props.reportData,
-  val => {
+  (val) => {
     if (val.id) {
       formDataFormat();
     }
@@ -574,7 +628,7 @@ watch(
 
 const wellOptions = computed(() => {
   return (
-    props.reportData.platforms?.map(v => ({
+    props.reportData.platforms?.map((v) => ({
       text: v.wellName,
       value: v.reportId,
     })) ?? []
@@ -590,31 +644,20 @@ const reportDetailsTimeRangeRef = ref(null);
 
 const reportDetailIndex = ref(0);
 
-const handleClickTimeRangeItem = index => {
+const handleClickTimeRangeItem = (index) => {
   reportDetailIndex.value = index;
   reportDetailsTimeRangeRef.value.open();
 };
 
-const calculateDuration = row => {
-  if (!row.startTime || !row.endTime) {
-    row.duration = 0;
-    return;
-  }
-
-  const todayStr = dayjs().format("YYYY-MM-DD");
-  const start = dayjs(`${todayStr} ${row.startTime}`);
-  const end = dayjs(`${todayStr} ${row.endTime}`);
+const calculateDuration = (row) => {
+  row.duration = getReportDetailDuration(row);
+};
 
-  let diffMinutes = end.diff(start, "minute");
-
-  if (diffMinutes < 0) {
-    diffMinutes += 1440;
-  }
-
-  row.duration = Number((diffMinutes / 60).toFixed(2));
+const handleReportDetailDateChange = (index) => {
+  calculateDuration(form.reportDetails[index]);
 };
 
-const reportDetailsTimeRange = data => {
+const reportDetailsTimeRange = (data) => {
   form.reportDetails[reportDetailIndex.value].startTime = data[0];
   form.reportDetails[reportDetailIndex.value].endTime = data[1];
 
@@ -629,9 +672,9 @@ const chooseFile = () => {
   uploadRef.value.chooseFile({
     count: 9,
     size: 50,
-    success: files => {
+    success: (files) => {
       attachmentList.value = attachmentList.value.concat(files);
-      attachmentList.value.forEach(file => {
+      attachmentList.value.forEach((file) => {
         // 将等待上传和上传失败的文件提交上传到服务器
         // 提示:::如果接口不支持跨域,改成调用this.getTempFilePath(file)
         if (file.status === "waiting" || file.status === "fail") {
@@ -642,7 +685,7 @@ const chooseFile = () => {
   });
 };
 
-const uploadHandle = file => {
+const uploadHandle = (file) => {
   uploadRef.value.upload({
     url:
       config.default.apiUrl + config.default.apiUrlSuffix + "/rq/file/upload",
@@ -654,7 +697,7 @@ const uploadHandle = file => {
       "Device-id": "undefined",
     },
     method: "post",
-    success: e => {
+    success: (e) => {
       file.status = "success";
       const result = JSON.parse(e.result);
 
@@ -677,7 +720,7 @@ const uploadHandle = file => {
         type: "attachment",
       });
     },
-    fail: e => {
+    fail: (e) => {
       file.status = "fail";
       console.error("上传异常:", err);
       uni.showToast({ title: t("operation.uploadFail"), icon: "none" });
@@ -734,7 +777,7 @@ const uploadHandle = file => {
 // };
 
 // 删除附件
-const deleteFiles = index => {
+const deleteFiles = (index) => {
   // 1. 从formData.attachments中移除选中项
   form.attachments.splice(index, 1);
 };
@@ -743,16 +786,16 @@ function copyToPublicAndOpen(sourcePath, fileName) {
   // 获取 _downloads/ (公共下载目录) 的目录对象
   plus.io.resolveLocalFileSystemURL(
     "_downloads/",
-    entryDir => {
+    (entryDir) => {
       // 获取源文件对象
       plus.io.resolveLocalFileSystemURL(
         sourcePath,
-        entryFile => {
+        (entryFile) => {
           // 执行复制操作:将 sourcePath 复制到 _downloads/ 下,并重命名
           entryFile.copyTo(
             entryDir,
             fileName,
-            newEntry => {
+            (newEntry) => {
               console.log("文件已复制到公共目录:", newEntry.fullPath);
 
               uni.showToast({
@@ -767,27 +810,27 @@ function copyToPublicAndOpen(sourcePath, fileName) {
               plus.runtime.openFile(
                 newEntry.fullPath,
                 {},
-                e => {
+                (e) => {
                   console.log("打开成功");
                 },
-                e => {
+                (e) => {
                   console.error("打开失败", e);
                   uni.showToast({ title: "无法打开文件", icon: "none" });
                 }
               );
             },
-            e => {
+            (e) => {
               console.error("复制文件失败:", e);
               uni.showToast({ title: "保存到公共目录失败", icon: "none" });
             }
           );
         },
-        e => {
+        (e) => {
           console.error("读取源文件失败:", e);
         }
       );
     },
-    e => {
+    (e) => {
       console.error("读取下载目录失败:", e);
     }
   );
@@ -798,16 +841,16 @@ function saveTempFileToDownloads(tempPath, fileName) {
   // "_downloads/" 是 H5+ API 对安卓公共下载目录的映射
   plus.io.resolveLocalFileSystemURL(
     "_downloads/",
-    entryDir => {
+    (entryDir) => {
       // 2. 获取临时文件对象
       plus.io.resolveLocalFileSystemURL(
         tempPath,
-        entryFile => {
+        (entryFile) => {
           // 3. 执行复制:将临时文件复制到 Downloads 目录
           entryFile.copyTo(
             entryDir,
             fileName,
-            newEntry => {
+            (newEntry) => {
               console.log("文件路径:" + newEntry.fullPath);
 
               uni.showToast({
@@ -818,18 +861,18 @@ function saveTempFileToDownloads(tempPath, fileName) {
               // 4. (可选) 打开预览
               plus.runtime.openFile(newEntry.fullPath);
             },
-            e => {
+            (e) => {
               console.error("复制失败", e);
               uni.showToast({ title: "保存失败", icon: "none" });
             }
           );
         },
-        e => {
+        (e) => {
           console.error("读取临时文件失败", e);
         }
       );
     },
-    e => {
+    (e) => {
       console.error("无法访问下载目录", e);
       // 这里如果报错,通常是权限没给或者 Android 11+ 读写受限
     }
@@ -837,7 +880,7 @@ function saveTempFileToDownloads(tempPath, fileName) {
 }
 
 // 下载文件
-const downloadFile = async file => {
+const downloadFile = async (file) => {
   console.log("🚀 ~ downloadFile ~ file:", file);
   const { filePath: fileUrl, name: fileName } = file;
 
@@ -852,7 +895,7 @@ const downloadFile = async file => {
   if (platform === "android") {
     uni.downloadFile({
       url: fileUrl,
-      success: res => {
+      success: (res) => {
         console.log("🚀 ~ downloadFile ~ res:", res);
         if (res.statusCode === 200) {
           saveTempFileToDownloads(res.tempFilePath, fileName);
@@ -876,7 +919,7 @@ const downloadFile = async file => {
           // });
         }
       },
-      fail: err => {
+      fail: (err) => {
         console.log("🚀 ~ downloadFile ~ err:", err);
         uni.showToast({
           title: t("operation.downloadFail"),
@@ -961,12 +1004,12 @@ const downloadFileByBlob = async (fileUrl, fileName) => {
 
 const removeSelectedItem = (platformId, value) => {
   form[platformId].techniqueIds = form[platformId].techniqueIds.filter(
-    item => item !== value
+    (item) => item !== value
   );
   getWorkloadInfoByTechnique(platformId);
 };
 
-const getWorkloadInfoByTechnique = platformId => {
+const getWorkloadInfoByTechnique = (platformId) => {
   const ids = form[platformId].techniqueIds;
   if (!ids.length) {
     form[platformId].extProperty = [];
@@ -974,12 +1017,12 @@ const getWorkloadInfoByTechnique = platformId => {
   }
   getRuiDuReportAttrs({
     techniqueIds: ids.join(","),
-  }).then(res => {
+  }).then((res) => {
     const { data = [] } = res;
 
     // 1. 按 "identifier+unit" 去重:用Map保证唯一,key为拼接字段
     const uniqueMap = new Map();
-    data.forEach(item => {
+    data.forEach((item) => {
       // 生成去重key(identifier和unit都存在才拼接,避免异常)
       const key =
         item.identifier && item.unit
@@ -991,7 +1034,7 @@ const getWorkloadInfoByTechnique = platformId => {
     const uniqueData = Array.from(uniqueMap.values());
 
     // 2. 对比formData.extProperty,保留已有actualValue(避免覆盖用户输入)
-    const handledData = uniqueData.map(newItem => {
+    const handledData = uniqueData.map((newItem) => {
       // 生成当前新项的去重key
       const newKey =
         newItem.identifier && newItem.unit
@@ -999,7 +1042,7 @@ const getWorkloadInfoByTechnique = platformId => {
           : "";
 
       // 在原有extProperty中找匹配项
-      const oldItem = form[platformId].extProperty.find(old => {
+      const oldItem = form[platformId].extProperty.find((old) => {
         const oldKey =
           old.identifier && old.unit ? `${old.identifier}-${old.unit}` : "";
         return newKey && oldKey && newKey === oldKey;
@@ -1025,7 +1068,7 @@ defineExpose({
 // const props = defineProps({ reportData: Object });
 
 // --- 1. 公共工具函数 (保持不变) ---
-const parseNumber = val => {
+const parseNumber = (val) => {
   let num = parseFloat(val);
   if (isNaN(num)) num = 0;
   if (num < 0) num = 0;
@@ -1037,7 +1080,7 @@ const parseNumber = val => {
 // 列表变化 -> 算总和
 const handleListChange = useDebounceFn(() => {
   let total = 0;
-  form.reportFuels.forEach(item => {
+  form.reportFuels.forEach((item) => {
     const formattedVal = parseNumber(item.customFuel);
     if (item.customFuel !== formattedVal) {
       item.customFuel = formattedVal;
@@ -1082,7 +1125,7 @@ const initDailyFuel = () => {
     // 【情况B】Props 没值:根据列表计算初始值
     // 这里我们不使用防抖,直接立即计算一次,确保显示正确
     let total = 0;
-    form.reportFuels.forEach(item => {
+    form.reportFuels.forEach((item) => {
       // 初始化时顺便把列表里的脏数据格式化了
       const val = parseNumber(item.customFuel);
       item.customFuel = val;
@@ -1392,17 +1435,39 @@ watch(
 
         <template v-for="(item, index) in form.reportDetails" :key="index">
           <uv-divider v-if="index !== 0" class="divider"></uv-divider>
-          <uni-forms-item class="form-item" label="日期">
-            <uni-easyinput
-              class="digit-item"
-              :inputBorder="false"
-              :clearable="false"
+          <uni-forms-item
+            class="form-item"
+            label="开始日期"
+            required
+            :name="['reportDetails', index, 'reportDate']"
+            :rules="[{ required: true, errorMessage: '请选择开始日期' }]">
+            <uni-datetime-picker
+              type="date"
+              return-type="timestamp"
+              :clear-icon="false"
+              :border="false"
+              :placeholder="selectPlaceholder"
+              :disabled="formDisable"
               :styles="{ disableColor: '#fff' }"
-              :placeholder="inputPlaceholder"
-              :disabled="true"
-              :model-value="
-                dayjs(reportData.createTime).format('YYYY-MM-DD')
-              " />
+              v-model="item.reportDate"
+              @change="handleReportDetailDateChange(index)" />
+          </uni-forms-item>
+          <uni-forms-item
+            class="form-item"
+            label="结束日期"
+            required
+            :name="['reportDetails', index, 'endDateTime']"
+            :rules="[{ required: true, errorMessage: '请选择结束日期' }]">
+            <uni-datetime-picker
+              type="date"
+              return-type="timestamp"
+              :clear-icon="false"
+              :border="false"
+              :placeholder="selectPlaceholder"
+              :disabled="formDisable"
+              :styles="{ disableColor: '#fff' }"
+              v-model="item.endDateTime"
+              @change="handleReportDetailDateChange(index)" />
           </uni-forms-item>
           <uni-forms-item
             class="form-item"
@@ -1469,7 +1534,7 @@ watch(
         :key="platformId"
         class="content">
         <div class="content-title">{{
-          wellOptions.find(item => item.value === platformId)?.text ??
+          wellOptions.find((item) => item.value === platformId)?.text ??
           reportData.wellName ??
           ""
         }}</div>
@@ -1625,7 +1690,7 @@ watch(
             :styles="{ disableColor: '#fff' }"
             :disabled="istime !== 'true'"
             v-model.number="form[platformId][field.key]"
-            @input="val => onInputChange(val, platformId, field.key)" />
+            @input="(val) => onInputChange(val, platformId, field.key)" />
         </uni-forms-item>
 
         <uni-forms-item

+ 1 - 0
pages/ruiDu/index.vue

@@ -183,6 +183,7 @@ const queryList = (pageNo, pageSize) => {
   getRuiDuReportPage({
     pageNo,
     pageSize,
+    searchKey: orderName.value,
   })
     .then((res) => {
       // 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用

+ 541 - 0
pages/ruiDuReport/daily-detail.vue

@@ -0,0 +1,541 @@
+<template>
+  <view class="page report-page">
+    <z-paging
+      ref="paging"
+      v-model="dataList"
+      class="report-paging"
+      :default-page-size="10"
+      @query="queryList">
+      <view class="report-list">
+        <view class="report-card" v-for="item in dataList" :key="item.id">
+          <view class="card-header">
+            <view class="card-date">{{ formatDate(item.createTime) }}</view>
+            <view class="status-tag">{{
+              rdStatusDict[item.rdStatus] || item.rdStatus || "--"
+            }}</view>
+          </view>
+          <view class="card-body">
+            <view class="field-row">
+              <text class="field-label">{{
+                $t("ruiDu.constructionTeam")
+              }}</text>
+              <text class="field-value">{{ item.deptName || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">{{ $t("ruiDu.project") }}</text>
+              <text class="field-value">{{ item.contractName || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">{{ $t("ruiDu.task") }}</text>
+              <text class="field-value">{{ item.taskName || "--" }}</text>
+            </view>
+            <view class="field-row brief-row">
+              <text class="field-label">{{
+                $t("ruiDuReport.constructionBrief")
+              }}</text>
+              <UniTooltip
+                :content="item.constructionBrief || '--'"
+                placement="top">
+                <text class="field-value brief-text">{{
+                  item.constructionBrief || "--"
+                }}</text>
+              </UniTooltip>
+            </view>
+          </view>
+          <view class="card-footer">
+            <button
+              class="detail-btn"
+              type="primary"
+              plain
+              @click="viewDetail(item)">
+              {{ $t("operation.view") }}
+            </button>
+          </view>
+        </view>
+      </view>
+    </z-paging>
+
+    <UniFab
+      :pattern="fabPattern"
+      horizontal="right"
+      vertical="bottom"
+      direction="horizontal"
+      :popMenu="false"
+      @fabClick="openFilterPopup" />
+
+    <uni-popup
+      ref="filterPopup"
+      type="bottom"
+      background-color="#fff"
+      border-radius="10px 10px 0 0">
+      <view class="filter-popup">
+        <view class="filter-header">
+          <text class="filter-action" @click="closeFilterPopup">
+            {{ $t("operation.cancel") }}
+          </text>
+          <text class="filter-title">{{ $t("ruiDuReport.filterTitle") }}</text>
+          <text class="filter-action primary" @click="applyFilter">
+            {{ $t("operation.confirm") }}
+          </text>
+        </view>
+
+        <view class="filter-body">
+          <view class="filter-item">
+            <view class="filter-label">{{ $t("ruiDuReport.project") }}</view>
+            <uni-easyinput
+              v-model="filterForm.contractName"
+              :inputBorder="false"
+              :styles="inputStyles"
+              :placeholder="$t('ruiDuReport.projectPlaceholder')" />
+          </view>
+
+          <view class="filter-item">
+            <view class="filter-label">{{ $t("ruiDuReport.task") }}</view>
+            <uni-easyinput
+              v-model="filterForm.taskName"
+              :inputBorder="false"
+              :styles="inputStyles"
+              :placeholder="$t('ruiDuReport.taskPlaceholder')" />
+          </view>
+
+          <view class="filter-item">
+            <view class="filter-label">{{ $t("ruiDuReport.createTime") }}</view>
+            <uni-datetime-picker
+              v-model="filterForm.createTime"
+              type="datetimerange"
+              return-type="string"
+              :border="false"
+              :placeholder="$t('operation.PleaseSelect')" />
+          </view>
+
+          <view class="filter-item dept-item">
+            <view class="filter-label">{{ $t("ruiDuReport.dept") }}</view>
+            <view class="dept-selected">
+              {{ selectedDeptName || $t("operation.PleaseSelect") }}
+            </view>
+            <view class="tree">
+              <DaTree
+                :data="treeData"
+                labelField="name"
+                valueField="id"
+                disabledField="disabled"
+                defaultExpandAll
+                checkedDisabled
+                :defaultCheckedKeys="filterForm.deptId"
+                @change="handleTreeChange" />
+            </view>
+          </view>
+        </view>
+
+        <view class="filter-footer">
+          <button class="filter-button reset" @click="resetFilter">
+            {{ $t("inventory.search.reset") }}
+          </button>
+          <button class="filter-button" type="primary" @click="applyFilter">
+            {{ $t("operation.search") }}
+          </button>
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, onMounted, reactive, ref } from "vue";
+import dayjs from "dayjs";
+import DaTree from "@/components/da-tree/index.vue";
+import UniFab from "@/uni_modules/uni-fab/components/uni-fab/uni-fab.vue";
+import UniTooltip from "@/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue";
+import { getRuiduReportPage } from "@/api/ruiduReport";
+import { specifiedSimpleDepts } from "@/api";
+import { getDeptId } from "@/utils/auth";
+import { useDataDictStore } from "@/store/modules/dataDict";
+
+const paging = ref(null);
+const filterPopup = ref(null);
+const dataList = ref([]);
+const deptOptions = ref([]);
+const treeData = ref([]);
+const dictStore = useDataDictStore();
+const rdStatusDict = reactive({});
+
+const inputStyles = reactive({
+  backgroundColor: "#f7f8fa",
+  color: "#333",
+});
+
+const fabPattern = reactive({
+  color: "#fff",
+  backgroundColor: "#fff",
+  selectedColor: "#fff",
+  buttonColor: "#004098",
+  iconColor: "#fff",
+  icon: "search",
+});
+
+const filterForm = reactive({
+  contractName: "",
+  taskName: "",
+  deptId: "",
+  createTime: [],
+});
+
+const selectedDeptName = computed(() => {
+  const current = deptOptions.value.find(
+    (item) => String(item.value) === String(filterForm.deptId)
+  );
+  return current?.text || "";
+});
+
+const handleTree = (
+  data,
+  id = "id",
+  parentId = "parentId",
+  children = "children"
+) => {
+  if (!Array.isArray(data)) return [];
+
+  const childrenListMap = {};
+  const nodeIds = {};
+  const tree = [];
+
+  for (const item of data) {
+    const itemParentId = item[parentId];
+    if (childrenListMap[itemParentId] == null) {
+      childrenListMap[itemParentId] = [];
+    }
+    nodeIds[item[id]] = item;
+    childrenListMap[itemParentId].push(item);
+  }
+
+  for (const item of data) {
+    if (nodeIds[item[parentId]] == null) {
+      tree.push(item);
+    }
+  }
+
+  const adaptToChildrenList = (node) => {
+    if (childrenListMap[node[id]] != null) {
+      node[children] = childrenListMap[node[id]];
+    }
+    if (node[children]) {
+      node[children].forEach(adaptToChildrenList);
+    }
+  };
+
+  tree.forEach(adaptToChildrenList);
+  return tree;
+};
+
+const sortDeptTree = (nodes) => {
+  if (!Array.isArray(nodes)) return [];
+
+  return [...nodes]
+    .sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999))
+    .map((node) => ({
+      ...node,
+      children: sortDeptTree(node.children),
+    }));
+};
+
+const loadDeptOptions = async () => {
+  try {
+    const response = await specifiedSimpleDepts(getDeptId());
+    const list = response?.data || [];
+    deptOptions.value = list.map((item) => ({
+      text: item.name,
+      value: item.id,
+    }));
+    treeData.value = sortDeptTree(handleTree(list));
+  } catch (error) {
+    treeData.value = [];
+    deptOptions.value = [];
+  }
+};
+
+const queryList = (pageNo, pageSize) => {
+  getRuiduReportPage({
+    pageNo,
+    pageSize: 10,
+    contractName: filterForm.contractName,
+    taskName: filterForm.taskName,
+    deptId: filterForm.deptId,
+    createTime: filterForm.createTime,
+  })
+    .then((res) => {
+      paging.value?.complete(res.data?.list || []);
+    })
+    .catch(() => {
+      paging.value?.complete(false);
+    });
+};
+
+const formatDate = (time) => {
+  return time ? dayjs(time).format("YYYY-MM-DD") : "--";
+};
+
+const loadRdStatusDict = async () => {
+  if (dictStore.dataDict.length <= 0) {
+    await dictStore.loadDataDictList();
+  }
+  dictStore.getStrDictOptions("rdStatus").forEach((item) => {
+    rdStatusDict[item.value] = item.label;
+  });
+};
+
+const openFilterPopup = () => {
+  filterPopup.value?.open();
+};
+
+const closeFilterPopup = () => {
+  filterPopup.value?.close();
+};
+
+const handleTreeChange = (value) => {
+  filterForm.deptId = value;
+};
+
+const applyFilter = () => {
+  closeFilterPopup();
+  paging.value?.reload();
+};
+
+const viewDetail = (item) => {
+  if (item.projectId === null && item.taskId === null) {
+    uni.navigateTo({
+      url: "/pages/ruiDu/create?id=" + item.id + "&isview=detail",
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: "/pages/ruiDu/detail?id=" + item.id,
+  });
+};
+
+const resetFilter = () => {
+  filterForm.contractName = "";
+  filterForm.taskName = "";
+  filterForm.deptId = "";
+  filterForm.createTime = [];
+};
+
+onMounted(() => {
+  loadDeptOptions();
+  loadRdStatusDict();
+});
+</script>
+
+<style lang="scss" scoped>
+.report-page {
+  padding: 10px !important;
+}
+
+.report-paging {
+  height: 100%;
+}
+
+.report-list {
+  padding: 8px 6px;
+  box-sizing: border-box;
+}
+
+.report-card {
+  position: relative;
+  margin-bottom: 14px;
+  overflow: hidden;
+  background: #ffffff;
+  border: 1px solid rgba(0, 64, 152, 0.08);
+  border-radius: 8px;
+  box-shadow: 0 6px 18px rgba(35, 54, 79, 0.08);
+}
+
+.card-header {
+  min-height: 52px;
+  padding: 14px 16px 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+  border-bottom: 1px solid #edf1f7;
+}
+
+.card-date {
+  color: #000000;
+  font-weight: 700;
+  font-size: 18px;
+  line-height: 24px;
+}
+
+.status-tag {
+  max-width: 118px;
+  height: 24px;
+  line-height: 24px;
+  padding: 0 9px;
+  border-radius: 12px;
+  background: #004098;
+  color: #ffffff;
+  font-size: 12px;
+  font-weight: 600;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  box-sizing: border-box;
+}
+
+.card-body {
+  padding: 12px 16px 4px;
+  box-sizing: border-box;
+}
+
+.field-row {
+  display: flex;
+  align-items: flex-start;
+  min-height: 34px;
+  font-size: 14px;
+}
+
+.field-label {
+  width: 82px;
+  flex-shrink: 0;
+  color: #000000;
+  font-weight: 600;
+  font-size: 13px;
+  line-height: 22px;
+}
+
+.field-value {
+  min-width: 0;
+  flex: 1;
+  color: #233044;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 22px;
+}
+
+.brief-row {
+  padding-top: 4px;
+}
+
+.brief-row :deep(.uni-tooltip) {
+  min-width: 0;
+  flex: 1;
+  display: block;
+}
+
+.brief-text {
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-footer {
+  padding: 2px 16px 14px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.detail-btn {
+  min-width: 72px;
+  height: 30px;
+  line-height: 28px;
+  margin: 0;
+  padding: 0 14px;
+  border-radius: 15px;
+  font-size: 13px;
+}
+
+.filter-popup {
+  height: 86vh;
+  max-height: 86vh;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.filter-header {
+  height: 48px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-title {
+  font-weight: 600;
+  color: #333;
+  font-size: 16px;
+}
+
+.filter-action {
+  min-width: 48px;
+  color: #666;
+  font-size: 14px;
+
+  &.primary {
+    color: #004098;
+    text-align: right;
+  }
+}
+
+.filter-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 12px 16px;
+  box-sizing: border-box;
+}
+
+.filter-item {
+  margin-bottom: 14px;
+}
+
+.filter-label {
+  margin-bottom: 8px;
+  color: #333;
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.dept-selected {
+  min-height: 36px;
+  line-height: 36px;
+  padding: 0 10px;
+  margin-bottom: 8px;
+  background: #f7f8fa;
+  color: #666;
+  border-radius: 4px;
+  box-sizing: border-box;
+}
+
+.tree {
+  height: 420px;
+  overflow: hidden;
+  border: 1px solid #f0f0f0;
+  border-radius: 4px;
+}
+
+.filter-footer {
+  display: flex;
+  gap: 10px;
+  padding: 10px 16px 14px;
+  border-top: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-button {
+  flex: 1;
+  height: 38px;
+  line-height: 38px;
+  font-size: 14px;
+  margin: 0;
+
+  &.reset {
+    color: #004098;
+    background: #fff;
+    border: 1px solid #004098;
+  }
+}
+</style>

+ 794 - 0
pages/ruiDuReport/daily-team-statistic.vue

@@ -0,0 +1,794 @@
+<template>
+  <view class="page report-page">
+    <view class="mode-tabs">
+      <view
+        v-for="item in modeOptions"
+        :key="item.value"
+        class="mode-tab"
+        :class="{ active: currentMode === item.value }"
+        @click="switchMode(item.value)">
+        {{ item.label }}
+      </view>
+    </view>
+
+    <z-paging
+      ref="paging"
+      v-model="dataList"
+      class="report-paging"
+      :fixed="false"
+      :default-page-size="10"
+      @query="queryList">
+      <view class="report-list">
+        <view class="report-card" v-for="item in dataList" :key="item.id">
+          <view class="card-header">
+            <view>
+              <view class="card-date">{{ formatDate(item.createTime) }}</view>
+            </view>
+            <view class="status-tag">
+              {{ rdStatusDict[item.rdStatus] || item.rdStatus || "--" }}
+            </view>
+          </view>
+
+          <view class="card-body">
+            <view class="field-row">
+              <text class="field-label">施工队伍</text>
+              <text class="field-value">{{ item.deptName || "--" }}</text>
+            </view>
+            <view class="field-row">
+              <text class="field-label">任务</text>
+              <text class="field-value">{{ item.taskName || "--" }}</text>
+            </view>
+            <view class="field-row brief-row">
+              <text class="field-label">施工简报</text>
+              <UniTooltip
+                :content="item.constructionBrief || '--'"
+                placement="top">
+                <text class="field-value brief-text">{{
+                  item.constructionBrief || "--"
+                }}</text>
+              </UniTooltip>
+            </view>
+          </view>
+
+          <view v-if="item.lastGroupIdFlag" class="summary-panel">
+            <view class="summary-header" @click="toggleSummary(item.id)">
+              <text>组汇总</text>
+              <uni-icons
+                :type="isSummaryExpanded(item.id) ? 'up' : 'down'"
+                color="#004098"
+                size="16" />
+            </view>
+            <view v-if="isSummaryExpanded(item.id)" class="summary-grid">
+              <view
+                class="summary-item"
+                v-for="field in summaryFields"
+                :key="field.key">
+                <text class="summary-label">{{ field.label }}</text>
+                <text class="summary-value">{{
+                  formatValue(item[field.key])
+                }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </z-paging>
+
+    <UniFab
+      :pattern="fabPattern"
+      horizontal="right"
+      vertical="bottom"
+      direction="horizontal"
+      :popMenu="false"
+      @fabClick="openFilterPopup" />
+
+    <uni-popup
+      ref="filterPopup"
+      type="bottom"
+      background-color="#fff"
+      border-radius="10px 10px 0 0">
+      <view class="filter-popup">
+        <view class="filter-header">
+          <text class="filter-action" @click="closeFilterPopup">取消</text>
+          <text class="filter-title">筛选条件</text>
+          <text class="filter-action primary" @click="applyFilter">确定</text>
+        </view>
+
+        <view class="filter-body">
+          <view class="filter-item">
+            <view class="filter-label">项目</view>
+            <uni-easyinput
+              v-model="filterForm.contractName"
+              :inputBorder="false"
+              :styles="inputStyles"
+              placeholder="请输入项目" />
+          </view>
+
+          <view v-if="currentMode === 'team'" class="filter-item">
+            <view class="filter-label">任务</view>
+            <uni-easyinput
+              v-model="filterForm.taskName"
+              :inputBorder="false"
+              :styles="inputStyles"
+              placeholder="请输入任务" />
+          </view>
+
+          <view class="filter-item">
+            <view class="filter-label">创建时间</view>
+            <uni-datetime-picker
+              v-model="filterForm.createTime"
+              type="datetimerange"
+              return-type="string"
+              :border="false"
+              placeholder="请选择" />
+          </view>
+
+          <view v-if="currentMode === 'well'" class="filter-item well-tree-item">
+            <view class="filter-label">井</view>
+            <uni-easyinput
+              v-model="wellSearchKey"
+              :inputBorder="false"
+              :styles="inputStyles"
+              placeholder="请输入井名" />
+            <view class="dept-selected">
+              {{ selectedWellText || "请选择" }}
+            </view>
+            <view class="tree well-tree">
+              <DaTree
+                :key="wellTreeRenderKey"
+                :data="wellTreeData"
+                labelField="label"
+                valueField="value"
+                childrenField="children"
+                defaultExpandAll
+                checkedDisabled
+                :filterValue="wellSearchKey"
+                :defaultCheckedKeys="selectedWellKey"
+                @change="handleWellTreeChange" />
+            </view>
+          </view>
+
+          <view v-if="currentMode === 'team'" class="filter-item dept-item">
+            <view class="filter-label">队伍</view>
+            <view class="dept-selected">
+              {{ selectedDeptName || "请选择" }}
+            </view>
+            <view class="tree">
+              <DaTree
+                :data="treeData"
+                labelField="name"
+                valueField="id"
+                disabledField="disabled"
+                defaultExpandAll
+                checkedDisabled
+                :defaultCheckedKeys="filterForm.deptId"
+                @change="handleTreeChange" />
+            </view>
+          </view>
+        </view>
+
+        <view class="filter-footer">
+          <button class="filter-button reset" @click="resetFilter">重置</button>
+          <button class="filter-button" type="primary" @click="applyFilter"
+            >搜索</button
+          >
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, onMounted, reactive, ref } from "vue";
+import dayjs from "dayjs";
+import DaTree from "@/components/da-tree/index.vue";
+import UniFab from "@/uni_modules/uni-fab/components/uni-fab/uni-fab.vue";
+import UniTooltip from "@/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue";
+import {
+  getTaskWellNames,
+  getRuiduReportTeamPage,
+  getRuiduReportWellPage,
+} from "@/api/ruiduReport";
+import { specifiedSimpleDepts } from "@/api";
+import { getDeptId } from "@/utils/auth";
+import { useDataDictStore } from "@/store/modules/dataDict";
+
+const paging = ref(null);
+const filterPopup = ref(null);
+const dataList = ref([]);
+const deptOptions = ref([]);
+const treeData = ref([]);
+const wellTreeData = ref([]);
+const wellTreeRenderKey = ref(0);
+const selectedWellKey = ref("");
+const wellSearchKey = ref("");
+const expandedSummaryIds = ref([]);
+const currentMode = ref("well");
+const dictStore = useDataDictStore();
+const rdStatusDict = reactive({});
+
+const modeOptions = [
+  { label: "井", value: "well" },
+  { label: "队伍", value: "team" },
+];
+
+const summaryFields = [
+  { label: "桥塞", key: "groupIdBridgePlug" },
+  { label: "趟数", key: "groupIdRunCount" },
+  { label: "井数", key: "groupIdCumulativeWorkingWell" },
+  { label: "小时H", key: "groupIdHourCount" },
+  { label: "油耗L", key: "groupIdFuel" },
+  { label: "水方量", key: "groupIdWaterVolume" },
+  { label: "泵车台次", key: "groupIdPumpTrips" },
+  { label: "段数", key: "groupIdCumulativeWorkingLayers" },
+  { label: "仪表/混砂", key: "groupIdMixSand" },
+];
+
+const inputStyles = reactive({
+  backgroundColor: "#f7f8fa",
+  color: "#333",
+});
+
+const fabPattern = reactive({
+  color: "#fff",
+  backgroundColor: "#fff",
+  selectedColor: "#fff",
+  buttonColor: "#004098",
+  iconColor: "#fff",
+  icon: "search",
+});
+
+const getDefaultCreateTime = () => {
+  const end = dayjs().endOf("day").format("YYYY-MM-DD HH:mm:ss");
+  const start = dayjs().subtract(6, "day").startOf("day").format("YYYY-MM-DD HH:mm:ss");
+  return [start, end];
+};
+
+const filterForm = reactive({
+  contractName: "",
+  taskName: "",
+  wellName: "",
+  deptId: getDeptId(),
+  createTime: getDefaultCreateTime(),
+});
+
+const selectedDeptName = computed(() => {
+  const current = deptOptions.value.find(
+    (item) => String(item.value) === String(filterForm.deptId)
+  );
+  return current?.text || "";
+});
+
+const selectedWellText = computed(() => {
+  if (filterForm.wellName) return `井号:${filterForm.wellName}`;
+  if (filterForm.contractName) return `项目:${filterForm.contractName}`;
+  return "";
+});
+
+const handleTree = (
+  data,
+  id = "id",
+  parentId = "parentId",
+  children = "children"
+) => {
+  if (!Array.isArray(data)) return [];
+
+  const childrenListMap = {};
+  const nodeIds = {};
+  const tree = [];
+
+  for (const item of data) {
+    const itemParentId = item[parentId];
+    if (childrenListMap[itemParentId] == null) {
+      childrenListMap[itemParentId] = [];
+    }
+    nodeIds[item[id]] = item;
+    childrenListMap[itemParentId].push(item);
+  }
+
+  for (const item of data) {
+    if (nodeIds[item[parentId]] == null) {
+      tree.push(item);
+    }
+  }
+
+  const adaptToChildrenList = (node) => {
+    if (childrenListMap[node[id]] != null) {
+      node[children] = childrenListMap[node[id]];
+    }
+    if (node[children]) {
+      node[children].forEach(adaptToChildrenList);
+    }
+  };
+
+  tree.forEach(adaptToChildrenList);
+  return tree;
+};
+
+const sortDeptTree = (nodes) => {
+  if (!Array.isArray(nodes)) return [];
+
+  return [...nodes]
+    .sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999))
+    .map((node) => ({
+      ...node,
+      children: sortDeptTree(node.children),
+    }));
+};
+
+const loadDeptOptions = async () => {
+  try {
+    const response = await specifiedSimpleDepts(getDeptId());
+    const list = response?.data || [];
+    deptOptions.value = list.map((item) => ({
+      text: item.name,
+      value: item.id,
+    }));
+    treeData.value = sortDeptTree(handleTree(list));
+  } catch (error) {
+    treeData.value = [];
+    deptOptions.value = [];
+  }
+};
+
+const loadWellOptions = async () => {
+  try {
+    const response = await getTaskWellNames({
+      companyId: 163,
+      wellName: "",
+    });
+    const list = response?.data || [];
+    const parentMap = new Map();
+    const tree = [];
+
+    list.forEach((item) => {
+      if (item.type === "1") {
+        const node = {
+          label: item.projectName,
+          value: `project-${item.projectId}`,
+          type: "1",
+          rawData: item,
+          children: [],
+        };
+        parentMap.set(item.projectId, node);
+        tree.push(node);
+      }
+    });
+
+    list.forEach((item) => {
+      if (item.type === "2") {
+        const parent = parentMap.get(item.projectId);
+        const node = {
+          label: item.wellName,
+          value: `well-${item.projectId}-${item.wellName}`,
+          type: "2",
+          rawData: item,
+        };
+        if (parent) {
+          parent.children.push(node);
+        }
+      }
+    });
+
+    wellTreeData.value = tree;
+  } catch (error) {
+    wellTreeData.value = [];
+  }
+};
+
+const queryList = (pageNo, pageSize) => {
+  const request =
+    currentMode.value === "well"
+      ? getRuiduReportTeamPage({
+          pageNo,
+          pageSize,
+          contractName: filterForm.contractName,
+          taskName: filterForm.wellName,
+          createTime: filterForm.createTime,
+        })
+      : getRuiduReportWellPage({
+          pageNo,
+          pageSize,
+          deptId: filterForm.deptId,
+          contractName: filterForm.contractName,
+          taskName: filterForm.taskName,
+          createTime: filterForm.createTime,
+        });
+
+  request
+    .then((res) => {
+      const list = res.data?.list || [];
+      const summaryIds = list
+        .filter((item) => item.lastGroupIdFlag)
+        .map((item) => String(item.id));
+      expandedSummaryIds.value =
+        pageNo === 1
+          ? summaryIds
+          : Array.from(new Set([...expandedSummaryIds.value, ...summaryIds]));
+      paging.value?.complete(list);
+    })
+    .catch(() => {
+      paging.value?.complete(false);
+    });
+};
+
+const switchMode = (mode, reload = true) => {
+  if (currentMode.value === mode) return;
+  currentMode.value = mode;
+  expandedSummaryIds.value = [];
+  if (reload) {
+    paging.value?.reload();
+  }
+};
+
+const openFilterPopup = () => {
+  filterPopup.value?.open();
+};
+
+const closeFilterPopup = () => {
+  filterPopup.value?.close();
+};
+
+const applyFilter = () => {
+  closeFilterPopup();
+  paging.value?.reload();
+};
+
+const resetFilter = () => {
+  filterForm.contractName = "";
+  filterForm.taskName = "";
+  filterForm.wellName = "";
+  filterForm.deptId = getDeptId();
+  filterForm.createTime = getDefaultCreateTime();
+  selectedWellKey.value = "";
+  wellSearchKey.value = "";
+  wellTreeRenderKey.value += 1;
+};
+
+const handleTreeChange = (value) => {
+  filterForm.deptId = value;
+};
+
+const handleWellTreeChange = (value, item) => {
+  const node = item?.originItem;
+  selectedWellKey.value = value;
+  if (node?.type === "1") {
+    filterForm.contractName = node.rawData?.projectName || node.label || "";
+    filterForm.wellName = "";
+    return;
+  }
+  if (node?.type === "2") {
+    filterForm.wellName = node.rawData?.wellName || node.label || "";
+    filterForm.contractName = "";
+  }
+};
+
+const toggleSummary = (id) => {
+  const key = String(id);
+  if (expandedSummaryIds.value.includes(key)) {
+    expandedSummaryIds.value = expandedSummaryIds.value.filter(
+      (item) => item !== key
+    );
+    return;
+  }
+  expandedSummaryIds.value = [...expandedSummaryIds.value, key];
+};
+
+const isSummaryExpanded = (id) => {
+  return expandedSummaryIds.value.includes(String(id));
+};
+
+const formatDate = (time) => {
+  return time ? dayjs(time).format("YYYY-MM-DD") : "--";
+};
+
+const formatValue = (value) => {
+  if (value === 0) return "0";
+  return value ?? "--";
+};
+
+const loadRdStatusDict = async () => {
+  if (dictStore.dataDict.length <= 0) {
+    await dictStore.loadDataDictList();
+  }
+  dictStore.getStrDictOptions("rdStatus").forEach((item) => {
+    rdStatusDict[item.value] = item.label;
+  });
+};
+
+onMounted(() => {
+  loadDeptOptions();
+  loadWellOptions();
+  loadRdStatusDict();
+});
+</script>
+
+<style lang="scss" scoped>
+.report-page {
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+  padding: 10px !important;
+  overflow: hidden;
+}
+
+.mode-tabs {
+  flex-shrink: 0;
+  height: 42px;
+  padding: 4px;
+  margin-bottom: 8px;
+  display: flex;
+  gap: 4px;
+  background: #ffffff;
+  border-radius: 8px;
+  box-sizing: border-box;
+}
+
+.mode-tab {
+  flex: 1;
+  height: 34px;
+  line-height: 34px;
+  text-align: center;
+  color: #5c6675;
+  font-size: 14px;
+  border-radius: 6px;
+
+  &.active {
+    color: #ffffff;
+    background: #004098;
+    font-weight: 600;
+  }
+}
+
+.report-paging {
+  flex: 1;
+  min-height: 0;
+  height: auto;
+}
+
+.report-list {
+  padding: 8px 6px;
+  box-sizing: border-box;
+}
+
+.report-card {
+  margin-bottom: 14px;
+  overflow: hidden;
+  background: #ffffff;
+  border: 1px solid rgba(0, 64, 152, 0.08);
+  border-radius: 8px;
+  box-shadow: 0 6px 18px rgba(35, 54, 79, 0.08);
+}
+
+.card-header {
+  min-height: 52px;
+  padding: 14px 16px 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+  border-bottom: 1px solid #edf1f7;
+}
+
+.card-date {
+  color: #000000;
+  font-weight: 700;
+  font-size: 18px;
+  line-height: 24px;
+}
+
+.status-tag {
+  max-width: 112px;
+  height: 24px;
+  line-height: 24px;
+  padding: 0 9px;
+  border-radius: 12px;
+  background: #004098;
+  color: #ffffff;
+  font-size: 12px;
+  font-weight: 600;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  box-sizing: border-box;
+}
+
+.card-body {
+  padding: 12px 16px 6px;
+  box-sizing: border-box;
+}
+
+.field-row {
+  display: flex;
+  align-items: flex-start;
+  min-height: 30px;
+  font-size: 14px;
+}
+
+.field-label {
+  width: 70px;
+  flex-shrink: 0;
+  color: #000000;
+  font-weight: 600;
+  font-size: 13px;
+  line-height: 22px;
+}
+
+.field-value {
+  min-width: 0;
+  flex: 1;
+  color: #233044;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 22px;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 8px;
+}
+
+.summary-item {
+  min-width: 0;
+  padding: 9px 10px;
+  border-radius: 6px;
+  background: #f7f8fa;
+  box-sizing: border-box;
+}
+
+.summary-label {
+  display: block;
+  color: #7a8494;
+  font-size: 12px;
+  line-height: 17px;
+}
+
+.summary-value {
+  display: block;
+  margin-top: 4px;
+  color: #233044;
+  font-weight: 700;
+  font-size: 15px;
+  line-height: 20px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.brief-row {
+  padding-top: 4px;
+}
+
+.brief-row :deep(.uni-tooltip) {
+  min-width: 0;
+  flex: 1;
+  display: block;
+}
+
+.brief-text {
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.summary-panel {
+  margin: 0 16px 14px;
+  border: 1px solid rgba(0, 64, 152, 0.12);
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.summary-header {
+  height: 38px;
+  padding: 0 12px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  color: #004098;
+  font-weight: 600;
+  font-size: 14px;
+  background: #eef5ff;
+  box-sizing: border-box;
+}
+
+.summary-grid {
+  padding: 10px;
+  background: #ffffff;
+}
+
+.filter-popup {
+  height: 86vh;
+  max-height: 86vh;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.filter-header {
+  height: 48px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-title {
+  font-weight: 600;
+  color: #333;
+  font-size: 16px;
+}
+
+.filter-action {
+  min-width: 48px;
+  color: #666;
+  font-size: 14px;
+
+  &.primary {
+    color: #004098;
+    text-align: right;
+  }
+}
+
+.filter-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 12px 16px;
+  box-sizing: border-box;
+}
+
+.filter-item {
+  margin-bottom: 14px;
+}
+
+.filter-label {
+  margin-bottom: 8px;
+  color: #333;
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.dept-selected {
+  min-height: 36px;
+  line-height: 36px;
+  padding: 0 10px;
+  margin-bottom: 8px;
+  background: #f7f8fa;
+  color: #666;
+  border-radius: 4px;
+  box-sizing: border-box;
+}
+
+.tree {
+  height: 360px;
+  overflow: hidden;
+  border: 1px solid #f0f0f0;
+  border-radius: 4px;
+}
+
+.filter-footer {
+  display: flex;
+  gap: 10px;
+  padding: 10px 16px 14px;
+  border-top: 1px solid #f0f0f0;
+  box-sizing: border-box;
+}
+
+.filter-button {
+  flex: 1;
+  height: 38px;
+  line-height: 38px;
+  font-size: 14px;
+  margin: 0;
+
+  &.reset {
+    color: #004098;
+    background: #fff;
+    border: 1px solid #004098;
+  }
+}
+</style>

+ 25 - 0
uni_modules/uni-fab/changelog.md

@@ -0,0 +1,25 @@
+## 1.2.6(2024-10-12)
+- 修复 微信小程序中的getSystemInfo警告
+## 1.2.5(2023-03-29)
+- 新增 pattern.icon 属性,可自定义图标
+## 1.2.4(2022-09-07)
+小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false)
+## 1.2.3(2022-09-05)
+- 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310)
+## 1.2.2(2021-12-29)
+- 更新 组件依赖
+## 1.2.1(2021-11-19)
+- 修复 阴影颜色不正确的bug
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fab](https://uniapp.dcloud.io/component/uniui/uni-fab)
+## 1.1.1(2021-11-09) 
+- 新增 提供组件设计资源,组件样式调整
+## 1.1.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.0.7(2021-05-12)
+- 新增 组件示例地址
+## 1.0.6(2021-02-05)
+- 调整为uni_modules目录规范
+- 优化 按钮背景色调整
+- 优化 兼容pc端

+ 491 - 0
uni_modules/uni-fab/components/uni-fab/uni-fab.vue

@@ -0,0 +1,491 @@
+<template>
+	<view class="uni-cursor-point">
+		<view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop) && content.length > 0" :class="{
+        'uni-fab--leftBottom': leftBottom,
+        'uni-fab--rightBottom': rightBottom,
+        'uni-fab--leftTop': leftTop,
+        'uni-fab--rightTop': rightTop
+      }" class="uni-fab"
+				:style="nvueBottom"
+			>
+			<view :class="{
+          'uni-fab__content--left': horizontal === 'left',
+          'uni-fab__content--right': horizontal === 'right',
+          'uni-fab__content--flexDirection': direction === 'vertical',
+          'uni-fab__content--flexDirectionStart': flexDirectionStart,
+          'uni-fab__content--flexDirectionEnd': flexDirectionEnd,
+		  'uni-fab__content--other-platform': !isAndroidNvue
+        }" :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }"
+				class="uni-fab__content" elevation="5">
+				<view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" />
+				<view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }"
+					class="uni-fab__item" @click="_onItemClick(index, item)">
+					<image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image"
+						mode="aspectFit" />
+					<text class="uni-fab__item-text"
+						:style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text>
+				</view>
+				<view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" />
+			</view>
+		</view>
+		<view :class="{
+		  'uni-fab__circle--leftBottom': leftBottom,
+		  'uni-fab__circle--rightBottom': rightBottom,
+		  'uni-fab__circle--leftTop': leftTop,
+		  'uni-fab__circle--rightTop': rightTop,
+		  'uni-fab__content--other-platform': !isAndroidNvue
+		}" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor, 'bottom': nvueBottom }" @click="_onClick">
+			<uni-icons class="fab-circle-icon" :type="styles.icon" :color="styles.iconColor" size="32"
+				:class="{'uni-fab__plus--active': isShow && content.length > 0}"></uni-icons>
+			<!-- <view class="fab-circle-v"  :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
+			<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow  && content.length > 0}"></view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	let platform = 'other'
+	// #ifdef APP-NVUE
+	platform = uni.getSystemInfoSync().platform
+	// #endif
+
+	/**
+	 * Fab 悬浮按钮
+	 * @description 点击可展开一个图形按钮菜单
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=144
+	 * @property {Object} pattern 可选样式配置项
+	 * @property {Object} horizontal = [left | right] 水平对齐方式
+	 * 	@value left 左对齐
+	 * 	@value right 右对齐
+	 * @property {Object} vertical = [bottom | top] 垂直对齐方式
+	 * 	@value bottom 下对齐
+	 * 	@value top 上对齐
+	 * @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
+	 * 	@value horizontal 水平显示
+	 * 	@value vertical 垂直显示
+	 * @property {Array} content 展开菜单内容配置项
+	 * @property {Boolean} popMenu 是否使用弹出菜单
+	 * @event {Function} trigger 展开菜单点击事件,返回点击信息
+	 * @event {Function} fabClick 悬浮按钮点击事件
+	 */
+	export default {
+		name: 'UniFab',
+		emits: ['fabClick', 'trigger'],
+		props: {
+			pattern: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			horizontal: {
+				type: String,
+				default: 'left'
+			},
+			vertical: {
+				type: String,
+				default: 'bottom'
+			},
+			direction: {
+				type: String,
+				default: 'horizontal'
+			},
+			content: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			show: {
+				type: Boolean,
+				default: false
+			},
+			popMenu: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				fabShow: false,
+				isShow: false,
+				isAndroidNvue: platform === 'android',
+				styles: {
+					color: '#3c3e49',
+					selectedColor: '#007AFF',
+					backgroundColor: '#fff',
+					buttonColor: '#007AFF',
+					iconColor: '#fff',
+					icon: 'plusempty'
+				}
+			}
+		},
+		computed: {
+			contentWidth(e) {
+				return (this.content.length + 1) * 55 + 15 + 'px'
+			},
+			contentWidthMin() {
+				return '55px'
+			},
+			// 动态计算宽度
+			boxWidth() {
+				return this.getPosition(3, 'horizontal')
+			},
+			// 动态计算高度
+			boxHeight() {
+				return this.getPosition(3, 'vertical')
+			},
+			// 计算左下位置
+			leftBottom() {
+				return this.getPosition(0, 'left', 'bottom')
+			},
+			// 计算右下位置
+			rightBottom() {
+				return this.getPosition(0, 'right', 'bottom')
+			},
+			// 计算左上位置
+			leftTop() {
+				return this.getPosition(0, 'left', 'top')
+			},
+			rightTop() {
+				return this.getPosition(0, 'right', 'top')
+			},
+			flexDirectionStart() {
+				return this.getPosition(1, 'vertical', 'top')
+			},
+			flexDirectionEnd() {
+				return this.getPosition(1, 'vertical', 'bottom')
+			},
+			horizontalLeft() {
+				return this.getPosition(2, 'horizontal', 'left')
+			},
+			horizontalRight() {
+				return this.getPosition(2, 'horizontal', 'right')
+			},
+			// 计算 nvue bottom
+			nvueBottom() {
+				// #ifdef APP-NVUE
+				const safeBottom = uni.getSystemInfoSync().windowBottom;
+				return 30 + safeBottom
+				// #endif
+				// #ifndef APP-NVUE
+				return 30
+				// #endif
+			}
+		},
+		watch: {
+			pattern: {
+				handler(val, oldVal) {
+					this.styles = Object.assign({}, this.styles, val)
+				},
+				deep: true
+			}
+		},
+		created() {
+			this.isShow = this.show
+			if (this.top === 0) {
+				this.fabShow = true
+			}
+			// 初始化样式
+			this.styles = Object.assign({}, this.styles, this.pattern)
+		},
+		methods: {
+			_onClick() {
+				this.$emit('fabClick')
+				if (!this.popMenu) {
+					return
+				}
+				this.isShow = !this.isShow
+			},
+			open() {
+				this.isShow = true
+			},
+			close() {
+				this.isShow = false
+			},
+			/**
+			 * 按钮点击事件
+			 */
+			_onItemClick(index, item) {
+				if (!this.isShow) {
+					return
+				}
+				this.$emit('trigger', {
+					index,
+					item
+				})
+			},
+			/**
+			 * 获取 位置信息
+			 */
+			getPosition(types, paramA, paramB) {
+				if (types === 0) {
+					return this.horizontal === paramA && this.vertical === paramB
+				} else if (types === 1) {
+					return this.direction === paramA && this.vertical === paramB
+				} else if (types === 2) {
+					return this.direction === paramA && this.horizontal === paramB
+				} else {
+					return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	$uni-shadow-base:0 1px 5px 2px rgba($color: #000000, $alpha: 0.3) !default;
+
+	.uni-fab {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		z-index: 10;
+		border-radius: 45px;
+		box-shadow: $uni-shadow-base;
+	}
+
+	.uni-cursor-point {
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-fab--active {
+		opacity: 1;
+	}
+
+	.uni-fab--leftBottom {
+		left: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--leftTop {
+		left: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--rightBottom {
+		right: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--rightTop {
+		right: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab__circle {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: 55px;
+		height: 55px;
+		background-color: #3c3e49;
+		border-radius: 45px;
+		z-index: 11;
+		// box-shadow: $uni-shadow-base;
+	}
+
+	.uni-fab__circle--leftBottom {
+		left: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--leftTop {
+		left: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightBottom {
+		right: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightTop {
+		right: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+	}
+
+	.uni-fab__circle--left {
+		left: 0;
+	}
+
+	.uni-fab__circle--right {
+		right: 0;
+	}
+
+	.uni-fab__circle--top {
+		top: 0;
+	}
+
+	.uni-fab__circle--bottom {
+		bottom: 0;
+	}
+
+	.uni-fab__plus {
+		font-weight: bold;
+	}
+
+	// .fab-circle-v {
+	// 	position: absolute;
+	// 	width: 2px;
+	// 	height: 24px;
+	// 	left: 0;
+	// 	top: 0;
+	// 	right: 0;
+	// 	bottom: 0;
+	// 	/* #ifndef APP-NVUE */
+	// 	margin: auto;
+	// 	/* #endif */
+	// 	background-color: white;
+	// 	transform: rotate(0deg);
+	// 	transition: transform 0.3s;
+	// }
+
+	// .fab-circle-h {
+	// 	position: absolute;
+	// 	width: 24px;
+	// 	height: 2px;
+	// 	left: 0;
+	// 	top: 0;
+	// 	right: 0;
+	// 	bottom: 0;
+	// 	/* #ifndef APP-NVUE */
+	// 	margin: auto;
+	// 	/* #endif */
+	// 	background-color: white;
+	// 	transform: rotate(0deg);
+	// 	transition: transform 0.3s;
+	// }
+
+	.fab-circle-icon {
+		transform: rotate(0deg);
+		transition: transform 0.3s;
+		font-weight: 200;
+	}
+
+	.uni-fab__plus--active {
+		transform: rotate(135deg);
+	}
+
+	.uni-fab__content {
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-radius: 55px;
+		overflow: hidden;
+		transition-property: width, height;
+		transition-duration: 0.2s;
+		width: 55px;
+		border-color: #DDDDDD;
+		border-width: 1rpx;
+		border-style: solid;
+	}
+
+	.uni-fab__content--other-platform {
+		border-width: 0px;
+		box-shadow: $uni-shadow-base;
+	}
+
+	.uni-fab__content--left {
+		justify-content: flex-start;
+	}
+
+	.uni-fab__content--right {
+		justify-content: flex-end;
+	}
+
+	.uni-fab__content--flexDirection {
+		flex-direction: column;
+		justify-content: flex-end;
+	}
+
+	.uni-fab__content--flexDirectionStart {
+		flex-direction: column;
+		justify-content: flex-start;
+	}
+
+	.uni-fab__content--flexDirectionEnd {
+		flex-direction: column;
+		justify-content: flex-end;
+	}
+
+	.uni-fab__item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		width: 55px;
+		height: 55px;
+		opacity: 0;
+		transition: opacity 0.2s;
+	}
+
+	.uni-fab__item--active {
+		opacity: 1;
+	}
+
+	.uni-fab__item-image {
+		width: 20px;
+		height: 20px;
+		margin-bottom: 4px;
+	}
+
+	.uni-fab__item-text {
+		color: #FFFFFF;
+		font-size: 12px;
+		line-height: 12px;
+		margin-top: 2px;
+	}
+
+	.uni-fab__item--first {
+		width: 55px;
+	}
+</style>

+ 85 - 0
uni_modules/uni-fab/package.json

@@ -0,0 +1,85 @@
+{
+  "id": "uni-fab",
+  "displayName": "uni-fab 悬浮按钮",
+  "version": "1.2.6",
+  "description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "按钮",
+    "悬浮按钮",
+    "fab"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss","uni-icons"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "n"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 9 - 0
uni_modules/uni-fab/readme.md

@@ -0,0 +1,9 @@
+## Fab 悬浮按钮
+> **组件名:uni-fab**
+> 代码块: `uFab`
+
+
+点击可展开一个图形按钮菜单
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fab)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 4 - 2
uni_modules/uni-icons/changelog.md

@@ -1,5 +1,7 @@
-## 2.0.10(2024-06-07)
-- 优化 uni-app x 中,size 属性的类型
+## 2.0.12(2025-08-26)
+- 优化 uni-app x 下 size 类型问题
+## 2.0.11(2025-08-18)
+- 修复 图标点击事件返回
 ## 2.0.9(2024-01-12)
 fix: 修复图标大小默认值错误的问题
 ## 2.0.8(2023-12-14)

+ 79 - 79
uni_modules/uni-icons/components/uni-icons/uni-icons.uvue

@@ -1,91 +1,91 @@
 <template>
-  <text class="uni-icons" :style="styleObj">
-    <slot>{{unicode}}</slot>
-  </text>
+	<text class="uni-icons" :style="styleObj">
+		<slot>{{unicode}}</slot>
+	</text>
 </template>
 
 <script>
-  import { fontData, IconsDataItem } from './uniicons_file'
+	import { fontData, IconsDataItem } from './uniicons_file'
 
-  /**
-   * Icons 图标
-   * @description 用于展示 icon 图标
-   * @tutorial https://ext.dcloud.net.cn/plugin?id=28
-   * @property {Number,String} size 图标大小
-   * @property {String} type 图标图案,参考示例
-   * @property {String} color 图标颜色
-   * @property {String} customPrefix 自定义图标
-   * @event {Function} click 点击 Icon 触发事件
-   */
-  export default {
-    name: "uni-icons",
-    props: {
-      type: {
-        type: String,
-        default: ''
-      },
-      color: {
-        type: String,
-        default: '#333333'
-      },
-      size: {
+	/**
+	 * Icons 图标
+	 * @description 用于展示 icon 图标
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=28
+	 * @property {Number} size 图标大小
+	 * @property {String} type 图标图案,参考示例
+	 * @property {String} color 图标颜色
+	 * @property {String} customPrefix 自定义图标
+	 * @event {Function} click 点击 Icon 触发事件
+	 */
+	export default {
+		name: "uni-icons",
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			color: {
+				type: String,
+				default: '#333333'
+			},
+			size: {
         type: [Number, String],
         default: 16
-      },
-      fontFamily: {
-        type: String,
-        default: ''
-      }
-    },
-    data() {
-      return {};
-    },
-    computed: {
-      unicode() : string {
-        let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
-        if (codes !== null) {
-          return codes.unicode
-        }
-        return ''
-      },
-      iconSize() : string {
-        const size = this.size
-        if (typeof size == 'string') {
-          const reg = /^[0-9]*$/g
-          return reg.test(size as string) ? '' + size + 'px' : '' + size;
-          // return '' + this.size
-        }
-        return this.getFontSize(size as number)
-      },
-      styleObj() : UTSJSONObject {
-        if (this.fontFamily !== '') {
-          return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
-        }
-        return { color: this.color, fontSize: this.iconSize }
-      }
-    },
-    created() { },
-    methods: {
-      /**
-       * 字体大小
-       */
-      getFontSize(size : number) : string {
-        return size + 'px';
-      },
-    },
-  }
+			},
+			fontFamily: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {};
+		},
+		computed: {
+			unicode() : string {
+				let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
+				if (codes !== null) {
+					return codes.unicode
+				}
+				return ''
+			},
+			iconSize() : string {
+				const size = this.size
+				if (typeof size == 'string') {
+				  const reg = /^[0-9]*$/g
+				  return reg.test(size as string) ? '' + size + 'px' : '' + size;
+				  // return '' + this.size
+				}
+				return this.getFontSize(size as number)
+			},
+			styleObj() : UTSJSONObject {
+				if (this.fontFamily !== '') {
+					return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
+				}
+				return { color: this.color, fontSize: this.iconSize }
+			}
+		},
+		created() { },
+		methods: {
+			/**
+			 * 字体大小
+			 */
+			getFontSize(size : number) : string {
+				return size + 'px';
+			},
+		},
+	}
 </script>
 
 <style scoped>
-  @font-face {
-    font-family: UniIconsFontFamily;
-    src: url('./uniicons.ttf');
-  }
+	@font-face {
+		font-family: UniIconsFontFamily;
+		src: url('./uniicons.ttf');
+	}
 
-  .uni-icons {
-    font-family: UniIconsFontFamily;
-    font-size: 18px;
-    font-style: normal;
-    color: #333;
-  }
+	.uni-icons {
+		font-family: UniIconsFontFamily;
+		font-size: 18px;
+		font-style: normal;
+		color: #333;
+	}
 </style>

+ 3 - 3
uni_modules/uni-icons/components/uni-icons/uni-icons.vue

@@ -85,8 +85,8 @@
 			}
 		},
 		methods: {
-			_onClick() {
-				this.$emit('click')
+			_onClick(e) {
+				this.$emit('click', e)
 			}
 		}
 	}
@@ -107,4 +107,4 @@
 		text-decoration: none;
 		text-align: center;
 	}
-</style>
+</style>

+ 66 - 44
uni_modules/uni-icons/package.json

@@ -1,7 +1,7 @@
 {
   "id": "uni-icons",
   "displayName": "uni-icons 图标",
-  "version": "2.0.10",
+  "version": "2.0.12",
   "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
   "keywords": [
     "uni-ui",
@@ -11,12 +11,14 @@
 ],
   "repository": "https://github.com/dcloudio/uni-ui",
   "engines": {
-    "HBuilderX": "^3.2.14"
+    "HBuilderX": "^3.2.14",
+    "uni-app": "^4.08",
+    "uni-app-x": "^4.61"
   },
   "directories": {
     "example": "../../temps/example_temps"
   },
-"dcloudext": {
+  "dcloudext": {
     "sale": {
       "regular": {
         "price": "0.00"
@@ -34,56 +36,76 @@
       "permissions": "无"
     },
     "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
-    "type": "component-vue"
+    "type": "component-vue",
+    "darkmode": "x",
+    "i18n": "x",
+    "widescreen": "x"
   },
   "uni_modules": {
-    "dependencies": ["uni-scss"],
+    "dependencies": [
+      "uni-scss"
+    ],
     "encrypt": [],
     "platforms": {
       "cloud": {
-        "tcb": "y",
-        "aliyun": "y",
-        "alipay": "n"
+        "tcb": "x",
+        "aliyun": "x",
+        "alipay": "x"
       },
       "client": {
-        "App": {
-          "app-vue": "y",
-          "app-nvue": "y",
-          "app-uvue": "y"
+        "uni-app": {
+          "vue": {
+            "vue2": "√",
+            "vue3": "√"
+          },
+          "web": {
+            "safari": "√",
+            "chrome": "√"
+          },
+          "app": {
+            "vue": "√",
+            "nvue": "-",
+            "android": {
+                "extVersion": "",
+                "minVersion": "29"
+            },
+            "ios": "√",
+            "harmony": "√"
+          },
+          "mp": {
+            "weixin": "√",
+            "alipay": "√",
+            "toutiao": "√",
+            "baidu": "√",
+            "kuaishou": "-",
+            "jd": "-",
+            "harmony": "-",
+            "qq": "√",
+            "lark": "-"
+          },
+          "quickapp": {
+            "huawei": "√",
+            "union": "√"
+          }
         },
-        "H5-mobile": {
-          "Safari": "y",
-          "Android Browser": "y",
-          "微信浏览器(Android)": "y",
-          "QQ浏览器(Android)": "y"
-        },
-        "H5-pc": {
-          "Chrome": "y",
-          "IE": "y",
-          "Edge": "y",
-          "Firefox": "y",
-          "Safari": "y"
-        },
-        "小程序": {
-          "微信": "y",
-          "阿里": "y",
-          "百度": "y",
-          "字节跳动": "y",
-          "QQ": "y",
-					"钉钉": "y",
-					"快手": "y",
-					"飞书": "y",
-					"京东": "y"
-        },
-        "快应用": {
-          "华为": "y",
-          "联盟": "y"
-        },
-        "Vue": {
-            "vue2": "y",
-            "vue3": "y"
+        "uni-app-x": {
+          "web": {
+            "safari": "√",
+            "chrome": "√"
+          },
+          "app": {
+            "android": {
+                "extVersion": "",
+                "minVersion": "29"
+            },
+            "ios": "√",
+            "harmony": "√"
+          },
+          "mp": {
+            "weixin": "√"
+          }
         }
       }
     }
   }
-}
+}

+ 16 - 0
uni_modules/uni-tooltip/changelog.md

@@ -0,0 +1,16 @@
+## 0.2.4(2024-04-23)
+- 修复 弹出位置默认值不一致导致的错位
+## 0.2.3(2024-03-20)
+- 修复 弹出位置修正
+## 0.2.2(2024-01-15)
+- 新增 placement支持设置四个方向:top bottom left right
+## 0.2.1(2022-05-09)
+- 修复 content 为空时仍然弹出的bug
+## 0.2.0(2022-05-07)
+**注意:破坏性更新**
+- 更新 text 属性变更为 content
+- 更新 移除 width 属性
+## 0.1.1(2022-04-27)
+- 修复 组件根 text 嵌套组件 warning
+## 0.1.0(2022-04-21)
+- 初始化

+ 108 - 0
uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue

@@ -0,0 +1,108 @@
+<template>
+	<view class="uni-tooltip">
+		<slot></slot>
+		<view v-if="content || $slots.content" class="uni-tooltip-popup" :style="initPlacement">
+			<slot name="content">
+				{{content}}
+			</slot>
+		</view>
+	</view>
+</template>
+
+
+<script>
+	/**
+	 * Tooltip 提示文字
+	 * @description 常用于展示鼠标 hover 时的提示信息。
+	 * @tutorial https://uniapp.dcloud.io/component/uniui/uni-tooltip
+	 * @property {String} content   弹出层显示的内容
+	 * @property {String}  placement出现位置, 目前支持:left right top bottom
+	 */
+	export default {
+		name: "uni-tooltip",
+		data() {
+			return {
+
+			};
+		},
+		methods: {},
+		computed: {
+			initPlacement() {
+				let style = {};
+				switch (this.placement) {
+					case 'left':
+						style = {
+							top: '50%',
+							transform: 'translateY(-50%)',
+							right: '100%',
+							"margin-right": '10rpx',
+						}
+						break;
+					case 'right':
+						style = {
+							top: '50%',
+							transform: 'translateY(-50%)',
+							left: '100%',
+							"margin-left": '10rpx',
+						}
+						break;
+					case 'top':
+						style = {
+							bottom: '100%',
+							transform: 'translateX(-50%)',
+							left: '50%',
+							"margin-bottom": '10rpx',
+						}
+						break;
+					case 'bottom':
+						style = {
+							top: '100%',
+							transform: 'translateX(-50%)',
+							left: '50%',
+							"margin-top": '10rpx',
+						}
+						break;
+				}
+				return style;
+			}
+		},
+		props: {
+			content: {
+				type: String,
+				default: ''
+			},
+
+			placement: {
+				type: String,
+				default: 'bottom'
+			},
+		}
+	}
+</script>
+
+<style>
+	.uni-tooltip {
+		position: relative;
+		cursor: pointer;
+		display: inline-block;
+	}
+
+	.uni-tooltip-popup {
+		z-index: 1;
+		display: none;
+		position: absolute;
+		background-color: #333;
+		border-radius: 8px;
+		color: #fff;
+		font-size: 12px;
+		text-align: left;
+		line-height: 16px;
+		padding: 12px;
+		overflow: auto;
+	}
+
+
+	.uni-tooltip:hover .uni-tooltip-popup {
+		display: block;
+	}
+</style>

+ 86 - 0
uni_modules/uni-tooltip/package.json

@@ -0,0 +1,86 @@
+{
+  "id": "uni-tooltip",
+  "displayName": "uni-tooltip 提示文字",
+  "version": "0.2.4",
+  "description": "Tooltip 提示文字",
+  "keywords": [
+    "uni-tooltip",
+    "uni-ui",
+    "tooltip",
+    "tip",
+    "文字提示"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无 ",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "n"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 8 - 0
uni_modules/uni-tooltip/readme.md

@@ -0,0 +1,8 @@
+## Badge 数字角标
+> **组件名:uni-tooltip**
+> 代码块: `uTooltip`
+
+数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tooltip)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839